allow vendord
All checks were successful
Build, Push and Deploy / build-and-push (push) Successful in 3m3s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 16s
Build, Push and Deploy / deploy-staging (push) Successful in 32s
Build, Push and Deploy / deploy-production (push) Has been skipped
Build, Push and Deploy / cleanup (push) Successful in 1s

This commit is contained in:
2026-03-30 14:54:57 +07:00
parent 66aed7c4e8
commit b5e3a778ce
21316 changed files with 2777892 additions and 13 deletions

132
vendor/google/gax/src/AgentHeader.php vendored Normal file
View File

@@ -0,0 +1,132 @@
<?php
/*
* Copyright 2016 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore;
/**
* Class containing functions used to build the Agent header.
*/
class AgentHeader
{
const AGENT_HEADER_KEY = 'x-goog-api-client';
const UNKNOWN_VERSION = '';
/**
* @param array $headerInfo {
* Optional.
*
* @type string $phpVersion the PHP version.
* @type string $libName the name of the client application.
* @type string $libVersion the version of the client application.
* @type string $gapicVersion the code generator version of the GAPIC library.
* @type string $apiCoreVersion the ApiCore version.
* @type string $grpcVersion the gRPC version.
* @type string $restVersion the REST transport version (typically same as the
* ApiCore version).
* @type string $protobufVersion the protobuf version in format 'x.y.z+a' where both 'x.y.z'
* and '+a' are optional, and where 'a' is a single letter representing the
* implementation type of the protobuf runtime. It is recommended to use 'c' for a C
* implementation, and 'n' for the native language implementation (PHP).
* }
* @return array Agent header array
*/
public static function buildAgentHeader(array $headerInfo)
{
$metricsHeaders = [];
// The ordering of the headers is important. We use the fact that $metricsHeaders is an
// ordered dict. The desired ordering is:
// - phpVersion (gl-php/)
// - clientName (e.g. gccl/)
// - gapicVersion (gapic/)
// - apiCoreVersion (gax/)
// - grpcVersion (grpc/)
// - restVersion (rest/)
// - protobufVersion (pb/)
$metricsHeaders['gl-php'] = $headerInfo['phpVersion'] ?? phpversion();
if (isset($headerInfo['libName'])) {
$metricsHeaders[$headerInfo['libName']] =
$headerInfo['libVersion'] ?? self::UNKNOWN_VERSION;
}
$apiCoreVersion = $headerInfo['apiCoreVersion'] ?? Version::getApiCoreVersion();
$metricsHeaders['gapic'] = $headerInfo['gapicVersion'] ?? self::UNKNOWN_VERSION;
$metricsHeaders['gax'] = $apiCoreVersion;
// Context on library type identification (between gRPC+REST and REST-only):
// This uses the gRPC extension's version if 'grpcVersion' is not set, so we
// cannot use the presence of 'grpcVersion' to determine whether or not a library
// is gRPC+REST or REST-only. However, we cannot use the extension's presence
// either, since some clients may have the extension installed but opt to use a
// REST-only library (e.g. GCE).
// TODO: Should we stop sending empty gRPC headers?
$metricsHeaders['grpc'] = $headerInfo['grpcVersion'] ?? phpversion('grpc');
$metricsHeaders['rest'] = $headerInfo['restVersion'] ?? $apiCoreVersion;
// The native version is not set by default because it is complex and costly to retrieve.
// Users can override this default behavior if needed.
$metricsHeaders['pb'] = $headerInfo['protobufVersion']
?? (phpversion('protobuf') ? phpversion('protobuf') . '+c' : '+n');
$metricsList = [];
foreach ($metricsHeaders as $key => $value) {
$metricsList[] = $key . '/' . $value;
}
return [self::AGENT_HEADER_KEY => [implode(' ', $metricsList)]];
}
/**
* Reads the gapic version string from a VERSION file. In order to determine the file
* location, this method follows this procedure:
* - accepts a class name $callingClass
* - identifies the file defining that class
* - searches up the directory structure for the 'src' directory
* - looks in the directory above 'src' for a file named VERSION
*
* @param string $callingClass
* @return string the gapic version
* @throws \ReflectionException
*/
public static function readGapicVersionFromFile(string $callingClass)
{
$callingClassFile = (new \ReflectionClass($callingClass))->getFileName();
$versionFile = substr(
$callingClassFile,
0,
strrpos($callingClassFile, DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR)
) . DIRECTORY_SEPARATOR . 'VERSION';
return Version::readVersionFile($versionFile);
}
}

336
vendor/google/gax/src/ApiException.php vendored Normal file
View File

@@ -0,0 +1,336 @@
<?php
/*
* Copyright 2016 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore;
use Exception;
use Google\Protobuf\RepeatedField;
use Google\Rpc\Status;
use GuzzleHttp\Exception\RequestException;
use stdClass;
/**
* Represents an exception thrown during an RPC.
*/
class ApiException extends Exception
{
private $status;
private $metadata;
private $basicMessage;
private $decodedMetadataErrorInfo;
/**
* ApiException constructor.
* @param string $message
* @param int $code
* @param string|null $status
* @param array $optionalArgs {
* @type Exception|null $previous
* @type array|null $metadata
* @type string|null $basicMessage
* }
*/
public function __construct(
string $message,
int $code,
?string $status = null,
array $optionalArgs = []
) {
$optionalArgs += [
'previous' => null,
'metadata' => null,
'basicMessage' => $message,
];
parent::__construct($message, $code, $optionalArgs['previous']);
$this->status = $status;
$this->metadata = $optionalArgs['metadata'];
$this->basicMessage = $optionalArgs['basicMessage'];
if ($this->metadata) {
$this->decodedMetadataErrorInfo = self::decodeMetadataErrorInfo($this->metadata);
}
}
public function getStatus()
{
return $this->status;
}
/**
* Returns null if metadata does not contain error info, or returns containsErrorInfo() array
* if the metadata does contain error info.
* @param array $metadata
* @return array $details {
* @type string|null $reason
* @type string|null $domain
* @type array|null $errorInfoMetadata
* }
*/
private static function decodeMetadataErrorInfo(array $metadata)
{
$details = [];
// ApiExceptions created from RPC status have metadata that is an array of objects.
if (is_object(reset($metadata))) {
$metadataRpcStatus = Serializer::decodeAnyMessages($metadata);
$details = self::containsErrorInfo($metadataRpcStatus);
} elseif (self::containsErrorInfo($metadata)) {
$details = self::containsErrorInfo($metadata);
} else {
// For GRPC-based responses, the $metadata needs to be decoded.
$metadataGrpc = Serializer::decodeMetadata($metadata);
$details = self::containsErrorInfo($metadataGrpc);
}
return $details;
}
/**
* Returns the `reason` in ErrorInfo for an exception, or null if there is no ErrorInfo.
* @return string|null $reason
*/
public function getReason()
{
return ($this->decodedMetadataErrorInfo) ? $this->decodedMetadataErrorInfo['reason'] : null;
}
/**
* Returns the `domain` in ErrorInfo for an exception, or null if there is no ErrorInfo.
* @return string|null $domain
*/
public function getDomain()
{
return ($this->decodedMetadataErrorInfo) ? $this->decodedMetadataErrorInfo['domain'] : null;
}
/**
* Returns the `metadata` in ErrorInfo for an exception, or null if there is no ErrorInfo.
* @return array|null $errorInfoMetadata
*/
public function getErrorInfoMetadata()
{
return ($this->decodedMetadataErrorInfo) ? $this->decodedMetadataErrorInfo['errorInfoMetadata'] : null;
}
/**
* @param stdClass $status
* @return ApiException
*/
public static function createFromStdClass(stdClass $status)
{
$metadata = property_exists($status, 'metadata') ? $status->metadata : null;
return self::create(
$status->details,
$status->code,
$metadata,
Serializer::decodeMetadata((array) $metadata)
);
}
/**
* @param string $basicMessage
* @param int $rpcCode
* @param array|null $metadata
* @param Exception $previous
* @return ApiException
*/
public static function createFromApiResponse(
$basicMessage,
$rpcCode,
?array $metadata = null,
?Exception $previous = null
) {
return self::create(
$basicMessage,
$rpcCode,
$metadata,
Serializer::decodeMetadata((array) $metadata),
$previous
);
}
/**
* For REST-based responses, the metadata does not need to be decoded.
*
* @param string $basicMessage
* @param int $rpcCode
* @param array|null $metadata
* @param Exception $previous
* @return ApiException
*/
public static function createFromRestApiResponse(
$basicMessage,
$rpcCode,
?array $metadata = null,
?Exception $previous = null
) {
return self::create(
$basicMessage,
$rpcCode,
$metadata,
is_null($metadata) ? [] : $metadata,
$previous
);
}
/**
* Checks if decoded metadata includes errorInfo message.
* If errorInfo is set, it will always contain `reason`, `domain`, and `metadata` keys.
* @param array $decodedMetadata
* @return array {
* @type string $reason
* @type string $domain
* @type array $errorInfoMetadata
* }
*/
private static function containsErrorInfo(array $decodedMetadata)
{
if (empty($decodedMetadata)) {
return [];
}
foreach ($decodedMetadata as $value) {
$isErrorInfoArray = isset($value['reason']) && isset($value['domain']) && isset($value['metadata']);
if ($isErrorInfoArray) {
return [
'reason' => $value['reason'],
'domain' => $value['domain'],
'errorInfoMetadata' => $value['metadata'],
];
}
}
return [];
}
/**
* Construct an ApiException with a useful message, including decoded metadata.
* If the decoded metadata includes an errorInfo message, then the domain, reason,
* and metadata fields from that message are hoisted directly into the error.
*
* @param string $basicMessage
* @param int $rpcCode
* @param iterable|null $metadata
* @param array $decodedMetadata
* @param Exception|null $previous
* @return ApiException
*/
private static function create(
string $basicMessage,
int $rpcCode,
$metadata,
array $decodedMetadata,
?Exception $previous = null
) {
$containsErrorInfo = self::containsErrorInfo($decodedMetadata);
$rpcStatus = ApiStatus::statusFromRpcCode($rpcCode);
$messageData = [
'message' => $basicMessage,
'code' => $rpcCode,
'status' => $rpcStatus,
'details' => $decodedMetadata
];
if ($containsErrorInfo) {
$messageData = array_merge($containsErrorInfo, $messageData);
}
$message = json_encode($messageData, JSON_PRETTY_PRINT);
if ($metadata instanceof RepeatedField) {
$metadata = iterator_to_array($metadata);
}
return new ApiException($message, $rpcCode, $rpcStatus, [
'previous' => $previous,
'metadata' => $metadata,
'basicMessage' => $basicMessage,
]);
}
/**
* @param Status $status
* @return ApiException
*/
public static function createFromRpcStatus(Status $status)
{
return self::create(
$status->getMessage(),
$status->getCode(),
$status->getDetails(),
Serializer::decodeAnyMessages($status->getDetails())
);
}
/**
* Creates an ApiException from a GuzzleHttp RequestException.
*
* @param RequestException $ex
* @param boolean $isStream
* @return ApiException
* @throws ValidationException
*/
public static function createFromRequestException(RequestException $ex, bool $isStream = false)
{
$res = $ex->getResponse();
$body = (string) $res->getBody();
$decoded = json_decode($body, true);
// A streaming response body will return one error in an array. Parse
// that first (and only) error message, if provided.
if ($isStream && isset($decoded[0])) {
$decoded = $decoded[0];
}
if (isset($decoded['error']) && $decoded['error']) {
$error = $decoded['error'];
$basicMessage = $error['message'] ?? '';
$code = isset($error['status'])
? ApiStatus::rpcCodeFromStatus($error['status'])
: $ex->getCode();
$metadata = $error['details'] ?? null;
return static::createFromRestApiResponse($basicMessage, $code, $metadata);
}
// Use the RPC code instead of the HTTP Status Code.
$code = ApiStatus::rpcCodeFromHttpStatusCode($res->getStatusCode());
return static::createFromApiResponse($body, $code);
}
/**
* @return null|string
*/
public function getBasicMessage()
{
return $this->basicMessage;
}
/**
* @return mixed[]
*/
public function getMetadata()
{
return $this->metadata;
}
}

View File

@@ -0,0 +1,95 @@
<?php
/*
* Copyright 2024 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore;
use Google\Auth\GetQuotaProjectInterface;
/**
* The ApiKeyHeaderCredentials object provides a wrapper around an API key.
*/
class ApiKeyHeaderCredentials implements HeaderCredentialsInterface, GetQuotaProjectInterface
{
private string $apiKey;
private ?string $quotaProject;
/**
* ApiKeyHeaderCredentials constructor.
* @param string $apiKey The API key to set in the header for the request
* @param string|null $quotaProject The quota project associated with the API key.
* @throws ValidationException
*/
public function __construct(string $apiKey, ?string $quotaProject = null)
{
if (empty($apiKey)) {
throw new ValidationException('API key cannot be empty');
}
$this->apiKey = $apiKey;
$this->quotaProject = $quotaProject;
}
/**
* @return string|null The quota project associated with the credentials.
*/
public function getQuotaProject(): ?string
{
return $this->quotaProject;
}
/**
* @param string|null $unusedAudience audiences are not supported for API keys.
*
* @return callable|null Callable function that returns the API key header.
*/
public function getAuthorizationHeaderCallback(?string $unusedAudience = null): ?callable
{
$apiKey = $this->apiKey;
// NOTE: changes to this function should be treated carefully and tested thoroughly. It will
// be passed into the gRPC c extension, and changes have the potential to trigger very
// difficult-to-diagnose segmentation faults.
return function () use ($apiKey) {
return ['x-goog-api-key' => [$apiKey]];
};
}
/**
* Verify that the expected universe domain matches the universe domain from the credentials.
*
* @throws ValidationException if the universe domain does not match.
*/
public function checkUniverseDomain(): void
{
// This is a no-op, as API keys are not tied to a universe domain. As a result, the
// potential for leaking API keys to the GDU is higher, and it's recommended to specify
// the "universeDomain" option with the GAPIC client.
}
}

171
vendor/google/gax/src/ApiStatus.php vendored Normal file
View File

@@ -0,0 +1,171 @@
<?php
/*
* Copyright 2017 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore;
use Google\Rpc\Code;
class ApiStatus
{
const OK = 'OK';
const CANCELLED = 'CANCELLED';
const UNKNOWN = 'UNKNOWN';
const INVALID_ARGUMENT = 'INVALID_ARGUMENT';
const DEADLINE_EXCEEDED = 'DEADLINE_EXCEEDED';
const NOT_FOUND = 'NOT_FOUND';
const ALREADY_EXISTS = 'ALREADY_EXISTS';
const PERMISSION_DENIED = 'PERMISSION_DENIED';
const RESOURCE_EXHAUSTED = 'RESOURCE_EXHAUSTED';
const FAILED_PRECONDITION = 'FAILED_PRECONDITION';
const ABORTED = 'ABORTED';
const OUT_OF_RANGE = 'OUT_OF_RANGE';
const UNIMPLEMENTED = 'UNIMPLEMENTED';
const INTERNAL = 'INTERNAL';
const UNAVAILABLE = 'UNAVAILABLE';
const DATA_LOSS = 'DATA_LOSS';
const UNAUTHENTICATED = 'UNAUTHENTICATED';
const UNRECOGNIZED_STATUS = 'UNRECOGNIZED_STATUS';
const UNRECOGNIZED_CODE = -1;
private static $apiStatusToCodeMap = [
ApiStatus::OK => Code::OK,
ApiStatus::CANCELLED => Code::CANCELLED,
ApiStatus::UNKNOWN => Code::UNKNOWN,
ApiStatus::INVALID_ARGUMENT => Code::INVALID_ARGUMENT,
ApiStatus::DEADLINE_EXCEEDED => Code::DEADLINE_EXCEEDED,
ApiStatus::NOT_FOUND => Code::NOT_FOUND,
ApiStatus::ALREADY_EXISTS => Code::ALREADY_EXISTS,
ApiStatus::PERMISSION_DENIED => Code::PERMISSION_DENIED,
ApiStatus::RESOURCE_EXHAUSTED => Code::RESOURCE_EXHAUSTED,
ApiStatus::FAILED_PRECONDITION => Code::FAILED_PRECONDITION,
ApiStatus::ABORTED => Code::ABORTED,
ApiStatus::OUT_OF_RANGE => Code::OUT_OF_RANGE,
ApiStatus::UNIMPLEMENTED => Code::UNIMPLEMENTED,
ApiStatus::INTERNAL => Code::INTERNAL,
ApiStatus::UNAVAILABLE => Code::UNAVAILABLE,
ApiStatus::DATA_LOSS => Code::DATA_LOSS,
ApiStatus::UNAUTHENTICATED => Code::UNAUTHENTICATED,
];
private static $codeToApiStatusMap = [
Code::OK => ApiStatus::OK,
Code::CANCELLED => ApiStatus::CANCELLED,
Code::UNKNOWN => ApiStatus::UNKNOWN,
Code::INVALID_ARGUMENT => ApiStatus::INVALID_ARGUMENT,
Code::DEADLINE_EXCEEDED => ApiStatus::DEADLINE_EXCEEDED,
Code::NOT_FOUND => ApiStatus::NOT_FOUND,
Code::ALREADY_EXISTS => ApiStatus::ALREADY_EXISTS,
Code::PERMISSION_DENIED => ApiStatus::PERMISSION_DENIED,
Code::RESOURCE_EXHAUSTED => ApiStatus::RESOURCE_EXHAUSTED,
Code::FAILED_PRECONDITION => ApiStatus::FAILED_PRECONDITION,
Code::ABORTED => ApiStatus::ABORTED,
Code::OUT_OF_RANGE => ApiStatus::OUT_OF_RANGE,
Code::UNIMPLEMENTED => ApiStatus::UNIMPLEMENTED,
Code::INTERNAL => ApiStatus::INTERNAL,
Code::UNAVAILABLE => ApiStatus::UNAVAILABLE,
Code::DATA_LOSS => ApiStatus::DATA_LOSS,
Code::UNAUTHENTICATED => ApiStatus::UNAUTHENTICATED,
];
private static $httpStatusCodeToRpcCodeMap = [
400 => Code::INVALID_ARGUMENT,
401 => Code::UNAUTHENTICATED,
403 => Code::PERMISSION_DENIED,
404 => Code::NOT_FOUND,
409 => Code::ABORTED,
416 => Code::OUT_OF_RANGE,
429 => Code::RESOURCE_EXHAUSTED,
499 => Code::CANCELLED,
501 => Code::UNIMPLEMENTED,
503 => Code::UNAVAILABLE,
504 => Code::DEADLINE_EXCEEDED,
];
/**
* @param string $status
* @return bool
*/
public static function isValidStatus(string $status)
{
return array_key_exists($status, self::$apiStatusToCodeMap);
}
/**
* @param int $code
* @return string
*/
public static function statusFromRpcCode(int $code)
{
if (array_key_exists($code, self::$codeToApiStatusMap)) {
return self::$codeToApiStatusMap[$code];
}
return ApiStatus::UNRECOGNIZED_STATUS;
}
/**
* @param string $status
* @return int
*/
public static function rpcCodeFromStatus(string $status)
{
if (array_key_exists($status, self::$apiStatusToCodeMap)) {
return self::$apiStatusToCodeMap[$status];
}
return ApiStatus::UNRECOGNIZED_CODE;
}
/**
* Maps HTTP status codes to Google\Rpc\Code codes.
* Some codes are left out because they map to multiple gRPC codes (e.g. 500).
*
* @param int $httpStatusCode
* @return int
*/
public static function rpcCodeFromHttpStatusCode(int $httpStatusCode)
{
if (array_key_exists($httpStatusCode, self::$httpStatusCodeToRpcCodeMap)) {
return self::$httpStatusCodeToRpcCodeMap[$httpStatusCode];
}
// All 2xx
if ($httpStatusCode >= 200 && $httpStatusCode < 300) {
return Code::OK;
}
// All 4xx
if ($httpStatusCode >= 400 && $httpStatusCode < 500) {
return Code::FAILED_PRECONDITION;
}
// All 5xx
if ($httpStatusCode >= 500 && $httpStatusCode < 600) {
return Code::INTERNAL;
}
// Everything else (We cannot change this to Code::UNKNOWN because it would break BC)
return ApiStatus::UNRECOGNIZED_CODE;
}
}

156
vendor/google/gax/src/ArrayTrait.php vendored Normal file
View File

@@ -0,0 +1,156 @@
<?php
/*
* Copyright 2018 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore;
/**
* Provides basic array helper methods.
*
* @internal
*/
trait ArrayTrait
{
/**
* Pluck a value out of an array.
*
* @param string $key
* @param array $arr
* @param bool $isRequired
* @return mixed|null
* @throws \InvalidArgumentException
*/
private function pluck(string $key, array &$arr, bool $isRequired = true)
{
if (!array_key_exists($key, $arr)) {
if ($isRequired) {
throw new \InvalidArgumentException(
"Key $key does not exist in the provided array."
);
}
return null;
}
$value = $arr[$key];
unset($arr[$key]);
return $value;
}
/**
* Pluck a subset of an array.
*
* @param array $keys
* @param array $arr
* @return array
*/
private function pluckArray(array $keys, array &$arr)
{
$values = [];
foreach ($keys as $key) {
if (array_key_exists($key, $arr)) {
$values[$key] = $this->pluck($key, $arr, false);
}
}
return $values;
}
/**
* Determine whether given array is associative.
*
* @param array $arr
* @return bool
*/
private function isAssoc(array $arr)
{
return array_keys($arr) !== range(0, count($arr) - 1);
}
/**
* Just like array_filter(), but preserves falsey values except null.
*
* @param array $arr
* @return array
*/
private function arrayFilterRemoveNull(array $arr)
{
return array_filter($arr, function ($element) {
if (!is_null($element)) {
return true;
}
return false;
});
}
/**
* Return a subset of an array, like pluckArray, without modifying the original array.
*
* @param array $keys
* @param array $arr
* @return array
*/
private function subsetArray(array $keys, array $arr)
{
return array_intersect_key(
$arr,
array_flip($keys)
);
}
/**
* A method, similar to PHP's `array_merge_recursive`, with two differences.
*
* 1. Keys in $array2 take precedence over keys in $array1.
* 2. Non-array keys found in both inputs are not transformed into an array
* and appended. Rather, the value in $array2 is used.
*
* @param array $array1
* @param array $array2
* @return array
*/
private function arrayMergeRecursive(array $array1, array $array2)
{
foreach ($array2 as $key => $value) {
if (array_key_exists($key, $array1) && is_array($array1[$key]) && is_array($value)) {
$array1[$key] = ($this->isAssoc($array1[$key]) && $this->isAssoc($value))
? $this->arrayMergeRecursive($array1[$key], $value)
: array_merge($array1[$key], $value);
} else {
$array1[$key] = $value;
}
}
return $array1;
}
}

217
vendor/google/gax/src/BidiStream.php vendored Normal file
View File

@@ -0,0 +1,217 @@
<?php
/*
* Copyright 2016 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore;
use Google\Auth\Logging\LoggingTrait;
use Google\Auth\Logging\RpcLogEvent;
use Google\Protobuf\Internal\Message;
use Google\Rpc\Code;
use Grpc\BidiStreamingCall;
use Psr\Log\LoggerInterface;
/**
* BidiStream is the response object from a gRPC bidirectional streaming API call.
*/
class BidiStream
{
use LoggingTrait;
private $call;
private $isComplete = false;
private $writesClosed = false;
private $resourcesGetMethod = null;
private $pendingResources = [];
private null|LoggerInterface $logger = null;
/**
* BidiStream constructor.
*
* @param BidiStreamingCall $bidiStreamingCall The gRPC bidirectional streaming call object
* @param array $streamingDescriptor
* @param null|LoggerInterface $logger
*/
public function __construct(
BidiStreamingCall $bidiStreamingCall,
array $streamingDescriptor = [],
null|LoggerInterface $logger = null,
) {
$this->call = $bidiStreamingCall;
if (array_key_exists('resourcesGetMethod', $streamingDescriptor)) {
$this->resourcesGetMethod = $streamingDescriptor['resourcesGetMethod'];
}
$this->logger = $logger;
}
/**
* Write request to the server.
*
* @param mixed $request The request to write
* @throws ValidationException
*/
public function write($request)
{
if ($this->isComplete) {
throw new ValidationException('Cannot call write() after streaming call is complete.');
}
if ($this->writesClosed) {
throw new ValidationException('Cannot call write() after calling closeWrite().');
}
if ($this->logger && $request instanceof Message) {
$logEvent = new RpcLogEvent();
$logEvent->headers = null;
$logEvent->payload = $request->serializeToJsonString();
$logEvent->processId = (int) getmypid();
$logEvent->requestId = crc32((string) spl_object_id($this) . getmypid());
$this->logRequest($logEvent);
}
$this->call->write($request);
}
/**
* Write all requests in $requests.
*
* @param iterable $requests An Iterable of request objects to write to the server
*
* @throws ValidationException
*/
public function writeAll($requests = [])
{
foreach ($requests as $request) {
$this->write($request);
}
}
/**
* Inform the server that no more requests will be written. The write() function cannot be
* called after closeWrite() is called.
* @throws ValidationException
*/
public function closeWrite()
{
if ($this->isComplete) {
throw new ValidationException(
'Cannot call closeWrite() after streaming call is complete.'
);
}
if (!$this->writesClosed) {
$this->call->writesDone();
$this->writesClosed = true;
}
}
/**
* Read the next response from the server. Returns null if the streaming call completed
* successfully. Throws an ApiException if the streaming call failed.
*
* @throws ValidationException
* @throws ApiException
* @return mixed
*/
public function read()
{
if ($this->isComplete) {
throw new ValidationException('Cannot call read() after streaming call is complete.');
}
$resourcesGetMethod = $this->resourcesGetMethod;
if (!is_null($resourcesGetMethod)) {
if (count($this->pendingResources) === 0) {
$response = $this->call->read();
if (!is_null($response)) {
$pendingResources = [];
foreach ($response->$resourcesGetMethod() as $resource) {
$pendingResources[] = $resource;
}
$this->pendingResources = array_reverse($pendingResources);
}
}
$result = array_pop($this->pendingResources);
} else {
$result = $this->call->read();
}
if (is_null($result)) {
$status = $this->call->getStatus();
$this->isComplete = true;
if (!($status->code == Code::OK)) {
throw ApiException::createFromStdClass($status);
}
}
if ($this->logger) {
$responseEvent = new RpcLogEvent();
$responseEvent->headers = $this->call->getMetadata();
$responseEvent->status = $status->code ?? null;
$responseEvent->processId = (int) getmypid();
$responseEvent->requestId = crc32((string) spl_object_id($this) . getmypid());
if ($result instanceof Message) {
$responseEvent->payload = $result->serializeToJsonString();
}
$this->logResponse($responseEvent);
}
return $result;
}
/**
* Call closeWrite(), and read all responses from the server, until the streaming call is
* completed. Throws an ApiException if the streaming call failed.
*
* @throws ValidationException
* @throws ApiException
* @return \Generator|mixed[]
*/
public function closeWriteAndReadAll()
{
$this->closeWrite();
$response = $this->read();
while (!is_null($response)) {
yield $response;
$response = $this->read();
}
}
/**
* Return the underlying gRPC call object
*
* @return \Grpc\BidiStreamingCall|mixed
*/
public function getBidiStreamingCall()
{
return $this->call;
}
}

131
vendor/google/gax/src/Call.php vendored Normal file
View File

@@ -0,0 +1,131 @@
<?php
/*
* Copyright 2018 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore;
use Google\Protobuf\Internal\Message;
/**
* Contains information necessary to manage a network request.
*/
class Call
{
const UNARY_CALL = 0;
const BIDI_STREAMING_CALL = 1;
const CLIENT_STREAMING_CALL = 2;
const SERVER_STREAMING_CALL = 3;
const LONGRUNNING_CALL = 4;
const PAGINATED_CALL = 5;
private $method;
private $callType;
private $decodeType;
private $message;
private $descriptor;
/**
* @param string $method
* @param string $decodeType
* @param mixed|Message $message
* @param array|null $descriptor
* @param int $callType
*/
public function __construct(
string $method,
?string $decodeType = null,
$message = null,
$descriptor = [],
int $callType = Call::UNARY_CALL
) {
$this->method = $method;
$this->decodeType = $decodeType;
$this->message = $message;
$this->descriptor = $descriptor;
$this->callType = $callType;
}
/**
* @return string
*/
public function getMethod()
{
return $this->method;
}
/**
* @return int
*/
public function getCallType()
{
return $this->callType;
}
/**
* @return string
*/
public function getDecodeType()
{
return $this->decodeType;
}
/**
* @return mixed|Message
*/
public function getMessage()
{
return $this->message;
}
/**
* @return array|null
*/
public function getDescriptor()
{
return $this->descriptor;
}
/**
* @param mixed|Message $message
* @return Call
*/
public function withMessage($message)
{
// @phpstan-ignore-next-line
return new static(
$this->method,
$this->decodeType,
$message,
$this->descriptor,
$this->callType
);
}
}

View File

@@ -0,0 +1,400 @@
<?php
/*
* Copyright 2024 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore;
use Google\ApiCore\Options\ClientOptions;
use Google\Auth\ApplicationDefaultCredentials;
use Google\Auth\CredentialsLoader;
use Google\Auth\FetchAuthTokenInterface;
use Google\Auth\GetUniverseDomainInterface;
use Google\Auth\HttpHandler\HttpHandlerFactory;
use Grpc\Gcp\ApiConfig;
use Grpc\Gcp\Config;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
/**
* Common functions used to work with various clients.
*
* @internal
*/
trait ClientOptionsTrait
{
use ArrayTrait;
private static $gapicVersionFromFile;
private static function getGapicVersion(array $options)
{
if (isset($options['libVersion'])) {
return $options['libVersion'];
}
if (!isset(self::$gapicVersionFromFile)) {
self::$gapicVersionFromFile = AgentHeader::readGapicVersionFromFile(__CLASS__);
}
return self::$gapicVersionFromFile;
}
private static function initGrpcGcpConfig(string $hostName, string $confPath)
{
$apiConfig = new ApiConfig();
$apiConfig->mergeFromJsonString(file_get_contents($confPath));
$config = new Config($hostName, $apiConfig);
return $config;
}
/**
* Get default options. This function should be "overridden" by clients using late static
* binding to provide default options to the client.
*
* @return array
* @access private
*/
private static function getClientDefaults()
{
return [];
}
/**
* Resolve client options based on the client's default
* ({@see ClientOptionsTrait::getClientDefault}) and the default for all
* Google APIs.
*
* 1. Set default client option values
* 2. Set default logger (and log user-supplied configuration options)
* 3. Set default transport configuration
* 4. Call "modifyClientOptions" (for backwards compatibility)
* 5. Use "defaultScopes" when custom endpoint is supplied
* 6. Load mTLS from the environment if configured
* 7. Resolve endpoint based on universe domain template when possible
* 8. Load sysvshm grpc config when possible
*/
private function buildClientOptions(array|ClientOptions $options)
{
if ($options instanceof ClientOptions) {
$options = $options->toArray();
}
// Build $defaultOptions starting from top level
// variables, then going into deeper nesting, so that
// we will not encounter missing keys
$defaultOptions = self::getClientDefaults();
$defaultOptions += [
'disableRetries' => false,
'credentials' => null,
'credentialsConfig' => [],
'transport' => null,
'transportConfig' => [],
'gapicVersion' => self::getGapicVersion($options),
'libName' => null,
'libVersion' => null,
'apiEndpoint' => null,
'clientCertSource' => null,
'universeDomain' => null,
'logger' => null,
];
$supportedTransports = $this->supportedTransports();
foreach ($supportedTransports as $transportName) {
if (!array_key_exists($transportName, $defaultOptions['transportConfig'])) {
$defaultOptions['transportConfig'][$transportName] = [];
}
}
if (in_array('grpc', $supportedTransports)) {
$defaultOptions['transportConfig']['grpc'] = [
'stubOpts' => ['grpc.service_config_disable_resolution' => 1]
];
}
// Keep track of the API Endpoint
$apiEndpoint = $options['apiEndpoint'] ?? null;
// Keep track of the original user supplied options for logging the configuration
$clientSuppliedOptions = $options;
// Merge defaults into $options starting from top level
// variables, then going into deeper nesting, so that
// we will not encounter missing keys
$options += $defaultOptions;
// If logger is explicitly set to false, logging is disabled
if (is_null($options['logger'])) {
$options['logger'] = ApplicationDefaultCredentials::getDefaultLogger();
}
if (
$options['logger'] !== null
&& $options['logger'] !== false
&& !$options['logger'] instanceof LoggerInterface
) {
throw new ValidationException(
'The "logger" option in the options array should be PSR-3 LoggerInterface compatible'
);
}
// Log the user supplied configuration.
$this->logConfiguration($options['logger'], $clientSuppliedOptions);
if (isset($options['logger'])) {
$options['credentialsConfig']['authHttpHandler'] = HttpHandlerFactory::build(
logger: $options['logger']
);
}
$options['credentialsConfig'] += $defaultOptions['credentialsConfig'];
$options['transportConfig'] += $defaultOptions['transportConfig']; // @phpstan-ignore-line
if (isset($options['transportConfig']['grpc'])) {
$options['transportConfig']['grpc'] += $defaultOptions['transportConfig']['grpc'];
$options['transportConfig']['grpc']['stubOpts'] += $defaultOptions['transportConfig']['grpc']['stubOpts'];
$options['transportConfig']['grpc']['logger'] = $options['logger'] ?? null;
}
if (isset($options['transportConfig']['rest'])) {
$options['transportConfig']['rest'] += $defaultOptions['transportConfig']['rest'];
$options['transportConfig']['rest']['logger'] = $options['logger'] ?? null;
}
if (isset($options['transportConfig']['grpc-fallback'])) {
$options['transportConfig']['grpc-fallback']['logger'] = $options['logger'] ?? null;
}
// These calls do not apply to "New Surface" clients.
if ($this->isBackwardsCompatibilityMode()) {
$preModifiedOptions = $options;
$this->modifyClientOptions($options);
// NOTE: this is required to ensure backwards compatiblity with $options['apiEndpoint']
if ($options['apiEndpoint'] !== $preModifiedOptions['apiEndpoint']) {
$apiEndpoint = $options['apiEndpoint'];
}
// serviceAddress is now deprecated and acts as an alias for apiEndpoint
if (isset($options['serviceAddress'])) {
$apiEndpoint = $this->pluck('serviceAddress', $options, false);
}
} else {
// Ads is using this method in their new surface clients, so we need to call it.
// However, this method is not used anywhere else for the new surface clients
// @TODO: Remove this in GAX V2
$this->modifyClientOptions($options);
}
// If an API endpoint is different form the default, ensure the "audience" does not conflict
// with the custom endpoint by setting "user defined" scopes.
if ($apiEndpoint
&& $apiEndpoint != $defaultOptions['apiEndpoint']
&& empty($options['credentialsConfig']['scopes'])
&& !empty($options['credentialsConfig']['defaultScopes'])
) {
$options['credentialsConfig']['scopes'] = $options['credentialsConfig']['defaultScopes'];
}
// mTLS: detect and load the default clientCertSource if the environment variable
// "GOOGLE_API_USE_CLIENT_CERTIFICATE" is true, and the cert source is available
if (empty($options['clientCertSource']) && CredentialsLoader::shouldLoadClientCertSource()) {
if ($defaultCertSource = CredentialsLoader::getDefaultClientCertSource()) {
$options['clientCertSource'] = function () use ($defaultCertSource) {
$cert = call_user_func($defaultCertSource);
// the key and the cert are returned in one string
return [$cert, $cert];
};
}
}
// mTLS: If no apiEndpoint has been supplied by the user, and either
// GOOGLE_API_USE_MTLS_ENDPOINT tells us to, or mTLS is available, use the mTLS endpoint.
if (is_null($apiEndpoint) && $this->shouldUseMtlsEndpoint($options)) {
$apiEndpoint = self::determineMtlsEndpoint($options['apiEndpoint']);
}
// If the user has not supplied a universe domain, use the environment variable if set.
// Otherwise, use the default ("googleapis.com").
$options['universeDomain'] ??= getenv('GOOGLE_CLOUD_UNIVERSE_DOMAIN')
?: GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN;
// mTLS: It is not valid to configure mTLS outside of "googleapis.com" (yet)
if (isset($options['clientCertSource'])
&& $options['universeDomain'] !== GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN
) {
throw new ValidationException(
'mTLS is not supported outside the "googleapis.com" universe'
);
}
if (is_null($apiEndpoint)) {
if (defined('self::SERVICE_ADDRESS_TEMPLATE')) {
// Derive the endpoint from the service address template and the universe domain
$apiEndpoint = str_replace(
'UNIVERSE_DOMAIN',
$options['universeDomain'],
self::SERVICE_ADDRESS_TEMPLATE
);
} else {
// For older clients, the service address template does not exist. Use the default
// endpoint instead.
$apiEndpoint = $defaultOptions['apiEndpoint'];
}
}
if (extension_loaded('sysvshm')
&& isset($options['gcpApiConfigPath'])
&& file_exists($options['gcpApiConfigPath'])
&& !empty($apiEndpoint)
) {
$grpcGcpConfig = self::initGrpcGcpConfig(
$apiEndpoint,
$options['gcpApiConfigPath']
);
if (!array_key_exists('stubOpts', $options['transportConfig']['grpc'])) {
$options['transportConfig']['grpc']['stubOpts'] = [];
}
$options['transportConfig']['grpc']['stubOpts'] += [
'grpc_call_invoker' => $grpcGcpConfig->callInvoker()
];
}
$options['apiEndpoint'] = $apiEndpoint;
return $options;
}
private function shouldUseMtlsEndpoint(array $options)
{
$mtlsEndpointEnvVar = getenv('GOOGLE_API_USE_MTLS_ENDPOINT');
if ('always' === $mtlsEndpointEnvVar) {
return true;
}
if ('never' === $mtlsEndpointEnvVar) {
return false;
}
// For all other cases, assume "auto" and return true if clientCertSource exists
return !empty($options['clientCertSource']);
}
private static function determineMtlsEndpoint(string $apiEndpoint)
{
$parts = explode('.', $apiEndpoint);
if (count($parts) < 3) {
return $apiEndpoint; // invalid endpoint!
}
return sprintf('%s.mtls.%s', array_shift($parts), implode('.', $parts));
}
/**
* @param mixed $credentials
* @param array $credentialsConfig
* @return CredentialsWrapper
* @throws ValidationException
*/
private function createCredentialsWrapper($credentials, array $credentialsConfig, string $universeDomain)
{
if (is_null($credentials)) {
// If the user has explicitly set the apiKey option, use Api Key credentials
return CredentialsWrapper::build($credentialsConfig, $universeDomain);
}
if (is_string($credentials) || is_array($credentials)) {
return CredentialsWrapper::build(['keyFile' => $credentials] + $credentialsConfig, $universeDomain);
}
if ($credentials instanceof FetchAuthTokenInterface) {
$authHttpHandler = $credentialsConfig['authHttpHandler'] ?? null;
return new CredentialsWrapper($credentials, $authHttpHandler, $universeDomain);
}
if ($credentials instanceof CredentialsWrapper) {
return $credentials;
}
throw new ValidationException(sprintf(
'Unexpected value in $auth option, got: %s',
print_r($credentials, true)
));
}
/**
* This defaults to all three transports, which One-Platform supports.
* Discovery clients should define this function and only return ['rest'].
*/
private static function supportedTransports()
{
return ['grpc', 'grpc-fallback', 'rest'];
}
// Gapic Client Extension Points
// The methods below provide extension points that can be used to customize client
// functionality. These extension points are currently considered
// private and may change at any time.
/**
* Modify options passed to the client before calling setClientOptions.
*
* @param array $options
* @access private
* @internal
*/
protected function modifyClientOptions(array &$options)
{
// Do nothing - this method exists to allow option modification by partial veneers.
}
/**
* @internal
*/
private function isBackwardsCompatibilityMode(): bool
{
return false;
}
/**
* @param null|false|LoggerInterface $logger
* @param string $options
*/
private function logConfiguration(null|false|LoggerInterface $logger, array $options): void
{
if (!$logger) {
return;
}
$configurationLog = [
'timestamp' => date(DATE_RFC3339),
'severity' => strtoupper(LogLevel::DEBUG),
'processId' => getmypid(),
'jsonPayload' => [
'serviceName' => self::SERVICE_NAME, // @phpstan-ignore-line
'clientConfiguration' => $options,
]
];
$logger->debug(json_encode($configurationLog));
}
}

143
vendor/google/gax/src/ClientStream.php vendored Normal file
View File

@@ -0,0 +1,143 @@
<?php
/*
* Copyright 2016 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore;
use Google\Auth\Logging\LoggingTrait;
use Google\Auth\Logging\RpcLogEvent;
use Google\Protobuf\Internal\Message;
use Google\Rpc\Code;
use Grpc\ClientStreamingCall;
use Psr\Log\LoggerInterface;
/**
* ClientStream is the response object from a gRPC client streaming API call.
*/
class ClientStream
{
use LoggingTrait;
private $call;
private null|LoggerInterface $logger;
/**
* ClientStream constructor.
*
* @param ClientStreamingCall $clientStreamingCall The gRPC client streaming call object
* @param array $streamingDescriptor
* @param null|LoggerInterface $logger A PSR-3 compliant logger.
*/
public function __construct(// @phpstan-ignore-line
ClientStreamingCall $clientStreamingCall,
array $streamingDescriptor = [],
null|LoggerInterface $logger = null,
) {
$this->call = $clientStreamingCall;
$this->logger = $logger;
}
/**
* Write request to the server.
*
* @param mixed $request The request to write
*/
public function write($request)
{
// In some cases, $request can be a string
if ($this->logger && $request instanceof Message) {
$requestEvent = new RpcLogEvent();
$requestEvent->payload = $request->serializeToJsonString();
$requestEvent->processId = (int) getmypid();
$requestEvent->requestId = crc32((string) spl_object_id($this) . getmypid());
$this->logRequest($requestEvent);
}
$this->call->write($request);
}
/**
* Read the response from the server, completing the streaming call.
*
* @throws ApiException
* @return mixed The response object from the server
*/
public function readResponse()
{
list($response, $status) = $this->call->wait();
if ($status->code == Code::OK) {
if ($this->logger) {
$responseEvent = new RpcLogEvent();
$responseEvent->headers = $status->metadata;
$responseEvent->status = $status->code;
$responseEvent->processId = (int) getmypid();
$responseEvent->requestId = crc32((string) spl_object_id($this) . getmypid());
if ($response instanceof Message) {
$response->serializeToJsonString();
}
$this->logResponse($responseEvent);
}
return $response;
} else {
throw ApiException::createFromStdClass($status);
}
}
/**
* Write all data in $dataArray and read the response from the server, completing the streaming
* call.
*
* @param mixed[] $requests An iterator of request objects to write to the server
* @return mixed The response object from the server
*/
public function writeAllAndReadResponse(array $requests)
{
foreach ($requests as $request) {
$this->write($request);
}
return $this->readResponse();
}
/**
* Return the underlying gRPC call object
*
* @return \Grpc\ClientStreamingCall|mixed
*/
public function getClientStreamingCall()
{
return $this->call;
}
}

View File

@@ -0,0 +1,359 @@
<?php
/*
* Copyright 2018 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore;
use DomainException;
use Exception;
use Google\Auth\ApplicationDefaultCredentials;
use Google\Auth\Cache\MemoryCacheItemPool;
use Google\Auth\Credentials\GCECredentials;
use Google\Auth\Credentials\ServiceAccountCredentials;
use Google\Auth\CredentialsLoader;
use Google\Auth\FetchAuthTokenCache;
use Google\Auth\FetchAuthTokenInterface;
use Google\Auth\GetQuotaProjectInterface;
use Google\Auth\GetUniverseDomainInterface;
use Google\Auth\ProjectIdProviderInterface;
use Google\Auth\UpdateMetadataInterface;
use Psr\Cache\CacheItemPoolInterface;
/**
* The CredentialsWrapper object provides a wrapper around a FetchAuthTokenInterface.
*/
class CredentialsWrapper implements HeaderCredentialsInterface, ProjectIdProviderInterface
{
use ValidationTrait;
/** @var FetchAuthTokenInterface $credentialsFetcher */
private ?FetchAuthTokenInterface $credentialsFetcher = null;
/** @var callable $authHttpHandle */
private $authHttpHandler;
private string $universeDomain;
private bool $hasCheckedUniverse = false;
/** @var int */
private static int $eagerRefreshThresholdSeconds = 10;
/**
* CredentialsWrapper constructor.
* @param FetchAuthTokenInterface $credentialsFetcher A credentials loader
* used to fetch access tokens.
* @param callable $authHttpHandler A handler used to deliver PSR-7 requests
* specifically for authentication. Should match a signature of
* `function (RequestInterface $request, array $options) : ResponseInterface`.
* @throws ValidationException
*/
public function __construct(
FetchAuthTokenInterface $credentialsFetcher,
?callable $authHttpHandler = null,
string $universeDomain = GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN
) {
$this->credentialsFetcher = $credentialsFetcher;
$this->authHttpHandler = $authHttpHandler;
if (empty($universeDomain)) {
throw new ValidationException('The universe domain cannot be empty');
}
$this->universeDomain = $universeDomain;
}
/**
* Factory method to create a CredentialsWrapper from an array of options.
*
* @param array $args {
* An array of optional arguments.
*
* @type string|array $keyFile
* Credentials to be used. Accepts either a path to a credentials file, or a decoded
* credentials file as a PHP array. If this is not specified, application default
* credentials will be used.
* @type string[] $scopes
* A string array of scopes to use when acquiring credentials.
* @type callable $authHttpHandler
* A handler used to deliver PSR-7 requests specifically
* for authentication. Should match a signature of
* `function (RequestInterface $request, array $options) : ResponseInterface`.
* @type bool $enableCaching
* Enable caching of access tokens. Defaults to true.
* @type CacheItemPoolInterface $authCache
* A cache for storing access tokens. Defaults to a simple in memory implementation.
* @type array $authCacheOptions
* Cache configuration options.
* @type string $quotaProject
* Specifies a user project to bill for access charges associated with the request.
* @type string[] $defaultScopes
* A string array of default scopes to use when acquiring
* credentials.
* @type bool $useJwtAccessWithScope
* Ensures service account credentials use JWT Access (also known as self-signed
* JWTs), even when user-defined scopes are supplied.
* }
* @param string $universeDomain The expected universe of the credentials. Defaults to
* "googleapis.com"
* @return CredentialsWrapper
* @throws ValidationException
*/
public static function build(
array $args = [],
string $universeDomain = GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN
) {
$args += [
'keyFile' => null,
'scopes' => null,
'authHttpHandler' => null,
'enableCaching' => true,
'authCache' => null,
'authCacheOptions' => [],
'quotaProject' => null,
'defaultScopes' => null,
'useJwtAccessWithScope' => true,
];
$keyFile = $args['keyFile'];
if (is_null($keyFile)) {
$loader = self::buildApplicationDefaultCredentials(
$args['scopes'],
$args['authHttpHandler'],
$args['authCacheOptions'],
$args['authCache'],
$args['quotaProject'],
$args['defaultScopes']
);
if ($loader instanceof FetchAuthTokenCache) {
$loader = $loader->getFetcher();
}
} else {
if (is_string($keyFile)) {
if (!file_exists($keyFile)) {
throw new ValidationException("Could not find keyfile: $keyFile");
}
$keyFile = json_decode(file_get_contents($keyFile), true);
}
if (isset($args['quotaProject'])) {
$keyFile['quota_project_id'] = $args['quotaProject'];
}
$loader = CredentialsLoader::makeCredentials(
$args['scopes'],
$keyFile,
$args['defaultScopes']
);
}
if ($loader instanceof ServiceAccountCredentials && $args['useJwtAccessWithScope']) {
// Ensures the ServiceAccountCredentials uses JWT Access, also known
// as self-signed JWTs, even when user-defined scopes are supplied.
$loader->useJwtAccessWithScope();
}
if ($args['enableCaching']) {
$authCache = $args['authCache'] ?: new MemoryCacheItemPool();
$loader = new FetchAuthTokenCache(
$loader,
$args['authCacheOptions'],
$authCache
);
}
return new CredentialsWrapper($loader, $args['authHttpHandler'], $universeDomain);
}
/**
* @return string|null The quota project associated with the credentials.
*/
public function getQuotaProject(): ?string
{
if ($this->credentialsFetcher instanceof GetQuotaProjectInterface) {
return $this->credentialsFetcher->getQuotaProject();
}
return null;
}
public function getProjectId(?callable $httpHandler = null): ?string
{
// Ensure that FetchAuthTokenCache does not throw an exception
if ($this->credentialsFetcher instanceof FetchAuthTokenCache
&& !$this->credentialsFetcher->getFetcher() instanceof ProjectIdProviderInterface) {
return null;
}
if ($this->credentialsFetcher instanceof ProjectIdProviderInterface) {
return $this->credentialsFetcher->getProjectId($httpHandler);
}
return null;
}
/**
* @deprecated
* @return string Bearer string containing access token.
*/
public function getBearerString()
{
$token = $this->credentialsFetcher->getLastReceivedToken();
if (self::isExpired($token)) {
$this->checkUniverseDomain();
$token = $this->credentialsFetcher->fetchAuthToken($this->authHttpHandler);
if (!self::isValid($token)) {
return '';
}
}
return empty($token['access_token']) ? '' : 'Bearer ' . $token['access_token'];
}
/**
* @param string $audience optional audience for self-signed JWTs.
* @return callable Callable function that returns an authorization header.
*/
public function getAuthorizationHeaderCallback($audience = null): ?callable
{
// NOTE: changes to this function should be treated carefully and tested thoroughly. It will
// be passed into the gRPC c extension, and changes have the potential to trigger very
// difficult-to-diagnose segmentation faults.
return function () use ($audience) {
$token = $this->credentialsFetcher->getLastReceivedToken();
if (self::isExpired($token)) {
$this->checkUniverseDomain();
// Call updateMetadata to take advantage of self-signed JWTs
if ($this->credentialsFetcher instanceof UpdateMetadataInterface) {
return $this->credentialsFetcher->updateMetadata([], $audience, $this->authHttpHandler);
}
// In case a custom fetcher is provided (unlikely) which doesn't
// implement UpdateMetadataInterface
$token = $this->credentialsFetcher->fetchAuthToken($this->authHttpHandler);
if (!self::isValid($token)) {
return [];
}
}
$tokenString = $token['access_token'];
if (!empty($tokenString)) {
return ['authorization' => ["Bearer $tokenString"]];
}
return [];
};
}
/**
* Verify that the expected universe domain matches the universe domain from the credentials.
*
* @throws ValidationException if the universe domain does not match.
*/
public function checkUniverseDomain(): void
{
if (false === $this->hasCheckedUniverse && $this->shouldCheckUniverseDomain()) {
$credentialsUniverse = $this->credentialsFetcher instanceof GetUniverseDomainInterface
? $this->credentialsFetcher->getUniverseDomain()
: GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN;
if ($credentialsUniverse !== $this->universeDomain) {
throw new ValidationException(sprintf(
'The configured universe domain (%s) does not match the credential universe domain (%s)',
$this->universeDomain,
$credentialsUniverse
));
}
$this->hasCheckedUniverse = true;
}
}
/**
* Skip universe domain check for Metadata server (e.g. GCE) credentials.
*
* @return bool
*/
private function shouldCheckUniverseDomain(): bool
{
$fetcher = $this->credentialsFetcher instanceof FetchAuthTokenCache
? $this->credentialsFetcher->getFetcher()
: $this->credentialsFetcher;
if ($fetcher instanceof GCECredentials) {
return false;
}
return true;
}
/**
* @param array $scopes
* @param callable $authHttpHandler
* @param array $authCacheOptions
* @param CacheItemPoolInterface $authCache
* @param string $quotaProject
* @param array $defaultScopes
* @return FetchAuthTokenInterface
* @throws ValidationException
*/
private static function buildApplicationDefaultCredentials(
?array $scopes = null,
?callable $authHttpHandler = null,
?array $authCacheOptions = null,
?CacheItemPoolInterface $authCache = null,
$quotaProject = null,
?array $defaultScopes = null
) {
try {
return ApplicationDefaultCredentials::getCredentials(
$scopes,
$authHttpHandler,
$authCacheOptions,
$authCache,
$quotaProject,
$defaultScopes
);
} catch (DomainException $ex) {
throw new ValidationException('Could not construct ApplicationDefaultCredentials', $ex->getCode(), $ex);
}
}
/**
* @param mixed $token
*/
private static function isValid($token)
{
return is_array($token)
&& array_key_exists('access_token', $token);
}
/**
* @param mixed $token
*/
private static function isExpired($token)
{
return !(self::isValid($token)
&& array_key_exists('expires_at', $token)
&& $token['expires_at'] > time() + self::$eagerRefreshThresholdSeconds);
}
}

View File

@@ -0,0 +1,190 @@
<?php
/*
* Copyright 2016 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore;
use Generator;
use InvalidArgumentException;
use IteratorAggregate;
use LengthException;
/**
* A collection of elements retrieved using one or more API calls. The
* collection will attempt to retrieve a fixed number of elements, and
* will make API calls until that fixed number is reached, or there
* are no more elements to retrieve.
*/
class FixedSizeCollection implements IteratorAggregate
{
private $collectionSize;
private $pageList;
/**
* FixedSizeCollection constructor.
* @param Page $initialPage
* @param int $collectionSize
*/
public function __construct(Page $initialPage, int $collectionSize)
{
if ($collectionSize <= 0) {
throw new InvalidArgumentException(
"collectionSize must be > 0. collectionSize: $collectionSize"
);
}
if ($collectionSize < $initialPage->getPageElementCount()) {
$ipc = $initialPage->getPageElementCount();
throw new InvalidArgumentException(
'collectionSize must be greater than or equal to the number of ' .
"elements in initialPage. collectionSize: $collectionSize, " .
"initialPage size: $ipc"
);
}
$this->collectionSize = $collectionSize;
$this->pageList = FixedSizeCollection::createPageArray($initialPage, $collectionSize);
}
/**
* Returns the number of elements in the collection. This will be
* equal to the collectionSize parameter used at construction
* unless there are no elements remaining to be retrieved.
*
* @return int
*/
public function getCollectionSize()
{
$size = 0;
foreach ($this->pageList as $page) {
$size += $page->getPageElementCount();
}
return $size;
}
/**
* Returns true if there are more elements that can be retrieved
* from the API.
*
* @return bool
*/
public function hasNextCollection()
{
return $this->getLastPage()->hasNextPage();
}
/**
* Returns a page token that can be passed into the API list
* method to retrieve additional elements.
*
* @return string
*/
public function getNextPageToken()
{
return $this->getLastPage()->getNextPageToken();
}
/**
* Retrieves the next FixedSizeCollection using one or more API calls.
*
* @return FixedSizeCollection
*/
public function getNextCollection()
{
$lastPage = $this->getLastPage();
$nextPage = $lastPage->getNextPage($this->collectionSize);
return new FixedSizeCollection($nextPage, $this->collectionSize);
}
/**
* Returns an iterator over the elements of the collection.
*
* @return Generator
*/
#[\ReturnTypeWillChange]
public function getIterator()
{
foreach ($this->pageList as $page) {
foreach ($page as $element) {
yield $element;
}
}
}
/**
* Returns an iterator over FixedSizeCollections, starting with this
* and making API calls as required until all of the elements have
* been retrieved.
*
* @return Generator|FixedSizeCollection[]
*/
public function iterateCollections()
{
$currentCollection = $this;
yield $this;
while ($currentCollection->hasNextCollection()) {
$currentCollection = $currentCollection->getNextCollection();
yield $currentCollection;
}
}
private function getLastPage()
{
$pageList = $this->pageList;
// Get last element in array...
$lastPage = end($pageList);
reset($pageList);
return $lastPage;
}
/**
* @param Page $initialPage
* @param int $collectionSize
* @return Page[]
*/
private static function createPageArray(Page $initialPage, int $collectionSize)
{
$pageList = [$initialPage];
$currentPage = $initialPage;
$itemCount = $currentPage->getPageElementCount();
while ($itemCount < $collectionSize && $currentPage->hasNextPage()) {
$remainingCount = $collectionSize - $itemCount;
$currentPage = $currentPage->getNextPage($remainingCount);
$rxElementCount = $currentPage->getPageElementCount();
if ($rxElementCount > $remainingCount) {
throw new LengthException('API returned a number of elements ' .
'exceeding the specified page size limit. page size: ' .
"$remainingCount, elements received: $rxElementCount");
}
array_push($pageList, $currentPage);
$itemCount += $rxElementCount;
}
return $pageList;
}
}

43
vendor/google/gax/src/GPBLabel.php vendored Normal file
View File

@@ -0,0 +1,43 @@
<?php
/*
* Copyright 2017 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore;
/**
* Container class for Protobuf label constants. See FieldDescriptorProto.Label in
* https://github.com/google/protobuf/blob/master/src/google/protobuf/descriptor.proto
*/
class GPBLabel
{
const OPTIONAL = 1;
const REQUIRED = 2;
const REPEATED = 3;
}

58
vendor/google/gax/src/GPBType.php vendored Normal file
View File

@@ -0,0 +1,58 @@
<?php
/*
* Copyright 2017 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore;
/**
* Container class for Protobuf type constants. See FieldDescriptorProto.Type in
* https://github.com/google/protobuf/blob/master/src/google/protobuf/descriptor.proto
*/
class GPBType
{
const DOUBLE = 1;
const FLOAT = 2;
const INT64 = 3;
const UINT64 = 4;
const INT32 = 5;
const FIXED64 = 6;
const FIXED32 = 7;
const BOOL = 8;
const STRING = 9;
const GROUP = 10;
const MESSAGE = 11;
const BYTES = 12;
const UINT32 = 13;
const ENUM = 14;
const SFIXED32 = 15;
const SFIXED64 = 16;
const SINT32 = 17;
const SINT64 = 18;
}

1018
vendor/google/gax/src/GapicClientTrait.php vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,63 @@
<?php
/*
* Copyright 2018 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore;
/**
* Provides helper methods for gRPC support.
*
* @internal
*/
trait GrpcSupportTrait
{
/**
* @return bool
*/
private static function getGrpcDependencyStatus()
{
return extension_loaded('grpc');
}
/**
* @throws ValidationException
*/
private static function validateGrpcSupport()
{
if (!self::getGrpcDependencyStatus()) {
throw new ValidationException(
'gRPC support has been requested but required dependencies ' .
'have not been found. For details on how to install the ' .
'gRPC extension please see https://cloud.google.com/php/grpc.'
);
}
}
}

View File

@@ -0,0 +1,48 @@
<?php
/*
* Copyright 2024 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore;
interface HeaderCredentialsInterface
{
/**
* @param string|null $audience optional audience for self-signed JWTs.
* @return callable|null Callable function that returns an authorization header.
*/
public function getAuthorizationHeaderCallback(?string $audience = null): ?callable;
/**
* Verify that the expected universe domain matches the universe domain from the credentials.
*
* @throws ValidationException if the universe domain does not match.
*/
public function checkUniverseDomain(): void;
}

View File

@@ -0,0 +1,57 @@
<?php
/*
* Copyright 2024 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore;
/**
* For connect to emulator.
* @TODO: implement HeaderCredentialsInterface instead of extending CredentialsWrapper
*/
class InsecureCredentialsWrapper extends CredentialsWrapper
{
public function __construct()
{
}
/**
* @param string $audience
* @return callable|null Returns null so the gRPC can accept it as an insecure channel.
*/
public function getAuthorizationHeaderCallback($audience = null): ?callable
{
return null;
}
public function checkUniverseDomain(): void
{
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace Google\ApiCore;
use GuzzleHttp\Psr7\Utils;
use Psr\Http\Message\UriInterface;
/**
* @internal
*/
class InsecureRequestBuilder extends RequestBuilder
{
/**
* @param string $path
* @param array $queryParams
* @return UriInterface
*/
protected function buildUri(string $path, array $queryParams)
{
$uri = Utils::uriFor(sprintf(
'http://%s%s',
$this->baseUri,
$path
));
if ($queryParams) {
$uri = $this->buildUriWithQuery(
$uri,
$queryParams
);
}
return $uri;
}
}

View File

@@ -0,0 +1,67 @@
<?php
/*
* Copyright 2018 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\Middleware;
use Google\ApiCore\Call;
use Google\ApiCore\CredentialsWrapper;
use Google\ApiCore\HeaderCredentialsInterface;
/**
* Middleware which adds a CredentialsWrapper object to the call options.
*
* @internal
*/
class CredentialsWrapperMiddleware implements MiddlewareInterface
{
/** @var callable */
private $nextHandler;
/** @var HeaderCredentialsInterface */
private HeaderCredentialsInterface $credentialsWrapper;
public function __construct(
callable $nextHandler,
HeaderCredentialsInterface $credentialsWrapper
) {
$this->nextHandler = $nextHandler;
$this->credentialsWrapper = $credentialsWrapper;
}
public function __invoke(Call $call, array $options)
{
$next = $this->nextHandler;
return $next(
$call,
$options + ['credentialsWrapper' => $this->credentialsWrapper]
);
}
}

View File

@@ -0,0 +1,74 @@
<?php
/*
* Copyright 2018 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\Middleware;
use Google\ApiCore\Call;
/**
* Middleware to add fixed headers to an API call.
*
* @internal
*/
class FixedHeaderMiddleware implements MiddlewareInterface
{
/** @var callable */
private $nextHandler;
private array $headers;
private bool $overrideUserHeaders;
public function __construct(
callable $nextHandler,
array $headers,
bool $overrideUserHeaders = false
) {
$this->nextHandler = $nextHandler;
$this->headers = $headers;
$this->overrideUserHeaders = $overrideUserHeaders;
}
public function __invoke(Call $call, array $options)
{
$userHeaders = $options['headers'] ?? [];
if ($this->overrideUserHeaders) {
$options['headers'] = $this->headers + $userHeaders;
} else {
$options['headers'] = $userHeaders + $this->headers;
}
$next = $this->nextHandler;
return $next(
$call,
$options
);
}
}

View File

@@ -0,0 +1,91 @@
<?php
/*
* Copyright 2023 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\Middleware;
use Google\ApiCore\BidiStream;
use Google\ApiCore\Call;
use Google\ApiCore\ClientStream;
use Google\ApiCore\ServerStream;
use GuzzleHttp\Promise\PromiseInterface;
/**
* Middlewares must take a MiddlewareInterface as their first constructor
* argument {@see Google\ApiCore\Middleware\ResponseMetadataMiddleware}, which
* represents the next middleware in the chain. This next middleware MUST be
* invoked by this MiddlewareInterface, and the result must be returned as part
* of the `__invoke` method implementation.
*
* To create your own middleware, first implement the interface, as well as pass the handler
* in through the constructor:
*
* ```
* use Google\ApiCore\Call;
* use Google\ApiCore\Middleware\MiddlewareInterface;
*
* class MyTestMiddleware implements MiddlewareInterface
* {
* public function __construct(MiddlewareInterface $handler)
* {
*. $this->handler = $handler;
* }
* public function __invoke(Call $call, array $options)
* {
* echo "Logging info about the call: " . $call->getMethod();
* return ($this->handler)($call, $options);
* }
* }
* ```
*
* Next, add the middleware to any class implementing `GapicClientTrait` by passing in a
* callable which returns the new middleware:
*
* ```
* $client = new ExampleGoogleApiServiceClient();
* $client->addMiddleware(function (MiddlewareInterface $handler) {
* return new MyTestMiddleware($handler);
* });
* ```
*/
interface MiddlewareInterface
{
/**
* Modify or observe the API call request and response.
* The returned value must include the result of the next MiddlewareInterface invocation in the
* chain.
*
* @param Call $call
* @param array $options
* @return PromiseInterface|ClientStream|ServerStream|BidiStream
*/
public function __invoke(Call $call, array $options);
}

View File

@@ -0,0 +1,75 @@
<?php
/*
* Copyright 2018 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\Middleware;
use Google\ApiCore\Call;
use Google\ApiCore\OperationResponse;
use Google\Protobuf\Internal\Message;
/**
* Middleware which wraps the response in an OperationResponse object.
*
* @internal
*/
class OperationsMiddleware implements MiddlewareInterface
{
/** @var callable */
private $nextHandler;
private object $operationsClient;
private array $descriptor;
public function __construct(
callable $nextHandler,
$operationsClient,
array $descriptor
) {
$this->nextHandler = $nextHandler;
$this->operationsClient = $operationsClient;
$this->descriptor = $descriptor;
}
public function __invoke(Call $call, array $options)
{
$next = $this->nextHandler;
return $next(
$call,
$options
)->then(function (Message $response) {
$options = $this->descriptor + [
'lastProtoResponse' => $response
];
$operationNameMethod = $options['operationNameMethod'] ?? 'getName';
$operationName = call_user_func([$response, $operationNameMethod]);
return new OperationResponse($operationName, $this->operationsClient, $options);
});
}
}

View File

@@ -0,0 +1,67 @@
<?php
/*
* Copyright 2018 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\Middleware;
use Google\ApiCore\ArrayTrait;
use Google\ApiCore\Call;
/**
* Middleware which filters the $options array.
*
* @internal
*/
class OptionsFilterMiddleware implements MiddlewareInterface
{
use ArrayTrait;
/** @var callable */
private $nextHandler;
private array $permittedOptions;
public function __construct(
callable $nextHandler,
array $permittedOptions
) {
$this->nextHandler = $nextHandler;
$this->permittedOptions = $permittedOptions;
}
public function __invoke(Call $call, array $options)
{
$next = $this->nextHandler;
$filteredOptions = $this->pluckArray($this->permittedOptions, $options);
return $next(
$call,
$filteredOptions
);
}
}

View File

@@ -0,0 +1,80 @@
<?php
/*
* Copyright 2018 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\Middleware;
use Google\ApiCore\Call;
use Google\ApiCore\Page;
use Google\ApiCore\PagedListResponse;
use Google\ApiCore\PageStreamingDescriptor;
use Google\Protobuf\Internal\Message;
/**
* Middleware which wraps the response in an PagedListResponses object.
*
* @internal
*/
class PagedMiddleware implements MiddlewareInterface
{
/** @var callable */
private $nextHandler;
private PageStreamingDescriptor $descriptor;
/**
* @param callable $nextHandler
* @param PageStreamingDescriptor $descriptor
*/
public function __construct(
callable $nextHandler,
PageStreamingDescriptor $descriptor
) {
$this->nextHandler = $nextHandler;
$this->descriptor = $descriptor;
}
public function __invoke(Call $call, array $options)
{
$next = $this->nextHandler;
$descriptor = $this->descriptor;
return $next($call, $options)->then(
function (Message $response) use ($call, $next, $options, $descriptor) {
$page = new Page(
$call,
$options,
$next,
$descriptor,
$response
);
return new PagedListResponse($page);
}
);
}
}

View File

@@ -0,0 +1,104 @@
<?php
/*
* Copyright 2024 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\Middleware;
use Google\Api\FieldInfo\Format;
use Google\ApiCore\Call;
use GuzzleHttp\Promise\PromiseInterface;
use Ramsey\Uuid\Uuid;
/**
* Middleware that adds autopopulation functionality. This middlware is
* added iff auto population settings are present in the resource
* descriptor config for the rpc method in context.
*
* @internal
*/
class RequestAutoPopulationMiddleware implements MiddlewareInterface
{
/** @var callable */
private $nextHandler;
/** @var array<string, string> */
private $autoPopulationSettings;
public function __construct(
callable $nextHandler,
array $autoPopulationSettings
) {
$this->nextHandler = $nextHandler;
$this->autoPopulationSettings = $autoPopulationSettings;
}
/**
* @param Call $call
* @param array $options
*
* @return PromiseInterface
*/
public function __invoke(Call $call, array $options)
{
$next = $this->nextHandler;
if (empty($this->autoPopulationSettings)) {
return $next($call, $options);
}
$request = $call->getMessage();
foreach ($this->autoPopulationSettings as $fieldName => $valueType) {
$getFieldName = 'get' . ucwords($fieldName);
// We use a getter instead of a hazzer here because there's no need to
// differentiate between isset and an empty default value. Even if a
// field is explicitly set to an empty string, we want to autopopulate it.
if (empty($request->$getFieldName())) {
$setFieldName = 'set' . ucwords($fieldName);
switch ($valueType) {
case Format::UUID4:
$request->$setFieldName(Uuid::uuid4()->toString());
break;
default:
throw new \UnexpectedValueException(sprintf(
'Value type %s::%s not supported for auto population of the field %s',
Format::class,
Format::name($valueType),
$fieldName
));
}
}
}
$call = $call->withMessage($request);
return $next(
$call,
$options
);
}
}

View File

@@ -0,0 +1,73 @@
<?php
/*
* Copyright 2018 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\Middleware;
use Google\ApiCore\Call;
use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\PromiseInterface;
/**
* Middleware which transforms $response into [$response, $metadata]
*
* @internal
*/
class ResponseMetadataMiddleware implements MiddlewareInterface
{
/** @var callable */
private $nextHandler;
/**
* @param callable $nextHandler
*/
public function __construct(callable $nextHandler)
{
$this->nextHandler = $nextHandler;
}
public function __invoke(Call $call, array $options)
{
$metadataReceiver = new Promise();
$options['metadataCallback'] = function ($metadata) use ($metadataReceiver) {
$metadataReceiver->resolve($metadata);
};
$next = $this->nextHandler;
return $next($call, $options)->then(
function ($response) use ($metadataReceiver) {
if ($metadataReceiver->getState() === PromiseInterface::FULFILLED) {
return [$response, $metadataReceiver->wait()];
} else {
return [$response, []];
}
}
);
}
}

View File

@@ -0,0 +1,201 @@
<?php
/*
* Copyright 2018 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\Middleware;
use Google\ApiCore\ApiException;
use Google\ApiCore\ApiStatus;
use Google\ApiCore\Call;
use Google\ApiCore\RetrySettings;
use GuzzleHttp\Promise\PromiseInterface;
/**
* Middleware that adds retry functionality.
*
* @internal
*/
class RetryMiddleware implements MiddlewareInterface
{
/** @var callable */
private $nextHandler;
private RetrySettings $retrySettings;
private ?float $deadlineMs;
/*
* The number of retries that have already been attempted.
* The original API call will have $retryAttempts set to 0.
*/
private int $retryAttempts;
public function __construct(
callable $nextHandler,
RetrySettings $retrySettings,
$deadlineMs = null,
$retryAttempts = 0
) {
$this->nextHandler = $nextHandler;
$this->retrySettings = $retrySettings;
$this->deadlineMs = $deadlineMs;
$this->retryAttempts = $retryAttempts;
}
/**
* @param Call $call
* @param array $options
*
* @return PromiseInterface
*/
public function __invoke(Call $call, array $options)
{
$nextHandler = $this->nextHandler;
if (!isset($options['timeoutMillis'])) {
// default to "noRetriesRpcTimeoutMillis" when retries are disabled, otherwise use "initialRpcTimeoutMillis"
if (!$this->retrySettings->retriesEnabled() && $this->retrySettings->getNoRetriesRpcTimeoutMillis() > 0) {
$options['timeoutMillis'] = $this->retrySettings->getNoRetriesRpcTimeoutMillis();
} elseif ($this->retrySettings->getInitialRpcTimeoutMillis() > 0) {
$options['timeoutMillis'] = $this->retrySettings->getInitialRpcTimeoutMillis();
}
}
// Setting the retry attempt for logging
if ($this->retryAttempts > 0) {
$options['retryAttempt'] = $this->retryAttempts;
}
// Call the handler immediately if retry settings are disabled.
if (!$this->retrySettings->retriesEnabled()) {
return $nextHandler($call, $options);
}
return $nextHandler($call, $options)->then(null, function ($e) use ($call, $options) {
$retryFunction = $this->getRetryFunction();
// If the number of retries has surpassed the max allowed retries
// then throw the exception as we normally would.
// If the maxRetries is set to 0, then we don't check this condition.
if (0 !== $this->retrySettings->getMaxRetries()
&& $this->retryAttempts >= $this->retrySettings->getMaxRetries()
) {
throw $e;
}
// If the retry function returns false then throw the
// exception as we normally would.
if (!$retryFunction($e, $options)) {
throw $e;
}
// Retry function returned true, so we attempt another retry
return $this->retry($call, $options, $e->getStatus());
});
}
/**
* @param Call $call
* @param array $options
* @param string $status
*
* @return PromiseInterface
* @throws ApiException
*/
private function retry(Call $call, array $options, string $status)
{
$delayMult = $this->retrySettings->getRetryDelayMultiplier();
$maxDelayMs = $this->retrySettings->getMaxRetryDelayMillis();
$timeoutMult = $this->retrySettings->getRpcTimeoutMultiplier();
$maxTimeoutMs = $this->retrySettings->getMaxRpcTimeoutMillis();
$totalTimeoutMs = $this->retrySettings->getTotalTimeoutMillis();
$delayMs = $this->retrySettings->getInitialRetryDelayMillis();
$timeoutMs = $options['timeoutMillis'];
$currentTimeMs = $this->getCurrentTimeMs();
$deadlineMs = $this->deadlineMs ?: $currentTimeMs + $totalTimeoutMs;
if ($currentTimeMs >= $deadlineMs) {
throw new ApiException(
'Retry total timeout exceeded.',
\Google\Rpc\Code::DEADLINE_EXCEEDED,
ApiStatus::DEADLINE_EXCEEDED
);
}
$delayMs = min($delayMs * $delayMult, $maxDelayMs);
$timeoutMs = (int) min(
$timeoutMs * $timeoutMult,
$maxTimeoutMs,
$deadlineMs - $this->getCurrentTimeMs()
);
$nextHandler = new RetryMiddleware(
$this->nextHandler,
$this->retrySettings->with([
'initialRetryDelayMillis' => $delayMs,
]),
$deadlineMs,
$this->retryAttempts + 1
);
// Set the timeout for the call
$options['timeoutMillis'] = $timeoutMs;
return $nextHandler(
$call,
$options
);
}
protected function getCurrentTimeMs()
{
return microtime(true) * 1000.0;
}
/**
* This is the default retry behaviour.
*/
private function getRetryFunction()
{
return $this->retrySettings->getRetryFunction() ??
function (\Throwable $e, array $options): bool {
// This is the default retry behaviour, i.e. we don't retry an ApiException
// and for other exception types, we only retry when the error code is in
// the list of retryable error codes.
if (!$e instanceof ApiException) {
return false;
}
if (!in_array($e->getStatus(), $this->retrySettings->getRetryableCodes())) {
return false;
}
return true;
};
}
}

View File

@@ -0,0 +1,60 @@
<?php
/*
* Copyright 2025 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\Middleware;
use Google\ApiCore\Call;
use Google\ApiCore\Transport\TransportInterface;
/**
* A Middleware in charge of handling the end of the callstack to call the transport layer.
* This middleware is made so the callstack in the GapicClientTrait is always a middleware.
*
* @internal
*/
class TransportCallMiddleware implements MiddlewareInterface
{
/*
* @param TransportInterface $transport
* @param array $transportCallMethods
*/
public function __construct(
private TransportInterface $transport,
private array $transportCallMethods
)
{}
public function __invoke(Call $call, array $options)
{
$startCallMethod = $this->transportCallMethods[$call->getCallType()];
return $this->transport->$startCallMethod($call, $options);
}
}

View File

@@ -0,0 +1,539 @@
<?php
/*
* Copyright 2016 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore;
use Google\LongRunning\CancelOperationRequest;
use Google\LongRunning\Client\OperationsClient;
use Google\LongRunning\DeleteOperationRequest;
use Google\LongRunning\GetOperationRequest;
use Google\LongRunning\Operation;
use Google\LongRunning\OperationsClient as LegacyOperationsClient;
use Google\Protobuf\Any;
use Google\Protobuf\Internal\Message;
use Google\Rpc\Status;
use LogicException;
/**
* Response object from a long running API method.
*
* The OperationResponse object is returned by API methods that perform
* a long running operation. It provides methods that can be used to
* poll the status of the operation, retrieve the results, and cancel
* the operation.
*
* To support a long running operation, the server must implement the
* Operations API, which is used by the OperationResponse object. If
* more control is required, it is possible to make calls against the
* Operations API directly instead of via the OperationResponse object
* using an Operations Client instance.
*
* @template T = mixed
*/
class OperationResponse
{
use PollingTrait;
const DEFAULT_POLLING_INTERVAL = 1000;
const DEFAULT_POLLING_MULTIPLIER = 2;
const DEFAULT_MAX_POLLING_INTERVAL = 60000;
const DEFAULT_MAX_POLLING_DURATION = 0;
private const NEW_CLIENT_NAMESPACE = '\\Client\\';
private string $operationName;
private ?object $operationsClient;
private ?string $operationReturnType;
private ?string $metadataReturnType;
private array $defaultPollSettings = [
'initialPollDelayMillis' => self::DEFAULT_POLLING_INTERVAL,
'pollDelayMultiplier' => self::DEFAULT_POLLING_MULTIPLIER,
'maxPollDelayMillis' => self::DEFAULT_MAX_POLLING_INTERVAL,
'totalPollTimeoutMillis' => self::DEFAULT_MAX_POLLING_DURATION,
];
private ?object $lastProtoResponse;
private bool $deleted = false;
private array $additionalArgs;
private string $getOperationMethod;
private ?string $cancelOperationMethod;
private ?string $deleteOperationMethod;
private string $getOperationRequest;
private ?string $cancelOperationRequest;
private ?string $deleteOperationRequest;
private string $operationStatusMethod;
/** @var mixed */
private $operationStatusDoneValue;
private ?string $operationErrorCodeMethod;
private ?string $operationErrorMessageMethod;
/**
* OperationResponse constructor.
*
* @param string $operationName
* @param object $operationsClient
* @param array $options {
* Optional. Options for configuring the operation response object.
*
* @type string $operationReturnType The return type of the longrunning operation.
* @type string $metadataReturnType The type of the metadata returned in the operation response.
* @type int $initialPollDelayMillis The initial polling interval to use, in milliseconds.
* @type int $pollDelayMultiplier Multiplier applied to the polling interval on each retry.
* @type int $maxPollDelayMillis The maximum polling interval to use, in milliseconds.
* @type int $totalPollTimeoutMillis The maximum amount of time to continue polling.
* @type object $lastProtoResponse A response already received from the server.
* @type string $getOperationMethod The method on $operationsClient to get the operation.
* @type string $cancelOperationMethod The method on $operationsClient to cancel the operation.
* @type string $deleteOperationMethod The method on $operationsClient to delete the operation.
* @type string $operationStatusMethod The method on the operation to get the status.
* @type string $operationStatusDoneValue The method on the operation to determine if the status is done.
* @type array $additionalOperationArguments Additional arguments to pass to $operationsClient methods.
* @type string $operationErrorCodeMethod The method on the operation to get the error code
* @type string $operationErrorMessageMethod The method on the operation to get the error status
* }
*/
public function __construct(string $operationName, $operationsClient, array $options = [])
{
$this->operationName = $operationName;
$this->operationsClient = $operationsClient;
$options += [
'operationReturnType' => null,
'metadataReturnType' => null,
'lastProtoResponse' => null,
'getOperationMethod' => 'getOperation',
'cancelOperationMethod' => 'cancelOperation',
'deleteOperationMethod' => 'deleteOperation',
'operationStatusMethod' => 'getDone',
'operationStatusDoneValue' => true,
'additionalOperationArguments' => [],
'operationErrorCodeMethod' => null,
'operationErrorMessageMethod' => null,
'getOperationRequest' => GetOperationRequest::class,
'cancelOperationRequest' => CancelOperationRequest::class,
'deleteOperationRequest' => DeleteOperationRequest::class,
];
$this->operationReturnType = $options['operationReturnType'];
$this->metadataReturnType = $options['metadataReturnType'];
$this->lastProtoResponse = $options['lastProtoResponse'];
$this->getOperationMethod = $options['getOperationMethod'];
$this->cancelOperationMethod = $options['cancelOperationMethod'];
$this->deleteOperationMethod = $options['deleteOperationMethod'];
$this->additionalArgs = $options['additionalOperationArguments'];
$this->operationStatusMethod = $options['operationStatusMethod'];
$this->operationStatusDoneValue = $options['operationStatusDoneValue'];
$this->operationErrorCodeMethod = $options['operationErrorCodeMethod'];
$this->operationErrorMessageMethod = $options['operationErrorMessageMethod'];
$this->getOperationRequest = $options['getOperationRequest'];
$this->cancelOperationRequest = $options['cancelOperationRequest'];
$this->deleteOperationRequest = $options['deleteOperationRequest'];
if (isset($options['initialPollDelayMillis'])) {
$this->defaultPollSettings['initialPollDelayMillis'] = $options['initialPollDelayMillis'];
}
if (isset($options['pollDelayMultiplier'])) {
$this->defaultPollSettings['pollDelayMultiplier'] = $options['pollDelayMultiplier'];
}
if (isset($options['maxPollDelayMillis'])) {
$this->defaultPollSettings['maxPollDelayMillis'] = $options['maxPollDelayMillis'];
}
if (isset($options['totalPollTimeoutMillis'])) {
$this->defaultPollSettings['totalPollTimeoutMillis'] = $options['totalPollTimeoutMillis'];
}
}
/**
* Check whether the operation has completed.
*
* @return bool
*/
public function isDone()
{
if (!$this->hasProtoResponse()) {
return false;
}
$status = call_user_func([$this->lastProtoResponse, $this->operationStatusMethod]);
if (is_null($status)) {
return false;
}
return $status === $this->operationStatusDoneValue;
}
/**
* Check whether the operation completed successfully. If the operation is not complete, or if the operation
* failed, return false.
*
* @return bool
*/
public function operationSucceeded()
{
if (!$this->hasProtoResponse()) {
return false;
}
if (!$this->canHaveResult()) {
// For Operations which do not have a result, we consider a successful
// operation when the operation has completed without errors.
return $this->isDone() && !$this->hasErrors();
}
return !is_null($this->getResult());
}
/**
* Check whether the operation failed. If the operation is not complete, or if the operation
* succeeded, return false.
*
* @return bool
*/
public function operationFailed()
{
return $this->hasErrors();
}
/**
* Get the formatted name of the operation
*
* @return string The formatted name of the operation
*/
public function getName()
{
return $this->operationName;
}
/**
* Poll the server in a loop until the operation is complete.
*
* Return true if the operation completed, otherwise return false. If the
* $options['totalPollTimeoutMillis'] setting is not set (or set <= 0) then
* pollUntilComplete will continue polling until the operation completes,
* and therefore will always return true.
*
* @param array $options {
* Options for configuring the polling behaviour.
*
* @type int $initialPollDelayMillis The initial polling interval to use, in milliseconds.
* @type int $pollDelayMultiplier Multiplier applied to the polling interval on each retry.
* @type int $maxPollDelayMillis The maximum polling interval to use, in milliseconds.
* @type int $totalPollTimeoutMillis The maximum amount of time to continue polling, in milliseconds.
* }
* @throws ApiException If an API call fails.
* @throws ValidationException
* @return bool Indicates if the operation completed.
*/
public function pollUntilComplete(array $options = [])
{
if ($this->isDone()) {
return true;
}
$pollSettings = array_merge($this->defaultPollSettings, $options);
return $this->poll(function () {
$this->reload();
return $this->isDone();
}, $pollSettings);
}
/**
* Reload the status of the operation with a request to the service.
*
* @throws ApiException If the API call fails.
* @throws ValidationException If called on a deleted operation.
*/
public function reload()
{
if ($this->deleted) {
throw new ValidationException('Cannot call reload() on a deleted operation');
}
$requestClass = $this->isNewSurfaceOperationsClient() ? $this->getOperationRequest : null;
$this->lastProtoResponse = $this->operationsCall($this->getOperationMethod, $requestClass);
}
/**
* Return the result of the operation. If operationSucceeded() is false,
* return null.
*
* @return T|null
*/
public function getResult()
{
if (!$this->hasProtoResponse()) {
return null;
}
if (!$this->canHaveResult()) {
return null;
}
if (!$this->isDone()) {
return null;
}
/** @var Any|null $anyResponse */
$anyResponse = $this->lastProtoResponse->getResponse();
if (is_null($anyResponse)) {
return null;
}
if (is_null($this->operationReturnType)) {
return $anyResponse;
}
$operationReturnType = $this->operationReturnType;
/** @var Message $response */
$response = new $operationReturnType();
$response->mergeFromString($anyResponse->getValue());
return $response;
}
/**
* If the operation failed, return the status. If operationFailed() is false, return null.
*
* @return Status|null The status of the operation in case of failure, or null if
* operationFailed() is false.
*/
public function getError()
{
if (!$this->hasProtoResponse() || !$this->isDone()) {
return null;
}
if ($this->operationErrorCodeMethod || $this->operationErrorMessageMethod) {
$errorCode = $this->operationErrorCodeMethod
? call_user_func([$this->lastProtoResponse, $this->operationErrorCodeMethod])
: null;
$errorMessage = $this->operationErrorMessageMethod
? call_user_func([$this->lastProtoResponse, $this->operationErrorMessageMethod])
: null;
return (new Status())
->setCode(ApiStatus::rpcCodeFromHttpStatusCode($errorCode))
->setMessage($errorMessage);
}
if (method_exists($this->lastProtoResponse, 'getError')) {
return $this->lastProtoResponse->getError();
}
return null;
}
/**
* Get an array containing the values of 'operationReturnType', 'metadataReturnType', and
* the polling options `initialPollDelayMillis`, `pollDelayMultiplier`, `maxPollDelayMillis`,
* and `totalPollTimeoutMillis`. The array can be passed as the $options argument to the
* constructor when creating another OperationResponse object.
*
* @return array
*/
public function getDescriptorOptions()
{
return [
'operationReturnType' => $this->operationReturnType,
'metadataReturnType' => $this->metadataReturnType,
] + $this->defaultPollSettings;
}
/**
* @return Operation|mixed|null The last Operation object received from the server.
*/
public function getLastProtoResponse()
{
return $this->lastProtoResponse;
}
/**
* @return object The OperationsClient object used to make
* requests to the operations API.
*/
public function getOperationsClient()
{
return $this->operationsClient;
}
/**
* Cancel the long-running operation.
*
* For operations of type Google\LongRunning\Operation, this method starts
* asynchronous cancellation on a long-running operation. The server
* makes a best effort to cancel the operation, but success is not
* guaranteed. If the server doesn't support this method, it will throw an
* ApiException with code \Google\Rpc\Code::UNIMPLEMENTED. Clients can continue
* to use reload and pollUntilComplete methods to check whether the cancellation
* succeeded or whether the operation completed despite cancellation.
* On successful cancellation, the operation is not deleted; instead, it becomes
* an operation with a getError() value with a \Google\Rpc\Status code of 1,
* corresponding to \Google\Rpc\Code::CANCELLED.
*
* @throws ApiException If the API call fails.
* @throws LogicException If the API call method has not been configured
*/
public function cancel()
{
if (is_null($this->cancelOperationMethod)) {
throw new LogicException('The cancel operation is not supported by this API');
}
$requestClass = $this->isNewSurfaceOperationsClient() ? $this->cancelOperationRequest : null;
$this->operationsCall($this->cancelOperationMethod, $requestClass);
}
/**
* Delete the long-running operation.
*
* For operations of type Google\LongRunning\Operation, this method
* indicates that the client is no longer interested in the operation result.
* It does not cancel the operation. If the server doesn't support this method,
* it will throw an ApiException with code \Google\Rpc\Code::UNIMPLEMENTED.
*
* @throws ApiException If the API call fails.
* @throws LogicException If the API call method has not been configured
*/
public function delete()
{
if (is_null($this->deleteOperationMethod)) {
throw new LogicException('The delete operation is not supported by this API');
}
$requestClass = $this->isNewSurfaceOperationsClient() ? $this->deleteOperationRequest : null;
$this->operationsCall($this->deleteOperationMethod, $requestClass);
$this->deleted = true;
}
/**
* Get the metadata returned with the last proto response. If a metadata type was provided, then
* the return value will be of that type - otherwise, the return value will be of type Any. If
* no metadata object is available, returns null.
*
* @return mixed The metadata returned from the server in the last response.
*/
public function getMetadata()
{
if (!$this->hasProtoResponse()) {
return null;
}
if (!method_exists($this->lastProtoResponse, 'getMetadata')) {
// The call to getMetadata is only for OnePlatform LROs, and is not
// supported by other LRO GAPIC clients (e.g. Compute)
return null;
}
/** @var Any|null $any */
$any = $this->lastProtoResponse->getMetadata();
if (is_null($this->metadataReturnType)) {
return $any;
}
if (is_null($any)) {
return null;
}
// @TODO: This is probably not doing anything and can be removed in the next release.
if (is_null($any->getValue())) {
return null;
}
$metadataReturnType = $this->metadataReturnType;
/** @var Message $metadata */
$metadata = new $metadataReturnType();
$metadata->mergeFromString($any->getValue());
return $metadata;
}
/**
* Call the operations client to perform an operation.
*
* @param string $method The method to call on the operations client.
* @param string|null $requestClass The request class to use for the call.
* Will be null for legacy operations clients.
*/
private function operationsCall(string $method, ?string $requestClass)
{
// V1 GAPIC clients have an empty $requestClass
if (empty($requestClass)) {
if ($this->additionalArgs) {
return $this->operationsClient->$method(
$this->getName(),
...array_values($this->additionalArgs)
);
}
return $this->operationsClient->$method($this->getName());
}
if (!method_exists($requestClass, 'build')) {
throw new LogicException('Request class must support the static build method');
}
// In V2 of Compute, the Request "build" methods contain the operation ID last instead
// of first. Compute is the only API which uses $additionalArgs, so switching the order
// will not break anything.
$request = $requestClass::build(...array_merge(
array_values($this->additionalArgs),
[$this->getName()]
));
return $this->operationsClient->$method($request);
}
private function canHaveResult()
{
// The call to getResponse is only for OnePlatform LROs, and is not
// supported by other LRO GAPIC clients (e.g. Compute)
return method_exists($this->lastProtoResponse, 'getResponse');
}
private function hasErrors()
{
if (!$this->hasProtoResponse()) {
return false;
}
if (method_exists($this->lastProtoResponse, 'getError')) {
return !empty($this->lastProtoResponse->getError());
}
if ($this->operationErrorCodeMethod) {
$errorCode = call_user_func([$this->lastProtoResponse, $this->operationErrorCodeMethod]);
return !empty($errorCode);
}
// This should never happen unless an API is misconfigured
throw new LogicException('Unable to determine operation error status for this service');
}
private function hasProtoResponse()
{
return !is_null($this->lastProtoResponse);
}
private function isNewSurfaceOperationsClient(): bool
{
return !$this->operationsClient instanceof LegacyOperationsClient
&& false !== strpos(get_class($this->operationsClient), self::NEW_CLIENT_NAMESPACE);
}
}

View File

@@ -0,0 +1,160 @@
<?php
/*
* Copyright 2023 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\Options;
use ArrayAccess;
use Google\ApiCore\CredentialsWrapper;
use Google\ApiCore\RetrySettings;
/**
* The CallOptions class provides typing to the associative array of options
* passed to transport RPC methods. See
* {@see \Google\ApiCore\Transport\TransportInterface::startUnaryCall()},
* {@see \Google\ApiCore\Transport\TransportInterface::startBidiStreamingCall()},
* {@see \Google\ApiCore\Transport\TransportInterface::startClientStreamingCall()}, and
* {@see \Google\ApiCore\Transport\TransportInterface::startServerStreamingCall()}.
*/
class CallOptions implements ArrayAccess, OptionsInterface
{
use OptionsTrait;
private array $headers;
private ?int $timeoutMillis;
private array $transportOptions;
/** @var RetrySettings|array|null $retrySettings */
private $retrySettings;
/**
* @param array $options {
* Call options
*
* @type array<string, array<string>> $headers
* Key-value array containing headers.
* @type int $timeoutMillis
* The timeout in milliseconds for the call.
* @type array $transportOptions
* Transport-specific call options. See {@see CallOptions::setTransportOptions}.
* @type RetrySettings|array $retrySettings
* A retry settings override for the call. If $retrySettings is an
* array, the settings will be merged with the method's default
* retry settings. If $retrySettings is a RetrySettings object,
* that object will be used instead of the method defaults.
* }
*/
public function __construct(array $options)
{
$this->fromArray($options);
}
/**
* Sets the array of options as class properites.
*
* @param array $arr See the constructor for the list of supported options.
*/
private function fromArray(array $arr): void
{
$this->setHeaders($arr['headers'] ?? []);
$this->setTimeoutMillis($arr['timeoutMillis'] ?? null);
$this->setTransportOptions($arr['transportOptions'] ?? []);
$this->setRetrySettings($arr['retrySettings'] ?? null);
}
/**
* @param array $headers
*/
public function setHeaders(array $headers): self
{
$this->headers = $headers;
return $this;
}
/**
* @param int|null $timeoutMillis
*/
public function setTimeoutMillis(?int $timeoutMillis): self
{
$this->timeoutMillis = $timeoutMillis;
return $this;
}
/**
* @param array $transportOptions {
* Transport-specific call-time options.
*
* @type array $grpcOptions
* Key-value pairs for gRPC-specific options passed as the `$options` argument to {@see \Grpc\BaseStub}
* request methods. Current options are `call_credentials_callback` and `timeout`.
* **NOTE**: This library sets `call_credentials_callback` using {@see CredentialsWrapper}, and `timeout`
* using the `timeoutMillis` call option, so these options are not very useful.
* @type array $grpcFallbackOptions
* Key-value pairs for gRPC fallback specific options passed as the `$options` argument to the
* `$httpHandler` callable. By default these are passed to {@see \GuzzleHttp\Client} as request options.
* See {@link https://docs.guzzlephp.org/en/stable/request-options.html}.
* @type array $restOptions
* Key-value pairs for REST-specific options passed as the `$options` argument to the `$httpHandler`
* callable. By default these are passed to {@see \GuzzleHttp\Client} as request options.
* See {@link https://docs.guzzlephp.org/en/stable/request-options.html}.
* }
*/
public function setTransportOptions(array $transportOptions): self
{
$this->transportOptions = $transportOptions;
return $this;
}
/**
* @deprecated use CallOptions::setTransportOptions
*/
public function setTransportSpecificOptions(array $transportSpecificOptions): self
{
$this->setTransportOptions($transportSpecificOptions);
return $this;
}
/**
* @param RetrySettings|array|null $retrySettings
*
* @return $this
*/
public function setRetrySettings($retrySettings): self
{
$this->retrySettings = $retrySettings;
return $this;
}
}

View File

@@ -0,0 +1,421 @@
<?php
/*
* Copyright 2023 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\Options;
use ArrayAccess;
use Closure;
use Google\ApiCore\CredentialsWrapper;
use Google\ApiCore\Transport\TransportInterface;
use Google\Auth\FetchAuthTokenInterface;
use InvalidArgumentException;
use Psr\Log\LoggerInterface;
/**
* The ClientOptions class adds typing to the associative array of options
* passed into each API client constructor. To use this class directly, pass
* the result of {@see \Google\ApiCore\Options\ClientOptions::toArray()} to the
* client constructor:
*
* ```
* use Google\ApiCore\ClientOptions;
* use Google\Cloud\SecretManager\Client\SecretManagerClient;
*
* $options = new ClientOptions([
* 'credentials' => '/path/to/my/credentials.json'
* ]);
* $secretManager = new SecretManagerClient($options->toArray());
* ```
*
* Note: It's possible to pass an associative array to the API clients as well,
* as ClientOptions will still be used internally for validation.
*/
class ClientOptions implements ArrayAccess, OptionsInterface
{
use OptionsTrait;
private ?string $apiEndpoint;
private bool $disableRetries;
private array $clientConfig;
/** @var string|array|FetchAuthTokenInterface|CredentialsWrapper|null */
private $credentials;
private array $credentialsConfig;
/** @var string|TransportInterface|null $transport */
private $transport;
private TransportOptions $transportConfig;
private ?string $versionFile;
private ?string $descriptorsConfigPath;
private ?string $serviceName;
private ?string $libName;
private ?string $libVersion;
private ?string $gapicVersion;
private ?Closure $clientCertSource;
private ?string $universeDomain;
private ?string $apiKey;
private null|false|LoggerInterface $logger;
/**
* @param array $options {
* @type string $apiEndpoint
* The address of the API remote host, for example "example.googleapis.com. May also
* include the port, for example "example.googleapis.com:443"
* @type bool $disableRetries
* Determines whether or not retries defined by the client configuration should be
* disabled. Defaults to `false`.
* @type string|array $clientConfig
* Client method configuration, including retry settings. This option can be either a
* path to a JSON file, or a PHP array containing the decoded JSON data.
* By default this settings points to the default client config file, which is provided
* in the resources folder.
* @type FetchAuthTokenInterface|CredentialsWrapper $credentials
* This option should only be used with a pre-constructed \Google\Auth\FetchAuthTokenInterface
* object or \Google\ApiCore\CredentialsWrapper object. Note that when one of these objects
* are provided, any settings in $authConfig will be ignored.
* **Important**: If you are providing a path to a credentials file, or a decoded credentials
* file as a PHP array, this usage is now DEPRECATED. Providing an unvalidated credential
* configuration to Google APIs can compromise the security of your systems and data. It is now
* recommended to create the credentials explicitly:
* ```
* use Google\Auth\Credentials\ServiceAccountCredentials;
* use Google\ApiCore\Options\ClientOptions;
* $creds = new ServiceAccountCredentials($scopes, $json);
* $options = new ClientOptions(['credentials' => $creds]);
* ```
* For more information
* {@see https://cloud.google.com/docs/authentication/external/externally-sourced-credentials}
* @type array $credentialsConfig
* Options used to configure credentials, including auth token caching, for the client.
* For a full list of supporting configuration options, see
* \Google\ApiCore\CredentialsWrapper::build.
* @type string|TransportInterface|null $transport
* The transport used for executing network requests. May be either the string `rest`,
* `grpc`, or 'grpc-fallback'. Defaults to `grpc` if gRPC support is detected on the system.
* *Advanced usage*: Additionally, it is possible to pass in an already instantiated
* TransportInterface object. Note that when this objects is provided, any settings in
* $transportConfig, and any `$apiEndpoint` setting, will be ignored.
* @type array $transportConfig
* Configuration options that will be used to construct the transport. Options for
* each supported transport type should be passed in a key for that transport. For
* example:
* $transportConfig = [
* 'grpc' => [...],
* 'rest' => [...],
* 'grpc-fallback' => [...],
* ];
* See the GrpcTransport::build and RestTransport::build
* methods for the supported options.
* @type string $versionFile
* The path to a file which contains the current version of the client.
* @type string $descriptorsConfigPath
* The path to a descriptor configuration file.
* @type string $serviceName
* The name of the service.
* @type string $libName
* The name of the client application.
* @type string $libVersion
* The version of the client application.
* @type string $gapicVersion
* The code generator version of the GAPIC library.
* @type callable $clientCertSource
* A callable which returns the client cert as a string.
* @type string $universeDomain
* The default service domain for a given Cloud universe.
* @type string $apiKey
* The API key to be used for the client.
* @type null|false|LoggerInterface
* A PSR-3 compliant logger.
* }
*/
public function __construct(array $options)
{
$this->fromArray($options);
}
/**
* Sets the array of options as class properites.
*
* @param array $arr See the constructor for the list of supported options.
*/
private function fromArray(array $arr): void
{
$this->setApiEndpoint($arr['apiEndpoint'] ?? null);
$this->setDisableRetries($arr['disableRetries'] ?? false);
$this->setClientConfig($arr['clientConfig'] ?? []);
$this->setCredentials($arr['credentials'] ?? null);
$this->setCredentialsConfig($arr['credentialsConfig'] ?? []);
$this->setTransport($arr['transport'] ?? null);
$this->setTransportConfig(new TransportOptions($arr['transportConfig'] ?? []));
$this->setVersionFile($arr['versionFile'] ?? null);
$this->setDescriptorsConfigPath($arr['descriptorsConfigPath'] ?? null);
$this->setServiceName($arr['serviceName'] ?? null);
$this->setLibName($arr['libName'] ?? null);
$this->setLibVersion($arr['libVersion'] ?? null);
$this->setGapicVersion($arr['gapicVersion'] ?? null);
$this->setClientCertSource($arr['clientCertSource'] ?? null);
$this->setUniverseDomain($arr['universeDomain'] ?? null);
$this->setApiKey($arr['apiKey'] ?? null);
$this->setLogger($arr['logger'] ?? null);
}
/**
* @param ?string $apiEndpoint
*
* @return $this
*/
public function setApiEndpoint(?string $apiEndpoint): self
{
$this->apiEndpoint = $apiEndpoint;
return $this;
}
/**
* @param bool $disableRetries
*
* @return $this
*/
public function setDisableRetries(bool $disableRetries): self
{
$this->disableRetries = $disableRetries;
return $this;
}
/**
* @param string|array $clientConfig
*
* @return $this
* @throws InvalidArgumentException
*/
public function setClientConfig($clientConfig): self
{
if (is_string($clientConfig)) {
$this->clientConfig = json_decode(file_get_contents($clientConfig), true);
} elseif (is_array($clientConfig)) {
$this->clientConfig = $clientConfig;
} else {
throw new InvalidArgumentException('Invalid client config');
}
return $this;
}
/**
* @param string|array|FetchAuthTokenInterface|CredentialsWrapper|null $credentials
*
* @return $this
*/
public function setCredentials($credentials): self
{
$this->credentials = $credentials;
return $this;
}
/**
* @param array $credentialsConfig
*
* @return $this
*/
public function setCredentialsConfig(array $credentialsConfig): self
{
$this->credentialsConfig = $credentialsConfig;
return $this;
}
/**
* @param string|TransportInterface|null $transport
*
* @return $this
*/
public function setTransport($transport): self
{
$this->transport = $transport;
return $this;
}
/**
* @param TransportOptions $transportConfig
*
* @return $this
*/
public function setTransportConfig(TransportOptions $transportConfig): self
{
$this->transportConfig = $transportConfig;
return $this;
}
/**
* @param ?string $versionFile
*
* @return $this
*/
public function setVersionFile(?string $versionFile): self
{
$this->versionFile = $versionFile;
return $this;
}
/**
* @param ?string $descriptorsConfigPath
*
* @return $this
*/
private function setDescriptorsConfigPath(?string $descriptorsConfigPath): self
{
if (!is_null($descriptorsConfigPath)) {
self::validateFileExists($descriptorsConfigPath);
}
$this->descriptorsConfigPath = $descriptorsConfigPath;
return $this;
}
/**
* @param ?string $serviceName
*
* @return $this
*/
public function setServiceName(?string $serviceName): self
{
$this->serviceName = $serviceName;
return $this;
}
/**
* @param ?string $libName
*
* @return $this
*/
public function setLibName(?string $libName): self
{
$this->libName = $libName;
return $this;
}
/**
* @param ?string $libVersion
*
* @return $this
*/
public function setLibVersion(?string $libVersion): self
{
$this->libVersion = $libVersion;
return $this;
}
/**
* @param ?string $gapicVersion
*
* @return $this
*/
public function setGapicVersion(?string $gapicVersion): self
{
$this->gapicVersion = $gapicVersion;
return $this;
}
/**
* @param ?callable $clientCertSource
*
* @return $this
*/
public function setClientCertSource(?callable $clientCertSource): self
{
if (!is_null($clientCertSource)) {
$clientCertSource = Closure::fromCallable($clientCertSource);
}
$this->clientCertSource = $clientCertSource;
return $this;
}
/**
* @param string $universeDomain
*
* @return $this
*/
public function setUniverseDomain(?string $universeDomain): self
{
$this->universeDomain = $universeDomain;
return $this;
}
/**
* @param string $apiKey
*
* @return $this
*/
public function setApiKey(?string $apiKey): self
{
$this->apiKey = $apiKey;
return $this;
}
/**
* @param null|false|LoggerInterface $logger
*
* @return $this
*/
public function setLogger(null|false|LoggerInterface $logger): self
{
$this->logger = $logger;
return $this;
}
}

View File

@@ -0,0 +1,38 @@
<?php
/*
* Copyright 2025 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\Options;
interface OptionsInterface
{
public function toArray(): array;
}

View File

@@ -0,0 +1,95 @@
<?php
/*
* Copyright 2023 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\Options;
use BadMethodCallException;
use Google\ApiCore\ValidationException;
/**
* Trait implemented by any class representing an associative array of PHP options.
* This provides validation and typehinting to loosely typed associative arrays.
*/
trait OptionsTrait
{
/**
* @param string $filePath
* @throws ValidationException
*/
private static function validateFileExists(string $filePath)
{
if (!file_exists($filePath)) {
throw new ValidationException("Could not find specified file: $filePath");
}
}
public function offsetExists($offset): bool
{
return isset($this->$offset);
}
/**
* @return mixed
*/
#[\ReturnTypeWillChange]
public function offsetGet($offset)
{
return $this->$offset;
}
/**
* @throws BadMethodCallException
*/
public function offsetSet($offset, $value): void
{
throw new BadMethodCallException('Cannot set options through array access. Use the setters instead');
}
/**
* @throws BadMethodCallException
*/
public function offsetUnset($offset): void
{
throw new BadMethodCallException('Cannot unset options through array access. Use the setters instead');
}
public function toArray(): array
{
$arr = [];
foreach (get_object_vars($this) as $key => $value) {
$arr[$key] = $value instanceof OptionsInterface
? $value->toArray()
: $value;
}
return $arr;
}
}

View File

@@ -0,0 +1,111 @@
<?php
/*
* Copyright 2023 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\Options;
use ArrayAccess;
use Google\ApiCore\Options\TransportOptions\GrpcFallbackTransportOptions;
use Google\ApiCore\Options\TransportOptions\GrpcTransportOptions;
use Google\ApiCore\Options\TransportOptions\RestTransportOptions;
class TransportOptions implements ArrayAccess, OptionsInterface
{
use OptionsTrait;
private GrpcTransportOptions $grpc;
private GrpcFallbackTransportOptions $grpcFallback;
private RestTransportOptions $rest;
/**
* @param array $options {
* Config options used to construct the transport.
*
* @type array $grpc
* @type array $grpcFallback
* @type array $rest
* }
*/
public function __construct(array $options)
{
$this->fromArray($options);
}
/**
* Sets the array of options as class properites.
*
* @param array $arr See the constructor for the list of supported options.
*/
private function fromArray(array $arr): void
{
$this->setGrpc(new GrpcTransportOptions($arr['grpc'] ?? []));
$this->setGrpcFallback(new GrpcFallbackTransportOptions($arr['grpc-fallback'] ?? []));
$this->setRest(new RestTransportOptions($arr['rest'] ?? []));
}
/**
* @param GrpcTransportOptions $grpc
*
* @return $this
*/
public function setGrpc(GrpcTransportOptions $grpc): self
{
$this->grpc = $grpc;
return $this;
}
/**
* @param GrpcFallbackTransportOptions $grpcFallback
*
* @return $this
*/
public function setGrpcFallback(GrpcFallbackTransportOptions $grpcFallback): self
{
$this->grpcFallback = $grpcFallback;
return $this;
}
/**
* @param RestTransportOptions $rest
*
* @return $this
*/
public function setRest(RestTransportOptions $rest): self
{
$this->rest = $rest;
return $this;
}
}

View File

@@ -0,0 +1,125 @@
<?php
/*
* Copyright 2023 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\Options\TransportOptions;
use ArrayAccess;
use Closure;
use Google\ApiCore\Options\OptionsInterface;
use Google\ApiCore\Options\OptionsTrait;
use Psr\Log\LoggerInterface;
/**
* The GrpcFallbackTransportOptions class provides typing to the associative array of options used
* to configure {@see \Google\ApiCore\Transport\GrpcFallbackTransport}.
*/
class GrpcFallbackTransportOptions implements ArrayAccess, OptionsInterface
{
use OptionsTrait;
private ?Closure $clientCertSource;
private ?Closure $httpHandler;
private null|false|LoggerInterface $logger;
/**
* @param array $options {
* Config options used to construct the gRPC Fallback transport.
*
* @type callable $clientCertSource
* A callable which returns the client cert as a string.
* @type callable $httpHandler
* A handler used to deliver PSR-7 requests.
* @type null|false|LoggerInterface
* A PSR-3 logger interface instance.
* }
*/
public function __construct(array $options)
{
$this->fromArray($options);
}
/**
* Sets the array of options as class properites.
*
* @param array $arr See the constructor for the list of supported options.
*/
private function fromArray(array $arr): void
{
$this->setClientCertSource($arr['clientCertSource'] ?? null);
$this->setHttpHandler($arr['httpHandler'] ?? null);
$this->setLogger($arr['logger'] ?? null);
}
/**
* @param ?callable $httpHandler
*
* @return $this
*/
public function setHttpHandler(?callable $httpHandler): self
{
if (!is_null($httpHandler)) {
$httpHandler = Closure::fromCallable($httpHandler);
}
$this->httpHandler = $httpHandler;
return $this;
}
/**
* @param ?callable $clientCertSource
*
* @return $this
*/
public function setClientCertSource(?callable $clientCertSource): self
{
if (!is_null($clientCertSource)) {
$clientCertSource = Closure::fromCallable($clientCertSource);
}
$this->clientCertSource = $clientCertSource;
return $this;
}
/**
* @param null|false|LoggerInterface $logger
*
* @return $this
*/
public function setLogger(null|false|LoggerInterface $logger): self
{
$this->logger = $logger;
return $this;
}
}

View File

@@ -0,0 +1,165 @@
<?php
/*
* Copyright 2023 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\Options\TransportOptions;
use ArrayAccess;
use Closure;
use Google\ApiCore\Options\OptionsInterface;
use Google\ApiCore\Options\OptionsTrait;
use Google\ApiCore\Transport\Grpc\UnaryInterceptorInterface;
use Grpc\Channel;
use Grpc\Interceptor;
use Psr\Log\LoggerInterface;
/**
* The GrpcTransportOptions class provides typing to the associative array of options used to
* configure {@see \Google\ApiCore\Transport\GrpcTransport}.
*/
class GrpcTransportOptions implements ArrayAccess, OptionsInterface
{
use OptionsTrait;
private array $stubOpts;
private ?Channel $channel;
private null|false|LoggerInterface $logger;
/**
* @var Interceptor[]|UnaryInterceptorInterface[]
*/
private array $interceptors;
private ?Closure $clientCertSource;
/**
* @param array $options {
* Config options used to construct the gRPC transport.
*
* @type array $stubOpts Options used to construct the gRPC stub (see
* {@link https://grpc.github.io/grpc/core/group__grpc__arg__keys.html}).
* @type Channel $channel Grpc channel to be used.
* @type Interceptor[]|UnaryInterceptorInterface[] $interceptors *EXPERIMENTAL*
* Interceptors used to intercept RPC invocations before a call starts.
* Please note that implementations of
* {@see \Google\ApiCore\Transport\Grpc\UnaryInterceptorInterface} are
* considered deprecated and support will be removed in a future
* release. To prepare for this, please take the time to convert
* `UnaryInterceptorInterface` implementations over to a class which
* extends {@see Grpc\Interceptor}.
* @type callable $clientCertSource A callable which returns the client cert as a string.
* @type null|false|LoggerInterface A PSR-3 Logger Interface.
* }
*/
public function __construct(array $options)
{
$this->fromArray($options);
}
/**
* Sets the array of options as class properites.
*
* @param array $arr See the constructor for the list of supported options.
*/
private function fromArray(array $arr): void
{
$this->setStubOpts($arr['stubOpts'] ?? []);
$this->setChannel($arr['channel'] ?? null);
$this->setInterceptors($arr['interceptors'] ?? []);
$this->setClientCertSource($arr['clientCertSource'] ?? null);
$this->setLogger($arr['logger'] ?? null);
}
/**
* @param array $stubOpts
*
* @return $this
*/
public function setStubOpts(array $stubOpts): self
{
$this->stubOpts = $stubOpts;
return $this;
}
/**
* @param ?Channel $channel
*
* @return $this
*/
public function setChannel(?Channel $channel): self
{
$this->channel = $channel;
return $this;
}
/**
* @param Interceptor[]|UnaryInterceptorInterface[] $interceptors
*
* @return $this
*/
public function setInterceptors(array $interceptors): self
{
$this->interceptors = $interceptors;
return $this;
}
/**
* @param ?callable $clientCertSource
*
* @return $this
*/
public function setClientCertSource(?callable $clientCertSource): self
{
if (!is_null($clientCertSource)) {
$clientCertSource = Closure::fromCallable($clientCertSource);
}
$this->clientCertSource = $clientCertSource;
return $this;
}
/**
* @param null|false|LoggerInterface $logger
*
* @return $this
*/
public function setLogger(null|false|LoggerInterface $logger): self
{
$this->logger = $logger;
return $this;
}
}

View File

@@ -0,0 +1,142 @@
<?php
/*
* Copyright 2023 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\Options\TransportOptions;
use ArrayAccess;
use Closure;
use Google\ApiCore\Options\OptionsInterface;
use Google\ApiCore\Options\OptionsTrait;
use Psr\Log\LoggerInterface;
/**
* The RestTransportOptions class provides typing to the associative array of options used to
* configure {@see \Google\ApiCore\Transport\RestTransport}.
*/
class RestTransportOptions implements ArrayAccess, OptionsInterface
{
use OptionsTrait;
private ?Closure $httpHandler;
private ?Closure $clientCertSource;
private ?string $restClientConfigPath;
private null|false|LoggerInterface $logger;
/**
* @param array $options {
* Config options used to construct the REST transport.
*
* @type callable $httpHandler
* A handler used to deliver PSR-7 requests.
* @type callable $clientCertSource
* A callable which returns the client cert as a string.
* @type string $restClientConfigPath
* The path to the REST client config file.
* @typo null|false|LoggerInterface
* A PSR-3 compliant logger instance.
* }
*/
public function __construct(array $options)
{
$this->fromArray($options);
}
/**
* Sets the array of options as class properites.
*
* @param array $arr See the constructor for the list of supported options.
*/
private function fromArray(array $arr): void
{
$this->setHttpHandler($arr['httpHandler'] ?? null);
$this->setClientCertSource($arr['clientCertSource'] ?? null);
$this->setRestClientConfigPath($arr['restClientConfigPath'] ?? null);
$this->setLogger($arr['logger'] ?? null);
}
/**
* @param ?callable $httpHandler
*
* @return $this
*/
public function setHttpHandler(?callable $httpHandler): self
{
if (!is_null($httpHandler)) {
$httpHandler = Closure::fromCallable($httpHandler);
}
$this->httpHandler = $httpHandler;
return $this;
}
/**
* @param ?callable $clientCertSource
*
* @return $this
*/
public function setClientCertSource(?callable $clientCertSource): self
{
if (!is_null($clientCertSource)) {
$clientCertSource = Closure::fromCallable($clientCertSource);
}
$this->clientCertSource = $clientCertSource;
return $this;
}
/**
* @param ?string $restClientConfigPath
*
* @return $this
*/
public function setRestClientConfigPath(?string $restClientConfigPath): self
{
$this->restClientConfigPath = $restClientConfigPath;
return $this;
}
/**
* @param null|false|LoggerInterface $logger
*
* @return $this
*/
public function setLogger(null|false|LoggerInterface $logger): self
{
$this->logger = $logger;
return $this;
}
}

271
vendor/google/gax/src/Page.php vendored Normal file
View File

@@ -0,0 +1,271 @@
<?php
/*
* Copyright 2016 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore;
use Generator;
use Google\Protobuf\Internal\MapField;
use Google\Protobuf\Internal\Message;
use IteratorAggregate;
/**
* A Page object wraps an API list method response and provides methods
* to retrieve additional pages using the page token.
*/
class Page implements IteratorAggregate
{
const FINAL_PAGE_TOKEN = '';
private $call;
private $callable;
private $options;
private $pageStreamingDescriptor;
private $pageToken; // @phpstan-ignore-line
private $response;
/**
* Page constructor.
*
* @param Call $call
* @param array $options
* @param callable $callable
* @param PageStreamingDescriptor $pageStreamingDescriptor
* @param Message $response
*/
public function __construct(
Call $call,
array $options,
callable $callable,
PageStreamingDescriptor $pageStreamingDescriptor,
Message $response
) {
$this->call = $call;
$this->options = $options;
$this->callable = $callable;
$this->pageStreamingDescriptor = $pageStreamingDescriptor;
$this->response = $response;
$requestPageTokenGetMethod = $this->pageStreamingDescriptor->getRequestPageTokenGetMethod();
$this->pageToken = $this->call->getMessage()->$requestPageTokenGetMethod();
}
/**
* Returns true if there are more pages that can be retrieved from the
* API.
*
* @return bool
*/
public function hasNextPage()
{
return strcmp($this->getNextPageToken(), Page::FINAL_PAGE_TOKEN) != 0;
}
/**
* Returns the next page token from the response.
*
* @return string
*/
public function getNextPageToken()
{
$responsePageTokenGetMethod = $this->pageStreamingDescriptor->getResponsePageTokenGetMethod();
return $this->getResponseObject()->$responsePageTokenGetMethod();
}
/**
* Retrieves the next Page object using the next page token.
*
* @param int|null $pageSize
* @throws ValidationException if there are no pages remaining, or if pageSize is supplied but
* is not supported by the API
* @throws ApiException if the call to fetch the next page fails.
* @return Page
*/
public function getNextPage(?int $pageSize = null)
{
if (!$this->hasNextPage()) {
throw new ValidationException(
'Could not complete getNextPage operation: ' .
'there are no more pages to retrieve.'
);
}
$oldRequest = $this->getRequestObject();
$requestClass = get_class($oldRequest);
$newRequest = new $requestClass();
$newRequest->mergeFrom($oldRequest);
$requestPageTokenSetMethod = $this->pageStreamingDescriptor->getRequestPageTokenSetMethod();
$newRequest->$requestPageTokenSetMethod($this->getNextPageToken());
if (isset($pageSize)) {
if (!$this->pageStreamingDescriptor->requestHasPageSizeField()) {
throw new ValidationException(
'pageSize argument was defined, but the method does not ' .
'support a page size parameter in the optional array argument'
);
}
$requestPageSizeSetMethod = $this->pageStreamingDescriptor->getRequestPageSizeSetMethod();
$newRequest->$requestPageSizeSetMethod($pageSize);
}
$this->call = $this->call->withMessage($newRequest);
$callable = $this->callable;
$response = $callable(
$this->call,
$this->options
)->wait();
return new Page(
$this->call,
$this->options,
$this->callable,
$this->pageStreamingDescriptor,
$response
);
}
/**
* Return the number of elements in the response.
*
* @return int
*/
public function getPageElementCount()
{
$resourcesGetMethod = $this->pageStreamingDescriptor->getResourcesGetMethod();
return count($this->getResponseObject()->$resourcesGetMethod());
}
/**
* Return an iterator over the elements in the response.
*
* @return Generator
*/
#[\ReturnTypeWillChange]
public function getIterator()
{
$resourcesGetMethod = $this->pageStreamingDescriptor->getResourcesGetMethod();
$items = $this->getResponseObject()->$resourcesGetMethod();
foreach ($items as $key => $element) {
if ($items instanceof MapField) {
yield $key => $element;
} else {
yield $element;
}
}
}
/**
* Return an iterator over Page objects, beginning with this object.
* Additional Page objects are retrieved lazily via API calls until
* all elements have been retrieved.
*
* @return Generator|array<Page>
* @throws ValidationException
* @throws ApiException
*/
public function iteratePages()
{
$currentPage = $this;
yield $this;
while ($currentPage->hasNextPage()) {
$currentPage = $currentPage->getNextPage();
yield $currentPage;
}
}
/**
* Gets the request object used to generate the Page.
*
* @return mixed|Message
*/
public function getRequestObject()
{
return $this->call->getMessage();
}
/**
* Gets the API response object.
*
* @return mixed|Message
*/
public function getResponseObject()
{
return $this->response;
}
/**
* Returns a collection of elements with a fixed size set by
* the collectionSize parameter. The collection will only contain
* fewer than collectionSize elements if there are no more
* pages to be retrieved from the server.
*
* NOTE: it is an error to call this method if an optional parameter
* to set the page size is not supported or has not been set in the
* API call that was used to create this page. It is also an error
* if the collectionSize parameter is less than the page size that
* has been set.
*
* @param int $collectionSize
* @throws ValidationException if a FixedSizeCollection of the specified size cannot be constructed
* @return FixedSizeCollection
*/
public function expandToFixedSizeCollection($collectionSize)
{
if (!$this->pageStreamingDescriptor->requestHasPageSizeField()) {
throw new ValidationException(
'FixedSizeCollection is not supported for this method, because ' .
'the method does not support an optional argument to set the ' .
'page size.'
);
}
$request = $this->getRequestObject();
$pageSizeGetMethod = $this->pageStreamingDescriptor->getRequestPageSizeGetMethod();
$pageSize = $request->$pageSizeGetMethod();
if (is_null($pageSize)) {
throw new ValidationException(
'Error while expanding Page to FixedSizeCollection: No page size ' .
'parameter found. The page size parameter must be set in the API ' .
'optional arguments array, and must be less than the collectionSize ' .
'parameter, in order to create a FixedSizeCollection object.'
);
}
if ($pageSize > $collectionSize) {
throw new ValidationException(
'Error while expanding Page to FixedSizeCollection: collectionSize ' .
'parameter is less than the page size optional argument specified in ' .
"the API call. collectionSize: $collectionSize, page size: $pageSize"
);
}
return new FixedSizeCollection($this, $collectionSize);
}
}

View File

@@ -0,0 +1,178 @@
<?php
/*
* Copyright 2016 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore;
use InvalidArgumentException;
/**
* Holds the description information used for page streaming.
*/
class PageStreamingDescriptor
{
private $descriptor;
/**
* @param array $descriptor {
* Required.
* @type string $requestPageTokenGetMethod the get method for the page token in the request object.
* @type string $requestPageTokenSetMethod the set method for the page token in the request object.
* @type string $responsePageTokenGetMethod the get method for the page token in the response object.
* @type string resourcesGetMethod the get method for the resources in the response object.
*
* Optional.
* @type string $requestPageSizeGetMethod the get method for the page size in the request object.
* }
*/
public function __construct(array $descriptor)
{
self::validate($descriptor);
$this->descriptor = $descriptor;
}
/**
* @param array $fields {
* Required.
*
* @type string $requestPageTokenField the page token field in the request object.
* @type string $responsePageTokenField the page token field in the response object.
* @type string $resourceField the resource field in the response object.
*
* Optional.
* @type string $requestPageSizeField the page size field in the request object.
* }
* @return PageStreamingDescriptor
*/
public static function createFromFields(array $fields)
{
$requestPageToken = $fields['requestPageTokenField'];
$responsePageToken = $fields['responsePageTokenField'];
$resources = $fields['resourceField'];
$descriptor = [
'requestPageTokenGetMethod' => PageStreamingDescriptor::getMethod($requestPageToken),
'requestPageTokenSetMethod' => PageStreamingDescriptor::setMethod($requestPageToken),
'responsePageTokenGetMethod' => PageStreamingDescriptor::getMethod($responsePageToken),
'resourcesGetMethod' => PageStreamingDescriptor::getMethod($resources),
];
if (isset($fields['requestPageSizeField'])) {
$requestPageSize = $fields['requestPageSizeField'];
$descriptor['requestPageSizeGetMethod'] = PageStreamingDescriptor::getMethod($requestPageSize);
$descriptor['requestPageSizeSetMethod'] = PageStreamingDescriptor::setMethod($requestPageSize);
}
return new PageStreamingDescriptor($descriptor);
}
private static function getMethod(string $field)
{
return 'get' . ucfirst($field);
}
private static function setMethod(string $field)
{
return 'set' . ucfirst($field);
}
/**
* @return string The page token get method on the request object
*/
public function getRequestPageTokenGetMethod()
{
return $this->descriptor['requestPageTokenGetMethod'];
}
/**
* @return string The page size get method on the request object
*/
public function getRequestPageSizeGetMethod()
{
return $this->descriptor['requestPageSizeGetMethod'];
}
/**
* @return bool True if the request object has a page size field
*/
public function requestHasPageSizeField()
{
return array_key_exists('requestPageSizeGetMethod', $this->descriptor);
}
/**
* @return string The page token get method on the response object
*/
public function getResponsePageTokenGetMethod()
{
return $this->descriptor['responsePageTokenGetMethod'];
}
/**
* @return string The resources get method on the response object
*/
public function getResourcesGetMethod()
{
return $this->descriptor['resourcesGetMethod'];
}
/**
* @return string The page token set method on the request object
*/
public function getRequestPageTokenSetMethod()
{
return $this->descriptor['requestPageTokenSetMethod'];
}
/**
* @return string The page size set method on the request object
*/
public function getRequestPageSizeSetMethod()
{
return $this->descriptor['requestPageSizeSetMethod'];
}
private static function validate(array $descriptor)
{
$requiredFields = [
'requestPageTokenGetMethod',
'requestPageTokenSetMethod',
'responsePageTokenGetMethod',
'resourcesGetMethod',
];
foreach ($requiredFields as $field) {
if (empty($descriptor[$field])) {
throw new InvalidArgumentException(
"$field is required for PageStreamingDescriptor"
);
}
}
}
}

View File

@@ -0,0 +1,197 @@
<?php
/*
* Copyright 2016 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore;
use Generator;
use IteratorAggregate;
/**
* Response object for paged results from a list API method
*
* The PagedListResponse object is returned by API methods that implement
* pagination, and makes it easier to access multiple pages of results
* without having to manually manipulate page tokens. Pages are retrieved
* lazily, with additional API calls being made as additional results
* are required.
*
* The list elements can be accessed in the following ways:
* - As a single iterable used in a foreach loop or via the getIterator method
* - As pages of elements, using the getPage and iteratePages methods
* - As fixed size collections of elements, using the
* getFixedSizeCollection and iterateFixedSizeCollections methods
*
* Example of using PagedListResponse as an iterator:
* ```
* $pagedListResponse = $client->getList(...);
* foreach ($pagedListResponse as $element) {
* // doSomethingWith($element);
* }
* ```
*
* Example of iterating over each page of elements:
* ```
* $pagedListResponse = $client->getList(...);
* foreach ($pagedListResponse->iteratePages() as $page) {
* foreach ($page as $element) {
* // doSomethingWith($element);
* }
* }
* ```
*
* Example of accessing the current page, and manually iterating
* over pages:
* ```
* $pagedListResponse = $client->getList(...);
* $page = $pagedListResponse->getPage();
* // doSomethingWith($page);
* while ($page->hasNextPage()) {
* $page = $page->getNextPage();
* // doSomethingWith($page);
* }
* ```
*/
class PagedListResponse implements IteratorAggregate
{
private $firstPage;
/**
* PagedListResponse constructor.
*
* @param Page $firstPage A page containing response details.
*/
public function __construct(
Page $firstPage
) {
$this->firstPage = $firstPage;
}
/**
* Returns an iterator over the full list of elements. If the
* API response contains a (non-empty) next page token, then
* the PagedListResponse object will make calls to the underlying
* API to retrieve additional elements as required.
*
* NOTE: The result of this method is the same as getIterator().
* Prefer using getIterator(), or iterate directly on the
* PagedListResponse object.
*
* @return Generator
* @throws ValidationException
*/
public function iterateAllElements()
{
return $this->getIterator();
}
/**
* Returns an iterator over the full list of elements. If the
* API response contains a (non-empty) next page token, then
* the PagedListResponse object will make calls to the underlying
* API to retrieve additional elements as required.
*
* @return Generator
* @throws ValidationException
*/
#[\ReturnTypeWillChange]
public function getIterator()
{
foreach ($this->iteratePages() as $page) {
foreach ($page as $key => $element) {
yield $key => $element;
}
}
}
/**
* Return the current page of results.
*
* @return Page
*/
public function getPage()
{
return $this->firstPage;
}
/**
* Returns an iterator over pages of results. The pages are
* retrieved lazily from the underlying API.
*
* @return Page[]
* @throws ValidationException
*/
public function iteratePages()
{
return $this->getPage()->iteratePages();
}
/**
* Returns a collection of elements with a fixed size set by
* the collectionSize parameter. The collection will only contain
* fewer than collectionSize elements if there are no more
* pages to be retrieved from the server.
*
* NOTE: it is an error to call this method if an optional parameter
* to set the page size is not supported or has not been set in the
* original API call. It is also an error if the collectionSize parameter
* is less than the page size that has been set.
*
* @param int $collectionSize
* @throws ValidationException if a FixedSizeCollection of the specified size cannot be constructed
* @return FixedSizeCollection
*/
public function expandToFixedSizeCollection(int $collectionSize)
{
return $this->getPage()->expandToFixedSizeCollection($collectionSize);
}
/**
* Returns an iterator over fixed size collections of results.
* The collections are retrieved lazily from the underlying API.
*
* Each collection will have collectionSize elements, with the
* exception of the final collection which may contain fewer
* elements.
*
* NOTE: it is an error to call this method if an optional parameter
* to set the page size is not supported or has not been set in the
* original API call. It is also an error if the collectionSize parameter
* is less than the page size that has been set.
*
* @param int $collectionSize
* @throws ValidationException if a FixedSizeCollection of the specified size cannot be constructed
* @return Generator|FixedSizeCollection[]
*/
public function iterateFixedSizeCollections(int $collectionSize)
{
return $this->expandToFixedSizeCollection($collectionSize)->iterateCollections();
}
}

113
vendor/google/gax/src/PathTemplate.php vendored Normal file
View File

@@ -0,0 +1,113 @@
<?php
/*
* Copyright 2016 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore;
use Google\ApiCore\ResourceTemplate\AbsoluteResourceTemplate;
use Google\ApiCore\ResourceTemplate\RelativeResourceTemplate;
use Google\ApiCore\ResourceTemplate\ResourceTemplateInterface;
/**
* Represents a path template.
*
* Templates use the syntax of the API platform; see the protobuf of HttpRule for
* details. A template consists of a sequence of literals, wildcards, and variable bindings,
* where each binding can have a sub-path. A string representation can be parsed into an
* instance of PathTemplate, which can then be used to perform matching and instantiation.
*/
class PathTemplate implements ResourceTemplateInterface
{
private $resourceTemplate;
/**
* PathTemplate constructor.
*
* @param string $path A path template string
* @throws ValidationException When $path cannot be parsed into a valid PathTemplate
*/
public function __construct(?string $path = null)
{
if (empty($path)) {
throw new ValidationException('Cannot construct PathTemplate from empty string');
}
if ($path[0] === '/') {
$this->resourceTemplate = new AbsoluteResourceTemplate($path);
} else {
$this->resourceTemplate = new RelativeResourceTemplate($path);
}
}
/**
* @return string A string representation of the path template
*/
public function __toString()
{
return $this->resourceTemplate->__toString();
}
/**
* Renders a path template using the provided bindings.
*
* @param array $bindings An array matching var names to binding strings.
* @throws ValidationException if a key isn't provided or if a sub-template
* can't be parsed.
* @return string A rendered representation of this path template.
*/
public function render(array $bindings)
{
return $this->resourceTemplate->render($bindings);
}
/**
* Check if $path matches a resource string.
*
* @param string $path A resource string.
* @return bool
*/
public function matches(string $path)
{
return $this->resourceTemplate->matches($path);
}
/**
* Matches a fully qualified path template string.
*
* @param string $path A fully qualified path template string.
* @throws ValidationException if path can't be matched to the template.
* @return array Array matching var names to binding values.
*/
public function match(string $path)
{
return $this->resourceTemplate->match($path);
}
}

97
vendor/google/gax/src/PollingTrait.php vendored Normal file
View File

@@ -0,0 +1,97 @@
<?php
/*
* Copyright 2018 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore;
/**
* @internal
*/
trait PollingTrait
{
/**
* @param callable $pollCallable The call to poll. Must return a boolean indicating whether the
* operation has completed, and the polling loop can be terminated.
* @param array $options {
* Options for configuring the polling behaviour.
*
* @type int $initialPollDelayMillis The initial polling interval to use, in milliseconds.
* @type int $pollDelayMultiplier Multiplier applied to the polling interval on each retry.
* @type int $maxPollDelayMillis The maximum polling interval to use, in milliseconds.
* @type int $totalPollTimeoutMillis The maximum amount of time to continue polling, in milliseconds.
* }
* @return bool
*/
private function poll(callable $pollCallable, array $options)
{
$currentPollDelayMillis = $options['initialPollDelayMillis'];
$pollDelayMultiplier = $options['pollDelayMultiplier'];
$maxPollDelayMillis = $options['maxPollDelayMillis'];
$totalPollTimeoutMillis = $options['totalPollTimeoutMillis'];
$hasTotalPollTimeout = $totalPollTimeoutMillis > 0.0;
$endTime = $this->getCurrentTimeMillis() + $totalPollTimeoutMillis;
while (true) {
if ($hasTotalPollTimeout && $this->getCurrentTimeMillis() > $endTime) {
return false;
}
$this->sleepMillis($currentPollDelayMillis);
if ($pollCallable()) {
return true;
}
$currentPollDelayMillis = (int) min([
$currentPollDelayMillis * $pollDelayMultiplier,
$maxPollDelayMillis
]);
}
}
/**
* Protected to allow overriding for tests
*
* @return float Current time in milliseconds
*/
protected function getCurrentTimeMillis()
{
return microtime(true) * 1000.0;
}
/**
* Protected to allow overriding for tests
*
* @param int $millis
*/
protected function sleepMillis(int $millis)
{
usleep($millis * 1000);
}
}

290
vendor/google/gax/src/RequestBuilder.php vendored Normal file
View File

@@ -0,0 +1,290 @@
<?php
/*
* Copyright 2018 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore;
use Google\ApiCore\ResourceTemplate\AbsoluteResourceTemplate;
use Google\Protobuf\Internal\Message;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Utils;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\UriInterface;
/**
* Builds a PSR-7 request from a set of request information.
*
* @internal
*/
class RequestBuilder
{
use ArrayTrait;
use UriTrait;
use ValidationTrait;
protected $baseUri;
private $restConfig;
/**
* @param string $baseUri
* @param string $restConfigPath
* @throws ValidationException
*/
public function __construct(string $baseUri, string $restConfigPath)
{
self::validateFileExists($restConfigPath);
$this->baseUri = $baseUri;
$this->restConfig = require($restConfigPath);
}
/**
* @param string $path
* @return bool
*/
public function pathExists(string $path)
{
list($interface, $method) = explode('/', $path);
return isset($this->restConfig['interfaces'][$interface][$method]);
}
/**
* @param string $path
* @param Message $message
* @param array $headers
* @return RequestInterface
* @throws ValidationException
*/
public function build(string $path, Message $message, array $headers = [])
{
list($interface, $method) = explode('/', $path);
if (!isset($this->restConfig['interfaces'][$interface][$method])) {
throw new ValidationException(
"Failed to build request, as the provided path ($path) was not found in the configuration."
);
}
$numericEnums = isset($this->restConfig['numericEnums']) && $this->restConfig['numericEnums'];
$methodConfig = $this->restConfig['interfaces'][$interface][$method] + [
'placeholders' => [],
'body' => null,
'additionalBindings' => null,
];
$bindings = $this->buildBindings($methodConfig['placeholders'], $message);
$uriTemplateConfigs = $this->getConfigsForUriTemplates($methodConfig);
foreach ($uriTemplateConfigs as $config) {
$pathTemplate = $this->tryRenderPathTemplate($config['uriTemplate'], $bindings);
if ($pathTemplate) {
// We found a valid uriTemplate - now build and return the Request
list($body, $queryParams) = $this->constructBodyAndQueryParameters($message, $config);
// Request enum fields will be encoded as numbers rather than strings (in the response).
if ($numericEnums) {
$queryParams['$alt'] = 'json;enum-encoding=int';
}
$uri = $this->buildUri($pathTemplate, $queryParams);
return new Request(
$config['method'],
$uri,
['Content-Type' => 'application/json'] + $headers,
$body
);
}
}
// No valid uriTemplate found - construct an exception
$uriTemplates = [];
foreach ($uriTemplateConfigs as $config) {
$uriTemplates[] = $config['uriTemplate'];
}
throw new ValidationException("Could not map bindings for $path to any Uri template.\n" .
'Bindings: ' . print_r($bindings, true) .
'UriTemplates: ' . print_r($uriTemplates, true));
}
/**
* Create a list of all possible configs using the additionalBindings
*
* @param array $config
* @return array[] An array of configs
*/
private function getConfigsForUriTemplates(array $config)
{
$configs = [$config];
if ($config['additionalBindings']) {
foreach ($config['additionalBindings'] as $additionalBinding) {
$configs[] = $additionalBinding + $config;
}
}
return $configs;
}
/**
* @param Message $message
* @param array $config
* @return array Tuple [$body, $queryParams]
*/
private function constructBodyAndQueryParameters(Message $message, array $config)
{
$messageDataJson = $message->serializeToJsonString();
if ($config['body'] === '*') {
return [$messageDataJson, []];
}
$body = null;
$queryParams = [];
$messageData = json_decode($messageDataJson, true);
foreach ($messageData as $name => $value) {
if (array_key_exists($name, $config['placeholders'])) {
continue;
}
if (Serializer::toSnakeCase($name) === $config['body']) {
if (($bodyMessage = $message->{"get$name"}()) instanceof Message) {
$body = $bodyMessage->serializeToJsonString();
} else {
$body = json_encode($value);
}
continue;
}
if (is_array($value) && $this->isAssoc($value)) {
foreach ($value as $key => $value2) {
$queryParams[$name . '.' . $key] = $value2;
}
} else {
$queryParams[$name] = $value;
}
}
// Ensures required query params with default values are always sent
// over the wire.
if (isset($config['queryParams'])) {
foreach ($config['queryParams'] as $requiredQueryParam) {
$requiredQueryParam = Serializer::toCamelCase($requiredQueryParam);
if (!array_key_exists($requiredQueryParam, $queryParams)) {
$getter = Serializer::getGetter($requiredQueryParam);
$queryParamValue = $message->$getter();
if ($queryParamValue instanceof Message) {
// Decode message for the query parameter.
$queryParamValue = json_decode($queryParamValue->serializeToJsonString(), true);
}
if (is_array($queryParamValue)) {
// If the message has properties, add them as nested querystring values.
// NOTE: This only supports nesting at one level of depth.
foreach ($queryParamValue as $key => $value) {
$queryParams[$requiredQueryParam . '.' . $key] = $value;
}
} else {
$queryParams[$requiredQueryParam] = $queryParamValue;
}
}
}
}
return [$body, $queryParams];
}
/**
* @param array $placeholders
* @param Message $message
* @return array Bindings from path template fields to values from message
*/
private function buildBindings(array $placeholders, Message $message)
{
$bindings = [];
foreach ($placeholders as $placeholder => $metadata) {
$value = array_reduce(
$metadata['getters'],
function (?Message $result = null, $getter = null) {
if ($result && $getter) {
return $result->$getter();
}
},
$message
);
$bindings[$placeholder] = $value;
}
return $bindings;
}
/**
* Try to render the resource name. The rendered resource name will always contain a leading '/'
*
* @param string $uriTemplate
* @param array $bindings
* @return null|string
* @throws ValidationException
*/
private function tryRenderPathTemplate(string $uriTemplate, array $bindings)
{
$template = new AbsoluteResourceTemplate($uriTemplate);
try {
return $template->render($bindings);
} catch (ValidationException $e) {
return null;
}
}
/**
* @param string $path
* @param array $queryParams
* @return UriInterface
*/
protected function buildUri(string $path, array $queryParams)
{
$uri = Utils::uriFor(
sprintf(
'https://%s%s',
$this->baseUri,
$path
)
);
if ($queryParams) {
$uri = $this->buildUriWithQuery(
$uri,
$queryParams
);
}
return $uri;
}
}

View File

@@ -0,0 +1,78 @@
<?php
/*
* Copyright 2017 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore;
/**
* Encapsulates request params header metadata.
*/
class RequestParamsHeaderDescriptor
{
const HEADER_KEY = 'x-goog-request-params';
/**
* @var array
*/
private $header;
/**
* RequestParamsHeaderDescriptor constructor.
*
* @param array $requestParams An associative array which contains request params header data in
* a form ['field_name.subfield_name' => value].
*/
public function __construct(array $requestParams)
{
$headerKey = self::HEADER_KEY;
$headerValue = '';
foreach ($requestParams as $key => $value) {
if ('' !== $headerValue) {
$headerValue .= '&';
}
$headerValue .= $key . '=' . urlencode(strval($value));
}
$this->header = [$headerKey => [$headerValue]];
}
/**
* Returns an associative array that contains request params header metadata.
*
* @return array
*/
public function getHeader()
{
return $this->header;
}
}

View File

@@ -0,0 +1,108 @@
<?php
/*
* Copyright 2022 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore;
/**
* Provides functionality for loading a resource name template map from a descriptor config,
* retrieving a PathTemplate, and parsing values using registered templates.
*
* @internal
*/
trait ResourceHelperTrait
{
/** @var array|null */
private static $templateMap;
/**
* placeholder for this function like we have in GapicClientTrait
*/
private static function getClientDefaults()
{
return [];
}
private static function registerPathTemplates()
{
$templateConfigPath = self::getClientDefaults()['descriptorsConfigPath'];
// self::SERVICE_NAME is a constant set per-client.
self::loadPathTemplates($templateConfigPath, self::SERVICE_NAME);
}
private static function loadPathTemplates(string $configPath, string $serviceName)
{
// TODO: Add void return type hint.
if (!is_null(self::$templateMap)) {
return;
}
$descriptors = require($configPath);
$templates = $descriptors['interfaces'][$serviceName]['templateMap'] ?? [];
self::$templateMap = [];
foreach ($templates as $name => $template) {
self::$templateMap[$name] = new PathTemplate($template);
}
}
private static function getPathTemplate(string $key)
{
// TODO: Add nullable return type reference once PHP 7.1 is minimum.
if (is_null(self::$templateMap)) {
self::registerPathTemplates();
}
return self::$templateMap[$key] ?? null;
}
private static function parseFormattedName(string $formattedName, ?string $template = null): array
{
if (is_null(self::$templateMap)) {
self::registerPathTemplates();
}
if ($template) {
if (!isset(self::$templateMap[$template])) {
throw new ValidationException("Template name $template does not exist");
}
return self::$templateMap[$template]->match($formattedName);
}
foreach (self::$templateMap as $templateName => $pathTemplate) {
try {
return $pathTemplate->match($formattedName);
} catch (ValidationException $ex) {
// Swallow the exception to continue trying other path templates
}
}
throw new ValidationException("Input did not match any known format. Input: $formattedName");
}
}

View File

@@ -0,0 +1,146 @@
<?php
/*
* Copyright 2018 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\ResourceTemplate;
use Google\ApiCore\ValidationException;
/**
* Represents an absolute resource template, meaning that it will always contain a leading slash,
* and may contain a trailing verb (":<verb>").
*
* Examples:
* /projects
* /projects/{project}
* /foo/{bar=**}/fizz/*:action
*
* Templates use the syntax of the API platform; see
* https://github.com/googleapis/api-common-protos/blob/master/google/api/http.proto
* for details. A template consists of a sequence of literals, wildcards, and variable bindings,
* where each binding can have a sub-path. A string representation can be parsed into an
* instance of AbsoluteResourceTemplate, which can then be used to perform matching and instantiation.
*
* @internal
*/
class AbsoluteResourceTemplate implements ResourceTemplateInterface
{
private RelativeResourceTemplate $resourceTemplate;
/** @var string|bool */
private $verb;
/**
* AbsoluteResourceTemplate constructor.
* @param string $path
* @throws ValidationException
*/
public function __construct(string $path)
{
if (empty($path)) {
throw new ValidationException('Cannot construct AbsoluteResourceTemplate from empty string');
}
if ($path[0] !== '/') {
throw new ValidationException(
"Could not construct AbsoluteResourceTemplate from '$path': must begin with '/'"
);
}
$verbSeparatorPos = $this->verbSeparatorPos($path);
$this->resourceTemplate = new RelativeResourceTemplate(substr($path, 1, $verbSeparatorPos - 1));
$this->verb = substr($path, $verbSeparatorPos + 1);
}
/**
* @inheritdoc
*/
public function __toString()
{
return sprintf('/%s%s', $this->resourceTemplate, $this->renderVerb());
}
/**
* @inheritdoc
*/
public function render(array $bindings)
{
return sprintf('/%s%s', $this->resourceTemplate->render($bindings), $this->renderVerb());
}
/**
* @inheritdoc
*/
public function matches(string $path)
{
try {
$this->match($path);
return true;
} catch (ValidationException $ex) {
return false;
}
}
/**
* @inheritdoc
*/
public function match(string $path)
{
if (empty($path)) {
throw $this->matchException($path, 'path cannot be empty');
}
if ($path[0] !== '/') {
throw $this->matchException($path, "missing leading '/'");
}
$verbSeparatorPos = $this->verbSeparatorPos($path);
if (substr($path, $verbSeparatorPos + 1) !== $this->verb) {
throw $this->matchException($path, "trailing verb did not match '{$this->verb}'");
}
return $this->resourceTemplate->match(substr($path, 1, $verbSeparatorPos - 1));
}
private function matchException(string $path, string $reason)
{
return new ValidationException("Could not match path '$path' to template '$this': $reason");
}
private function renderVerb()
{
return $this->verb ? ':' . $this->verb : '';
}
private function verbSeparatorPos(string $path)
{
$finalSeparatorPos = strrpos($path, '/');
$verbSeparatorPos = strrpos($path, ':', $finalSeparatorPos);
if ($verbSeparatorPos === false) {
$verbSeparatorPos = strlen($path);
}
return $verbSeparatorPos;
}
}

View File

@@ -0,0 +1,227 @@
<?php
/*
* Copyright 2018 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\ResourceTemplate;
use Google\ApiCore\ValidationException;
/**
* Collection of methods for parsing Segments.
*
* @internal
*/
class Parser
{
/**
* Parses a path into an array of segments.
*
* @param string|null $path
* @return array
* @throws ValidationException
*/
public static function parseSegments(?string $path = null)
{
if (empty($path)) {
throw new ValidationException('Cannot parse empty path');
}
$segments = [];
$index = 0;
$nextLiteral = '';
$segments[] = self::parseSegmentFromPath($path, $nextLiteral, $index);
while ($index < strlen($path)) {
self::parseLiteralFromPath($nextLiteral, $path, $index);
$segments[] = self::parseSegmentFromPath($path, $nextLiteral, $index);
}
return $segments;
}
/**
* Given a path and an index, reads a Segment from the path and updates
* the index.
*
* @param string $path
* @param string $nextLiteral
* @param int $index
* @return Segment
* @throws ValidationException
*/
private static function parseSegmentFromPath(string $path, string &$nextLiteral, int &$index)
{
if ($index >= strlen($path)) {
// A trailing '/' has caused the index to exceed the bounds
// of the string - provide a helpful error message.
throw self::parseError($path, strlen($path) - 1, "invalid trailing '/'");
}
if ($path[$index] === '{') {
// Validate that the { has a matching }
$closingBraceIndex = strpos($path, '}', $index);
if ($closingBraceIndex === false) {
throw self::parseError(
$path,
strlen($path),
"Expected '}' to match '{' at index $index, got end of string"
);
}
$segmentStringLengthWithoutBraces = $closingBraceIndex - $index - 1;
$segmentStringWithoutBraces = substr($path, $index + 1, $segmentStringLengthWithoutBraces);
$index = $closingBraceIndex + 1;
$nextLiteral = '/';
$remainingPath = substr($path, $index);
if (!empty($remainingPath)) {
// Find the firstnon-slash separator seen, if any.
$nextSlashIndex = strpos($remainingPath, '/', 0);
$nonSlashSeparators = ['-', '_', '~', '.'];
foreach ($nonSlashSeparators as $nonSlashSeparator) {
$nonSlashSeparatorIndex = strpos($remainingPath, $nonSlashSeparator, 0);
$nextOpenBraceIndex = strpos($remainingPath, '{', 0);
if ($nonSlashSeparatorIndex !== false && $nonSlashSeparatorIndex === $nextOpenBraceIndex - 1) {
$index += $nonSlashSeparatorIndex;
$nextLiteral = $nonSlashSeparator;
break;
}
}
}
return self::parseVariableSegment($segmentStringWithoutBraces, $nextLiteral);
} else {
$nextSlash = strpos($path, '/', $index);
if ($nextSlash === false) {
$nextSlash = strlen($path);
}
$segmentString = substr($path, $index, $nextSlash - $index);
$nextLiteral = '/';
$index = $nextSlash;
return self::parse($segmentString, $path, $index);
}
}
/**
* @param string $segmentString
* @param string $path
* @param int $index
* @return Segment
* @throws ValidationException
*/
private static function parse(string $segmentString, string $path, int $index)
{
if ($segmentString === '*') {
return new Segment(Segment::WILDCARD_SEGMENT);
} elseif ($segmentString === '**') {
return new Segment(Segment::DOUBLE_WILDCARD_SEGMENT);
} else {
if (!self::isValidLiteral($segmentString)) {
if (empty($segmentString)) {
// Create user friendly message in case of empty segment
throw self::parseError($path, $index, "Unexpected empty segment (consecutive '/'s are invalid)");
} else {
throw self::parseError($path, $index, "Unexpected characters in literal segment $segmentString");
}
}
return new Segment(Segment::LITERAL_SEGMENT, $segmentString);
}
}
/**
* @param string $segmentStringWithoutBraces
* @param string $separatorLiteral
* @return Segment
* @throws ValidationException
*/
private static function parseVariableSegment(string $segmentStringWithoutBraces, string $separatorLiteral)
{
// Validate there are no nested braces
$nestedOpenBracket = strpos($segmentStringWithoutBraces, '{');
if ($nestedOpenBracket !== false) {
throw new ValidationException(
"Unexpected '{' parsing segment $segmentStringWithoutBraces at index $nestedOpenBracket"
);
}
$equalsIndex = strpos($segmentStringWithoutBraces, '=');
if ($equalsIndex === false) {
// If the variable does not contain '=', we assume the pattern is '*' as per google.rpc.Http
$variableKey = $segmentStringWithoutBraces;
$nestedResource = new RelativeResourceTemplate('*');
} else {
$variableKey = substr($segmentStringWithoutBraces, 0, $equalsIndex);
$nestedResourceString = substr($segmentStringWithoutBraces, $equalsIndex + 1);
$nestedResource = new RelativeResourceTemplate($nestedResourceString);
}
if (!self::isValidLiteral($variableKey)) {
throw new ValidationException(
"Unexpected characters in variable name $variableKey"
);
}
return new Segment(Segment::VARIABLE_SEGMENT, null, $variableKey, $nestedResource, $separatorLiteral);
}
/**
* @param string $literal
* @param string $path
* @param int $index
* @return string
* @throws ValidationException
*/
private static function parseLiteralFromPath(string $literal, string $path, int &$index)
{
$literalLength = strlen($literal);
if (strlen($path) < ($index + $literalLength)) {
throw self::parseError($path, $index, "expected '$literal'");
}
$consumedLiteral = substr($path, $index, $literalLength);
if ($consumedLiteral !== $literal) {
throw self::parseError($path, $index, "expected '$literal'");
}
$index += $literalLength;
return $consumedLiteral;
}
private static function parseError(string $path, int $index, string $reason)
{
return new ValidationException("Error parsing '$path' at index $index: $reason");
}
/**
* Check if $literal is a valid segment literal. Segment literals may only contain numbers,
* letters, and any of the following: .-~_
*
* @param string $literal
* @return bool
*/
private static function isValidLiteral(string $literal)
{
return preg_match('/^[0-9a-zA-Z\\.\\-~_]+$/', $literal) === 1;
}
}

View File

@@ -0,0 +1,390 @@
<?php
/*
* Copyright 2018 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\ResourceTemplate;
use Google\ApiCore\ValidationException;
/**
* Represents a relative resource template, meaning that it will never contain a leading slash or
* trailing verb (":<verb>").
*
* Examples:
* projects
* projects/{project}
* foo/{bar=**}/fizz/*
*
* Templates use the syntax of the API platform; see
* https://github.com/googleapis/api-common-protos/blob/master/google/api/http.proto
* for details. A template consists of a sequence of literals, wildcards, and variable bindings,
* where each binding can have a sub-path. A string representation can be parsed into an
* instance of AbsoluteResourceTemplate, which can then be used to perform matching and instantiation.
*
* @internal
*/
class RelativeResourceTemplate implements ResourceTemplateInterface
{
/** @var Segment[] */
private array $segments;
/**
* RelativeResourceTemplate constructor.
*
* @param string $path
* @throws ValidationException
*/
public function __construct(string $path)
{
if (empty($path)) {
throw new ValidationException('Cannot construct RelativeResourceTemplate from empty string');
}
$this->segments = Parser::parseSegments($path);
$doubleWildcardCount = self::countDoubleWildcards($this->segments);
if ($doubleWildcardCount > 1) {
throw new ValidationException(
"Cannot parse '$path': cannot contain more than one path wildcard"
);
}
// Check for duplicate keys
$keys = [];
foreach ($this->segments as $segment) {
if ($segment->getSegmentType() === Segment::VARIABLE_SEGMENT) {
if (isset($keys[$segment->getKey()])) {
throw new ValidationException(
"Duplicate key '{$segment->getKey()}' in path $path"
);
}
$keys[$segment->getKey()] = true;
}
}
}
/**
* @inheritdoc
*/
public function __toString()
{
return self::renderSegments($this->segments);
}
/**
* @inheritdoc
*/
public function render(array $bindings)
{
$literalSegments = [];
$keySegmentTuples = self::buildKeySegmentTuples($this->segments);
foreach ($keySegmentTuples as list($key, $segment)) {
/** @var Segment $segment */
if ($segment->getSegmentType() === Segment::LITERAL_SEGMENT) {
$literalSegments[] = $segment;
continue;
}
if (!array_key_exists($key, $bindings)) {
throw $this->renderingException($bindings, "missing required binding '$key' for segment '$segment'");
}
$value = $bindings[$key];
if (!is_null($value) && $segment->matches($value)) {
$literalSegments[] = new Segment(
Segment::LITERAL_SEGMENT,
$value,
$segment->getValue(),
$segment->getTemplate(),
$segment->getSeparator()
);
} else {
$valueString = is_null($value) ? 'null' : "'$value'";
throw $this->renderingException(
$bindings,
"expected binding '$key' to match segment '$segment', instead got $valueString"
);
}
}
return self::renderSegments($literalSegments);
}
/**
* @inheritdoc
*/
public function matches(string $path)
{
try {
$this->match($path);
return true;
} catch (ValidationException $ex) {
return false;
}
}
/**
* @inheritdoc
*/
public function match(string $path)
{
// High level strategy for matching:
// - Build a list of Segments from our template, where any variable segments are
// flattened into a single, non-nested list
// - Break $path into pieces based on '/'.
// - Use the segments to further subdivide the pieces using any applicable non-slash separators.
// - Match pieces of the path with Segments in the flattened list
// In order to build correct bindings after we match the $path against our template, we
// need to (a) calculate the correct positional keys for our wildcards, and (b) maintain
// information about the variable identifier of any flattened segments. To do this, we
// build a list of [string, Segment] tuples, where the string component is the appropriate
// key.
$keySegmentTuples = self::buildKeySegmentTuples($this->segments);
$flattenedKeySegmentTuples = self::flattenKeySegmentTuples($keySegmentTuples);
$flattenedKeySegmentTuplesCount = count($flattenedKeySegmentTuples);
assert($flattenedKeySegmentTuplesCount > 0);
$slashPathPieces = explode('/', $path);
$pathPieces = [];
$pathPiecesIndex = 0;
$startIndex = 0;
$slashPathPiecesCount = count($slashPathPieces);
$doubleWildcardPieceCount = $slashPathPiecesCount - $flattenedKeySegmentTuplesCount + 1;
for ($i = 0; $i < count($flattenedKeySegmentTuples); $i++) {
$segmentKey = $flattenedKeySegmentTuples[$i][0];
$segment = $flattenedKeySegmentTuples[$i][1];
// In our flattened list of segments, we should never encounter a variable segment
assert($segment->getSegmentType() !== Segment::VARIABLE_SEGMENT);
if ($segment->getSegmentType() == Segment::DOUBLE_WILDCARD_SEGMENT) {
$pathPiecesForSegment = array_slice($slashPathPieces, $pathPiecesIndex, $doubleWildcardPieceCount);
$pathPiece = implode('/', $pathPiecesForSegment);
$pathPiecesIndex += $doubleWildcardPieceCount;
$pathPieces[] = $pathPiece;
continue;
}
if ($segment->getSegmentType() == Segment::WILDCARD_SEGMENT) {
if ($pathPiecesIndex >= $slashPathPiecesCount) {
break;
}
}
if ($segment->getSeparator() === '/') {
if ($pathPiecesIndex >= $slashPathPiecesCount) {
throw $this->matchException($path, 'segment and path length mismatch');
}
$pathPiece = substr($slashPathPieces[$pathPiecesIndex++], $startIndex);
$startIndex = 0;
} else {
$rawPiece = substr($slashPathPieces[$pathPiecesIndex], $startIndex);
$pathPieceLength = strpos($rawPiece, $segment->getSeparator());
$pathPiece = substr($rawPiece, 0, $pathPieceLength);
$startIndex += $pathPieceLength + 1;
}
$pathPieces[] = $pathPiece;
}
if ($flattenedKeySegmentTuples[$i - 1][1]->getSegmentType() !== Segment::DOUBLE_WILDCARD_SEGMENT) {
// Process any remaining pieces. The binding logic will throw exceptions for any invalid paths.
for (; $pathPiecesIndex < count($slashPathPieces); $pathPiecesIndex++) {
$pathPieces[] = $slashPathPieces[$pathPiecesIndex];
}
}
$pathPiecesCount = count($pathPieces);
// We would like to match pieces of our path 1:1 with the segments of our template. However,
// this is confounded by the presence of double wildcards ('**') in the template, which can
// match multiple segments in the path.
// Because there can only be one '**' present, we can determine how many segments it must
// match by examining the difference in count between the template segments and the
// path pieces.
if ($pathPiecesCount < $flattenedKeySegmentTuplesCount) {
// Each segment in $flattenedKeyedSegments must consume at least one
// segment in $pathSegments, so matching must fail.
throw $this->matchException($path, 'path does not contain enough segments to be matched');
}
$doubleWildcardPieceCount = $pathPiecesCount - $flattenedKeySegmentTuplesCount + 1;
$bindings = [];
$pathPiecesIndex = 0;
/** @var Segment $segment */
foreach ($flattenedKeySegmentTuples as list($segmentKey, $segment)) {
$pathPiece = $pathPieces[$pathPiecesIndex++];
if (!$segment->matches($pathPiece)) {
throw $this->matchException($path, "expected path element matching '$segment', got '$pathPiece'");
}
// If we have a valid key, add our $pathPiece to the $bindings array. Note that there
// may be multiple copies of the same $segmentKey. This is because a flattened variable
// segment can match multiple pieces from the path. We can add these to an array and
// collapse them all once the bindings are complete.
if (isset($segmentKey)) {
$bindings += [$segmentKey => []];
$bindings[$segmentKey][] = $pathPiece;
}
}
// It is possible that we have left over path pieces, which can occur if our template does
// not have a double wildcard. In that case, the match should fail.
if ($pathPiecesIndex !== $pathPiecesCount) {
throw $this->matchException($path, "expected end of path, got '$pathPieces[$pathPiecesIndex]'");
}
// Collapse the bindings from lists into strings
$collapsedBindings = [];
foreach ($bindings as $key => $boundPieces) {
$collapsedBindings[$key] = implode('/', $boundPieces);
}
return $collapsedBindings;
}
private function matchException(string $path, string $reason)
{
return new ValidationException("Could not match path '$path' to template '$this': $reason");
}
private function renderingException(array $bindings, string $reason)
{
$bindingsString = print_r($bindings, true);
return new ValidationException(
"Error rendering '$this': $reason\n" .
"Provided bindings: $bindingsString"
);
}
/**
* @param Segment[] $segments
* @param string|null $separator An optional string separator
* @return array[] A list of [string, Segment] tuples
*/
private static function buildKeySegmentTuples(array $segments, ?string $separator = null)
{
$keySegmentTuples = [];
$positionalArgumentCounter = 0;
foreach ($segments as $segment) {
switch ($segment->getSegmentType()) {
case Segment::WILDCARD_SEGMENT:
case Segment::DOUBLE_WILDCARD_SEGMENT:
$positionalKey = "\$$positionalArgumentCounter";
$positionalArgumentCounter++;
$newSegment = $segment;
if ($separator !== null) {
$newSegment = new Segment(
$segment->getSegmentType(),
$segment->getValue(),
$segment->getKey(),
$segment->getTemplate(),
$separator
);
}
$keySegmentTuples[] = [$positionalKey, $newSegment];
break;
default:
$keySegmentTuples[] = [$segment->getKey(), $segment];
}
}
return $keySegmentTuples;
}
/**
* @param array[] $keySegmentTuples A list of [string, Segment] tuples
* @return array[] A list of [string, Segment] tuples
*/
private static function flattenKeySegmentTuples(array $keySegmentTuples)
{
$flattenedKeySegmentTuples = [];
foreach ($keySegmentTuples as list($key, $segment)) {
/** @var Segment $segment */
switch ($segment->getSegmentType()) {
case Segment::VARIABLE_SEGMENT:
// For segment variables, replace the segment with the segments of its children
$template = $segment->getTemplate();
$nestedKeySegmentTuples = self::buildKeySegmentTuples(
$template->segments,
$segment->getSeparator()
);
foreach ($nestedKeySegmentTuples as list($nestedKey, $nestedSegment)) {
/** @var Segment $nestedSegment */
// Nested variables are not allowed
assert($nestedSegment->getSegmentType() !== Segment::VARIABLE_SEGMENT);
// Insert the nested segment with key set to the outer key of the
// parent variable segment
$flattenedKeySegmentTuples[] = [$key, $nestedSegment];
}
break;
default:
// For all other segments, don't change the key or segment
$flattenedKeySegmentTuples[] = [$key, $segment];
}
}
return $flattenedKeySegmentTuples;
}
/**
* @param Segment[] $segments
* @return int
*/
private static function countDoubleWildcards(array $segments)
{
$doubleWildcardCount = 0;
foreach ($segments as $segment) {
switch ($segment->getSegmentType()) {
case Segment::DOUBLE_WILDCARD_SEGMENT:
$doubleWildcardCount++;
break;
case Segment::VARIABLE_SEGMENT:
$doubleWildcardCount += self::countDoubleWildcards($segment->getTemplate()->segments);
break;
}
}
return $doubleWildcardCount;
}
/**
* Joins segments using their separators.
* @param array $segmentsToRender
* @return string
*/
private static function renderSegments(array $segmentsToRender)
{
$renderResult = '';
for ($i = 0; $i < count($segmentsToRender); $i++) {
$segment = $segmentsToRender[$i];
$renderResult .= $segment;
if ($i < count($segmentsToRender) - 1) {
$renderResult .= $segment->getSeparator();
}
}
return $renderResult;
}
}

View File

@@ -0,0 +1,91 @@
<?php
/*
* Copyright 2018 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\ResourceTemplate;
use Google\ApiCore\ValidationException;
/**
* Represents a resource template that may or may not contain a leading slash, and if a leading
* slash is present may contain a trailing verb (":<verb>"). (Note that a trailing verb without a
* leading slash is not permitted).
*
* Examples:
* projects
* /projects
* foo/{bar=**}/fizz/*
* /foo/{bar=**}/fizz/*:action
*
* Templates use the syntax of the API platform; see
* https://github.com/googleapis/api-common-protos/blob/master/google/api/http.proto
* for details. A template consists of a sequence of literals, wildcards, and variable bindings,
* where each binding can have a sub-path. A string representation can be parsed into an
* instance of AbsoluteResourceTemplate, which can then be used to perform matching and instantiation.
*
* @internal
*/
interface ResourceTemplateInterface
{
/**
* @return string A string representation of the resource template
*/
public function __toString();
/**
* Renders a resource template using the provided bindings.
*
* @param array $bindings An array matching var names to binding strings.
* @return string A rendered representation of this resource template.
* @throws ValidationException If $bindings does not contain all required keys
* or if a sub-template can't be parsed.
*/
public function render(array $bindings);
/**
* Check if $path matches a resource string.
*
* @param string $path A resource string.
* @return bool
*/
public function matches(string $path);
/**
* Matches a given $path to a resource template, and returns an array of bindings between
* wildcards / variables in the template and values in the path. If $path does not match the
* template, then a ValidationException is thrown.
*
* @param string $path A resource string.
* @throws ValidationException if path can't be matched to the template.
* @return array Array matching var names to binding values.
*/
public function match(string $path);
}

View File

@@ -0,0 +1,195 @@
<?php
/*
* Copyright 2018 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\ResourceTemplate;
use Google\ApiCore\ValidationException;
/**
* Represents a segment in a resource template. This is used internally by RelativeResourceTemplate,
* but is not intended for public use and may change without notice.
*
* @internal
*/
class Segment
{
const LITERAL_SEGMENT = 0;
const WILDCARD_SEGMENT = 1;
const DOUBLE_WILDCARD_SEGMENT = 2;
const VARIABLE_SEGMENT = 3;
private int $segmentType;
private ?string $value;
private ?string $key;
private ?RelativeResourceTemplate $template;
private ?string $stringRepr;
private ?string $separator;
/**
* Segment constructor.
* @param int $segmentType
* @param string|null $value
* @param string|null $key
* @param RelativeResourceTemplate|null $template
* @param string $separator The separator that belongs at the end of a segment. Ending segments should use '/'.
* @throws ValidationException
*/
public function __construct(
int $segmentType,
?string $value = null,
?string $key = null,
?RelativeResourceTemplate $template = null,
string $separator = '/'
) {
$this->segmentType = $segmentType;
$this->value = $value;
$this->key = $key;
$this->template = $template;
$this->separator = $separator;
switch ($this->segmentType) {
case Segment::LITERAL_SEGMENT:
$this->stringRepr = "{$this->value}";
break;
case Segment::WILDCARD_SEGMENT:
$this->stringRepr = '*';
break;
case Segment::DOUBLE_WILDCARD_SEGMENT:
$this->stringRepr = '**';
break;
case Segment::VARIABLE_SEGMENT:
$this->stringRepr = "{{$this->key}={$this->template}}";
break;
default:
throw new ValidationException(
"Unexpected Segment type: {$this->segmentType}"
);
}
}
/**
* @return string A string representation of the segment.
*/
public function __toString()
{
return $this->stringRepr;
}
/**
* Checks if $value matches this Segment.
*
* @param string $value
* @return bool
* @throws ValidationException
*/
public function matches(string $value)
{
switch ($this->segmentType) {
case Segment::LITERAL_SEGMENT:
return $this->value === $value;
case Segment::WILDCARD_SEGMENT:
return self::isValidBinding($value);
case Segment::DOUBLE_WILDCARD_SEGMENT:
return self::isValidDoubleWildcardBinding($value);
case Segment::VARIABLE_SEGMENT:
return $this->template->matches($value);
default:
throw new ValidationException(
"Unexpected Segment type: {$this->segmentType}"
);
}
}
/**
* @return int
*/
public function getSegmentType()
{
return $this->segmentType;
}
/**
* @return string|null
*/
public function getKey()
{
return $this->key;
}
/**
* @return string|null
*/
public function getValue()
{
return $this->value;
}
/**
* @return RelativeResourceTemplate|null
*/
public function getTemplate()
{
return $this->template;
}
/**
* @return string
*/
public function getSeparator()
{
return $this->separator;
}
/**
* Check if $binding is a valid segment binding. Segment bindings may contain any characters
* except a forward slash ('/'), and may not be empty.
*
* @param string $binding
* @return bool
*/
private static function isValidBinding(string $binding)
{
return preg_match('-^[^/]+$-', $binding) === 1;
}
/**
* Check if $binding is a valid double wildcard binding. Segment bindings may contain any
* characters, but may not be empty.
*
* @param string $binding
* @return bool
*/
private static function isValidDoubleWildcardBinding(string $binding)
{
return preg_match('-^.+$-', $binding) === 1;
}
}

547
vendor/google/gax/src/RetrySettings.php vendored Normal file
View File

@@ -0,0 +1,547 @@
<?php
/*
* Copyright 2016 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore;
use Closure;
/**
* The RetrySettings class is used to configure retrying and timeouts for RPCs.
* This class can be passed as an optional parameter to RPC methods, or as part
* of an optional array in the constructor of a client object. In addition,
* many RPCs and API clients accept a PHP array in place of a RetrySettings
* object. This can be used to change particular retry parameters without
* needing to construct a complete RetrySettings object.
*
* Constructing a RetrySettings object
* -----------------------------------
*
* See the RetrySettings constructor for documentation about parameters that
* can be passed to RetrySettings.
*
* Example of creating a RetrySettings object using the constructor:
* ```
* $retrySettings = new RetrySettings([
* 'initialRetryDelayMillis' => 100,
* 'retryDelayMultiplier' => 1.3,
* 'maxRetryDelayMillis' => 60000,
* 'initialRpcTimeoutMillis' => 20000,
* 'rpcTimeoutMultiplier' => 1.0,
* 'maxRpcTimeoutMillis' => 20000,
* 'totalTimeoutMillis' => 600000,
* 'retryableCodes' => [ApiStatus::DEADLINE_EXCEEDED, ApiStatus::UNAVAILABLE],
* ]);
* ```
*
* It is also possible to create a new RetrySettings object from an existing
* object using the {@see \Google\ApiCore\RetrySettings::with()} method.
*
* Example modifying an existing RetrySettings object using `with()`:
* ```
* $newRetrySettings = $retrySettings->with([
* 'totalTimeoutMillis' => 700000,
* ]);
* ```
*
* Modifying the retry behavior of an RPC method
* ---------------------------------------------
*
* RetrySettings objects can be used to control retries for many RPC methods in
* [google-cloud-php](https://github.com/googleapis/google-cloud-php).
* The examples below make use of the
* [GroupServiceClient](https://cloud.google.com/php/docs/reference/cloud-monitoring/latest/V3.Client.GroupServiceClient)
* from the [Monitoring V3 API](https://github.com/googleapis/google-cloud-php/tree/master/src/Monitoring/V3),
* but they can be applied to other APIs in the
* [google-cloud-php](https://github.com/googleapis/google-cloud-php) repository.
*
* It is possible to specify the retry behavior to be used by an RPC via the
* `retrySettings` field in the `optionalArgs` parameter. The `retrySettings`
* field can contain either a RetrySettings object, or a PHP array containing
* the particular retry parameters to be updated.
*
* Example of disabling retries for a single call to the
* [listGroups](https://cloud.google.com/php/docs/reference/cloud-monitoring/latest/V3.Client.GroupServiceClient#_Google_Cloud_Monitoring_V3_Client_GroupServiceClient__listGroups__)
* method, and setting a custom timeout:
* ```
* $result = $client->listGroups($name, [
* 'retrySettings' => [
* 'retriesEnabled' => false,
* 'noRetriesRpcTimeoutMillis' => 5000,
* ]
* ]);
* ```
*
* Example of creating a new RetrySettings object and using it to override
* the retry settings for a call to the
* [listGroups](https://cloud.google.com/php/docs/reference/cloud-monitoring/latest/V3.Client.GroupServiceClient#_Google_Cloud_Monitoring_V3_Client_GroupServiceClient__listGroups__)
* method:
* ```
* $customRetrySettings = new RetrySettings([
* 'initialRetryDelayMillis' => 100,
* 'retryDelayMultiplier' => 1.3,
* 'maxRetryDelayMillis' => 60000,
* 'initialRpcTimeoutMillis' => 20000,
* 'rpcTimeoutMultiplier' => 1.0,
* 'maxRpcTimeoutMillis' => 20000,
* 'totalTimeoutMillis' => 600000,
* 'retryableCodes' => [ApiStatus::DEADLINE_EXCEEDED, ApiStatus::UNAVAILABLE],
* ]);
*
* $result = $client->listGroups($name, [
* 'retrySettings' => $customRetrySettings
* ]);
* ```
*
* Modifying the default retry behavior for RPC methods on a Client object
* -----------------------------------------------------------------------
*
* It is also possible to specify the retry behavior for RPC methods when
* constructing a client object using the 'retrySettingsArray'. The examples
* below again make use of the
* [GroupServiceClient](https://cloud.google.com/php/docs/reference/cloud-monitoring/latest/V3.Client.GroupServiceClient)
* from the [Monitoring V3 API](https://github.com/googleapis/google-cloud-php/tree/main/Monitoring/src/V3),
* but they can be applied to other APIs in the
* [google-cloud-php](https://github.com/googleapis/google-cloud-php) repository.
*
* The GroupServiceClient object accepts an optional `retrySettingsArray`
* parameter, which can be used to specify retry behavior for RPC methods
* on the client. The `retrySettingsArray` accepts a PHP array in which keys
* are the names of RPC methods on the client, and values are either a
* RetrySettings object or a PHP array containing the particular retry
* parameters to be updated.
*
* Example updating the retry settings for four methods of GroupServiceClient:
* ```
* use Google\Cloud\Monitoring\V3\GroupServiceClient;
*
* $customRetrySettings = new RetrySettings([
* 'initialRetryDelayMillis' => 100,
* 'retryDelayMultiplier' => 1.3,
* 'maxRetryDelayMillis' => 60000,
* 'initialRpcTimeoutMillis' => 20000,
* 'rpcTimeoutMultiplier' => 1.0,
* 'maxRpcTimeoutMillis' => 20000,
* 'totalTimeoutMillis' => 600000,
* 'retryableCodes' => [ApiStatus::DEADLINE_EXCEEDED, ApiStatus::UNAVAILABLE],
* ]);
*
* $updatedCustomRetrySettings = $customRetrySettings->with([
* 'totalTimeoutMillis' => 700000
* ]);
*
* $client = new GroupServiceClient([
* 'retrySettingsArray' => [
* 'listGroups' => ['retriesEnabled' => false],
* 'getGroup' => [
* 'initialRpcTimeoutMillis' => 10000,
* 'maxRpcTimeoutMillis' => 30000,
* 'totalTimeoutMillis' => 60000,
* ],
* 'deleteGroup' => $customRetrySettings,
* 'updateGroup' => $updatedCustomRetrySettings
* ],
* ]);
* ```
*
* Configure the use of logical timeout
* ------------------------------------
*
* To configure the use of a logical timeout, where a logical timeout is the
* duration a method is given to complete one or more RPC attempts, with each
* attempt using only the time remaining in the logical timeout, use
* {@see \Google\ApiCore\RetrySettings::logicalTimeout()} combined with
* {@see \Google\ApiCore\RetrySettings::with()}.
*
* ```
* $timeoutSettings = RetrySettings::logicalTimeout(30000);
*
* $customRetrySettings = $customRetrySettings->with($timeoutSettings);
*
* $result = $client->listGroups($name, [
* 'retrySettings' => $customRetrySettings
* ]);
* ```
*
* {@see \Google\ApiCore\RetrySettings::logicalTimeout()} can also be used on a
* method call independent of a RetrySettings instance.
*
* ```
* $timeoutSettings = RetrySettings::logicalTimeout(30000);
*
* $result = $client->listGroups($name, [
* 'retrySettings' => $timeoutSettings
* ]);
* ```
*/
class RetrySettings
{
use ValidationTrait;
const DEFAULT_MAX_RETRIES = 0;
private $retriesEnabled;
private $retryableCodes;
private $initialRetryDelayMillis;
private $retryDelayMultiplier;
private $maxRetryDelayMillis;
private $initialRpcTimeoutMillis;
private $rpcTimeoutMultiplier;
private $maxRpcTimeoutMillis;
private $totalTimeoutMillis;
private $noRetriesRpcTimeoutMillis;
/**
* The number of maximum retries an operation can do.
* This doesn't include the original API call.
* Setting this to 0 means no limit.
*/
private int $maxRetries;
/**
* When set, this function will be used to evaluate if the retry should
* take place or not. The callable will have the following signature:
* function (Exception $e, array $options): bool
*/
private ?Closure $retryFunction;
/**
* Constructs an instance.
*
* @param array $settings {
* Required. Settings for configuring the retry behavior. All parameters are required except
* $retriesEnabled and $noRetriesRpcTimeoutMillis, which are optional and have defaults
* determined based on the other settings provided.
*
* @type bool $retriesEnabled Optional. Enables retries. If not specified, the value is
* determined using the $retryableCodes setting. If $retryableCodes is empty,
* then $retriesEnabled is set to false; otherwise, it is set to true.
* @type int $noRetriesRpcTimeoutMillis Optional. The timeout of the rpc call to be used
* if $retriesEnabled is false, in milliseconds. It not specified, the value
* of $initialRpcTimeoutMillis is used.
* @type array $retryableCodes The Status codes that are retryable. Each status should be
* either one of the string constants defined on {@see \Google\ApiCore\ApiStatus}
* or an integer constant defined on {@see \Google\Rpc\Code}.
* @type int $initialRetryDelayMillis The initial delay of retry in milliseconds.
* @type int $retryDelayMultiplier The exponential multiplier of retry delay.
* @type int $maxRetryDelayMillis The max delay of retry in milliseconds.
* @type int $initialRpcTimeoutMillis The initial timeout of rpc call in milliseconds.
* @type int $rpcTimeoutMultiplier The exponential multiplier of rpc timeout.
* @type int $maxRpcTimeoutMillis The max timeout of rpc call in milliseconds.
* @type int $totalTimeoutMillis The max accumulative timeout in total.
* @type int $maxRetries The max retries allowed for an operation.
* Defaults to the value of the DEFAULT_MAX_RETRIES constant.
* This option is experimental.
* @type callable $retryFunction This function will be used to decide if we should retry or not.
* Callable signature: `function (Exception $e, array $options): bool`
* This option is experimental.
* }
*/
public function __construct(array $settings)
{
$this->validateNotNull($settings, [
'initialRetryDelayMillis',
'retryDelayMultiplier',
'maxRetryDelayMillis',
'initialRpcTimeoutMillis',
'rpcTimeoutMultiplier',
'maxRpcTimeoutMillis',
'totalTimeoutMillis',
'retryableCodes'
]);
$this->initialRetryDelayMillis = $settings['initialRetryDelayMillis'];
$this->retryDelayMultiplier = $settings['retryDelayMultiplier'];
$this->maxRetryDelayMillis = $settings['maxRetryDelayMillis'];
$this->initialRpcTimeoutMillis = $settings['initialRpcTimeoutMillis'];
$this->rpcTimeoutMultiplier = $settings['rpcTimeoutMultiplier'];
$this->maxRpcTimeoutMillis = $settings['maxRpcTimeoutMillis'];
$this->totalTimeoutMillis = $settings['totalTimeoutMillis'];
$this->retryableCodes = $settings['retryableCodes'];
$this->retriesEnabled = array_key_exists('retriesEnabled', $settings)
? $settings['retriesEnabled']
: (count($this->retryableCodes) > 0);
$this->noRetriesRpcTimeoutMillis = array_key_exists('noRetriesRpcTimeoutMillis', $settings)
? $settings['noRetriesRpcTimeoutMillis']
: $this->initialRpcTimeoutMillis;
$this->maxRetries = $settings['maxRetries'] ?? self::DEFAULT_MAX_RETRIES;
$this->retryFunction = $settings['retryFunction'] ?? null;
}
/**
* Constructs an array mapping method names to CallSettings.
*
* @param string $serviceName
* The fully-qualified name of this service, used as a key into
* the client config file.
* @param array $clientConfig
* An array parsed from the standard API client config file.
* @param bool $disableRetries
* Disable retries in all loaded RetrySettings objects. Defaults to false.
* @throws ValidationException
* @return RetrySettings[] $retrySettings
*/
public static function load(
string $serviceName,
array $clientConfig,
bool $disableRetries = false
) {
$serviceRetrySettings = [];
$serviceConfig = $clientConfig['interfaces'][$serviceName];
$retryCodes = $serviceConfig['retry_codes'];
$retryParams = $serviceConfig['retry_params'];
foreach ($serviceConfig['methods'] as $methodName => $methodConfig) {
$timeoutMillis = $methodConfig['timeout_millis'];
if (empty($methodConfig['retry_codes_name']) || empty($methodConfig['retry_params_name'])) {
// Construct a RetrySettings object with retries disabled
$retrySettings = self::constructDefault()->with([
'noRetriesRpcTimeoutMillis' => $timeoutMillis,
]);
} else {
$retryCodesName = $methodConfig['retry_codes_name'];
$retryParamsName = $methodConfig['retry_params_name'];
if (!array_key_exists($retryCodesName, $retryCodes)) {
throw new ValidationException("Invalid retry_codes_name setting: '$retryCodesName'");
}
if (!array_key_exists($retryParamsName, $retryParams)) {
throw new ValidationException("Invalid retry_params_name setting: '$retryParamsName'");
}
foreach ($retryCodes[$retryCodesName] as $status) {
if (!ApiStatus::isValidStatus($status)) {
throw new ValidationException("Invalid status code: '$status'");
}
}
$retryParameters = self::convertArrayFromSnakeCase($retryParams[$retryParamsName]) + [
'retryableCodes' => $retryCodes[$retryCodesName],
'noRetriesRpcTimeoutMillis' => $timeoutMillis,
];
if ($disableRetries) {
$retryParameters['retriesEnabled'] = false;
}
$retrySettings = new RetrySettings($retryParameters);
}
$serviceRetrySettings[$methodName] = $retrySettings;
}
return $serviceRetrySettings;
}
public static function constructDefault()
{
return new RetrySettings([
'retriesEnabled' => false,
'noRetriesRpcTimeoutMillis' => 30000,
'initialRetryDelayMillis' => 100,
'retryDelayMultiplier' => 1.3,
'maxRetryDelayMillis' => 60000,
'initialRpcTimeoutMillis' => 20000,
'rpcTimeoutMultiplier' => 1,
'maxRpcTimeoutMillis' => 20000,
'totalTimeoutMillis' => 600000,
'retryableCodes' => [],
'maxRetries' => self::DEFAULT_MAX_RETRIES,
'retryFunction' => null]);
}
/**
* Creates a new instance of RetrySettings that updates the settings in the existing instance
* with the settings specified in the $settings parameter.
*
* @param array $settings {
* Settings for configuring the retry behavior. Supports all of the options supported by
* the constructor; see {@see \Google\ApiCore\RetrySettings::__construct()}. All parameters
* are optional - all unset parameters will default to the value in the existing instance.
* }
* @return RetrySettings
*/
public function with(array $settings)
{
$existingSettings = [
'initialRetryDelayMillis' => $this->getInitialRetryDelayMillis(),
'retryDelayMultiplier' => $this->getRetryDelayMultiplier(),
'maxRetryDelayMillis' => $this->getMaxRetryDelayMillis(),
'initialRpcTimeoutMillis' => $this->getInitialRpcTimeoutMillis(),
'rpcTimeoutMultiplier' => $this->getRpcTimeoutMultiplier(),
'maxRpcTimeoutMillis' => $this->getMaxRpcTimeoutMillis(),
'totalTimeoutMillis' => $this->getTotalTimeoutMillis(),
'retryableCodes' => $this->getRetryableCodes(),
'retriesEnabled' => $this->retriesEnabled(),
'noRetriesRpcTimeoutMillis' => $this->getNoRetriesRpcTimeoutMillis(),
'maxRetries' => $this->getMaxRetries(),
'retryFunction' => $this->getRetryFunction(),
];
return new RetrySettings($settings + $existingSettings);
}
/**
* Creates an associative array of the {@see \Google\ApiCore\RetrySettings} timeout fields configured
* with the given timeout specified in the $timeout parameter interpreted as a logical timeout.
*
* @param int $timeout The timeout in milliseconds to be used as a logical call timeout.
* @return array
*/
public static function logicalTimeout(int $timeout)
{
return [
'initialRpcTimeoutMillis' => $timeout,
'maxRpcTimeoutMillis' => $timeout,
'totalTimeoutMillis' => $timeout,
'noRetriesRpcTimeoutMillis' => $timeout,
'rpcTimeoutMultiplier' => 1.0
];
}
/**
* @return bool Returns true if retries are enabled, otherwise returns false.
*/
public function retriesEnabled()
{
return $this->retriesEnabled;
}
/**
* @return int The timeout of the rpc call to be used if $retriesEnabled is false,
* in milliseconds.
*/
public function getNoRetriesRpcTimeoutMillis()
{
return $this->noRetriesRpcTimeoutMillis;
}
/**
* @return int[] Status codes to retry
*/
public function getRetryableCodes()
{
return $this->retryableCodes;
}
/**
* @return int The initial retry delay in milliseconds. If $this->retriesEnabled()
* is false, this setting is unused.
*/
public function getInitialRetryDelayMillis()
{
return $this->initialRetryDelayMillis;
}
/**
* @return float The retry delay multiplier. If $this->retriesEnabled()
* is false, this setting is unused.
*/
public function getRetryDelayMultiplier()
{
return $this->retryDelayMultiplier;
}
/**
* @return int The maximum retry delay in milliseconds. If $this->retriesEnabled()
* is false, this setting is unused.
*/
public function getMaxRetryDelayMillis()
{
return $this->maxRetryDelayMillis;
}
/**
* @return int The initial rpc timeout in milliseconds. If $this->retriesEnabled()
* is false, this setting is unused - use noRetriesRpcTimeoutMillis to
* set the timeout in that case.
*/
public function getInitialRpcTimeoutMillis()
{
return $this->initialRpcTimeoutMillis;
}
/**
* @return float The rpc timeout multiplier. If $this->retriesEnabled()
* is false, this setting is unused.
*/
public function getRpcTimeoutMultiplier()
{
return $this->rpcTimeoutMultiplier;
}
/**
* @return int The maximum rpc timeout in milliseconds. If $this->retriesEnabled()
* is false, this setting is unused - use noRetriesRpcTimeoutMillis to
* set the timeout in that case.
*/
public function getMaxRpcTimeoutMillis()
{
return $this->maxRpcTimeoutMillis;
}
/**
* @return int The total time in milliseconds to spend on the call, including all
* retry attempts and delays between attempts. If $this->retriesEnabled()
* is false, this setting is unused - use noRetriesRpcTimeoutMillis to
* set the timeout in that case.
*/
public function getTotalTimeoutMillis()
{
return $this->totalTimeoutMillis;
}
/**
* @experimental
*/
public function getMaxRetries()
{
return $this->maxRetries;
}
/**
* @experimental
*/
public function getRetryFunction()
{
return $this->retryFunction;
}
private static function convertArrayFromSnakeCase(array $settings)
{
$camelCaseSettings = [];
foreach ($settings as $key => $value) {
$camelCaseKey = str_replace(' ', '', ucwords(str_replace('_', ' ', $key)));
$camelCaseSettings[lcfirst($camelCaseKey)] = $value;
}
return $camelCaseSettings;
}
}

541
vendor/google/gax/src/Serializer.php vendored Normal file
View File

@@ -0,0 +1,541 @@
<?php
/*
* Copyright 2017 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore;
use Google\Protobuf\Any;
use Google\Protobuf\Descriptor;
use Google\Protobuf\DescriptorPool;
use Google\Protobuf\FieldDescriptor;
use Google\Protobuf\Internal\Message;
use RuntimeException;
/**
* Collection of methods to help with serialization of protobuf objects
*/
class Serializer
{
const MAP_KEY_FIELD_NAME = 'key';
const MAP_VALUE_FIELD_NAME = 'value';
private static $phpArraySerializer;
// Caches for different helper functions
private static array $getterMap = [];
private static array $setterMap = [];
private static array $snakeCaseMap = [];
private static array $camelCaseMap = [];
private static $metadataKnownTypes = [
'google.rpc.retryinfo-bin' => \Google\Rpc\RetryInfo::class,
'google.rpc.debuginfo-bin' => \Google\Rpc\DebugInfo::class,
'google.rpc.quotafailure-bin' => \Google\Rpc\QuotaFailure::class,
'google.rpc.badrequest-bin' => \Google\Rpc\BadRequest::class,
'google.rpc.requestinfo-bin' => \Google\Rpc\RequestInfo::class,
'google.rpc.resourceinfo-bin' => \Google\Rpc\ResourceInfo::class,
'google.rpc.errorinfo-bin' => \Google\Rpc\ErrorInfo::class,
'google.rpc.help-bin' => \Google\Rpc\Help::class,
'google.rpc.localizedmessage-bin' => \Google\Rpc\LocalizedMessage::class,
];
private $fieldTransformers;
private $messageTypeTransformers;
private $decodeFieldTransformers;
private $decodeMessageTypeTransformers;
// Array of key-value pairs which specify a custom encoding function.
// The key is the proto class and the value is the function
// which will be used to convert the proto instead of the
// encodeMessage method from the Serializer class.
private $customEncoders;
private $descriptorMaps = [];
/**
* Serializer constructor.
*
* @param array $fieldTransformers An array mapping field names to transformation functions
* @param array $messageTypeTransformers An array mapping message names to transformation functions
* @param array $decodeFieldTransformers An array mapping field names to transformation functions
* @param array $decodeMessageTypeTransformers An array mapping message names to transformation functions
*/
public function __construct(
$fieldTransformers = [],
$messageTypeTransformers = [],
$decodeFieldTransformers = [],
$decodeMessageTypeTransformers = [],
$customEncoders = [],
) {
$this->fieldTransformers = $fieldTransformers;
$this->messageTypeTransformers = $messageTypeTransformers;
$this->decodeFieldTransformers = $decodeFieldTransformers;
$this->decodeMessageTypeTransformers = $decodeMessageTypeTransformers;
$this->customEncoders = $customEncoders;
}
/**
* Encode protobuf message as a PHP array
*
* @param mixed $message
* @return array
* @throws ValidationException
*/
public function encodeMessage($message)
{
$cls = get_class($message);
// If we have supplied a customEncoder for this class type,
// then we use that instead of the general encodeMessage definition.
if (array_key_exists($cls, $this->customEncoders)) {
$func = $this->customEncoders[$cls];
return call_user_func($func, $message);
}
// Get message descriptor
$pool = DescriptorPool::getGeneratedPool();
$messageType = $pool->getDescriptorByClassName(get_class($message));
try {
return $this->encodeMessageImpl($message, $messageType);
} catch (\Exception $e) {
throw new ValidationException(
'Error encoding message: ' . $e->getMessage(),
$e->getCode(),
$e
);
}
}
/**
* Decode PHP array into the specified protobuf message
*
* @param mixed $message
* @param array $data
* @return mixed
* @throws ValidationException
*/
public function decodeMessage($message, array $data)
{
// Get message descriptor
$pool = DescriptorPool::getGeneratedPool();
$messageType = $pool->getDescriptorByClassName(get_class($message));
try {
return $this->decodeMessageImpl($message, $messageType, $data);
} catch (\Exception $e) {
throw new ValidationException(
'Error decoding message: ' . $e->getMessage(),
$e->getCode(),
$e
);
}
}
/**
* @param Message $message
* @return string Json representation of $message
* @throws ValidationException
*/
public static function serializeToJson(Message $message)
{
return json_encode(self::serializeToPhpArray($message), JSON_PRETTY_PRINT);
}
/**
* @param Message $message
* @return array PHP array representation of $message
* @throws ValidationException
*/
public static function serializeToPhpArray(Message $message)
{
return self::getPhpArraySerializer()->encodeMessage($message);
}
/**
* Decode metadata received from gRPC status object
*
* @param array $metadata
* @return array
*/
public static function decodeMetadata(array $metadata)
{
if (count($metadata) == 0) {
return [];
}
$result = [];
foreach ($metadata as $key => $values) {
foreach ($values as $value) {
$decodedValue = [
'@type' => $key,
];
if (self::hasBinaryHeaderSuffix($key)) {
if (isset(self::$metadataKnownTypes[$key])) {
$class = self::$metadataKnownTypes[$key];
/** @var Message $message */
$message = new $class();
try {
$message->mergeFromString($value);
$decodedValue += self::serializeToPhpArray($message);
} catch (\Exception $e) {
// We encountered an error trying to deserialize the data
$decodedValue += [
'data' => '<Unable to deserialize data>',
];
}
} else {
// The metadata contains an unexpected binary type
$decodedValue += [
'data' => '<Unknown Binary Data>',
];
}
} else {
$decodedValue += [
'data' => $value,
];
}
$result[] = $decodedValue;
}
}
return $result;
}
/**
* Decode an array of Any messages into a printable PHP array.
*
* @param iterable $anyArray
* @return array
*/
public static function decodeAnyMessages($anyArray)
{
$results = [];
foreach ($anyArray as $any) {
try {
/** @var Any $any */
/** @var Message $unpacked */
$unpacked = $any->unpack();
$results[] = self::serializeToPhpArray($unpacked);
} catch (\Exception $ex) {
echo "$ex\n";
// failed to unpack the $any object - show as unknown binary data
$results[] = [
'typeUrl' => $any->getTypeUrl(),
'value' => '<Unknown Binary Data>',
];
}
}
return $results;
}
/**
* @param FieldDescriptor $field
* @param Message|array|string $data
* @return mixed
* @throws \Exception
*/
private function encodeElement(FieldDescriptor $field, $data)
{
switch ($field->getType()) {
case GPBType::MESSAGE:
if (is_array($data)) {
$result = $data;
} else {
$result = $this->encodeMessageImpl($data, $field->getMessageType());
}
$messageType = $field->getMessageType()->getFullName();
if (isset($this->messageTypeTransformers[$messageType])) {
$result = $this->messageTypeTransformers[$messageType]($result);
}
break;
default:
$result = $data;
break;
}
if (isset($this->fieldTransformers[$field->getName()])) {
$result = $this->fieldTransformers[$field->getName()]($result);
}
return $result;
}
private function getDescriptorMaps(Descriptor $descriptor)
{
if (!isset($this->descriptorMaps[$descriptor->getFullName()])) {
$fieldsByName = [];
$fieldCount = $descriptor->getFieldCount();
for ($i = 0; $i < $fieldCount; $i++) {
$field = $descriptor->getField($i);
$fieldsByName[$field->getName()] = $field;
}
$fieldToOneof = [];
$oneofCount = $descriptor->getOneofDeclCount();
for ($i = 0; $i < $oneofCount; $i++) {
$oneof = $descriptor->getOneofDecl($i);
$oneofFieldCount = $oneof->getFieldCount();
for ($j = 0; $j < $oneofFieldCount; $j++) {
$field = $oneof->getField($j);
$fieldToOneof[$field->getName()] = $oneof->getName();
}
}
$this->descriptorMaps[$descriptor->getFullName()] = [$fieldsByName, $fieldToOneof];
}
return $this->descriptorMaps[$descriptor->getFullName()];
}
/**
* @param Message $message
* @param Descriptor $messageType
* @return array
* @throws \Exception
*/
private function encodeMessageImpl(Message $message, Descriptor $messageType)
{
$data = [];
// Call the getDescriptorMaps outside of the loop to save processing.
// Use the same set of fields to loop over, instead of using field count.
list($fields, $fieldsToOneof) = $this->getDescriptorMaps($messageType);
foreach ($fields as $field) {
$key = $field->getName();
$getter = $this->getGetter($key);
$v = $message->$getter();
if (is_null($v)) {
continue;
}
// Check and skip unset fields inside oneofs
if (isset($fieldsToOneof[$key])) {
$oneofName = $fieldsToOneof[$key];
$oneofGetter = $this->getGetter($oneofName);
if ($message->$oneofGetter() !== $key) {
continue;
}
}
if ($field->isMap()) {
list($mapFieldsByName, $_) = $this->getDescriptorMaps($field->getMessageType());
$keyField = $mapFieldsByName[self::MAP_KEY_FIELD_NAME];
$valueField = $mapFieldsByName[self::MAP_VALUE_FIELD_NAME];
$arr = [];
foreach ($v as $k => $vv) {
$arr[$this->encodeElement($keyField, $k)] = $this->encodeElement($valueField, $vv);
}
$v = $arr;
} elseif ($this->checkFieldRepeated($field)) {
$arr = [];
foreach ($v as $k => $vv) {
$arr[$k] = $this->encodeElement($field, $vv);
}
$v = $arr;
} else {
$v = $this->encodeElement($field, $v);
}
$key = self::toCamelCase($key);
$data[$key] = $v;
}
return $data;
}
/**
* @param FieldDescriptor $field
* @param mixed $data
* @return mixed
* @throws \Exception
*/
private function decodeElement(FieldDescriptor $field, $data)
{
if (isset($this->decodeFieldTransformers[$field->getName()])) {
$data = $this->decodeFieldTransformers[$field->getName()]($data);
}
switch ($field->getType()) {
case GPBType::MESSAGE:
if ($data instanceof Message) {
return $data;
}
$messageType = $field->getMessageType();
$messageTypeName = $messageType->getFullName();
$klass = $messageType->getClass();
$msg = new $klass();
if (isset($this->decodeMessageTypeTransformers[$messageTypeName])) {
$data = $this->decodeMessageTypeTransformers[$messageTypeName]($data);
}
return $this->decodeMessageImpl($msg, $messageType, $data);
default:
return $data;
}
}
/**
* @param Message $message
* @param Descriptor $messageType
* @param array $data
* @return mixed
* @throws \Exception
*/
private function decodeMessageImpl(Message $message, Descriptor $messageType, array $data)
{
list($fieldsByName, $_) = $this->getDescriptorMaps($messageType);
foreach ($data as $key => $v) {
// Get the field by tag number or name
$fieldName = self::toSnakeCase($key);
// Unknown field found
if (!isset($fieldsByName[$fieldName])) {
throw new RuntimeException(sprintf(
'cannot handle unknown field %s on message %s',
$fieldName,
$messageType->getFullName()
));
}
/** @var FieldDescriptor $field */
$field = $fieldsByName[$fieldName];
if ($field->isMap()) {
list($mapFieldsByName, $_) = $this->getDescriptorMaps($field->getMessageType());
$keyField = $mapFieldsByName[self::MAP_KEY_FIELD_NAME];
$valueField = $mapFieldsByName[self::MAP_VALUE_FIELD_NAME];
$arr = [];
foreach ($v as $k => $vv) {
$arr[$this->decodeElement($keyField, $k)] = $this->decodeElement($valueField, $vv);
}
$value = $arr;
} elseif ($this->checkFieldRepeated($field)) {
$arr = [];
foreach ($v as $k => $vv) {
$arr[$k] = $this->decodeElement($field, $vv);
}
$value = $arr;
} else {
$value = $this->decodeElement($field, $v);
}
$setter = $this->getSetter($field->getName());
$message->$setter($value);
// We must unset $value here, otherwise the protobuf c extension will mix up the references
// and setting one value will change all others
unset($value);
}
return $message;
}
/**
* @param FieldDescriptor $field
* @return bool
*/
private function checkFieldRepeated(FieldDescriptor $field): bool
{
return method_exists($field, 'isRepeated')
? $field->isRepeated()
: $field->getLabel() === GPBLabel::REPEATED;
}
/**
* @param string $name
* @return string Getter function
*/
public static function getGetter(string $name)
{
if (!isset(self::$getterMap[$name])) {
self::$getterMap[$name] = 'get' . ucfirst(self::toCamelCase($name));
}
return self::$getterMap[$name];
}
/**
* @param string $name
* @return string Setter function
*/
public static function getSetter(string $name)
{
if (!isset(self::$setterMap[$name])) {
self::$setterMap[$name] = 'set' . ucfirst(self::toCamelCase($name));
}
return self::$setterMap[$name];
}
/**
* Convert string from camelCase to snake_case
*
* @param string $key
* @return string
*/
public static function toSnakeCase(string $key)
{
if (!isset(self::$snakeCaseMap[$key])) {
self::$snakeCaseMap[$key] = strtolower(
preg_replace(['/([a-z\d])([A-Z])/', '/([^_])([A-Z][a-z])/'], '$1_$2', $key)
);
}
return self::$snakeCaseMap[$key];
}
/**
* Convert string from snake_case to camelCase
*
* @param string $key
* @return string
*/
public static function toCamelCase(string $key)
{
if (!isset(self::$camelCaseMap[$key])) {
self::$camelCaseMap[$key] = lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', $key))));
}
return self::$camelCaseMap[$key];
}
private static function hasBinaryHeaderSuffix(string $key)
{
return substr_compare($key, '-bin', strlen($key) - 4) === 0;
}
private static function getPhpArraySerializer()
{
if (is_null(self::$phpArraySerializer)) {
self::$phpArraySerializer = new Serializer();
}
return self::$phpArraySerializer;
}
public static function loadKnownMetadataTypes()
{
foreach (self::$metadataKnownTypes as $key => $class) {
new $class();
}
}
}
// It is necessary to call this when this file is included. Otherwise we cannot be
// guaranteed that the relevant classes will be loaded into the protobuf descriptor
// pool when we try to unpack an Any object containing that class.
// phpcs:disable PSR1.Files.SideEffects
Serializer::loadKnownMetadataTypes();
// phpcs:enable

128
vendor/google/gax/src/ServerStream.php vendored Normal file
View File

@@ -0,0 +1,128 @@
<?php
/*
* Copyright 2016 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore;
use Google\Auth\Logging\LoggingTrait;
use Google\Auth\Logging\RpcLogEvent;
use Google\Protobuf\Internal\Message;
use Google\Rpc\Code;
use Psr\Log\LoggerInterface;
/**
* ServerStream is the response object from a server streaming API call.
*
* @template T = mixed
*/
class ServerStream
{
use LoggingTrait;
private $call;
private $resourcesGetMethod;
private null|LoggerInterface $logger;
/**
* ServerStream constructor.
*
* @param ServerStreamingCallInterface $serverStreamingCall The server streaming call object
* @param array $streamingDescriptor
* @param null|LoggerInterface $logger A PSR-3 compliant logger.
*/
public function __construct(
$serverStreamingCall,
array $streamingDescriptor = [],
null|LoggerInterface $logger = null
) {
$this->call = $serverStreamingCall;
if (array_key_exists('resourcesGetMethod', $streamingDescriptor)) {
$this->resourcesGetMethod = $streamingDescriptor['resourcesGetMethod'];
}
$this->logger = $logger;
}
/**
* A generator which yields results from the server until the streaming call
* completes. Throws an ApiException if the streaming call failed.
*
* @throws ApiException
* @return \Generator<int, T>|mixed
*/
public function readAll()
{
$resourcesGetMethod = $this->resourcesGetMethod;
foreach ($this->call->responses() as $response) {
if ($this->logger && $response instanceof Message) {
$responseEvent = new RpcLogEvent();
$responseEvent->payload = $response->serializeToJsonString();
$responseEvent->processId = (int) getmypid();
$responseEvent->requestId = crc32((string) spl_object_id($this) . getmypid());
$this->logResponse($responseEvent);
}
if (!is_null($resourcesGetMethod)) {
foreach ($response->$resourcesGetMethod() as $resource) {
yield $resource;
}
} else {
yield $response;
}
}
// Errors in the REST transport will be thrown from there and not reach
// this handling. Successful REST server-streams will have an OK status.
$status = $this->call->getStatus();
if ($this->logger) {
$statusEvent = new RpcLogEvent();
$statusEvent->status = $status->code;
$statusEvent->processId = (int) getmypid();
$statusEvent->requestId = crc32((string) spl_object_id($this) . getmypid());
$this->logResponse($statusEvent);
}
if ($status->code !== Code::OK) {
throw ApiException::createFromStdClass($status);
}
}
/**
* Return the underlying call object.
*
* @return ServerStreamingCallInterface
*/
public function getServerStreamingCall()
{
return $this->call;
}
}

View File

@@ -0,0 +1,95 @@
<?php
/*
* Copyright 2021 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore;
/**
* @internal
*/
interface ServerStreamingCallInterface
{
/**
* Start the call.
*
* @param mixed $data The data to send
* @param array<mixed> $metadata Metadata to send with the call, if applicable
* (optional)
* @param array<mixed> $options An array of options, possible keys:
* 'flags' => a number (optional)
* @return void
*/
public function start($data, array $metadata = [], array $options = []);
/**
* @return mixed An iterator of response values.
*/
public function responses();
/**
* Return the status of the server stream.
*
* @return \stdClass The API status.
*/
public function getStatus();
/**
* @return mixed The metadata sent by the server.
*/
public function getMetadata();
/**
* @return mixed The trailing metadata sent by the server.
*/
public function getTrailingMetadata();
/**
* @return string The URI of the endpoint.
*/
public function getPeer();
/**
* Cancels the call.
*
* @return void
*/
public function cancel();
/**
* Set the CallCredentials for the underlying Call.
*
* @param mixed $call_credentials The CallCredentials object
*
* @return void
*/
public function setCallCredentials($call_credentials);
}

View File

@@ -0,0 +1,64 @@
<?php
/*
* Copyright 2018 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore;
/**
* Provides helper methods for service address handling.
*
* @deprecated
* @todo (dwsupplee) serviceAddress is deprecated now in favor of
* apiEndpoint. Rename the trait/method in our next major release.
*/
trait ServiceAddressTrait
{
private static $defaultPort = 443;
/**
* @param string $apiEndpoint
* @return array
* @throws ValidationException
*/
private static function normalizeServiceAddress(string $apiEndpoint)
{
$components = explode(':', $apiEndpoint);
if (count($components) == 2) {
// Port is included in service address
return [$components[0], $components[1]];
} elseif (count($components) == 1) {
// Port is not included - append default port
return [$components[0], self::$defaultPort];
} else {
throw new ValidationException("Invalid apiEndpoint: $apiEndpoint");
}
}
}

View File

@@ -0,0 +1,102 @@
<?php
/*
* Copyright 2017 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\Testing;
use Google\ApiCore\Serializer;
use Google\Protobuf\DescriptorPool;
use Google\Protobuf\Internal\Message;
use Google\Protobuf\RepeatedField;
use PHPUnit\Framework\TestCase;
/**
* @internal
*/
abstract class GeneratedTest extends TestCase
{
/**
* @param mixed $expected
* @param mixed $actual
*/
public function assertProtobufEquals(&$expected, &$actual)
{
if ($expected === $actual) {
// This is not needed but reduces the number of "This test did not perform any assertions" messages
$this->assertSame($expected, $actual);
return;
}
if (is_array($expected) || $expected instanceof RepeatedField) {
if (is_array($expected) === is_array($actual)) {
$this->assertEquals($expected, $actual);
}
$this->assertCount(count($expected), $actual);
$expectedValues = $this->getValues($expected);
$actualValues = $this->getValues($actual);
for ($i = 0; $i < count($expectedValues); $i++) {
$expectedElement = $expectedValues[$i];
$actualElement = $actualValues[$i];
$this->assertProtobufEquals($expectedElement, $actualElement);
}
} else {
$this->assertEquals($expected, $actual);
if ($expected instanceof Message) {
$pool = DescriptorPool::getGeneratedPool();
$descriptor = $pool->getDescriptorByClassName(get_class($expected));
$fieldCount = $descriptor->getFieldCount();
for ($i = 0; $i < $fieldCount; $i++) {
$field = $descriptor->getField($i);
$getter = Serializer::getGetter($field->getName());
$expectedFieldValue = $expected->$getter();
$actualFieldValue = $actual->$getter();
$this->assertProtobufEquals($expectedFieldValue, $actualFieldValue);
}
}
}
}
/**
* @param iterable $field
*/
private function getValues($field)
{
return array_values(
is_array($field)
? $field
: iterator_to_array($field)
);
}
}

View File

@@ -0,0 +1,32 @@
<?php
/**
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Google\ApiCore\Testing;
use SebastianBergmann\Comparator\ArrayComparator;
/**
* @internal
*/
class MessageAwareArrayComparator extends ArrayComparator
{
public function __construct()
{
parent::__construct();
$this->exporter = new MessageAwareExporter();
}
}

View File

@@ -0,0 +1,47 @@
<?php
/**
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Google\ApiCore\Testing;
use Google\Protobuf\Internal\Message;
use SebastianBergmann\Exporter\Exporter;
/**
* @internal
*/
class MessageAwareExporter extends Exporter
{
/**
* Exports a value into a single-line string
*
* @param mixed $value
* @return string
*
* @see \SebastianBergmann\Exporter\Exporter::export
*/
public function shortenedExport($value)
{
if (\is_object($value) && $value instanceof Message) {
return \sprintf(
'%s Object (%s)',
\get_class($value),
\spl_object_hash($value)
);
}
return parent::shortenedExport($value);
}
}

View File

@@ -0,0 +1,168 @@
<?php
/*
* Copyright 2016 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\Testing;
use Google\ApiCore\ApiException;
use Google\Protobuf\Internal\Message;
use Google\Rpc\Code;
use Grpc;
use stdClass;
/**
* The MockBidiStreamingCall class is used to mock out the \Grpc\BidiStreamingCall class
* (https://github.com/grpc/grpc/blob/master/src/php/lib/Grpc/BidiStreamingCall.php)
*
* @internal
*/
class MockBidiStreamingCall extends Grpc\BidiStreamingCall
{
use SerializationTrait;
private $responses;
private $status;
private $writesDone = false;
private $receivedWrites = [];
/**
* MockBidiStreamingCall constructor.
* @param mixed[] $responses A list of response objects.
* @param mixed|null $deserialize An optional deserialize method for the response object.
* @param stdClass|null $status An optional status object. If set to null, a status of OK is used.
*/
public function __construct(array $responses, $deserialize = null, ?stdClass $status = null)
{
$this->responses = $responses;
$this->deserialize = $deserialize;
if (is_null($status)) {
$status = new MockStatus(Code::OK);
}
$this->status = $status;
}
/**
* @return mixed|null
* @throws ApiException
*/
public function read()
{
if (count($this->responses) > 0) {
$resp = array_shift($this->responses);
if (is_null($resp)) {
// Null was added to the responses list to simulate a failed stream
// To ensure that getStatus can now be called, we clear the remaining
// responses and set writesDone to true
$this->responses = [];
$this->writesDone();
return null;
}
$obj = $this->deserializeMessage($resp, $this->deserialize);
return $obj;
} elseif ($this->writesDone) {
return null;
} else {
throw new ApiException(
'No more responses to read, but closeWrite() not called - '
. 'this would be blocking',
Grpc\STATUS_INTERNAL,
null
);
}
}
/**
* @return stdClass|null
* @throws ApiException
*/
public function getStatus()
{
if (count($this->responses) > 0) {
throw new ApiException(
'Calls to getStatus() will block if all responses are not read',
Grpc\STATUS_INTERNAL,
null
);
}
if (!$this->writesDone) {
throw new ApiException(
'Calls to getStatus() will block if closeWrite() not called',
Grpc\STATUS_INTERNAL,
null
);
}
return $this->status;
}
/**
* Save the request object, to be retrieved via getReceivedCalls()
* @param Message|mixed $request The request object
* @param array $options An array of options.
* @throws ApiException
*/
public function write($request, array $options = [])
{
if ($this->writesDone) {
throw new ApiException(
'Cannot call write() after writesDone()',
Grpc\STATUS_INTERNAL,
null
);
}
if (is_a($request, '\Google\Protobuf\Internal\Message')) {
/** @var Message $newRequest */
$newRequest = new $request();
$newRequest->mergeFromString($request->serializeToString());
$request = $newRequest;
}
$this->receivedWrites[] = $request;
}
/**
* Set writesDone to true
*/
public function writesDone()
{
$this->writesDone = true;
}
/**
* Return a list of calls made to write(), and clear $receivedFuncCalls.
*
* @return mixed[] An array of received requests
*/
public function popReceivedCalls()
{
$receivedFuncCallsTemp = $this->receivedWrites;
$this->receivedWrites = [];
return $receivedFuncCallsTemp;
}
}

View File

@@ -0,0 +1,111 @@
<?php
/*
* Copyright 2016 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\Testing;
use Google\ApiCore\ApiException;
use Google\ApiCore\ApiStatus;
use Google\Protobuf\Internal\Message;
use Google\Rpc\Code;
use Grpc;
use stdClass;
/**
* The MockClientStreamingCall class is used to mock out the \Grpc\ClientStreamingCall class
* (https://github.com/grpc/grpc/blob/master/src/php/lib/Grpc/ClientStreamingCall.php)
*
* The MockClientStreamingCall object is constructed with a response object, an optional deserialize
* method, and an optional status. The response object and status are returned immediately from the
* wait() method. It also provides a write() method that accepts request objects, and a
* getAllRequests() method that returns all request objects passed to write(), and clears them.
*
* @internal
*/
class MockClientStreamingCall extends Grpc\ClientStreamingCall
{
private $mockUnaryCall;
private $waitCalled = false;
private $receivedWrites = [];
/**
* MockClientStreamingCall constructor.
* @param Message|string $response The response object.
* @param callable|array|null $deserialize An optional deserialize method for the response object.
* @param stdClass|null $status An optional status object. If set to null, a status of OK is used.
*/
public function __construct($response, $deserialize = null, ?stdClass $status = null)
{
$this->mockUnaryCall = new MockUnaryCall($response, $deserialize, $status);
}
/**
* Immediately return the preset response object and status.
* @return array The response object and status.
*/
public function wait()
{
$this->waitCalled = true;
return $this->mockUnaryCall->wait();
}
/**
* Save the request object, to be retrieved via getReceivedCalls()
* @param Message|mixed $request The request object
* @param array $options An array of options
* @throws ApiException
*/
public function write($request, array $options = [])
{
if ($this->waitCalled) {
throw new ApiException('Cannot call write() after wait()', Code::INTERNAL, ApiStatus::INTERNAL);
}
if (is_a($request, '\Google\Protobuf\Internal\Message')) {
/** @var Message $newRequest */
$newRequest = new $request();
$newRequest->mergeFromString($request->serializeToString());
$request = $newRequest;
}
$this->receivedWrites[] = $request;
}
/**
* Return a list of calls made to write(), and clear $receivedFuncCalls.
*
* @return mixed[] An array of received requests
*/
public function popReceivedCalls()
{
$receivedFuncCallsTemp = $this->receivedWrites;
$this->receivedWrites = [];
return $receivedFuncCallsTemp;
}
}

View File

@@ -0,0 +1,142 @@
<?php
/*
* Copyright 2018 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\Testing;
use Google\ApiCore\Transport\GrpcTransport;
use Grpc\ChannelCredentials;
use Psr\Log\LoggerInterface;
/**
* @internal
*/
class MockGrpcTransport extends GrpcTransport
{
private $requestArguments;
private $mockCall;
/**
* @param mixed $mockCall
*/
public function __construct($mockCall = null, ?LoggerInterface $logger = null)
{
$this->mockCall = $mockCall;
$opts = ['credentials' => ChannelCredentials::createSsl()];
parent::__construct('', $opts, logger: $logger);
}
/**
* @param string $method
* @param array $arguments
* @param callable $deserialize
*/
protected function _simpleRequest(
$method,
$arguments,
$deserialize,
array $metadata = [],
array $options = []
) {
$this->logCall($method, $deserialize, $metadata, $options, $arguments);
return $this->mockCall;
}
/**
* @param string $method
* @param callable $deserialize
*/
protected function _clientStreamRequest(
$method,
$deserialize,
array $metadata = [],
array $options = []
) {
$this->logCall($method, $deserialize, $metadata, $options);
return $this->mockCall;
}
/**
* @param string $method
* @param array $arguments
* @param callable $deserialize
*/
protected function _serverStreamRequest(
$method,
$arguments,
$deserialize,
array $metadata = [],
array $options = []
) {
$this->logCall($method, $deserialize, $metadata, $options, $arguments);
return $this->mockCall;
}
/**
* @param string $method
* @param callable $deserialize
*/
protected function _bidiRequest(
$method,
$deserialize,
array $metadata = [],
array $options = []
) {
$this->logCall($method, $deserialize, $metadata, $options);
return $this->mockCall;
}
/**
* @param string $method
* @param callable $deserialize
* @param array $arguments
*/
private function logCall(
$method,
$deserialize,
array $metadata = [],
array $options = [],
$arguments = null
) {
$this->requestArguments = [
'method' => $method,
'arguments' => $arguments,
'deserialize' => $deserialize,
'metadata' => $metadata,
'options' => $options,
];
}
public function getRequestArguments()
{
return $this->requestArguments;
}
}

View File

@@ -0,0 +1,86 @@
<?php
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: Testing/mocks.proto
namespace Google\ApiCore\Testing;
use Google\Protobuf\Internal\GPBUtil;
use GPBMetadata\ApiCore\Testing\Mocks;
/**
* Generated from protobuf message <code>google.apicore.testing.MockRequest</code>
*
* @internal
*/
class MockRequest extends \Google\Protobuf\Internal\Message
{
/**
* Generated from protobuf field <code>string page_token = 1;</code>
*/
protected $page_token = '';
/**
* Generated from protobuf field <code>uint64 page_size = 2;</code>
*/
protected $page_size = 0;
/**
* Constructor.
*
* @param array $data {
* Optional. Data for populating the Message object.
*
* @type string $page_token
* @type int|string $page_size
* }
*/
public function __construct($data = null)
{
Mocks::initOnce();
parent::__construct($data);
}
/**
* Generated from protobuf field <code>string page_token = 1;</code>
* @return string
*/
public function getPageToken()
{
return $this->page_token;
}
/**
* Generated from protobuf field <code>string page_token = 1;</code>
* @param string $var
* @return $this
*/
public function setPageToken($var)
{
GPBUtil::checkString($var, true);
$this->page_token = $var;
return $this;
}
/**
* Generated from protobuf field <code>uint64 page_size = 2;</code>
* @return int|string
*/
public function getPageSize()
{
return $this->page_size;
}
/**
* Generated from protobuf field <code>uint64 page_size = 2;</code>
* @param int|string $var
* @return $this
*/
public function setPageSize($var)
{
GPBUtil::checkUint64($var);
$this->page_size = $var;
return $this;
}
}

View File

@@ -0,0 +1,647 @@
<?php
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: Testing/mocks.proto
namespace Google\ApiCore\Testing;
use Google\Protobuf\Internal\GPBUtil;
/**
* Generated from protobuf message <code>google.apicore.testing.MockRequestBody</code>
*
* @internal
*/
class MockRequestBody extends \Google\Protobuf\Internal\Message
{
/**
* Generated from protobuf field <code>string name = 1;</code>
*/
protected $name = '';
/**
* Generated from protobuf field <code>uint64 number = 2;</code>
*/
protected $number = 0;
/**
* Generated from protobuf field <code>repeated string repeated_field = 3;</code>
*/
private $repeated_field;
/**
* Generated from protobuf field <code>.google.apicore.testing.MockRequestBody nested_message = 4;</code>
*/
protected $nested_message = null;
/**
* Generated from protobuf field <code>.google.protobuf.BytesValue bytes_value = 5;</code>
*/
protected $bytes_value = null;
/**
* Generated from protobuf field <code>.google.protobuf.Duration duration_value = 6;</code>
*/
protected $duration_value = null;
/**
* Generated from protobuf field <code>.google.protobuf.FieldMask field_mask = 7;</code>
*/
protected $field_mask = null;
/**
* Generated from protobuf field <code>.google.protobuf.Int64Value int64_value = 8;</code>
*/
protected $int64_value = null;
/**
* Generated from protobuf field <code>.google.protobuf.ListValue list_value = 9;</code>
*/
protected $list_value = null;
/**
* Generated from protobuf field <code>.google.protobuf.StringValue string_value = 10;</code>
*/
protected $string_value = null;
/**
* Generated from protobuf field <code>.google.protobuf.Struct struct_value = 11;</code>
*/
protected $struct_value = null;
/**
* Generated from protobuf field <code>.google.protobuf.Timestamp timestamp_value = 12;</code>
*/
protected $timestamp_value = null;
/**
* Generated from protobuf field <code>.google.protobuf.Value value_value = 13;</code>
*/
protected $value_value = null;
protected $oneof_field;
/**
* Constructor.
*
* @param array $data {
* Optional. Data for populating the Message object.
*
* @type string $name
* @type int|string $number
* @type string[]|\Google\Protobuf\RepeatedField $repeated_field
* @type \Google\ApiCore\Testing\MockRequestBody $nested_message
* @type \Google\Protobuf\BytesValue $bytes_value
* @type \Google\Protobuf\Duration $duration_value
* @type \Google\Protobuf\FieldMask $field_mask
* @type \Google\Protobuf\Int64Value $int64_value
* @type \Google\Protobuf\ListValue $list_value
* @type \Google\Protobuf\StringValue $string_value
* @type \Google\Protobuf\Struct $struct_value
* @type \Google\Protobuf\Timestamp $timestamp_value
* @type \Google\Protobuf\Value $value_value
* @type string $field_1
* @type string $field_2
* @type string $field_3
* }
*/
public function __construct($data = null)
{
\GPBMetadata\ApiCore\Testing\Mocks::initOnce();
parent::__construct($data);
}
/**
* Generated from protobuf field <code>string name = 1;</code>
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Generated from protobuf field <code>string name = 1;</code>
* @param string $var
* @return $this
*/
public function setName($var)
{
GPBUtil::checkString($var, true);
$this->name = $var;
return $this;
}
/**
* Generated from protobuf field <code>uint64 number = 2;</code>
* @return int|string
*/
public function getNumber()
{
return $this->number;
}
/**
* Generated from protobuf field <code>uint64 number = 2;</code>
* @param int|string $var
* @return $this
*/
public function setNumber($var)
{
GPBUtil::checkUint64($var);
$this->number = $var;
return $this;
}
/**
* Generated from protobuf field <code>repeated string repeated_field = 3;</code>
* @return \Google\Protobuf\RepeatedField
*/
public function getRepeatedField()
{
return $this->repeated_field;
}
/**
* Generated from protobuf field <code>repeated string repeated_field = 3;</code>
* @param string[]|\Google\Protobuf\RepeatedField $var
* @return $this
*/
public function setRepeatedField($var)
{
$arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::STRING);
$this->repeated_field = $arr;
return $this;
}
/**
* Generated from protobuf field <code>.google.apicore.testing.MockRequestBody nested_message = 4;</code>
* @return \Google\ApiCore\Testing\MockRequestBody
*/
public function getNestedMessage()
{
return isset($this->nested_message) ? $this->nested_message : null;
}
public function hasNestedMessage()
{
return isset($this->nested_message);
}
public function clearNestedMessage()
{
unset($this->nested_message);
}
/**
* Generated from protobuf field <code>.google.apicore.testing.MockRequestBody nested_message = 4;</code>
* @param \Google\ApiCore\Testing\MockRequestBody $var
* @return $this
*/
public function setNestedMessage($var)
{
GPBUtil::checkMessage($var, \Google\ApiCore\Testing\MockRequestBody::class);
$this->nested_message = $var;
return $this;
}
/**
* Generated from protobuf field <code>.google.protobuf.BytesValue bytes_value = 5;</code>
* @return \Google\Protobuf\BytesValue
*/
public function getBytesValue()
{
return isset($this->bytes_value) ? $this->bytes_value : null;
}
public function hasBytesValue()
{
return isset($this->bytes_value);
}
public function clearBytesValue()
{
unset($this->bytes_value);
}
/**
* Returns the unboxed value from <code>getBytesValue()</code>
* Generated from protobuf field <code>.google.protobuf.BytesValue bytes_value = 5;</code>
* @return string|null
*/
public function getBytesValueUnwrapped()
{
return $this->readWrapperValue('bytes_value');
}
/**
* Generated from protobuf field <code>.google.protobuf.BytesValue bytes_value = 5;</code>
* @param \Google\Protobuf\BytesValue $var
* @return $this
*/
public function setBytesValue($var)
{
GPBUtil::checkMessage($var, \Google\Protobuf\BytesValue::class);
$this->bytes_value = $var;
return $this;
}
/**
* Sets the field by wrapping a primitive type in a Google\Protobuf\BytesValue object.
* Generated from protobuf field <code>.google.protobuf.BytesValue bytes_value = 5;</code>
* @param string|null $var
* @return $this
*/
public function setBytesValueUnwrapped($var)
{
$this->writeWrapperValue('bytes_value', $var);
return $this;
}
/**
* Generated from protobuf field <code>.google.protobuf.Duration duration_value = 6;</code>
* @return \Google\Protobuf\Duration
*/
public function getDurationValue()
{
return isset($this->duration_value) ? $this->duration_value : null;
}
public function hasDurationValue()
{
return isset($this->duration_value);
}
public function clearDurationValue()
{
unset($this->duration_value);
}
/**
* Generated from protobuf field <code>.google.protobuf.Duration duration_value = 6;</code>
* @param \Google\Protobuf\Duration $var
* @return $this
*/
public function setDurationValue($var)
{
GPBUtil::checkMessage($var, \Google\Protobuf\Duration::class);
$this->duration_value = $var;
return $this;
}
/**
* Generated from protobuf field <code>.google.protobuf.FieldMask field_mask = 7;</code>
* @return \Google\Protobuf\FieldMask
*/
public function getFieldMask()
{
return isset($this->field_mask) ? $this->field_mask : null;
}
public function hasFieldMask()
{
return isset($this->field_mask);
}
public function clearFieldMask()
{
unset($this->field_mask);
}
/**
* Generated from protobuf field <code>.google.protobuf.FieldMask field_mask = 7;</code>
* @param \Google\Protobuf\FieldMask $var
* @return $this
*/
public function setFieldMask($var)
{
GPBUtil::checkMessage($var, \Google\Protobuf\FieldMask::class);
$this->field_mask = $var;
return $this;
}
/**
* Generated from protobuf field <code>.google.protobuf.Int64Value int64_value = 8;</code>
* @return \Google\Protobuf\Int64Value
*/
public function getInt64Value()
{
return isset($this->int64_value) ? $this->int64_value : null;
}
public function hasInt64Value()
{
return isset($this->int64_value);
}
public function clearInt64Value()
{
unset($this->int64_value);
}
/**
* Returns the unboxed value from <code>getInt64Value()</code>
* Generated from protobuf field <code>.google.protobuf.Int64Value int64_value = 8;</code>
* @return int|string|null
*/
public function getInt64ValueUnwrapped()
{
return $this->readWrapperValue('int64_value');
}
/**
* Generated from protobuf field <code>.google.protobuf.Int64Value int64_value = 8;</code>
* @param \Google\Protobuf\Int64Value $var
* @return $this
*/
public function setInt64Value($var)
{
GPBUtil::checkMessage($var, \Google\Protobuf\Int64Value::class);
$this->int64_value = $var;
return $this;
}
/**
* Sets the field by wrapping a primitive type in a Google\Protobuf\Int64Value object.
* Generated from protobuf field <code>.google.protobuf.Int64Value int64_value = 8;</code>
* @param int|string|null $var
* @return $this
*/
public function setInt64ValueUnwrapped($var)
{
$this->writeWrapperValue('int64_value', $var);
return $this;
}
/**
* Generated from protobuf field <code>.google.protobuf.ListValue list_value = 9;</code>
* @return \Google\Protobuf\ListValue
*/
public function getListValue()
{
return isset($this->list_value) ? $this->list_value : null;
}
public function hasListValue()
{
return isset($this->list_value);
}
public function clearListValue()
{
unset($this->list_value);
}
/**
* Generated from protobuf field <code>.google.protobuf.ListValue list_value = 9;</code>
* @param \Google\Protobuf\ListValue $var
* @return $this
*/
public function setListValue($var)
{
GPBUtil::checkMessage($var, \Google\Protobuf\ListValue::class);
$this->list_value = $var;
return $this;
}
/**
* Generated from protobuf field <code>.google.protobuf.StringValue string_value = 10;</code>
* @return \Google\Protobuf\StringValue
*/
public function getStringValue()
{
return isset($this->string_value) ? $this->string_value : null;
}
public function hasStringValue()
{
return isset($this->string_value);
}
public function clearStringValue()
{
unset($this->string_value);
}
/**
* Returns the unboxed value from <code>getStringValue()</code>
* Generated from protobuf field <code>.google.protobuf.StringValue string_value = 10;</code>
* @return string|null
*/
public function getStringValueUnwrapped()
{
return $this->readWrapperValue('string_value');
}
/**
* Generated from protobuf field <code>.google.protobuf.StringValue string_value = 10;</code>
* @param \Google\Protobuf\StringValue $var
* @return $this
*/
public function setStringValue($var)
{
GPBUtil::checkMessage($var, \Google\Protobuf\StringValue::class);
$this->string_value = $var;
return $this;
}
/**
* Sets the field by wrapping a primitive type in a Google\Protobuf\StringValue object.
* Generated from protobuf field <code>.google.protobuf.StringValue string_value = 10;</code>
* @param string|null $var
* @return $this
*/
public function setStringValueUnwrapped($var)
{
$this->writeWrapperValue('string_value', $var);
return $this;
}
/**
* Generated from protobuf field <code>.google.protobuf.Struct struct_value = 11;</code>
* @return \Google\Protobuf\Struct
*/
public function getStructValue()
{
return isset($this->struct_value) ? $this->struct_value : null;
}
public function hasStructValue()
{
return isset($this->struct_value);
}
public function clearStructValue()
{
unset($this->struct_value);
}
/**
* Generated from protobuf field <code>.google.protobuf.Struct struct_value = 11;</code>
* @param \Google\Protobuf\Struct $var
* @return $this
*/
public function setStructValue($var)
{
GPBUtil::checkMessage($var, \Google\Protobuf\Struct::class);
$this->struct_value = $var;
return $this;
}
/**
* Generated from protobuf field <code>.google.protobuf.Timestamp timestamp_value = 12;</code>
* @return \Google\Protobuf\Timestamp
*/
public function getTimestampValue()
{
return isset($this->timestamp_value) ? $this->timestamp_value : null;
}
public function hasTimestampValue()
{
return isset($this->timestamp_value);
}
public function clearTimestampValue()
{
unset($this->timestamp_value);
}
/**
* Generated from protobuf field <code>.google.protobuf.Timestamp timestamp_value = 12;</code>
* @param \Google\Protobuf\Timestamp $var
* @return $this
*/
public function setTimestampValue($var)
{
GPBUtil::checkMessage($var, \Google\Protobuf\Timestamp::class);
$this->timestamp_value = $var;
return $this;
}
/**
* Generated from protobuf field <code>.google.protobuf.Value value_value = 13;</code>
* @return \Google\Protobuf\Value
*/
public function getValueValue()
{
return isset($this->value_value) ? $this->value_value : null;
}
public function hasValueValue()
{
return isset($this->value_value);
}
public function clearValueValue()
{
unset($this->value_value);
}
/**
* Generated from protobuf field <code>.google.protobuf.Value value_value = 13;</code>
* @param \Google\Protobuf\Value $var
* @return $this
*/
public function setValueValue($var)
{
GPBUtil::checkMessage($var, \Google\Protobuf\Value::class);
$this->value_value = $var;
return $this;
}
/**
* Generated from protobuf field <code>string field_1 = 14;</code>
* @return string
*/
public function getField1()
{
return $this->readOneof(14);
}
public function hasField1()
{
return $this->hasOneof(14);
}
/**
* Generated from protobuf field <code>string field_1 = 14;</code>
* @param string $var
* @return $this
*/
public function setField1($var)
{
GPBUtil::checkString($var, true);
$this->writeOneof(14, $var);
return $this;
}
/**
* Generated from protobuf field <code>string field_2 = 15;</code>
* @return string
*/
public function getField2()
{
return $this->readOneof(15);
}
public function hasField2()
{
return $this->hasOneof(15);
}
/**
* Generated from protobuf field <code>string field_2 = 15;</code>
* @param string $var
* @return $this
*/
public function setField2($var)
{
GPBUtil::checkString($var, true);
$this->writeOneof(15, $var);
return $this;
}
/**
* Generated from protobuf field <code>string field_3 = 16;</code>
* @return string
*/
public function getField3()
{
return $this->readOneof(16);
}
public function hasField3()
{
return $this->hasOneof(16);
}
/**
* Generated from protobuf field <code>string field_3 = 16;</code>
* @param string $var
* @return $this
*/
public function setField3($var)
{
GPBUtil::checkString($var, true);
$this->writeOneof(16, $var);
return $this;
}
/**
* @return string
*/
public function getOneofField()
{
return $this->whichOneof('oneof_field');
}
}

View File

@@ -0,0 +1,166 @@
<?php
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: Testing/mocks.proto
namespace Google\ApiCore\Testing;
use Google\Protobuf\Internal\GPBUtil;
/**
* Generated from protobuf message <code>google.apicore.testing.MockResponse</code>
*
* @internal
*/
class MockResponse extends \Google\Protobuf\Internal\Message
{
/**
* Generated from protobuf field <code>string name = 1;</code>
*/
protected $name = '';
/**
* Generated from protobuf field <code>uint64 number = 2;</code>
*/
protected $number = 0;
/**
* Generated from protobuf field <code>repeated string resources_list = 3;</code>
*/
private $resources_list;
/**
* Generated from protobuf field <code>string next_page_token = 4;</code>
*/
protected $next_page_token = '';
/**
* Generated from protobuf field <code>map<string, string> resources_map = 5;</code>
*/
private $resources_map;
/**
* Constructor.
*
* @param array $data {
* Optional. Data for populating the Message object.
*
* @type string $name
* @type int|string $number
* @type string[]|\Google\Protobuf\RepeatedField $resources_list
* @type string $next_page_token
* @type array|\Google\Protobuf\Internal\MapField $resources_map
* }
*/
public function __construct($data = null)
{
\GPBMetadata\ApiCore\Testing\Mocks::initOnce();
parent::__construct($data);
}
/**
* Generated from protobuf field <code>string name = 1;</code>
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Generated from protobuf field <code>string name = 1;</code>
* @param string $var
* @return $this
*/
public function setName($var)
{
GPBUtil::checkString($var, true);
$this->name = $var;
return $this;
}
/**
* Generated from protobuf field <code>uint64 number = 2;</code>
* @return int|string
*/
public function getNumber()
{
return $this->number;
}
/**
* Generated from protobuf field <code>uint64 number = 2;</code>
* @param int|string $var
* @return $this
*/
public function setNumber($var)
{
GPBUtil::checkUint64($var);
$this->number = $var;
return $this;
}
/**
* Generated from protobuf field <code>repeated string resources_list = 3;</code>
* @return \Google\Protobuf\RepeatedField
*/
public function getResourcesList()
{
return $this->resources_list;
}
/**
* Generated from protobuf field <code>repeated string resources_list = 3;</code>
* @param string[]|\Google\Protobuf\RepeatedField $var
* @return $this
*/
public function setResourcesList($var)
{
$arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::STRING);
$this->resources_list = $arr;
return $this;
}
/**
* Generated from protobuf field <code>string next_page_token = 4;</code>
* @return string
*/
public function getNextPageToken()
{
return $this->next_page_token;
}
/**
* Generated from protobuf field <code>string next_page_token = 4;</code>
* @param string $var
* @return $this
*/
public function setNextPageToken($var)
{
GPBUtil::checkString($var, true);
$this->next_page_token = $var;
return $this;
}
/**
* Generated from protobuf field <code>map<string, string> resources_map = 5;</code>
* @return \Google\Protobuf\Internal\MapField
*/
public function getResourcesMap()
{
return $this->resources_map;
}
/**
* Generated from protobuf field <code>map<string, string> resources_map = 5;</code>
* @param array|\Google\Protobuf\Internal\MapField $var
* @return $this
*/
public function setResourcesMap($var)
{
$arr = GPBUtil::checkMapField($var, \Google\Protobuf\Internal\GPBType::STRING, \Google\Protobuf\Internal\GPBType::STRING);
$this->resources_map = $arr;
return $this;
}
}

View File

@@ -0,0 +1,98 @@
<?php
/*
* Copyright 2016 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\Testing;
use Google\ApiCore\ApiException;
use Google\ApiCore\ApiStatus;
use Google\ApiCore\ServerStreamingCallInterface;
use Google\Rpc\Code;
use stdClass;
/**
* The MockServerStreamingCall class is used to mock out the \Grpc\ServerStreamingCall class
* (https://github.com/grpc/grpc/blob/master/src/php/lib/Grpc/ServerStreamingCall.php)
*
* @internal
*/
class MockServerStreamingCall extends \Grpc\ServerStreamingCall implements ServerStreamingCallInterface
{
use SerializationTrait;
private $responses;
private $status;
/**
* MockServerStreamingCall constructor.
* @param mixed[] $responses A list of response objects.
* @param callable|array|null $deserialize An optional deserialize method for the response object.
* @param stdClass|null $status An optional status object. If set to null, a status of OK is used.
*/
public function __construct(array $responses, $deserialize = null, ?stdClass $status = null)
{
$this->responses = $responses;
$this->deserialize = $deserialize;
if (is_null($status)) {
$status = new MockStatus(Code::OK, 'OK', []);
} elseif ($status instanceof stdClass) {
if (!property_exists($status, 'metadata')) {
$status->metadata = [];
}
}
$this->status = $status;
}
public function responses()
{
while (count($this->responses) > 0) {
$resp = array_shift($this->responses);
$obj = $this->deserializeMessage($resp, $this->deserialize);
yield $obj;
}
}
/**
* @return stdClass|null
* @throws ApiException
*/
public function getStatus()
{
if (count($this->responses) > 0) {
throw new ApiException(
'Calls to getStatus() will block if all responses are not read',
Code::INTERNAL,
ApiStatus::INTERNAL
);
}
return $this->status;
}
}

View File

@@ -0,0 +1,53 @@
<?php
/*
* Copyright 2016 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\Testing;
use Google\Rpc\Code;
use stdClass;
/**
* @internal
*/
class MockStatus extends stdClass
{
/** @var Code|int $code */
public $code;
public $details;
public $metadata;
public function __construct($code, ?string $details = null, array $metadata = [])
{
$this->code = $code;
$this->details = $details;
$this->metadata = $metadata;
}
}

View File

@@ -0,0 +1,296 @@
<?php
/*
* Copyright 2016 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\Testing;
use Google\Protobuf\Internal\Message;
use stdClass;
use UnderflowException;
/**
* The MockStubTrait is used by generated mock stub classes which extent \Grpc\BaseStub
* (https://github.com/grpc/grpc/blob/master/src/php/lib/Grpc/BaseStub.php)
* It provides functionality to add responses, get received calls, and overrides the _simpleRequest
* method so that the elements of $responses are returned instead of making a call to the API.
*
* @internal
*/
trait MockStubTrait
{
private $receivedFuncCalls = [];
private $responses = [];
private $serverStreamingStatus = null;
private $callObjects = [];
private $deserialize;
public function __construct(?callable $deserialize = null)
{
$this->deserialize = $deserialize;
}
/**
* Overrides the _simpleRequest method in \Grpc\BaseStub
* (https://github.com/grpc/grpc/blob/master/src/php/lib/Grpc/BaseStub.php)
* Returns a MockUnaryCall object that will return the first item from $responses
* @param string $method The API method name to be called
* @param \Google\Protobuf\Internal\Message $argument The request object to the API method
* @param callable $deserialize A function to deserialize the response object
* @param array $metadata
* @param array $options
* @return MockUnaryCall
*/
public function _simpleRequest(
$method,
$argument,
$deserialize,
array $metadata = [],
array $options = []
) {
$this->receivedFuncCalls[] = new ReceivedRequest($method, $argument, $deserialize, $metadata, $options);
if (count($this->responses) < 1) {
throw new UnderflowException('ran out of responses');
}
list($response, $status) = array_shift($this->responses);
$call = new MockUnaryCall($response, $deserialize, $status);
$this->callObjects[] = $call;
return $call;
}
/**
* Overrides the _clientStreamRequest method in \Grpc\BaseStub
* (https://github.com/grpc/grpc/blob/master/src/php/lib/Grpc/BaseStub.php)
* Returns a MockClientStreamingCall object that will return the first item from $responses
*
* @param string $method The name of the method to call
* @param callable $deserialize A function that deserializes the responses
* @param array $metadata A metadata map to send to the server
* (optional)
* @param array $options An array of options (optional)
*
* @return MockClientStreamingCall The active call object
*/
public function _clientStreamRequest(
$method,
$deserialize,
array $metadata = [],
array $options = []
) {
$this->receivedFuncCalls[] = new ReceivedRequest($method, null, $deserialize, $metadata, $options);
if (count($this->responses) < 1) {
throw new UnderflowException('ran out of responses');
}
list($response, $status) = array_shift($this->responses);
$call = new MockClientStreamingCall($response, $deserialize, $status);
$this->callObjects[] = $call;
return $call;
}
/**
* Overrides the _serverStreamRequest method in \Grpc\BaseStub
* (https://github.com/grpc/grpc/blob/master/src/php/lib/Grpc/BaseStub.php)
* Returns a MockServerStreamingCall object that will stream items from $responses, and return
* a final status of $serverStreamingStatus.
*
* @param string $method The name of the method to call
* @param \Google\Protobuf\Internal\Message $argument The argument to the method
* @param callable $deserialize A function that deserializes the responses
* @param array $metadata A metadata map to send to the server
* (optional)
* @param array $options An array of options (optional)
*
* @return MockServerStreamingCall The active call object
*/
public function _serverStreamRequest(
$method,
$argument,
$deserialize,
array $metadata = [],
array $options = []
) {
if (is_a($argument, '\Google\Protobuf\Internal\Message')) {
/** @var Message $newArgument */
$newArgument = new $argument();
$newArgument->mergeFromString($argument->serializeToString());
$argument = $newArgument;
}
$this->receivedFuncCalls[] = new ReceivedRequest($method, $argument, $deserialize, $metadata, $options);
$responses = self::stripStatusFromResponses($this->responses);
$this->responses = [];
$call = new MockServerStreamingCall($responses, $deserialize, $this->serverStreamingStatus);
$this->callObjects[] = $call;
return $call;
}
/**
* Overrides the _bidiRequest method in \Grpc\BaseStub
* (https://github.com/grpc/grpc/blob/master/src/php/lib/Grpc/BaseStub.php)
* Returns a MockBidiStreamingCall object that will stream items from $responses, and return
* a final status of $serverStreamingStatus.
*
* @param string $method The name of the method to call
* @param callable $deserialize A function that deserializes the responses
* @param array $metadata A metadata map to send to the server
* (optional)
* @param array $options An array of options (optional)
*
* @return MockBidiStreamingCall The active call object
*/
public function _bidiRequest(
$method,
$deserialize,
array $metadata = [],
array $options = []
) {
$this->receivedFuncCalls[] = new ReceivedRequest($method, null, $deserialize, $metadata, $options);
$responses = self::stripStatusFromResponses($this->responses);
$this->responses = [];
$call = new MockBidiStreamingCall($responses, $deserialize, $this->serverStreamingStatus);
$this->callObjects[] = $call;
return $call;
}
public static function stripStatusFromResponses($responses)
{
$strippedResponses = [];
foreach ($responses as $response) {
list($resp, $_) = $response;
$strippedResponses[] = $resp;
}
return $strippedResponses;
}
/**
* Add a response object, and an optional status, to the list of responses to be returned via
* _simpleRequest.
* @param \Google\Protobuf\Internal\Message $response
* @param stdClass $status
*/
public function addResponse($response, ?stdClass $status = null)
{
if (!$this->deserialize && $response) {
$this->deserialize = [get_class($response), 'decode'];
}
if (is_a($response, '\Google\Protobuf\Internal\Message')) {
$response = $response->serializeToString();
}
$this->responses[] = [$response, $status];
}
/**
* Set the status object to be used when creating streaming calls.
*
* @param stdClass $status
*/
public function setStreamingStatus(stdClass $status)
{
$this->serverStreamingStatus = $status;
}
/**
* Return a list of calls made to _simpleRequest, and clear $receivedFuncCalls.
*
* @return ReceivedRequest[] An array of received requests
*/
public function popReceivedCalls()
{
$receivedFuncCallsTemp = $this->receivedFuncCalls;
$this->receivedFuncCalls = [];
return $receivedFuncCallsTemp;
}
/**
* @return int The number of calls received.
*/
public function getReceivedCallCount()
{
return count($this->receivedFuncCalls);
}
/**
* @return mixed[] The call objects created by calls to the stub
*/
public function popCallObjects()
{
$callObjectsTemp = $this->callObjects;
$this->callObjects = [];
return $callObjectsTemp;
}
/**
* @return bool True if $receivedFuncCalls and $response are empty.
*/
public function isExhausted()
{
return count($this->receivedFuncCalls) === 0
&& count($this->responses) === 0;
}
/**
* @param mixed $responseObject
* @param stdClass|null $status
* @param callable $deserialize
* @return static An instance of the current class type.
*/
public static function create($responseObject, ?stdClass $status = null, ?callable $deserialize = null)
{
$stub = new static($deserialize); // @phpstan-ignore-line
$stub->addResponse($responseObject, $status);
return $stub;
}
/**
* Creates a sequence such that the responses are returned in order.
* @param mixed[] $sequence
* @param callable $deserialize
* @param stdClass $finalStatus
* @return static An instance of the current class type.
*/
public static function createWithResponseSequence(array $sequence, ?callable $deserialize = null, ?stdClass $finalStatus = null)
{
$stub = new static($deserialize); // @phpstan-ignore-line
foreach ($sequence as $elem) {
if (count($elem) == 1) {
list($resp, $status) = [$elem, null];
} else {
list($resp, $status) = $elem;
}
$stub->addResponse($resp, $status);
}
if ($finalStatus) {
$stub->setStreamingStatus($finalStatus);
}
return $stub;
}
}

View File

@@ -0,0 +1,114 @@
<?php
/*
* Copyright 2018 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\Testing;
use Google\ApiCore\ApiException;
use Google\ApiCore\BidiStream;
use Google\ApiCore\Call;
use Google\ApiCore\ClientStream;
use Google\ApiCore\ServerStream;
use Google\ApiCore\Transport\TransportInterface;
use Google\Rpc\Code;
use GuzzleHttp\Promise\Promise;
/**
* @internal
*/
class MockTransport implements TransportInterface
{
use MockStubTrait;
private $agentHeaderDescriptor; // @phpstan-ignore-line
public function setAgentHeaderDescriptor($agentHeaderDescriptor)
{
$this->agentHeaderDescriptor = $agentHeaderDescriptor;
}
public function startUnaryCall(Call $call, array $options)
{
$call = call_user_func([$this, $call->getMethod()], $call, $options);
return $promise = new Promise(
function () use ($call, &$promise) {
list($response, $status) = $call->wait();
if ($status->code == Code::OK) {
$promise->resolve($response);
} else {
throw ApiException::createFromStdClass($status);
}
},
[$call, 'cancel']
);
}
public function startBidiStreamingCall(Call $call, array $options)
{
$newArgs = ['/' . $call->getMethod(), $this->deserialize, $options, $options];
$response = $this->_bidiRequest(...$newArgs);
return new BidiStream($response, $call->getDescriptor());
}
public function startClientStreamingCall(Call $call, array $options)
{
$newArgs = ['/' . $call->getMethod(), $this->deserialize, $options, $options];
$response = $this->_clientStreamRequest(...$newArgs);
return new ClientStream($response, $call->getDescriptor());
}
public function startServerStreamingCall(Call $call, array $options)
{
$newArgs = ['/' . $call->getMethod(), $call->getMessage(), $this->deserialize, $options, $options];
$response = $this->_serverStreamRequest(...$newArgs);
return new ServerStream($response, $call->getDescriptor());
}
public function __call(string $name, array $arguments)
{
$call = $arguments[0];
$options = $arguments[1];
$decode = $call->getDecodeType() ? [$call->getDecodeType(), 'decode'] : null;
return $this->_simpleRequest(
'/' . $call->getMethod(),
$call->getMessage(),
$decode,
isset($options['headers']) ? $options['headers'] : [],
$options
);
}
public function close()
{
// does nothing
}
}

View File

@@ -0,0 +1,83 @@
<?php
/*
* Copyright 2016 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\Testing;
use Google\Protobuf\Internal\Message;
use Google\Rpc\Code;
use stdClass;
/**
* The MockUnaryCall class is used to mock out the \Grpc\UnaryCall class
* (https://github.com/grpc/grpc/blob/master/src/php/lib/Grpc/UnaryCall.php)
*
* The MockUnaryCall object is constructed with a response object, an optional deserialize
* method, and an optional status. The response object and status are returned immediately from the
* wait() method.
*
* @internal
*/
class MockUnaryCall extends \Grpc\UnaryCall
{
use SerializationTrait;
private $response;
private $status;
/**
* MockUnaryCall constructor.
* @param Message|string|null $response The response object.
* @param callable|array|null $deserialize An optional deserialize method for the response object.
* @param stdClass|null $status An optional status object. If set to null, a status of OK is used.
*/
public function __construct($response = null, $deserialize = null, ?stdClass $status = null)
{
$this->response = $response;
$this->deserialize = $deserialize;
if (is_null($status)) {
$status = new MockStatus(Code::OK);
}
$this->status = $status;
}
/**
* Immediately return the preset response object and status.
* @return array The response object and status.
*/
public function wait()
{
return [
$this->deserializeMessage($this->response, $this->deserialize),
$this->status,
];
}
}

View File

@@ -0,0 +1,61 @@
<?php
/**
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Google\ApiCore\Testing;
use Google\Protobuf\GPBEmpty;
use Google\Protobuf\Internal\Message;
use SebastianBergmann\Comparator\Comparator;
use SebastianBergmann\Comparator\ComparisonFailure;
/**
* @internal
*/
class ProtobufGPBEmptyComparator extends Comparator
{
/**
* Returns whether the comparator can compare two values.
*
* @param mixed $expected The first value to compare
* @param mixed $actual The second value to compare
* @return boolean
*/
public function accepts($expected, $actual)
{
return $expected instanceof GPBEmpty && $actual instanceof GPBEmpty;
}
/**
* Asserts that two values are equal.
*
* @param Message $expected The first value to compare
* @param Message $actual The second value to compare
* @param float|int $delta The allowed numerical distance between two values to
* consider them equal
* @param bool $canonicalize If set to TRUE, arrays are sorted before
* comparison
* @param bool $ignoreCase If set to TRUE, upper- and lowercasing is
* ignored when comparing string values
* @throws ComparisonFailure Thrown when the comparison
* fails. Contains information about the
* specific errors that lead to the failure.
*/
public function assertEquals($expected, $actual, $delta = 0, $canonicalize = false, $ignoreCase = false)
{
// No need to do anything here.
}
}

View File

@@ -0,0 +1,79 @@
<?php
/**
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Google\ApiCore\Testing;
use Google\Protobuf\Internal\Message;
use SebastianBergmann\Comparator\Comparator;
use SebastianBergmann\Comparator\ComparisonFailure;
use SebastianBergmann\Exporter\Exporter;
/**
* @internal
*/
class ProtobufMessageComparator extends Comparator
{
/** @var Exporter */
protected $exporter;
public function __construct()
{
parent::__construct();
$this->exporter = new MessageAwareExporter();
}
/**
* Returns whether the comparator can compare two values.
*
* @param mixed $expected The first value to compare
* @param mixed $actual The second value to compare
* @return boolean
*/
public function accepts($expected, $actual)
{
return $expected instanceof Message && $actual instanceof Message;
}
/**
* Asserts that two values are equal.
*
* @param Message $expected The first value to compare
* @param Message $actual The second value to compare
* @param float|int $delta The allowed numerical distance between two values to
* consider them equal
* @param bool $canonicalize If set to TRUE, arrays are sorted before
* comparison
* @param bool $ignoreCase If set to TRUE, upper- and lowercasing is
* ignored when comparing string values
* @throws ComparisonFailure Thrown when the comparison
* fails. Contains information about the
* specific errors that lead to the failure.
*/
public function assertEquals($expected, $actual, $delta = 0, $canonicalize = false, $ignoreCase = false)
{
if ($expected->serializeToString() !== $actual->serializeToString()) {
throw new ComparisonFailure(
$expected,
$actual,
$this->exporter->shortenedExport($expected),
$this->exporter->shortenedExport($actual),
false,
'Given 2 Message objects are not the same'
);
}
}
}

View File

@@ -0,0 +1,79 @@
<?php
/*
* Copyright 2016 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\Testing;
/**
* Class ReceivedRequest used to hold the function name and request object of a call
* make to a mock gRPC stub.
*
* @internal
*/
class ReceivedRequest
{
private $actualCall;
public function __construct($funcCall, $requestObject, $deserialize = null, $metadata = [], $options = [])
{
$this->actualCall = [
'funcCall' => $funcCall,
'request' => $requestObject,
'deserialize' => $deserialize,
'metadata' => $metadata,
'options' => $options,
];
}
public function getArray()
{
return $this->actualCall;
}
public function getFuncCall()
{
return $this->actualCall['funcCall'];
}
public function getRequestObject()
{
return $this->actualCall['request'];
}
public function getMetadata()
{
return $this->actualCall['metadata'];
}
public function getOptions()
{
return $this->actualCall['options'];
}
}

View File

@@ -0,0 +1,72 @@
<?php
/*
* Copyright 2017 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\Testing;
use Google\Protobuf\Internal\Message;
/**
* @internal
*/
trait SerializationTrait
{
/**
* @param mixed $message
* @param mixed $deserialize
*/
protected function deserializeMessage($message, $deserialize)
{
if ($message === null) {
return null;
}
if ($deserialize === null) {
return $message;
}
// Proto3 implementation
if (is_array($deserialize)) {
list($className, $deserializeFunc) = $deserialize;
/** @var Message $obj */
$obj = new $className();
if (method_exists($obj, $deserializeFunc)) {
$obj->$deserializeFunc($message);
} elseif (is_string($message)) {
$obj->mergeFromString($message);
}
return $obj;
}
// Protobuf-PHP implementation
return call_user_func($deserialize, $message);
}
}

View File

@@ -0,0 +1,46 @@
syntax = "proto3";
package google.apicore.testing;
import "google/protobuf/field_mask.proto";
import "google/protobuf/timestamp.proto";
import "google/protobuf/duration.proto";
import "google/protobuf/struct.proto";
import "google/protobuf/wrappers.proto";
option php_namespace = "Google\\ApiCore\\Testing";
option php_metadata_namespace = "GPBMetadata\\ApiCore\\Testing";
message MockRequest {
string page_token = 1;
uint64 page_size = 2;
}
message MockResponse {
string name = 1;
uint64 number = 2;
repeated string resources_list = 3;
string next_page_token = 4;
map<string, string> resources_map = 5;
}
message MockRequestBody {
string name = 1;
uint64 number = 2;
repeated string repeated_field = 3;
MockRequestBody nested_message = 4;
google.protobuf.BytesValue bytes_value = 5;
google.protobuf.Duration duration_value = 6;
google.protobuf.FieldMask field_mask = 7;
google.protobuf.Int64Value int64_value = 8;
google.protobuf.ListValue list_value = 9;
google.protobuf.StringValue string_value = 10;
google.protobuf.Struct struct_value = 11;
google.protobuf.Timestamp timestamp_value = 12;
google.protobuf.Value value_value = 13;
oneof oneof_field {
string field_1 = 14;
string field_2 = 15;
string field_3 = 16;
}
}

View File

@@ -0,0 +1,90 @@
<?php
/*
* Copyright 2018 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\Transport\Grpc;
use Grpc\AbstractCall;
/**
* Class ForwardingCall wraps a \Grpc\AbstractCall.
*
* @experimental
*/
abstract class ForwardingCall
{
/**
* @var AbstractCall|ForwardingCall
*/
protected object $innerCall;
/**
* ForwardingCall constructor.
*
* @param AbstractCall|ForwardingCall $innerCall
*/
public function __construct($innerCall)
{
$this->innerCall = $innerCall;
}
/**
* @return mixed The metadata sent by the server
*/
public function getMetadata()
{
return $this->innerCall->getMetadata();
}
/**
* @return mixed The trailing metadata sent by the server
*/
public function getTrailingMetadata()
{
return $this->innerCall->getTrailingMetadata();
}
/**
* @return string The URI of the endpoint
*/
public function getPeer()
{
return $this->innerCall->getPeer();
}
/**
* Cancels the call.
*/
public function cancel()
{
$this->innerCall->cancel();
}
}

View File

@@ -0,0 +1,65 @@
<?php
/*
* Copyright 2020 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\Transport\Grpc;
use Grpc\ServerStreamingCall;
/**
* Class ForwardingServerStreamingCall wraps a \Grpc\ServerStreamingCall.
*
* @experimental
*/
class ForwardingServerStreamingCall extends ForwardingCall
{
/** @var ServerStreamingCall */
protected object $innerCall;
/**
* @return mixed An iterator of response values
*/
public function responses()
{
return $this->innerCall->responses();
}
/**
* Wait for the server to send the status, and return it.
*
* @return \stdClass The status object, with integer $code, string
* $details, and array $metadata members
*/
public function getStatus()
{
return $this->innerCall->getStatus();
}
}

View File

@@ -0,0 +1,56 @@
<?php
/*
* Copyright 2018 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\Transport\Grpc;
use Grpc\UnaryCall;
/**
* Class ForwardingUnaryCall wraps a \Grpc\UnaryCall.
*
* @experimental
*/
class ForwardingUnaryCall extends ForwardingCall
{
/** @var UnaryCall */
protected object $innerCall;
/**
* Wait for the server to respond with data and a status.
*
* @return array [response data, status]
*/
public function wait()
{
return $this->innerCall->wait();
}
}

View File

@@ -0,0 +1,123 @@
<?php
/*
* Copyright 2021 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\Transport\Grpc;
use Google\ApiCore\ServerStreamingCallInterface;
use Grpc\Gcp\GCPServerStreamCall;
use Grpc\ServerStreamingCall;
/**
* Class ServerStreamingCallWrapper implements \Google\ApiCore\ServerStreamingCallInterface.
* This is essentially a wrapper class around the \Grpc\ServerStreamingCall.
*/
class ServerStreamingCallWrapper implements ServerStreamingCallInterface
{
/**
* @var ServerStreamingCall|GCPServerStreamCall
*/
private object $stream;
/**
* @param ServerStreamingCall|GCPServerStreamCall $stream
*/
public function __construct($stream)
{
$this->stream = $stream;
}
/**
* {@inheritdoc}
*/
public function start($data, array $metadata = [], array $callOptions = [])
{
$this->stream->start($data, $metadata, $callOptions);
}
/**
* {@inheritdoc}
*/
public function responses()
{
foreach ($this->stream->responses() as $response) {
yield $response;
}
}
/**
* {@inheritdoc}
*/
public function getStatus()
{
return $this->stream->getStatus();
}
/**
* {@inheritdoc}
*/
public function getMetadata()
{
return $this->stream->getMetadata();
}
/**
* {@inheritdoc}
*/
public function getTrailingMetadata()
{
return $this->stream->getTrailingMetadata();
}
/**
* {@inheritdoc}
*/
public function getPeer()
{
return $this->stream->getPeer();
}
/**
* {@inheritdoc}
*/
public function cancel()
{
$this->stream->cancel();
}
/**
* {@inheritdoc}
*/
public function setCallCredentials($call_credentials)
{
$this->stream->setCallCredentials($call_credentials);
}
}

View File

@@ -0,0 +1,61 @@
<?php
/*
* Copyright 2018 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\Transport\Grpc;
/**
* Temporary class to support an interceptor-like interface until gRPC interceptor support is
* available.
*
* @experimental
* @deprecated Deprecated in favor of implementations extending {@see \Grpc\Interceptor}.
*/
interface UnaryInterceptorInterface
{
/**
* @param string $method
* @param \Google\Protobuf\Internal\Message $argument
* @param callable $deserialize
* @param array $metadata
* @param array $options
* @param callable $continuation
* @return mixed
*/
public function interceptUnaryUnary(
$method,
$argument,
$deserialize,
array $metadata,
array $options,
callable $continuation
);
}

View File

@@ -0,0 +1,223 @@
<?php
/*
* Copyright 2018 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\Transport;
use Google\ApiCore\ApiException;
use Google\ApiCore\ApiStatus;
use Google\ApiCore\Call;
use Google\ApiCore\ServiceAddressTrait;
use Google\ApiCore\ValidationException;
use Google\ApiCore\ValidationTrait;
use Google\Protobuf\Internal\Message;
use Google\Rpc\Status;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Psr7\Request;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
/**
* A transport that sends protobuf over HTTP 1.1 that can be used when full gRPC support
* is not available.
*/
class GrpcFallbackTransport implements TransportInterface
{
use ValidationTrait;
use ServiceAddressTrait;
use HttpUnaryTransportTrait;
private string $baseUri;
/**
* @param string $baseUri
* @param callable $httpHandler A handler used to deliver PSR-7 requests.
*/
public function __construct(
string $baseUri,
callable $httpHandler,
) {
$this->baseUri = $baseUri;
$this->httpHandler = $httpHandler;
$this->transportName = 'grpc-fallback';
}
/**
* Builds a GrpcFallbackTransport.
*
* @param string $apiEndpoint
* The address of the API remote host, for example "example.googleapis.com".
* @param array $config {
* Config options used to construct the grpc-fallback transport.
*
* @type callable $httpHandler A handler used to deliver PSR-7 requests.
* }
* @return GrpcFallbackTransport
* @throws ValidationException
*/
public static function build(string $apiEndpoint, array $config = [])
{
$config += [
'httpHandler' => null,
'clientCertSource' => null,
'logger' => null,
];
list($baseUri, $port) = self::normalizeServiceAddress($apiEndpoint);
$httpHandler = $config['httpHandler'] ?: self::buildHttpHandlerAsync(logger: $config['logger']);
$transport = new GrpcFallbackTransport("$baseUri:$port", $httpHandler);
if ($config['clientCertSource']) {
$transport->configureMtlsChannel($config['clientCertSource']);
}
return $transport;
}
/**
* {@inheritdoc}
*/
public function startUnaryCall(Call $call, array $options)
{
$httpHandler = $this->httpHandler;
$options['requestId'] = crc32((string) spl_object_id($call) . getmypid());
return $httpHandler(
$this->buildRequest($call, $options),
$this->getCallOptions($options)
)->then(
function (ResponseInterface $response) use ($options) {
if (isset($options['metadataCallback'])) {
$metadataCallback = $options['metadataCallback'];
$metadataCallback($response->getHeaders());
}
return $response;
}
)->then(
function (ResponseInterface $response) use ($call) {
return $this->unpackResponse($call, $response);
},
function (\Exception $ex) {
throw $this->transformException($ex);
}
);
}
/**
* @param Call $call
* @param array $options
* @return RequestInterface
*/
private function buildRequest(Call $call, array $options)
{
// Build common headers and set the content type to 'application/x-protobuf'
$headers = ['Content-Type' => 'application/x-protobuf'] + self::buildCommonHeaders($options);
// It is necessary to supply 'grpc-web' in the 'x-goog-api-client' header
// when using the grpc-fallback protocol.
$headers += ['x-goog-api-client' => []];
$headers['x-goog-api-client'][] = 'grpc-web';
// Uri format: https://<service>/$rpc/<method>
$uri = "https://{$this->baseUri}/\$rpc/{$call->getMethod()}";
return new Request(
'POST',
$uri,
$headers,
$call->getMessage()->serializeToString()
);
}
/**
* @param Call $call
* @param ResponseInterface $response
* @return Message
*/
private function unpackResponse(Call $call, ResponseInterface $response)
{
$decodeType = $call->getDecodeType();
/** @var Message $responseMessage */
$responseMessage = new $decodeType();
$responseMessage->mergeFromString((string) $response->getBody());
return $responseMessage;
}
/**
* @param array $options
* @return array
*/
private function getCallOptions(array $options)
{
$callOptions = $options['transportOptions']['grpcFallbackOptions'] ?? [];
if (isset($options['timeoutMillis'])) {
$callOptions['timeout'] = $options['timeoutMillis'] / 1000;
}
if (isset($options['retryAttempt'])) {
$callOptions['retryAttempt'] = $options['retryAttempt'];
}
if (isset($options['requestId'])) {
$callOptions['requestId'] = $options['requestId'];
}
if ($this->clientCertSource) {
list($cert, $key) = self::loadClientCertSource($this->clientCertSource);
$callOptions['cert'] = $cert;
$callOptions['key'] = $key;
}
return $callOptions;
}
/**
* @param \Exception $ex
* @return \Exception
*/
private function transformException(\Exception $ex)
{
if ($ex instanceof RequestException && $ex->hasResponse()) {
$res = $ex->getResponse();
$body = (string) $res->getBody();
$status = new Status();
try {
$status->mergeFromString($body);
return ApiException::createFromRpcStatus($status);
} catch (\Exception $parseException) {
// We were unable to parse the response body into a $status object. Instead,
// create an ApiException using the unparsed $body as message.
$code = ApiStatus::rpcCodeFromHttpStatusCode($res->getStatusCode());
return ApiException::createFromApiResponse($body, $code, null, $parseException);
}
} else {
return $ex;
}
}
}

View File

@@ -0,0 +1,366 @@
<?php
/*
* Copyright 2018 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\Transport;
use Exception;
use Google\ApiCore\ApiException;
use Google\ApiCore\BidiStream;
use Google\ApiCore\Call;
use Google\ApiCore\ClientStream;
use Google\ApiCore\GrpcSupportTrait;
use Google\ApiCore\ServerStream;
use Google\ApiCore\ServiceAddressTrait;
use Google\ApiCore\Transport\Grpc\ServerStreamingCallWrapper;
use Google\ApiCore\Transport\Grpc\UnaryInterceptorInterface;
use Google\ApiCore\ValidationException;
use Google\ApiCore\ValidationTrait;
use Google\Auth\Logging\LoggingTrait;
use Google\Auth\Logging\RpcLogEvent;
use Google\Rpc\Code;
use Grpc\BaseStub;
use Grpc\Channel;
use Grpc\ChannelCredentials;
use Grpc\Interceptor;
use GuzzleHttp\Promise\Promise;
use Psr\Log\LoggerInterface;
/**
* A gRPC based transport implementation.
*/
class GrpcTransport extends BaseStub implements TransportInterface
{
use ValidationTrait;
use GrpcSupportTrait;
use ServiceAddressTrait;
use LoggingTrait;
private null|LoggerInterface $logger;
/**
* @param string $hostname
* @param array $opts
* - 'update_metadata': (optional) a callback function which takes in a
* metadata array, and returns an updated metadata array
* - 'grpc.primary_user_agent': (optional) a user-agent string
* @param Channel $channel An already created Channel object (optional)
* @param Interceptor[]|UnaryInterceptorInterface[] $interceptors *EXPERIMENTAL*
* Interceptors used to intercept RPC invocations before a call starts.
* Please note that implementations of
* {@see \Google\ApiCore\Transport\Grpc\UnaryInterceptorInterface} are
* considered deprecated and support will be removed in a future
* release. To prepare for this, please take the time to convert
* `UnaryInterceptorInterface` implementations over to a class which
* extends {@see Grpc\Interceptor}.
* @param null|false|LoggerInterface $logger A PSR-3 Compliant logger.
* @throws Exception
*/
public function __construct(
string $hostname,
array $opts,
?Channel $channel = null,
array $interceptors = [],
null|false|LoggerInterface $logger = null
) {
if ($interceptors) {
$channel = Interceptor::intercept(
$channel ?: new Channel($hostname, $opts),
$interceptors
);
}
parent::__construct($hostname, $opts, $channel);
$this->logger = $logger;
}
/**
* Builds a GrpcTransport.
*
* @param string $apiEndpoint
* The address of the API remote host, for example "example.googleapis.com. May also
* include the port, for example "example.googleapis.com:443"
* @param array $config {
* Config options used to construct the gRPC transport.
*
* @type array $stubOpts Options used to construct the gRPC stub (see
* {@link https://grpc.github.io/grpc/core/group__grpc__arg__keys.html}).
* @type Channel $channel Grpc channel to be used.
* @type Interceptor[]|UnaryInterceptorInterface[] $interceptors *EXPERIMENTAL*
* Interceptors used to intercept RPC invocations before a call starts.
* Please note that implementations of
* {@see \Google\ApiCore\Transport\Grpc\UnaryInterceptorInterface} are
* considered deprecated and support will be removed in a future
* release. To prepare for this, please take the time to convert
* `UnaryInterceptorInterface` implementations over to a class which
* extends {@see Grpc\Interceptor}.
* @type callable $clientCertSource A callable which returns the client cert as a string.
* }
* @return GrpcTransport
* @throws ValidationException
*/
public static function build(string $apiEndpoint, array $config = [])
{
self::validateGrpcSupport();
$config += [
'stubOpts' => [],
'channel' => null,
'interceptors' => [],
'clientCertSource' => null,
'logger' => null,
];
list($addr, $port) = self::normalizeServiceAddress($apiEndpoint);
$host = "$addr:$port";
$stubOpts = $config['stubOpts'];
// Set the required 'credentials' key in stubOpts if it is not already set. Use
// array_key_exists because null is a valid value.
if (!array_key_exists('credentials', $stubOpts)) {
if (isset($config['clientCertSource'])) {
list($cert, $key) = self::loadClientCertSource($config['clientCertSource']);
$stubOpts['credentials'] = ChannelCredentials::createSsl(null, $key, $cert);
} else {
$stubOpts['credentials'] = ChannelCredentials::createSsl();
}
}
$channel = $config['channel'];
if (!is_null($channel) && !($channel instanceof Channel)) {
throw new ValidationException(
"Channel argument to GrpcTransport must be of type \Grpc\Channel, " .
'instead got: ' . print_r($channel, true)
);
}
try {
if ($config['logger'] === false) {
$config['logger'] = null;
}
return new GrpcTransport($host, $stubOpts, $channel, $config['interceptors'], $config['logger']);
} catch (Exception $ex) {
throw new ValidationException(
'Failed to build GrpcTransport: ' . $ex->getMessage(),
$ex->getCode(),
$ex
);
}
}
/**
* {@inheritdoc}
*/
public function startBidiStreamingCall(Call $call, array $options)
{
$this->verifyUniverseDomain($options);
$bidiStream = new BidiStream(
$this->_bidiRequest(
'/' . $call->getMethod(),
[$call->getDecodeType(), 'decode'],
isset($options['headers']) ? $options['headers'] : [],
$this->getCallOptions($options)
),
$call->getDescriptor(),
$this->logger
);
if ($this->logger) {
$requestEvent = new RpcLogEvent();
$requestEvent->headers = $options['headers'] ?? [];
$requestEvent->retryAttempt = $options['retryAttempt'] ?? null;
$requestEvent->serviceName = $options['serviceName'] ?? null;
$requestEvent->rpcName = $call->getMethod();
$requestEvent->processId = (int) getmypid();
$requestEvent->requestId = crc32((string) spl_object_id($bidiStream) . getmypid());
$this->logRequest($requestEvent);
}
return $bidiStream;
}
/**
* {@inheritdoc}
*/
public function startClientStreamingCall(Call $call, array $options)
{
$this->verifyUniverseDomain($options);
return new ClientStream(
$this->_clientStreamRequest(
'/' . $call->getMethod(),
[$call->getDecodeType(), 'decode'],
isset($options['headers']) ? $options['headers'] : [],
$this->getCallOptions($options)
),
$call->getDescriptor(),
$this->logger
);
}
/**
* {@inheritdoc}
*/
public function startServerStreamingCall(Call $call, array $options)
{
$this->verifyUniverseDomain($options);
$message = $call->getMessage();
if (!$message) {
throw new \InvalidArgumentException('A message is required for ServerStreaming calls.');
}
// This simultaenously creates and starts a \Grpc\ServerStreamingCall.
$stream = $this->_serverStreamRequest(
'/' . $call->getMethod(),
$message,
[$call->getDecodeType(), 'decode'],
isset($options['headers']) ? $options['headers'] : [],
$this->getCallOptions($options)
);
$serverStream = new ServerStream(
new ServerStreamingCallWrapper($stream),
$call->getDescriptor(),
$this->logger
);
if ($this->logger) {
$requestEvent = new RpcLogEvent();
$requestEvent->headers = $options['headers'];
$requestEvent->payload = $call->getMessage()->serializeToJsonString();
$requestEvent->retryAttempt = $options['retryAttempt'] ?? null;
$requestEvent->serviceName = $options['serviceName'] ?? null;
$requestEvent->rpcName = $call->getMethod();
$requestEvent->processId = (int) getmypid();
$requestEvent->requestId = crc32((string) spl_object_id($serverStream) . getmypid());
$this->logRequest($requestEvent);
}
return $serverStream;
}
/**
* {@inheritdoc}
*/
public function startUnaryCall(Call $call, array $options)
{
$this->verifyUniverseDomain($options);
$headers = $options['headers'] ?? [];
$requestEvent = null;
$unaryCall = $this->_simpleRequest(
'/' . $call->getMethod(),
$call->getMessage(),
[$call->getDecodeType(), 'decode'],
isset($options['headers']) ? $options['headers'] : [],
$this->getCallOptions($options)
);
if ($this->logger) {
$requestEvent = new RpcLogEvent();
$requestEvent->headers = $headers;
$requestEvent->payload = $call->getMessage()->serializeToJsonString();
$requestEvent->retryAttempt = $options['retryAttempt'] ?? null;
$requestEvent->serviceName = $options['serviceName'] ?? null;
$requestEvent->rpcName = $call->getMethod();
$requestEvent->processId = (int) getmypid();
$requestEvent->requestId = crc32((string) spl_object_id($call) . getmypid());
$this->logRequest($requestEvent);
}
/** @var Promise $promise */
$promise = new Promise(
function () use ($unaryCall, $options, &$promise, $requestEvent) {
list($response, $status) = $unaryCall->wait();
if ($this->logger) {
$responseEvent = new RpcLogEvent($requestEvent->milliseconds);
$responseEvent->headers = $status->metadata;
$responseEvent->payload = ($response) ? $response->serializeToJsonString() : null;
$responseEvent->status = $status->code;
$responseEvent->processId = $requestEvent->processId;
$responseEvent->requestId = $requestEvent->requestId;
$this->logResponse($responseEvent);
}
if ($status->code == Code::OK) {
if (isset($options['metadataCallback'])) {
$metadataCallback = $options['metadataCallback'];
$metadataCallback($unaryCall->getMetadata());
}
$promise->resolve($response);
} else {
throw ApiException::createFromStdClass($status);
}
},
[$unaryCall, 'cancel']
);
return $promise;
}
private function verifyUniverseDomain(array $options)
{
if (isset($options['credentialsWrapper'])) {
$options['credentialsWrapper']->checkUniverseDomain();
}
}
private function getCallOptions(array $options)
{
$callOptions = $options['transportOptions']['grpcOptions'] ?? [];
if (isset($options['credentialsWrapper'])) {
$audience = $options['audience'] ?? null;
$credentialsWrapper = $options['credentialsWrapper'];
$callOptions['call_credentials_callback'] = $credentialsWrapper
->getAuthorizationHeaderCallback($audience);
}
if (isset($options['timeoutMillis'])) {
$callOptions['timeout'] = $options['timeoutMillis'] * 1000;
}
return $callOptions;
}
private static function loadClientCertSource(callable $clientCertSource)
{
return call_user_func($clientCertSource);
}
}

View File

@@ -0,0 +1,171 @@
<?php
/*
* Copyright 2018 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\Transport;
use Exception;
use Google\ApiCore\Call;
use Google\ApiCore\ValidationException;
use Google\Auth\HttpHandler\HttpHandlerFactory;
use Psr\Log\LoggerInterface;
/**
* A trait for shared functionality between transports that support only unary RPCs using simple
* HTTP requests.
*
* @internal
*/
trait HttpUnaryTransportTrait
{
private $httpHandler;
private $transportName;
private $clientCertSource;
/**
* {@inheritdoc}
* @return never
* @throws \BadMethodCallException
*/
public function startClientStreamingCall(Call $call, array $options)
{
$this->throwUnsupportedException();
}
/**
* {@inheritdoc}
* @return never
* @throws \BadMethodCallException
*/
public function startServerStreamingCall(Call $call, array $options)
{
$this->throwUnsupportedException();
}
/**
* {@inheritdoc}
* @return never
* @throws \BadMethodCallException
*/
public function startBidiStreamingCall(Call $call, array $options)
{
$this->throwUnsupportedException();
}
/**
* {@inheritdoc}
*/
public function close()
{
// Nothing to do.
}
/**
* @param array $options
* @return array
*/
private static function buildCommonHeaders(array $options)
{
$headers = $options['headers'] ?? [];
if (!is_array($headers)) {
throw new \InvalidArgumentException(
'The "headers" option must be an array'
);
}
// If not already set, add an auth header to the request
if (!isset($headers['Authorization']) && isset($options['credentialsWrapper'])) {
$credentialsWrapper = $options['credentialsWrapper'];
$audience = $options['audience'] ?? null;
$callback = $credentialsWrapper
->getAuthorizationHeaderCallback($audience);
// Prevent unexpected behavior, as the authorization header callback
// uses lowercase "authorization"
unset($headers['authorization']);
// Mitigate scenario where InsecureCredentialsWrapper returns null.
$authHeaders = empty($callback) ? [] : $callback();
if (!is_array($authHeaders)) {
throw new \UnexpectedValueException(
'Expected array response from authorization header callback'
);
}
$headers += $authHeaders;
}
return $headers;
}
/**
* @return callable
* @throws ValidationException
*/
private static function buildHttpHandlerAsync(null|false|LoggerInterface $logger = null)
{
try {
return [HttpHandlerFactory::build(logger: $logger), 'async'];
} catch (Exception $ex) {
throw new ValidationException('Failed to build HttpHandler', $ex->getCode(), $ex);
}
}
/**
* Set the path to a client certificate.
*
* @param callable $clientCertSource
*/
private function configureMtlsChannel(callable $clientCertSource)
{
$this->clientCertSource = $clientCertSource;
}
/**
* @return never
* @throws \BadMethodCallException
*/
private function throwUnsupportedException()
{
throw new \BadMethodCallException(
"Streaming calls are not supported while using the {$this->transportName} transport."
);
}
private static function loadClientCertSource(callable $clientCertSource)
{
$certFile = tempnam(sys_get_temp_dir(), 'cert');
$keyFile = tempnam(sys_get_temp_dir(), 'key');
list($cert, $key) = call_user_func($clientCertSource);
file_put_contents($certFile, $cert);
file_put_contents($keyFile, $key);
// the key and the cert are returned in one temporary file
return [$certFile, $keyFile];
}
}

View File

@@ -0,0 +1,238 @@
<?php
/*
* Copyright 2021 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\Transport\Rest;
use Psr\Http\Message\StreamInterface;
use RuntimeException;
class JsonStreamDecoder
{
const ESCAPE_CHAR = '\\';
private StreamInterface $stream;
private bool $closeCalled = false;
private string $decodeType;
private bool $ignoreUnknown = true;
private int $readChunkSize = 1024;
/**
* JsonStreamDecoder is a HTTP-JSON response stream decoder for JSON-ecoded
* protobuf messages. The response stream must be a JSON array, where the first
* byte is the opening of the array (i.e. '['), and the last byte is the closing
* of the array (i.e. ']'). Each array item must be a JSON object and comma
* separated.
*
* @param StreamInterface $stream The stream to decode.
* @param string $decodeType The type name of response messages to decode.
* @param array<mixed> $options {
* An array of optional arguments.
*
* @type bool $ignoreUnknown
* Toggles whether or not to throw an exception when an unknown field
* is encountered in a response message. The default is true.
* @type int $readChunkSizeBytes
* The upper size limit in bytes that can be read at a time from the
* response stream. The default is 1 KB.
* }
*
* @experimental
*/
public function __construct(StreamInterface $stream, string $decodeType, array $options = [])
{
$this->stream = $stream;
$this->decodeType = $decodeType;
if (isset($options['ignoreUnknown'])) {
$this->ignoreUnknown = $options['ignoreUnknown'];
}
if (isset($options['readChunkSize'])) {
$this->readChunkSize = $options['readChunkSizeBytes'];
}
}
/**
* Begins decoding the configured response stream. It is a generator which
* yields messages of the given decode type from the stream until the stream
* completes. Throws an Exception if the stream is closed before the closing
* byte is read or if it encounters an error while decoding a message.
*
* @throws RuntimeException
* @return \Generator
*/
public function decode()
{
try {
foreach ($this->_decode() as $response) {
yield $response;
}
} catch (RuntimeException $re) {
$msg = $re->getMessage();
$streamClosedException =
strpos($msg, 'Stream is detached') !== false ||
strpos($msg, 'Unexpected stream close') !== false;
// Only throw the exception if close() was not called and it was not
// a closing-related exception.
if (!$this->closeCalled || !$streamClosedException) {
throw $re;
}
}
}
/**
* @return \Generator
*/
private function _decode()
{
$decodeType = $this->decodeType;
$str = false;
$prev = $chunk = '';
$start = $end = $cursor = $level = 0;
while ($chunk !== '' || !$this->stream->eof()) {
// Read up to $readChunkSize bytes from the stream.
$chunk .= $this->stream->read($this->readChunkSize);
// If the response stream has been closed and the only byte
// remaining is the closing array bracket, we are done.
if ($this->stream->eof() && $chunk === ']') {
$level--;
break;
}
// Parse the freshly read data available in $chunk.
$chunkLength = strlen($chunk);
while ($cursor < $chunkLength) {
// Access the next byte for processing.
$b = $chunk[$cursor];
// Track open/close double quotes of a key or value. Do not
// toggle flag with the pervious byte was an escape character.
if ($b === '"' && $prev !== self::ESCAPE_CHAR) {
$str = !$str;
}
// Skip over new lines that break up items.
if ($b === "\n" && $level === 1) {
$start++;
}
// Ignore commas separating messages in the stream array.
if ($b === ',' && $level === 1) {
$start++;
}
// Track the opening of a new array or object if not in a string
// value.
if (($b === '{' || $b === '[') && !$str) {
$level++;
// Opening of the array/root object.
// Do not include it in the messageBuffer.
if ($level === 1) {
$start++;
}
}
// Track the closing of an object if not in a string value.
if ($b === '}' && !$str) {
$level--;
if ($level === 1) {
$end = $cursor + 1;
}
}
// Track the closing of an array if not in a string value.
if ($b === ']' && !$str) {
$level--;
// If we are seeing a closing square bracket at the
// response message level, e.g. {"foo], there is a problem.
if ($level === 1) {
throw new \RuntimeException('Received closing byte mid-message');
}
}
// A message-closing byte was just buffered. Decode the
// message with the decode type, clearing the messageBuffer,
// and yield it.
//
// TODO(noahdietz): Support google.protobuf.*Value messages that
// are encoded as primitives and separated by commas.
if ($end !== 0) {
$length = $end - $start;
/** @var \Google\Protobuf\Internal\Message $return */
$return = new $decodeType();
$return->mergeFromJsonString(
substr($chunk, $start, $length),
$this->ignoreUnknown
);
yield $return;
// Dump the part of the chunk used for parsing the message
// and use the remaining for the next message.
$remaining = $chunkLength - $length;
$chunk = substr($chunk, $end, $remaining);
// Reset all indices and exit chunk processing.
$start = 0;
$end = 0;
$cursor = 0;
break;
}
$cursor++;
// An escaped back slash should not escape the following character.
if ($b === self::ESCAPE_CHAR && $prev === self::ESCAPE_CHAR) {
$b = '';
}
$prev = $b;
}
// If after attempting to process the chunk, no progress was made and the
// stream is closed, we must break as the stream has closed prematurely.
if ($cursor === $chunkLength && $this->stream->eof()) {
break;
}
}
if ($level > 0) {
throw new \RuntimeException('Unexpected stream close before receiving the closing byte');
}
}
/**
* Closes the underlying stream. If the stream is actively being decoded, an
* exception will not be thrown due to the interruption.
*
* @return void
*/
public function close()
{
$this->closeCalled = true;
$this->stream->close();
}
}

View File

@@ -0,0 +1,197 @@
<?php
/*
* Copyright 2021 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\Transport\Rest;
use Google\ApiCore\ApiException;
use Google\ApiCore\ApiStatus;
use Google\ApiCore\ServerStreamingCallInterface;
use Google\Rpc\Code;
use GuzzleHttp\Exception\RequestException;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use stdClass;
/**
* Class RestServerStreamingCall implements \Google\ApiCore\ServerStreamingCallInterface.
*
* @experimental
*/
class RestServerStreamingCall implements ServerStreamingCallInterface
{
/** @var callable */
private $httpHandler;
/** @var array<mixed> */
private array $decoderOptions;
private RequestInterface $originalRequest;
private ?JsonStreamDecoder $decoder;
private string $decodeType;
private ?ResponseInterface $response;
private stdClass $status;
/**
* @param callable $httpHandler
* @param string $decodeType
* @param array<mixed> $decoderOptions
*/
public function __construct(callable $httpHandler, string $decodeType, array $decoderOptions)
{
$this->httpHandler = $httpHandler;
$this->decodeType = $decodeType;
$this->decoderOptions = $decoderOptions;
}
/**
* {@inheritdoc}
*/
public function start($request, array $headers = [], array $callOptions = [])
{
$this->originalRequest = $this->appendHeaders($request, $headers);
try {
$handler = $this->httpHandler;
$response = $handler(
$this->originalRequest,
$callOptions
)->wait();
} catch (\Exception $ex) {
if ($ex instanceof RequestException && $ex->hasResponse()) {
$ex = ApiException::createFromRequestException($ex, /* isStream */ true);
}
throw $ex;
}
// Create an OK Status for a successful request just so that it
// has a return value.
$this->status = new stdClass();
$this->status->code = Code::OK;
$this->status->message = ApiStatus::OK;
$this->status->details = [];
$this->response = $response;
}
/**
* @param RequestInterface $request
* @param array<mixed> $headers
* @return RequestInterface
*/
private function appendHeaders(RequestInterface $request, array $headers)
{
foreach ($headers as $key => $value) {
$request = $request->hasHeader($key) ?
$request->withAddedHeader($key, $value) :
$request->withHeader($key, $value);
}
return $request;
}
/**
* {@inheritdoc}
*/
public function responses()
{
if (is_null($this->response)) {
throw new \Exception('Stream has not been started.');
}
// Decode the stream and yield responses as they are read.
$this->decoder = new JsonStreamDecoder(
$this->response->getBody(),
$this->decodeType,
$this->decoderOptions
);
foreach ($this->decoder->decode() as $message) {
yield $message;
}
}
/**
* Return the status of the server stream. If the call has not been started
* this will be null.
*
* @return stdClass The status, with integer $code, string
* $details, and array $metadata members
*/
public function getStatus()
{
return $this->status;
}
/**
* {@inheritdoc}
*/
public function getMetadata()
{
return is_null($this->response) ? null : $this->response->getHeaders();
}
/**
* The Rest transport does not support trailing metadata. This is a
* passthrough to getMetadata().
*/
public function getTrailingMetadata()
{
return $this->getMetadata();
}
/**
* {@inheritdoc}
*/
public function getPeer()
{
return $this->originalRequest->getUri();
}
/**
* {@inheritdoc}
*/
public function cancel()
{
if (!is_null($this->decoder)) {
$this->decoder->close();
}
}
/**
* For the REST transport this is a no-op.
* {@inheritdoc}
*/
public function setCallCredentials($call_credentials)
{
// Do nothing.
}
}

View File

@@ -0,0 +1,283 @@
<?php
/*
* Copyright 2018 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\Transport;
use Google\ApiCore\ApiException;
use Google\ApiCore\Call;
use Google\ApiCore\InsecureRequestBuilder;
use Google\ApiCore\RequestBuilder;
use Google\ApiCore\ServerStream;
use Google\ApiCore\ServiceAddressTrait;
use Google\ApiCore\Transport\Rest\RestServerStreamingCall;
use Google\ApiCore\ValidationException;
use Google\ApiCore\ValidationTrait;
use Google\Protobuf\Internal\Message;
use GuzzleHttp\Exception\RequestException;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
/**
* A REST based transport implementation.
*/
class RestTransport implements TransportInterface
{
use ValidationTrait;
use ServiceAddressTrait;
use HttpUnaryTransportTrait {
startServerStreamingCall as protected unsupportedServerStreamingCall;
}
private RequestBuilder $requestBuilder;
/**
* @param RequestBuilder $requestBuilder A builder responsible for creating
* a PSR-7 request from a set of request information.
* @param callable $httpHandler A handler used to deliver PSR-7 requests.
*/
public function __construct(
RequestBuilder $requestBuilder,
callable $httpHandler
) {
$this->requestBuilder = $requestBuilder;
$this->httpHandler = $httpHandler;
$this->transportName = 'REST';
}
/**
* Builds a RestTransport.
*
* @param string $apiEndpoint
* The address of the API remote host, for example "example.googleapis.com".
* @param string $restConfigPath
* Path to rest config file.
* @param array<mixed> $config {
* Config options used to construct the gRPC transport.
*
* @type callable $httpHandler A handler used to deliver PSR-7 requests.
* @type callable $clientCertSource A callable which returns the client cert as a string.
* @type bool $hasEmulator True if the emulator is enabled.
* }
* @return RestTransport
* @throws ValidationException
*/
public static function build(string $apiEndpoint, string $restConfigPath, array $config = [])
{
$config += [
'httpHandler' => null,
'clientCertSource' => null,
'hasEmulator' => false,
'logger' => null,
];
list($baseUri, $port) = self::normalizeServiceAddress($apiEndpoint);
$requestBuilder = $config['hasEmulator']
? new InsecureRequestBuilder("$baseUri:$port", $restConfigPath)
: new RequestBuilder("$baseUri:$port", $restConfigPath);
$httpHandler = $config['httpHandler'] ?: self::buildHttpHandlerAsync($config['logger']);
$transport = new RestTransport($requestBuilder, $httpHandler);
if ($config['clientCertSource']) {
$transport->configureMtlsChannel($config['clientCertSource']);
}
return $transport;
}
/**
* {@inheritdoc}
*/
public function startUnaryCall(Call $call, array $options)
{
$headers = self::buildCommonHeaders($options);
// Add the $call object ID for logging
$options['requestId'] = crc32((string) spl_object_id($call) . getmypid());
// call the HTTP handler
$httpHandler = $this->httpHandler;
return $httpHandler(
$this->requestBuilder->build(
$call->getMethod(),
$call->getMessage(),
$headers
),
$this->getCallOptions($options)
)->then(
function (ResponseInterface $response) use ($call, $options) {
$decodeType = $call->getDecodeType();
/** @var Message $return */
$return = new $decodeType();
$body = (string) $response->getBody();
// In some rare cases LRO response metadata may not be loaded
// in the descriptor pool, triggering an exception. The catch
// statement handles this case and attempts to add the LRO
// metadata type to the pool by directly instantiating the
// metadata class.
try {
$return->mergeFromJsonString(
$body,
true
);
} catch (\Exception $ex) {
if (!isset($options['metadataReturnType'])) {
throw $ex;
}
if (strpos($ex->getMessage(), 'Error occurred during parsing:') !== 0) {
throw $ex;
}
new $options['metadataReturnType']();
$return->mergeFromJsonString(
$body,
true
);
}
if (isset($options['metadataCallback'])) {
$metadataCallback = $options['metadataCallback'];
$metadataCallback($response->getHeaders());
}
return $return;
},
function (\Throwable $ex) {
if ($ex instanceof RequestException && $ex->hasResponse()) {
throw ApiException::createFromRequestException($ex);
}
throw $ex;
}
);
}
/**
* {@inheritdoc}
* @throws \BadMethodCallException for forwards compatibility with older GAPIC clients
*/
public function startServerStreamingCall(Call $call, array $options)
{
$message = $call->getMessage();
if (!$message) {
throw new \InvalidArgumentException('A message is required for ServerStreaming calls.');
}
// Maintain forwards compatibility with older GAPIC clients not configured for REST server streaming
// @see https://github.com/googleapis/gax-php/issues/370
if (!$this->requestBuilder->pathExists($call->getMethod())) {
$this->unsupportedServerStreamingCall($call, $options);
}
$headers = self::buildCommonHeaders($options);
$callOptions = $this->getCallOptions($options);
$request = $this->requestBuilder->build(
$call->getMethod(),
$call->getMessage()
// Exclude headers here because they will be added in _serverStreamRequest().
);
$decoderOptions = [];
if (isset($options['decoderOptions'])) {
$decoderOptions = $options['decoderOptions'];
}
return new ServerStream(
$this->_serverStreamRequest(
$this->httpHandler,
$request,
$headers,
$call->getDecodeType(),
$callOptions,
$decoderOptions
),
$call->getDescriptor()
);
}
/**
* Creates and starts a RestServerStreamingCall.
*
* @param callable $httpHandler The HTTP Handler to invoke the request with.
* @param RequestInterface $request The request to invoke.
* @param array<mixed> $headers The headers to include in the request.
* @param string $decodeType The response stream message type to decode.
* @param array<mixed> $callOptions The call options to use when making the call.
* @param array<mixed> $decoderOptions The options to use for the JsonStreamDecoder.
*
* @return RestServerStreamingCall
*/
private function _serverStreamRequest(
$httpHandler,
$request,
$headers,
$decodeType,
$callOptions,
$decoderOptions = []
) {
$call = new RestServerStreamingCall(
$httpHandler,
$decodeType,
$decoderOptions
);
$call->start($request, $headers, $callOptions);
return $call;
}
/**
* @param array<mixed> $options
*
* @return array<mixed>
*/
private function getCallOptions(array $options)
{
$callOptions = $options['transportOptions']['restOptions'] ?? [];
if (isset($options['timeoutMillis'])) {
$callOptions['timeout'] = $options['timeoutMillis'] / 1000;
}
if ($this->clientCertSource) {
list($cert, $key) = self::loadClientCertSource($this->clientCertSource);
$callOptions['cert'] = $cert;
$callOptions['key'] = $key;
}
if (isset($options['retryAttempt'])) {
$callOptions['retryAttempt'] = $options['retryAttempt'];
}
if (isset($options['requestId'])) {
$callOptions['requestId'] = $options['requestId'];
}
return $callOptions;
}
}

View File

@@ -0,0 +1,91 @@
<?php
/*
* Copyright 2018 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore\Transport;
use Google\ApiCore\BidiStream;
use Google\ApiCore\Call;
use Google\ApiCore\ClientStream;
use Google\ApiCore\ServerStream;
use Google\ApiCore\ValidationException;
use GuzzleHttp\Promise\PromiseInterface;
interface TransportInterface
{
/**
* Starts a bidi streaming call.
*
* @param Call $call
* @param array<mixed> $options
*
* @return BidiStream
*/
public function startBidiStreamingCall(Call $call, array $options);
/**
* Starts a client streaming call.
*
* @param Call $call
* @param array<mixed> $options
*
* @return ClientStream
*/
public function startClientStreamingCall(Call $call, array $options);
/**
* Starts a server streaming call.
*
* @param Call $call
* @param array<mixed> $options
*
* @return ServerStream
*/
public function startServerStreamingCall(Call $call, array $options);
/**
* Returns a promise used to execute network requests.
*
* @param Call $call
* @param array<mixed> $options
*
* @return PromiseInterface
* @throws ValidationException
*/
public function startUnaryCall(Call $call, array $options);
/**
* Closes the connection, if one exists.
*
* @return void
*/
public function close();
}

69
vendor/google/gax/src/UriTrait.php vendored Normal file
View File

@@ -0,0 +1,69 @@
<?php
/*
* Copyright 2018 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore;
use GuzzleHttp\Psr7\Query;
use GuzzleHttp\Psr7\Utils;
use Psr\Http\Message\UriInterface;
/**
* Provides a light wrapper around often used URI related functions.
*
* @internal
*/
trait UriTrait
{
/**
* @param string|UriInterface $uri
* @param array $query
* @return UriInterface
*/
public function buildUriWithQuery($uri, array $query)
{
$query = array_filter($query, function ($v) {
return $v !== null;
});
// Casts bools to their string representation
foreach ($query as $k => &$v) {
if (is_bool($v)) {
$v = $v ? 'true' : 'false';
}
}
return Utils::uriFor($uri)
->withQuery(
Query::build($query)
);
}
}

View File

@@ -0,0 +1,41 @@
<?php
/*
* Copyright 2016 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore;
use Exception;
/**
* ValidationException represents a local error (i.e. not during an RPC call).
*/
class ValidationException extends Exception
{
}

View File

@@ -0,0 +1,81 @@
<?php
/*
* Copyright 2017 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore;
/**
* @internal
*/
trait ValidationTrait
{
/**
* @param array $arr Associative array
* @param array $requiredKeys List of keys to check for in $arr
* @return array Returns $arr for fluent use
*/
public static function validate(array $arr, array $requiredKeys)
{
return self::validateImpl($arr, $requiredKeys, true);
}
/**
* @param array $arr Associative array
* @param array $requiredKeys List of keys to check for in $arr
* @return array Returns $arr for fluent use
*/
public static function validateNotNull(array $arr, array $requiredKeys)
{
return self::validateImpl($arr, $requiredKeys, false);
}
private static function validateImpl($arr, $requiredKeys, $allowNull)
{
foreach ($requiredKeys as $requiredKey) {
$valid = array_key_exists($requiredKey, $arr)
&& ($allowNull || !is_null($arr[$requiredKey]));
if (!$valid) {
throw new ValidationException("Missing required argument $requiredKey");
}
}
return $arr;
}
/**
* @param string $filePath
* @throws ValidationException
*/
private static function validateFileExists(string $filePath)
{
if (!file_exists($filePath)) {
throw new ValidationException("Could not find specified file: $filePath");
}
}
}

77
vendor/google/gax/src/Version.php vendored Normal file
View File

@@ -0,0 +1,77 @@
<?php
/*
* Copyright 2018 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Google\ApiCore;
/**
* @internal
*/
class Version
{
/**
* @var ?string
*/
private static $version = null;
/**
* @return string The version of the ApiCore library.
*/
public static function getApiCoreVersion()
{
if (is_null(self::$version)) {
$versionFile = implode(DIRECTORY_SEPARATOR, [__DIR__, '..', 'VERSION']);
self::$version = self::readVersionFile($versionFile);
}
return self::$version;
}
/**
* Reads a VERSION file and returns the contents. If the file does not
* exist, returns "".
*
* @param string $file
* @return string
*/
public static function readVersionFile(string $file)
{
$versionString = file_exists($file)
? (string) file_get_contents($file)
: '';
return trim($versionString);
}
/**
* Private constructor.
*/
private function __construct()
{
}
}