CODE_OF_CONDUCT.md 0000644 00000003675 15174274026 0007366 0 ustar 00 # Contributor Code of Conduct
As contributors and maintainers of this project,
and in the interest of fostering an open and welcoming community,
we pledge to respect all people who contribute through reporting issues,
posting feature requests, updating documentation,
submitting pull requests or patches, and other activities.
We are committed to making participation in this project
a harassment-free experience for everyone,
regardless of level of experience, gender, gender identity and expression,
sexual orientation, disability, personal appearance,
body size, race, ethnicity, age, religion, or nationality.
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery
* Personal attacks
* Trolling or insulting/derogatory comments
* Public or private harassment
* Publishing other's private information,
such as physical or electronic
addresses, without explicit permission
* Other unethical or unprofessional conduct.
Project maintainers have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct.
By adopting this Code of Conduct,
project maintainers commit themselves to fairly and consistently
applying these principles to every aspect of managing this project.
Project maintainers who do not follow or enforce the Code of Conduct
may be permanently removed from the project team.
This code of conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community.
Instances of abusive, harassing, or otherwise unacceptable behavior
may be reported by opening an issue
or contacting one or more of the project maintainers.
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0,
available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/)
LICENSE 0000644 00000002700 15174274026 0005560 0 ustar 00 Copyright 2016, Google Inc.
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.
README.md 0000644 00000005461 15174274026 0006041 0 ustar 00 # Google API Core for PHP
[](https://travis-ci.org/googleapis/gax-php)
[](https://codecov.io/github/googleapis/gax-php)
- [Documentation](http://googleapis.github.io/gax-php)
Google API Core for PHP (gax-php) is a set of modules which aids the development
of APIs for clients based on [gRPC][] and Google API conventions.
Application code will rarely need to use most of the classes within this library
directly, but code generated automatically from the API definition files in
[Google APIs][] can use services such as page streaming and retry to provide a
more convenient and idiomatic API surface to callers.
[gRPC]: http://grpc.io
[Google APIs]: https://github.com/googleapis/googleapis/
## PHP Versions
gax-php currently requires PHP 5.5 or higher.
## Contributing
Contributions to this library are always welcome and highly encouraged.
See the [CONTRIBUTING][] documentation for more information on how to get
started.
[CONTRIBUTING]: https://github.com/googleapis/gax-php/blob/master/.github/CONTRIBUTING.md
## Versioning
This library follows [Semantic Versioning][].
This library is considered GA (generally available). As such, it will not
introduce backwards-incompatible changes in any minor or patch releases. We will
address issues and requests with the highest priority.
[Semantic Versioning]: http://semver.org/
## Repository Structure
All code lives under the src/ directory. Handwritten code lives in the
src/ApiCore directory and is contained in the `Google\ApiCore` namespace.
Generated classes for protobuf common types and LongRunning client live under
the src/ directory, in the appropriate directory and namespace.
Code in the metadata/ directory is provided to support generated protobuf
classes, and should not be used directly.
## Development Set-Up
These steps describe the dependencies to install for Linux, and equivalents can
be found for Mac or Windows.
1. Install dependencies.
```sh
> cd ~/
> sudo apt-get install php php-dev libcurl3-openssl-dev php-pear php-bcmath php-xml
> curl -sS https://getcomposer.org/installer | php
> sudo pecl install protobuf
```
2. Set up this repo.
```sh
> cd /path/to/gax-php
> cp ~/composer.phar ./
> php composer.phar install
```
3. Run tests.
```sh
> vendor/bin/phpunit --bootstrap tests/bootstrap.php tests
```
4. Updating dependencies after changing `composer.json`:
```sh
> php composer.phar update
`
```
5. Formatting source:
```sh
> vendor/bin/phpcbf -s --standard=./ruleset.xml
```
## License
BSD - See [LICENSE][] for more information.
[LICENSE]: https://github.com/googleapis/gax-php/blob/master/LICENSE
VERSION 0000644 00000000006 15174274026 0005620 0 ustar 00 1.6.0
composer.json 0000644 00000002150 15174274026 0007274 0 ustar 00 {
"name": "google/gax",
"type": "library",
"description": "Google API Core for PHP",
"keywords": ["google"],
"homepage": "https://github.com/googleapis/gax-php",
"license": "BSD-3-Clause",
"require": {
"php": ">=5.5",
"google/auth": "^1.2.0",
"google/grpc-gcp": "^0.1.0",
"grpc/grpc": "^1.13",
"google/protobuf": "^3.12.2",
"guzzlehttp/promises": "^1.3",
"guzzlehttp/psr7": "^1.2",
"google/common-protos": "^1.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8.36",
"squizlabs/php_codesniffer": "3.*"
},
"conflict": {
"ext-protobuf": "<3.7.0"
},
"autoload": {
"psr-4": {
"Google\\ApiCore\\": "src",
"GPBMetadata\\ApiCore\\": "metadata/ApiCore"
}
},
"autoload-dev": {
"psr-4": {
"Google\\ApiCore\\Dev\\": "dev/src",
"Google\\ApiCore\\": "tests",
"GPBMetadata\\Google\\": "metadata/Google"
}
},
"scripts": {
"regenerate-test-protos": "dev/sh/regenerate-test-protos.sh"
}
}
metadata/ApiCore/Testing/Mocks.php 0000644 00000006476 15174274026 0013075 0 ustar 00 internalAddGeneratedFile(hex2bin(
"0aeb070a0b6d6f636b732e70726f746f1216676f6f676c652e617069636f" .
"72652e74657374696e671a1f676f6f676c652f70726f746f6275662f7469" .
"6d657374616d702e70726f746f1a1e676f6f676c652f70726f746f627566" .
"2f6475726174696f6e2e70726f746f1a1c676f6f676c652f70726f746f62" .
"75662f7374727563742e70726f746f1a1e676f6f676c652f70726f746f62" .
"75662f77726170706572732e70726f746f22340a0b4d6f636b5265717565" .
"737412120a0a706167655f746f6b656e18012001280912110a0970616765" .
"5f73697a65180220012804225d0a0c4d6f636b526573706f6e7365120c0a" .
"046e616d65180120012809120e0a066e756d62657218022001280412160a" .
"0e7265736f75726365735f6c69737418032003280912170a0f6e6578745f" .
"706167655f746f6b656e180420012809228c050a0f4d6f636b5265717565" .
"7374426f6479120c0a046e616d65180120012809120e0a066e756d626572" .
"18022001280412160a0e72657065617465645f6669656c64180320032809" .
"123f0a0e6e65737465645f6d65737361676518042001280b32272e676f6f" .
"676c652e617069636f72652e74657374696e672e4d6f636b526571756573" .
"74426f647912300a0b62797465735f76616c756518052001280b321b2e67" .
"6f6f676c652e70726f746f6275662e427974657356616c756512310a0e64" .
"75726174696f6e5f76616c756518062001280b32192e676f6f676c652e70" .
"726f746f6275662e4475726174696f6e122e0a0a6669656c645f6d61736b" .
"18072001280b321a2e676f6f676c652e70726f746f6275662e4669656c64" .
"4d61736b12300a0b696e7436345f76616c756518082001280b321b2e676f" .
"6f676c652e70726f746f6275662e496e74363456616c7565122e0a0a6c69" .
"73745f76616c756518092001280b321a2e676f6f676c652e70726f746f62" .
"75662e4c69737456616c756512320a0c737472696e675f76616c7565180a" .
"2001280b321c2e676f6f676c652e70726f746f6275662e537472696e6756" .
"616c7565122d0a0c7374727563745f76616c7565180b2001280b32172e67" .
"6f6f676c652e70726f746f6275662e53747275637412330a0f74696d6573" .
"74616d705f76616c7565180c2001280b321a2e676f6f676c652e70726f74" .
"6f6275662e54696d657374616d70122b0a0b76616c75655f76616c756518" .
"0d2001280b32162e676f6f676c652e70726f746f6275662e56616c756512" .
"110a076669656c645f31180e20012809480012110a076669656c645f3218" .
"0f20012809480012110a076669656c645f331810200128094800420d0a0b" .
"6f6e656f665f6669656c644219ca0216476f6f676c655c417069436f7265" .
"5c54657374696e67620670726f746f33"
));
static::$is_initialized = true;
}
}
metadata/Google/ApiCore/Tests/Unit/Example.php 0000644 00000001534 15174274026 0015222 0 ustar 00 internalAddGeneratedFile(hex2bin(
"0a85010a0d6578616d706c652e70726f746f1219676f6f676c652e617069" .
"636f72652e74657374732e756e6974220b0a094d794d6573736167654244" .
"ca0219476f6f676c655c417069436f72655c54657374735c556e6974e202" .
"254750424d657461646174615c476f6f676c655c417069436f72655c5465" .
"7374735c556e6974620670726f746f33"
));
static::$is_initialized = true;
}
}
metadata/README.md 0000644 00000000243 15174274026 0007612 0 ustar 00 # Google Protobuf Metadata Classes
## WARNING!
These classes are not intended for direct use - they exist only to support
the generated protobuf classes in src/
phpunit.xml.dist 0000644 00000000520 15174274026 0007724 0 ustar 00
tests/Tests/Unit
src/ApiCore
src/AgentHeader.php 0000644 00000011465 15174274026 0010232 0 ustar 00 $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($callingClass)
{
$callingClassFile = (new \ReflectionClass($callingClass))->getFileName();
$versionFile = substr(
$callingClassFile,
0,
strrpos($callingClassFile, DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR)
) . DIRECTORY_SEPARATOR . 'VERSION';
return Version::readVersionFile($versionFile);
}
}
src/ApiException.php 0000644 00000012413 15174274026 0010445 0 ustar 00 null,
'metadata' => null,
'basicMessage' => $message,
];
parent::__construct($message, $code, $optionalArgs['previous']);
$this->status = $status;
$this->metadata = $optionalArgs['metadata'];
$this->basicMessage = $optionalArgs['basicMessage'];
}
public function getStatus()
{
return $this->status;
}
/**
* @param \stdClass $status
* @return ApiException
*/
public static function createFromStdClass($status)
{
$metadata = property_exists($status, 'metadata') ? $status->metadata : null;
return self::create(
$status->details,
$status->code,
$metadata,
Serializer::decodeMetadata($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($metadata),
$previous
);
}
/**
* Construct an ApiException with a useful message, including decoded metadata.
*
* @param string $basicMessage
* @param int $rpcCode
* @param mixed[]|RepeatedField $metadata
* @param array $decodedMetadata
* @param \Exception|null $previous
* @return ApiException
*/
private static function create($basicMessage, $rpcCode, $metadata, array $decodedMetadata, $previous = null)
{
$rpcStatus = ApiStatus::statusFromRpcCode($rpcCode);
$messageData = [
'message' => $basicMessage,
'code' => $rpcCode,
'status' => $rpcStatus,
'details' => $decodedMetadata
];
$message = json_encode($messageData, JSON_PRETTY_PRINT);
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())
);
}
/**
* @return null|string
*/
public function getBasicMessage()
{
return $this->basicMessage;
}
/**
* @return mixed[]
*/
public function getMetadata()
{
return $this->metadata;
}
/**
* String representation of ApiException
* @return string
*/
public function __toString()
{
return __CLASS__ . ": $this->message\n";
}
}
src/ApiStatus.php 0000644 00000013675 15174274026 0010005 0 ustar 00 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 = [
401 => Code::UNAUTHENTICATED,
403 => Code::PERMISSION_DENIED,
404 => Code::NOT_FOUND,
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($status)
{
return array_key_exists($status, self::$apiStatusToCodeMap);
}
/**
* @param int $code
* @return string
*/
public static function statusFromRpcCode($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($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.
* Only map codes which do not map to multiple gRPC codes (e.g. excludes
* 400, 409, and 500).
*
* @param int $httpStatusCode
* @return int
*/
public static function rpcCodeFromHttpStatusCode($httpStatusCode)
{
if (array_key_exists($httpStatusCode, self::$httpStatusCodeToRpcCodeMap)) {
return self::$httpStatusCodeToRpcCodeMap[$httpStatusCode];
}
return ApiStatus::UNRECOGNIZED_CODE;
}
}
src/ArrayTrait.php 0000644 00000007144 15174274026 0010144 0 ustar 00 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, $arr)
{
return array_intersect_key(
$arr,
array_flip($keys)
);
}
}
src/BidiStream.php 0000644 00000013453 15174274026 0010105 0 ustar 00 call = $bidiStreamingCall;
if (array_key_exists('resourcesGetMethod', $streamingDescriptor)) {
$this->resourcesGetMethod = $streamingDescriptor['resourcesGetMethod'];
}
}
/**
* 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().");
}
$this->call->write($request);
}
/**
* Write all requests in $requests.
*
* @param mixed[] $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);
}
}
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;
}
}
src/Call.php 0000644 00000006520 15174274026 0006732 0 ustar 00 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)
{
return new static(
$this->method,
$this->decodeType,
$message,
$this->descriptor,
$this->callType
);
}
}
src/ClientStream.php 0000644 00000006507 15174274026 0010456 0 ustar 00 call = $clientStreamingCall;
}
/**
* Write request to the server.
*
* @param mixed $request The request to write
*/
public function write($request)
{
$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) {
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($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;
}
}
src/CredentialsWrapper.php 0000644 00000025066 15174274026 0011663 0 ustar 00 credentialsFetcher = $credentialsFetcher;
$this->authHttpHandler = $authHttpHandler ?: self::buildHttpHandlerFactory();
}
/**
* 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.
* }
* @return CredentialsWrapper
* @throws ValidationException
*/
public static function build(array $args = [])
{
$args += [
'keyFile' => null,
'scopes' => null,
'authHttpHandler' => null,
'enableCaching' => true,
'authCache' => null,
'authCacheOptions' => [],
'quotaProject' => null,
'defaultScopes' => null,
];
$keyFile = $args['keyFile'];
$authHttpHandler = $args['authHttpHandler'] ?: self::buildHttpHandlerFactory();
if (is_null($keyFile)) {
$loader = self::buildApplicationDefaultCredentials(
$args['scopes'],
$authHttpHandler,
null,
null,
$args['quotaProject'],
$args['defaultScopes']
);
} 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 ($args['enableCaching']) {
$authCache = $args['authCache'] ?: new MemoryCacheItemPool();
$loader = new FetchAuthTokenCache(
$loader,
$args['authCacheOptions'],
$authCache
);
}
return new CredentialsWrapper($loader, $authHttpHandler);
}
/**
* @return string|null The quota project associated with the credentials.
*/
public function getQuotaProject()
{
if ($this->credentialsFetcher instanceof GetQuotaProjectInterface) {
return $this->credentialsFetcher->getQuotaProject();
}
}
/**
* @deprecated
* @return string Bearer string containing access token.
*/
public function getBearerString()
{
$token = self::getToken($this->credentialsFetcher, $this->authHttpHandler);
return empty($token) ? '' : "Bearer $token";
}
/**
* @param string $audience optional audience for self-signed JWTs.
* @return callable Callable function that returns an authorization header.
*/
public function getAuthorizationHeaderCallback($audience = null)
{
$credentialsFetcher = $this->credentialsFetcher;
$authHttpHandler = $this->authHttpHandler;
// 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 ($credentialsFetcher, $authHttpHandler, $audience) {
$token = $credentialsFetcher->getLastReceivedToken();
if (self::isExpired($token)) {
// Call updateMetadata to take advantage of self-signed JWTs
if ($credentialsFetcher instanceof UpdateMetadataInterface) {
return $credentialsFetcher->updateMetadata([], $audience);
}
// In case a custom fetcher is provided (unlikely) which doesn't
// implement UpdateMetadataInterface
$token = $credentialsFetcher->fetchAuthToken($authHttpHandler);
if (!self::isValid($token)) {
return [];
}
}
$tokenString = $token['access_token'];
if (!empty($tokenString)) {
return ['authorization' => ["Bearer $tokenString"]];
}
return [];
};
}
/**
* @return Guzzle5HttpHandler|Guzzle6HttpHandler
* @throws ValidationException
*/
private static function buildHttpHandlerFactory()
{
try {
return HttpHandlerFactory::build();
} catch (Exception $ex) {
throw new ValidationException("Failed to build HttpHandler", $ex->getCode(), $ex);
}
}
/**
* @param array $scopes
* @param callable $authHttpHandler
* @param array $authCacheOptions
* @param CacheItemPoolInterface $authCache
* @param string $quotaProject
* @param array $defaultScopes
* @return CredentialsLoader
* @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);
}
}
private static function getToken(FetchAuthTokenInterface $credentialsFetcher, $authHttpHandler)
{
$token = $credentialsFetcher->getLastReceivedToken();
if (self::isExpired($token)) {
$token = $credentialsFetcher->fetchAuthToken($authHttpHandler);
if (!self::isValid($token)) {
return '';
}
}
return $token['access_token'];
}
private static function isValid($token)
{
return is_array($token)
&& array_key_exists('access_token', $token);
}
private static function isExpired($token)
{
return !(self::isValid($token)
&& array_key_exists('expires_at', $token)
&& $token['expires_at'] > time());
}
}
src/FixedSizeCollection.php 0000644 00000014346 15174274026 0011772 0 ustar 00 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
*/
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 integer $collectionSize
* @return Page[]
*/
private static function createPageArray($initialPage, $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;
}
}
src/GPBLabel.php 0000644 00000003507 15174274026 0007431 0 ustar 00 'startUnaryCall',
Call::BIDI_STREAMING_CALL => 'startBidiStreamingCall',
Call::CLIENT_STREAMING_CALL => 'startClientStreamingCall',
Call::SERVER_STREAMING_CALL => 'startServerStreamingCall',
];
/**
* Initiates an orderly shutdown in which preexisting calls continue but new
* calls are immediately cancelled.
*
* @experimental
*/
public function close()
{
$this->transport->close();
}
/**
* Get the transport for the client. This method is protected to support
* use by customized clients.
*
* @access private
* @return TransportInterface
*/
protected function getTransport()
{
return $this->transport;
}
/**
* Get the credentials for the client. This method is protected to support
* use by customized clients.
*
* @access private
* @return CredentialsWrapper
*/
protected function getCredentialsWrapper()
{
return $this->credentialsWrapper;
}
private static function getGapicVersion(array $options)
{
if (isset($options['libVersion'])) {
return $options['libVersion'];
} else {
if (!isset(self::$gapicVersionFromFile)) {
self::$gapicVersionFromFile = AgentHeader::readGapicVersionFromFile(__CLASS__);
}
return self::$gapicVersionFromFile;
}
}
private static function initGrpcGcpConfig($hostName, $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 [];
}
private function buildClientOptions(array $options)
{
// 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,
];
$defaultOptions['transportConfig'] += [
'grpc' => ['stubOpts' => ['grpc.service_config_disable_resolution' => 1]],
'rest' => [],
'grpc-fallback' => [],
];
// Merge defaults into $options starting from top level
// variables, then going into deeper nesting, so that
// we will not encounter missing keys
$options += $defaultOptions;
$options['credentialsConfig'] += $defaultOptions['credentialsConfig'];
$options['transportConfig'] += $defaultOptions['transportConfig'];
$options['transportConfig']['grpc'] += $defaultOptions['transportConfig']['grpc'];
$options['transportConfig']['grpc']['stubOpts'] += $defaultOptions['transportConfig']['grpc']['stubOpts'];
$options['transportConfig']['rest'] += $defaultOptions['transportConfig']['rest'];
$options['transportConfig']['grpc-fallback'] += $defaultOptions['transportConfig']['grpc-fallback'];
$this->modifyClientOptions($options);
// serviceAddress is now deprecated and acts as an alias for apiEndpoint
if (isset($options['serviceAddress'])) {
$options['apiEndpoint'] = $this->pluck('serviceAddress', $options, false);
}
// If an API endpoint is set, ensure the "audience" does not conflict
// with the custom endpoint by setting "user defined" scopes.
if ($options['apiEndpoint'] != $defaultOptions['apiEndpoint']
&& empty($options['credentialsConfig']['scopes'])
&& !empty($options['credentialsConfig']['defaultScopes'])
) {
$options['credentialsConfig']['scopes'] = $options['credentialsConfig']['defaultScopes'];
}
if (extension_loaded('sysvshm')
&& isset($options['gcpApiConfigPath'])
&& file_exists($options['gcpApiConfigPath'])
&& isset($options['apiEndpoint'])) {
$grpcGcpConfig = self::initGrpcGcpConfig(
$options['apiEndpoint'],
$options['gcpApiConfigPath']
);
if (array_key_exists('stubOpts', $options['transportConfig']['grpc'])) {
$options['transportConfig']['grpc']['stubOpts'] += [
'grpc_call_invoker' => $grpcGcpConfig->callInvoker()
];
} else {
$options['transportConfig']['grpc'] += [
'stubOpts' => [
'grpc_call_invoker' => $grpcGcpConfig->callInvoker()
]
];
}
}
return $options;
}
/**
* Configures the GAPIC client based on an array of options.
*
* @param array $options {
* An array of required and optional arguments.
*
* @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 string $serviceAddress
* **Deprecated**. This option will be removed in the next major release. Please
* utilize the `$apiEndpoint` option instead.
* @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 string|array|FetchAuthTokenInterface|CredentialsWrapper $credentials
* The credentials to be used by the client to authorize API calls. This option
* accepts either a path to a credentials file, or a decoded credentials file as a
* PHP array.
* *Advanced usage*: In addition, this option can also accept 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.
* @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 $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.
* }
* @throws ValidationException
*/
private function setClientOptions(array $options)
{
// serviceAddress is now deprecated and acts as an alias for apiEndpoint
if (isset($options['serviceAddress'])) {
$options['apiEndpoint'] = $this->pluck('serviceAddress', $options, false);
}
$this->validateNotNull($options, [
'apiEndpoint',
'serviceName',
'descriptorsConfigPath',
'clientConfig',
'disableRetries',
'credentialsConfig',
'transportConfig',
]);
$this->validate($options, [
'credentials',
'transport',
'gapicVersion',
'libName',
'libVersion',
]);
$clientConfig = $options['clientConfig'];
if (is_string($clientConfig)) {
$clientConfig = json_decode(file_get_contents($clientConfig), true);
}
$this->serviceName = $options['serviceName'];
$this->retrySettings = RetrySettings::load(
$this->serviceName,
$clientConfig,
$options['disableRetries']
);
$this->agentHeader = AgentHeader::buildAgentHeader(
$this->pluckArray([
'libName',
'libVersion',
'gapicVersion'
], $options)
);
self::validateFileExists($options['descriptorsConfigPath']);
$descriptors = require($options['descriptorsConfigPath']);
$this->descriptors = $descriptors['interfaces'][$this->serviceName];
$this->credentialsWrapper = $this->createCredentialsWrapper(
$options['credentials'],
$options['credentialsConfig']
);
$transport = $options['transport'] ?: self::defaultTransport();
$this->transport = $transport instanceof TransportInterface
? $transport
: $this->createTransport($options['apiEndpoint'], $transport, $options['transportConfig']);
}
/**
* @param mixed $credentials
* @param array $credentialsConfig
* @return CredentialsWrapper
* @throws ValidationException
*/
private function createCredentialsWrapper($credentials, array $credentialsConfig)
{
if (is_null($credentials)) {
return CredentialsWrapper::build($credentialsConfig);
} elseif (is_string($credentials) || is_array($credentials)) {
return CredentialsWrapper::build(['keyFile' => $credentials] + $credentialsConfig);
} elseif ($credentials instanceof FetchAuthTokenInterface) {
$authHttpHandler = isset($credentialsConfig['authHttpHandler'])
? $credentialsConfig['authHttpHandler']
: null;
return new CredentialsWrapper($credentials, $authHttpHandler);
} elseif ($credentials instanceof CredentialsWrapper) {
return $credentials;
} else {
throw new ValidationException(
'Unexpected value in $auth option, got: ' .
print_r($credentials, true)
);
}
}
/**
* @param string $apiEndpoint
* @param string $transport
* @param array $transportConfig
* @return TransportInterface
* @throws ValidationException
*/
private function createTransport($apiEndpoint, $transport, array $transportConfig)
{
if (!is_string($transport)) {
throw new ValidationException(
"'transport' must be a string, instead got:" .
print_r($transport, true)
);
}
$supportedTransports = self::supportedTransports();
if (!in_array($transport, $supportedTransports)) {
throw new ValidationException(sprintf(
'Unexpected transport option "%s". Supported transports: %s',
$transport,
implode(', ', $supportedTransports)
));
}
$configForSpecifiedTransport = isset($transportConfig[$transport])
? $transportConfig[$transport]
: [];
switch ($transport) {
case 'grpc':
return GrpcTransport::build($apiEndpoint, $configForSpecifiedTransport);
case 'grpc-fallback':
return GrpcFallbackTransport::build($apiEndpoint, $configForSpecifiedTransport);
case 'rest':
if (!isset($configForSpecifiedTransport['restClientConfigPath'])) {
throw new ValidationException(
"The 'restClientConfigPath' config is required for 'rest' transport."
);
}
$restConfigPath = $configForSpecifiedTransport['restClientConfigPath'];
return RestTransport::build($apiEndpoint, $restConfigPath, $configForSpecifiedTransport);
default:
throw new ValidationException(
"Unexpected 'transport' option: $transport. " .
"Supported values: ['grpc', 'rest', 'grpc-fallback']"
);
}
}
/**
* @param array $options
* @return OperationsClient
*/
private function createOperationsClient(array $options)
{
$this->pluckArray([
'serviceName',
'clientConfig',
'descriptorsConfigPath',
], $options);
return $this->pluck('operationsClient', $options, false)
?: new OperationsClient($options);
}
/**
* @return string
*/
private static function defaultTransport()
{
return self::getGrpcDependencyStatus()
? 'grpc'
: 'rest';
}
/**
* @param string $methodName
* @param string $decodeType
* @param array $optionalArgs {
* Call Options
*
* @type array $headers [optional] key-value array containing headers
* @type int $timeoutMillis [optional] the timeout in milliseconds for the call
* @type array $transportOptions [optional] transport-specific call options
* @type RetrySettings|array $retrySettings [optional] A retry settings
* override for the call.
* }
* @param Message $request
* @param int $callType
* @param string $interfaceName
*
* @return PromiseInterface|BidiStream|ClientStream|ServerStream
*/
private function startCall(
$methodName,
$decodeType,
array $optionalArgs = [],
Message $request = null,
$callType = Call::UNARY_CALL,
$interfaceName = null
) {
$callStack = $this->createCallStack(
$this->configureCallConstructionOptions($methodName, $optionalArgs)
);
$descriptor = isset($this->descriptors[$methodName]['grpcStreaming'])
? $this->descriptors[$methodName]['grpcStreaming']
: null;
$call = new Call(
$this->buildMethod($interfaceName, $methodName),
$decodeType,
$request,
$descriptor,
$callType
);
switch ($callType) {
case Call::UNARY_CALL:
$this->modifyUnaryCallable($callStack);
break;
case Call::BIDI_STREAMING_CALL:
case Call::CLIENT_STREAMING_CALL:
case Call::SERVER_STREAMING_CALL:
$this->modifyStreamingCallable($callStack);
break;
}
return $callStack($call, $optionalArgs + array_filter([
'audience' => self::getDefaultAudience()
]));
}
/**
* @param array $callConstructionOptions {
* Call Construction Options
*
* @type RetrySettings $retrySettings [optional] A retry settings override
* For the call.
* }
*
* @return callable
*/
private function createCallStack(array $callConstructionOptions)
{
$quotaProject = $this->credentialsWrapper->getQuotaProject();
$fixedHeaders = $this->agentHeader;
if ($quotaProject) {
$fixedHeaders += [
'X-Goog-User-Project' => [$quotaProject]
];
}
$callStack = function (Call $call, array $options) {
$startCallMethod = $this->transportCallMethods[$call->getCallType()];
return $this->transport->$startCallMethod($call, $options);
};
$callStack = new CredentialsWrapperMiddleware($callStack, $this->credentialsWrapper);
$callStack = new FixedHeaderMiddleware($callStack, $fixedHeaders, true);
$callStack = new RetryMiddleware($callStack, $callConstructionOptions['retrySettings']);
$callStack = new OptionsFilterMiddleware($callStack, [
'headers',
'timeoutMillis',
'transportOptions',
'metadataCallback',
'audience',
]);
return $callStack;
}
/**
* @param string $methodName
* @param array $optionalArgs {
* Optional arguments
*
* @type RetrySettings|array $retrySettings [optional] A retry settings
* override for the call.
* }
*
* @return array
*/
private function configureCallConstructionOptions($methodName, array $optionalArgs)
{
$retrySettings = $this->retrySettings[$methodName];
// Allow for retry settings to be changed at call time
if (isset($optionalArgs['retrySettings'])) {
if ($optionalArgs['retrySettings'] instanceof RetrySettings) {
$retrySettings = $optionalArgs['retrySettings'];
} else {
$retrySettings = $retrySettings->with(
$optionalArgs['retrySettings']
);
}
}
return [
'retrySettings' => $retrySettings,
];
}
/**
* @param string $methodName
* @param array $optionalArgs {
* Call Options
*
* @type array $headers [optional] key-value array containing headers
* @type int $timeoutMillis [optional] the timeout in milliseconds for the call
* @type array $transportOptions [optional] transport-specific call options
* }
* @param Message $request
* @param OperationsClient $client
* @param string $interfaceName
*
* @return PromiseInterface
*/
private function startOperationsCall(
$methodName,
array $optionalArgs,
Message $request,
OperationsClient $client,
$interfaceName = null
) {
$callStack = $this->createCallStack(
$this->configureCallConstructionOptions($methodName, $optionalArgs)
);
$descriptor = $this->descriptors[$methodName]['longRunning'];
$callStack = new OperationsMiddleware($callStack, $client, $descriptor);
$call = new Call(
$this->buildMethod($interfaceName, $methodName),
Operation::class,
$request,
[],
Call::UNARY_CALL
);
$this->modifyUnaryCallable($callStack);
return $callStack($call, $optionalArgs + array_filter([
'audience' => self::getDefaultAudience()
]));
}
/**
* @param string $methodName
* @param array $optionalArgs
* @param string $decodeType
* @param Message $request
* @param string $interfaceName
*
* @return PagedListResponse
*/
private function getPagedListResponse(
$methodName,
array $optionalArgs,
$decodeType,
Message $request,
$interfaceName = null
) {
$callStack = $this->createCallStack(
$this->configureCallConstructionOptions($methodName, $optionalArgs)
);
$descriptor = new PageStreamingDescriptor(
$this->descriptors[$methodName]['pageStreaming']
);
$callStack = new PagedMiddleware($callStack, $descriptor);
$call = new Call(
$this->buildMethod($interfaceName, $methodName),
$decodeType,
$request,
[],
Call::UNARY_CALL
);
$this->modifyUnaryCallable($callStack);
return $callStack($call, $optionalArgs + array_filter([
'audience' => self::getDefaultAudience()
]))->wait();
}
/**
* @param string $interfaceName
* @param string $methodName
*
* @return string
*/
private function buildMethod($interfaceName, $methodName)
{
return sprintf(
'%s/%s',
$interfaceName ?: $this->serviceName,
$methodName
);
}
/**
* The SERVICE_ADDRESS constant is set by GAPIC clients
*/
private static function getDefaultAudience()
{
if (!defined('self::SERVICE_ADDRESS')) {
return null;
}
return 'https://' . self::SERVICE_ADDRESS . '/';
}
/**
* 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
*/
protected function modifyClientOptions(array &$options)
{
// Do nothing - this method exists to allow option modification by partial veneers.
}
/**
* Modify the unary callable.
*
* @param callable $callable
* @access private
*/
protected function modifyUnaryCallable(callable &$callable)
{
// Do nothing - this method exists to allow callable modification by partial veneers.
}
/**
* Modify the streaming callable.
*
* @param callable $callable
* @access private
*/
protected function modifyStreamingCallable(callable &$callable)
{
// Do nothing - this method exists to allow callable modification by partial veneers.
}
}
src/GrpcSupportTrait.php 0000644 00000004352 15174274026 0011354 0 ustar 00 'my-service-address',
* 'scopes' => ['my-service-scope'],
* ];
* $operationsClient = new OperationsClient($options);
* try {
* $name = '';
* $response = $operationsClient->getOperation($name);
* } finally {
* if (isset($operationsClient)) {
* $operationsClient->close();
* }
* }
* ```
*
* @experimental
*/
class OperationsGapicClient
{
use GapicClientTrait;
/**
* The name of the service.
*/
const SERVICE_NAME = 'google.longrunning.Operations';
/**
* The default port of the service.
*/
const DEFAULT_SERVICE_PORT = 443;
/**
* The name of the code generator, to be included in the agent header.
*/
const CODEGEN_NAME = 'gapic';
private static function getClientDefaults()
{
return [
'serviceName' => self::SERVICE_NAME,
'clientConfig' => __DIR__.'/../resources/operations_client_config.json',
'descriptorsConfigPath' => __DIR__.'/../resources/operations_descriptor_config.php',
'credentialsConfig' => [
],
'transportConfig' => [
'rest' => [
'restClientConfigPath' => __DIR__.'/../resources/operations_rest_client_config.php'
],
],
];
}
/**
* Constructor.
*
* @param array $options {
* Required. Options for configuring the service API wrapper. Those options
* that must be provided are marked as Required.
*
* @type string $apiEndpoint
* Required. The address of the API remote host. May optionally include the port,
* formatted as ":".
* @type string|array $credentials
* The credentials to be used by the client to authorize API calls. This option
* accepts either a path to a credentials file, or a decoded credentials file as a
* PHP array.
* *Advanced usage*: In addition, this option can also accept a pre-constructed
* \Google\Auth\FetchAuthTokenInterface object or \Google\ApiCore\CredentialsWrapper
* object. Note that when one of these objects are provided, any settings in
* $credentialsConfig will be ignored.
* @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 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 string $transport
* The transport used for executing network requests. May be either the string `rest`
* or `grpc`. 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' => [...]
* ];
* See the GrpcTransport::build and RestTransport::build methods for the supported
* options.
* }
* @experimental
*/
public function __construct($options = [])
{
$clientOptions = $this->buildClientOptions($options);
$this->setClientOptions($clientOptions);
}
/**
* Gets the latest state of a long-running operation. Clients can use this
* method to poll the operation result at intervals as recommended by the API
* service.
*
* Sample code:
* ```
* $options = [
* 'apiEndpoint' => 'my-service-address',
* 'scopes' => ['my-service-scope'],
* ];
* $operationsClient = new OperationsClient($options);
* try {
* $name = '';
* $response = $operationsClient->getOperation($name);
* } finally {
* if (isset($operationsClient)) {
* $operationsClient->close();
* }
* }
* ```
*
* @param string $name The name of the operation resource.
* @param array $optionalArgs {
* Optional.
*
* @type RetrySettings|array $retrySettings
* Retry settings to use for this call. Can be a
* {@see Google\ApiCore\RetrySettings} object, or an associative array
* of retry settings parameters. See the documentation on
* {@see Google\ApiCore\RetrySettings} for example usage.
* }
*
* @return \Google\LongRunning\Operation
*
* @throws ApiException if the remote call fails
* @experimental
*/
public function getOperation($name, $optionalArgs = [])
{
$request = new GetOperationRequest();
$request->setName($name);
return $this->startCall(
'GetOperation',
Operation::class,
$optionalArgs,
$request
)->wait();
}
/**
* Lists operations that match the specified filter in the request. If the
* server doesn't support this method, it returns `UNIMPLEMENTED`.
*
* NOTE: the `name` binding below allows API services to override the binding
* to use different resource name schemes, such as `users/*/operations`.
*
* Sample code:
* ```
* $options = [
* 'apiEndpoint' => 'my-service-address',
* 'scopes' => ['my-service-scope'],
* ];
* $operationsClient = new OperationsClient($options);
* try {
* $name = '';
* $filter = '';
* // Iterate through all elements
* $pagedResponse = $operationsClient->listOperations($name, $filter);
* foreach ($pagedResponse->iterateAllElements() as $element) {
* // doSomethingWith($element);
* }
*
* // OR iterate over pages of elements
* $pagedResponse = $operationsClient->listOperations($name, $filter);
* foreach ($pagedResponse->iteratePages() as $page) {
* foreach ($page as $element) {
* // doSomethingWith($element);
* }
* }
* } finally {
* if (isset($operationsClient)) {
* $operationsClient->close();
* }
* }
* ```
*
* @param string $name The name of the operation collection.
* @param string $filter The standard list filter.
* @param array $optionalArgs {
* Optional.
*
* @type int $pageSize
* The maximum number of resources contained in the underlying API
* response. The API may return fewer values in a page, even if
* there are additional values to be retrieved.
* @type string $pageToken
* A page token is used to specify a page of values to be returned.
* If no page token is specified (the default), the first page
* of values will be returned. Any page token used here must have
* been generated by a previous call to the API.
* @type RetrySettings|array $retrySettings
* Retry settings to use for this call. Can be a
* {@see Google\ApiCore\RetrySettings} object, or an associative array
* of retry settings parameters. See the documentation on
* {@see Google\ApiCore\RetrySettings} for example usage.
* }
*
* @return \Google\ApiCore\PagedListResponse
*
* @throws ApiException if the remote call fails
* @experimental
*/
public function listOperations($name, $filter, $optionalArgs = [])
{
$request = new ListOperationsRequest();
$request->setName($name);
$request->setFilter($filter);
if (isset($optionalArgs['pageSize'])) {
$request->setPageSize($optionalArgs['pageSize']);
}
if (isset($optionalArgs['pageToken'])) {
$request->setPageToken($optionalArgs['pageToken']);
}
return $this->getPagedListResponse(
'ListOperations',
$optionalArgs,
ListOperationsResponse::class,
$request
);
}
/**
* 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 returns
* `google.rpc.Code.UNIMPLEMENTED`. Clients can use
* [Operations.GetOperation][google.longrunning.Operations.GetOperation] or
* other 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
* an [Operation.error][google.longrunning.Operation.error] value with a [google.rpc.Status.code][google.rpc.Status.code] of 1,
* corresponding to `Code.CANCELLED`.
*
* Sample code:
* ```
* $options = [
* 'apiEndpoint' => 'my-service-address',
* 'scopes' => ['my-service-scope'],
* ];
* $operationsClient = new OperationsClient($options);
* try {
* $name = '';
* $operationsClient->cancelOperation($name);
* } finally {
* if (isset($operationsClient)) {
* $operationsClient->close();
* }
* }
* ```
*
* @param string $name The name of the operation resource to be cancelled.
* @param array $optionalArgs {
* Optional.
*
* @type RetrySettings|array $retrySettings
* Retry settings to use for this call. Can be a
* {@see Google\ApiCore\RetrySettings} object, or an associative array
* of retry settings parameters. See the documentation on
* {@see Google\ApiCore\RetrySettings} for example usage.
* }
*
* @throws ApiException if the remote call fails
* @experimental
*/
public function cancelOperation($name, $optionalArgs = [])
{
$request = new CancelOperationRequest();
$request->setName($name);
return $this->startCall(
'CancelOperation',
GPBEmpty::class,
$optionalArgs,
$request
)->wait();
}
/**
* Deletes a long-running 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 returns
* `google.rpc.Code.UNIMPLEMENTED`.
*
* Sample code:
* ```
* $options = [
* 'apiEndpoint' => 'my-service-address',
* 'scopes' => ['my-service-scope'],
* ];
* $operationsClient = new OperationsClient($options);
* try {
* $name = '';
* $operationsClient->deleteOperation($name);
* } finally {
* if (isset($operationsClient)) {
* $operationsClient->close();
* }
* }
* ```
*
* @param string $name The name of the operation resource to be deleted.
* @param array $optionalArgs {
* Optional.
*
* @type RetrySettings|array $retrySettings
* Retry settings to use for this call. Can be a
* {@see Google\ApiCore\RetrySettings} object, or an associative array
* of retry settings parameters. See the documentation on
* {@see Google\ApiCore\RetrySettings} for example usage.
* }
*
* @throws ApiException if the remote call fails
* @experimental
*/
public function deleteOperation($name, $optionalArgs = [])
{
$request = new DeleteOperationRequest();
$request->setName($name);
return $this->startCall(
'DeleteOperation',
GPBEmpty::class,
$optionalArgs,
$request
)->wait();
}
}
src/LongRunning/OperationsClient.php 0000644 00000004436 15174274026 0013605 0 ustar 00 [
'google.longrunning.Operations' => [
'ListOperations' => [
'pageStreaming' => [
'requestPageTokenGetMethod' => 'getPageToken',
'requestPageTokenSetMethod' => 'setPageToken',
'requestPageSizeGetMethod' => 'getPageSize',
'requestPageSizeSetMethod' => 'setPageSize',
'responsePageTokenGetMethod' => 'getNextPageToken',
'resourcesGetMethod' => 'getOperations',
],
],
],
],
];
src/LongRunning/resources/operations_rest_client_config.php 0000644 00000003057 15174274026 0020436 0 ustar 00 [
'google.longrunning.Operations' => [
'GetOperation' => [
'method' => 'get',
'uriTemplate' => '/v1/{name=operations/**}',
'placeholders' => [
'name' => [
'getters' => [
'getName',
],
],
],
],
'ListOperations' => [
'method' => 'get',
'uriTemplate' => '/v1/{name=operations}',
'placeholders' => [
'name' => [
'getters' => [
'getName',
],
],
],
],
'CancelOperation' => [
'method' => 'post',
'uriTemplate' => '/v1/{name=operations/**}:cancel',
'body' => '*',
'placeholders' => [
'name' => [
'getters' => [
'getName',
],
],
],
],
'DeleteOperation' => [
'method' => 'delete',
'uriTemplate' => '/v1/{name=operations/**}',
'placeholders' => [
'name' => [
'getters' => [
'getName',
],
],
],
],
],
],
];
src/Middleware/CredentialsWrapperMiddleware.php 0000644 00000004500 15174274026 0015724 0 ustar 00 nextHandler = $nextHandler;
$this->credentialsWrapper = $credentialsWrapper;
}
public function __invoke(Call $call, array $options)
{
$next = $this->nextHandler;
return $next(
$call,
$options + ['credentialsWrapper' => $this->credentialsWrapper]
);
}
}
src/Middleware/FixedHeaderMiddleware.php 0000644 00000004753 15174274026 0014310 0 ustar 00 nextHandler = $nextHandler;
$this->headers = $headers;
$this->overrideUserHeaders = $overrideUserHeaders;
}
public function __invoke(Call $call, array $options)
{
$userHeaders = isset($options['headers']) ? $options['headers'] : [];
if ($this->overrideUserHeaders) {
$options['headers'] = $this->headers + $userHeaders;
} else {
$options['headers'] = $userHeaders + $this->headers;
}
$next = $this->nextHandler;
return $next(
$call,
$options
);
}
}
src/Middleware/OperationsMiddleware.php 0000644 00000005213 15174274026 0014253 0 ustar 00 nextHandler = $nextHandler;
$this->client = $client;
$this->descriptor = $descriptor;
}
public function __invoke(Call $call, array $options)
{
$next = $this->nextHandler;
return $next(
$call,
$options
)->then(function (Operation $response) {
$options = $this->descriptor + [
'lastProtoResponse' => $response
];
return new OperationResponse($response->getName(), $this->client, $options);
});
}
}
src/Middleware/OptionsFilterMiddleware.php 0000644 00000004457 15174274026 0014742 0 ustar 00 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
);
}
}
src/Middleware/PagedMiddleware.php 0000644 00000005455 15174274026 0013160 0 ustar 00 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);
}
);
}
}
src/Middleware/ResponseMetadataMiddleware.php 0000644 00000005211 15174274026 0015365 0 ustar 00 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, []];
}
}
);
}
}
src/Middleware/RetryMiddleware.php 0000644 00000013010 15174274026 0013227 0 ustar 00 nextHandler = $nextHandler;
$this->retrySettings = $retrySettings;
$this->deadlineMs = $deadlineMs;
}
/**
* @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();
}
}
// 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) {
if (!$e instanceof ApiException) {
throw $e;
}
if (!in_array($e->getStatus(), $this->retrySettings->getRetryableCodes())) {
throw $e;
}
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, $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
);
}
// Don't sleep if the failure was a timeout
if ($status != ApiStatus::DEADLINE_EXCEEDED) {
usleep($delayMs * 1000);
}
$delayMs = min($delayMs * $delayMult, $maxDelayMs);
$timeoutMs = min(
$timeoutMs * $timeoutMult,
$maxTimeoutMs,
$deadlineMs - $this->getCurrentTimeMs()
);
$nextHandler = new RetryMiddleware(
$this->nextHandler,
$this->retrySettings->with([
'initialRetryDelayMillis' => $delayMs,
]),
$deadlineMs
);
// Set the timeout for the call
$options['timeoutMillis'] = $timeoutMs;
return $nextHandler(
$call,
$options
);
}
protected function getCurrentTimeMs()
{
return microtime(true) * 1000.0;
}
}
src/OperationResponse.php 0000644 00000031320 15174274026 0011532 0 ustar 00 self::DEFAULT_POLLING_INTERVAL,
'pollDelayMultiplier' => self::DEFAULT_POLLING_MULTIPLIER,
'maxPollDelayMillis' => self::DEFAULT_MAX_POLLING_INTERVAL,
'totalPollTimeoutMillis' => self::DEFAULT_MAX_POLLING_DURATION,
];
private $lastProtoResponse;
private $deleted = false;
/**
* OperationResponse constructor.
*
* @param string $operationName
* @param OperationsClient $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 Operation $lastProtoResponse A response already received from the server.
* }
*/
public function __construct($operationName, $operationsClient, $options = [])
{
$this->operationName = $operationName;
$this->operationsClient = $operationsClient;
if (isset($options['operationReturnType'])) {
$this->operationReturnType = $options['operationReturnType'];
}
if (isset($options['metadataReturnType'])) {
$this->metadataReturnType = $options['metadataReturnType'];
}
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'];
}
if (isset($options['lastProtoResponse'])) {
$this->lastProtoResponse = $options['lastProtoResponse'];
}
}
/**
* Check whether the operation has completed.
*
* @return bool
*/
public function isDone()
{
return (is_null($this->lastProtoResponse) || is_null($this->lastProtoResponse->getDone()))
? false
: $this->lastProtoResponse->getDone();
}
/**
* Check whether the operation completed successfully. If the operation is not complete, or if the operation
* failed, return false.
*
* @return bool
*/
public function operationSucceeded()
{
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 !is_null($this->getError());
}
/**
* 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($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");
}
$name = $this->getName();
$this->lastProtoResponse = $this->operationsClient->getOperation($name);
}
/**
* Return the result of the operation. If operationSucceeded() is false, return null.
*
* @return mixed|null The result of the operation, or null if operationSucceeded() is false
*/
public function getResult()
{
if (!$this->isDone() || is_null($this->lastProtoResponse->getResponse())) {
return null;
}
/** @var Any $anyResponse */
$anyResponse = $this->lastProtoResponse->getResponse();
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->isDone() || is_null($this->lastProtoResponse->getError())) {
return null;
}
return $this->lastProtoResponse->getError();
}
/**
* 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|null The last Operation object received from the server.
*/
public function getLastProtoResponse()
{
return $this->lastProtoResponse;
}
/**
* @return OperationsClient The OperationsClient object used to make
* requests to the operations API.
*/
public function getOperationsClient()
{
return $this->operationsClient;
}
/**
* 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.
*/
public function cancel()
{
$this->operationsClient->cancelOperation($this->getName());
}
/**
* Delete the long-running 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.
*/
public function delete()
{
$this->operationsClient->deleteOperation($this->getName());
$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 (is_null($this->lastProtoResponse)) {
return null;
}
/** @var Any $any */
$any = $this->lastProtoResponse->getMetadata();
if (is_null($this->metadataReturnType)) {
return $any;
}
if (is_null($any) || is_null($any->getValue())) {
return null;
}
$metadataReturnType = $this->metadataReturnType;
/** @var Message $metadata */
$metadata = new $metadataReturnType();
$metadata->mergeFromString($any->getValue());
return $metadata;
}
}
src/Page.php 0000644 00000021742 15174274026 0006736 0 ustar 00 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($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
*/
public function getIterator()
{
$resourcesGetMethod = $this->pageStreamingDescriptor->getResourcesGetMethod();
foreach ($this->getResponseObject()->$resourcesGetMethod() as $element) {
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|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 $collectionSize int
* @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);
}
}
src/PageStreamingDescriptor.php 0000644 00000014224 15174274026 0012644 0 ustar 00 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($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($field)
{
return 'get' . ucfirst($field);
}
private static function setMethod($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($descriptor)
{
$requiredFields = [
'requestPageTokenGetMethod',
'requestPageTokenSetMethod',
'responsePageTokenGetMethod',
'resourcesGetMethod',
];
foreach ($requiredFields as $field) {
if (empty($descriptor[$field])) {
throw new InvalidArgumentException(
"$field is required for PageStreamingDescriptor"
);
}
}
}
}
src/PagedListResponse.php 0000644 00000015347 15174274026 0011461 0 ustar 00 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
*/
public function getIterator()
{
foreach ($this->iteratePages() as $page) {
foreach ($page as $element) {
yield $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 $collectionSize int
* @throws ValidationException if a FixedSizeCollection of the specified size cannot be constructed
* @return FixedSizeCollection
*/
public function expandToFixedSizeCollection($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 $collectionSize int
* @throws ValidationException if a FixedSizeCollection of the specified size cannot be constructed
* @return Generator|FixedSizeCollection[]
*/
public function iterateFixedSizeCollections($collectionSize)
{
return $this->expandToFixedSizeCollection($collectionSize)->iterateCollections();
}
}
src/PathTemplate.php 0000644 00000010037 15174274026 0010445 0 ustar 00 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($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($path)
{
return $this->resourceTemplate->match($path);
}
}
src/PollingTrait.php 0000644 00000007173 15174274026 0010474 0 ustar 00 0.0;
$endTime = $this->getCurrentTimeMillis() + $totalPollTimeoutMillis;
while (true) {
if ($hasTotalPollTimeout && $this->getCurrentTimeMillis() > $endTime) {
return false;
}
$this->sleepMillis($currentPollDelayMillis);
if ($pollCallable()) {
return true;
}
$currentPollDelayMillis = 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 float $millis
*/
protected function sleepMillis($millis)
{
usleep($millis * 1000);
}
}
src/RequestBuilder.php 0000644 00000017407 15174274026 0011024 0 ustar 00 baseUri = $baseUri;
$this->restConfig = require($restConfigPath);
}
/**
* @param string $path
* @param Message $message
* @param array $headers
* @return RequestInterface
* @throws ValidationException
*/
public function build($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."
);
}
$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);
$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($config)
{
$configs = [$config];
if ($config['additionalBindings']) {
foreach ($config['additionalBindings'] as $additionalBinding) {
$configs[] = $additionalBinding + $config;
}
}
return $configs;
}
/**
* @param $message
* @param $config
* @return array Tuple [$body, $queryParams]
*/
private function constructBodyAndQueryParameters(Message $message, $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;
}
}
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) {
if ($result) {
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 $uriTemplate
* @param array $bindings
* @return null|string
* @throws ValidationException
*/
private function tryRenderPathTemplate($uriTemplate, array $bindings)
{
$template = new AbsoluteResourceTemplate($uriTemplate);
try {
return $template->render($bindings);
} catch (ValidationException $e) {
return null;
}
}
/**
* @param $path
* @param $queryParams
* @return UriInterface
*/
private function buildUri($path, $queryParams)
{
$uri = Psr7\uri_for(
sprintf(
'https://%s%s',
$this->baseUri,
$path
)
);
if ($queryParams) {
$uri = $this->buildUriWithQuery(
$uri,
$queryParams
);
}
return $uri;
}
}
src/RequestParamsHeaderDescriptor.php 0000644 00000005533 15174274026 0014026 0 ustar 00 value].
*/
public function __construct($requestParams)
{
$headerKey = self::HEADER_KEY;
$headerValue = '';
foreach ($requestParams as $key => $value) {
if ('' !== $headerValue) {
$headerValue .= '&';
}
$headerValue .= $key . '=' . strval($value);
}
// If the value contains non-ASCII characters, suffix the header key with `-bin`.
// see https://grpc.github.io/grpc/python/glossary.html#term-metadata
if (preg_match('/[^\x00-\x7F]/', $headerValue) !== 0) {
$headerKey = $headerKey . '-bin';
}
$this->header = [$headerKey => [$headerValue]];
}
/**
* Returns an associative array that contains request params header metadata.
*
* @return array
*/
public function getHeader()
{
return $this->header;
}
}
src/ResourceTemplate/AbsoluteResourceTemplate.php 0000644 00000011661 15174274026 0016326 0 ustar 00 ").
*
* 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.
*/
class AbsoluteResourceTemplate implements ResourceTemplateInterface
{
/** @var RelativeResourceTemplate */
private $resourceTemplate;
/** @var string */
private $verb;
/**
* AbsoluteResourceTemplate constructor.
* @param string $path
* @throws ValidationException
*/
public function __construct($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($path)
{
try {
$this->match($path);
return true;
} catch (ValidationException $ex) {
return false;
}
}
/**
* @inheritdoc
*/
public function match($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($path, $reason)
{
return new ValidationException("Could not match path '$path' to template '$this': $reason");
}
private function renderVerb()
{
return $this->verb ? ':' . $this->verb : '';
}
private function verbSeparatorPos($path)
{
$finalSeparatorPos = strrpos($path, '/');
$verbSeparatorPos = strrpos($path, ':', $finalSeparatorPos);
if ($verbSeparatorPos === false) {
$verbSeparatorPos = strlen($path);
}
return $verbSeparatorPos;
}
}
src/ResourceTemplate/Parser.php 0000644 00000021131 15174274026 0012571 0 ustar 00 = 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($segmentString, $path, $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($segmentStringWithoutBraces, $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($literal, $path, &$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($path, $index, $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($literal)
{
return preg_match("/^[0-9a-zA-Z\\.\\-~_]+$/", $literal) === 1;
}
}
src/ResourceTemplate/RelativeResourceTemplate.php 0000644 00000037273 15174274026 0016332 0 ustar 00 ").
*
* 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.
*/
class RelativeResourceTemplate implements ResourceTemplateInterface
{
/** @var Segment[] $segments */
private $segments;
/**
* RelativeResourceTemplate constructor.
*
* @param string $path
* @throws ValidationException
*/
public function __construct($path)
{
if (empty($path)) {
$msg = sprintf(
"Cannot construct RelativeResourceTemplate from %s string",
is_null($path) ? "null" : "empty"
);
throw new ValidationException($msg);
}
$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 ($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($path)
{
try {
$this->match($path);
return true;
} catch (ValidationException $ex) {
return false;
}
}
/**
* @inheritdoc
*/
public function match($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($path, $reason)
{
return new ValidationException("Could not match path '$path' to template '$this': $reason");
}
private function renderingException($bindings, $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, $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[] 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($segmentsToRender)
{
$renderResult = "";
for ($i = 0; $i < count($segmentsToRender); $i++) {
$segment = $segmentsToRender[$i];
$renderResult .= $segment;
if ($i < count($segmentsToRender) - 1) {
$renderResult .= $segment->getSeparator();
}
}
return $renderResult;
}
}
src/ResourceTemplate/ResourceTemplateInterface.php 0000644 00000007132 15174274026 0016446 0 ustar 00 "). (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.
*/
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($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($path);
}
src/ResourceTemplate/Segment.php 0000644 00000013506 15174274026 0012746 0 ustar 00 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($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 $binding
* @return bool
*/
private static function isValidBinding($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 $binding
* @return bool
*/
private static function isValidDoubleWildcardBinding($binding)
{
return preg_match("-^.+$-", $binding) === 1;
}
}
src/RetrySettings.php 0000644 00000044540 15174274026 0010711 0 ustar 00 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://googleapis.github.io/google-cloud-php/#/docs/google-cloud/monitoring/v3/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://googleapis.github.io/google-cloud-php/#/docs/google-cloud/monitoring/v3/groupserviceclient?method=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://googleapis.github.io/google-cloud-php/#/docs/google-cloud/monitoring/v3/groupserviceclient?method=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://googleapis.github.io/google-cloud-php/#/docs/google-cloud/monitoring/v3/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.
*
* 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
* ],
* ]);
* ```
*/
class RetrySettings
{
use ValidationTrait;
private $retriesEnabled;
private $retryableCodes;
private $initialRetryDelayMillis;
private $retryDelayMultiplier;
private $maxRetryDelayMillis;
private $initialRpcTimeoutMillis;
private $rpcTimeoutMultiplier;
private $maxRpcTimeoutMillis;
private $totalTimeoutMillis;
private $noRetriesRpcTimeoutMillis;
/**
* 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.
* }
*/
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;
}
/**
* 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(
$serviceName,
$clientConfig,
$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' => []]);
}
/**
* 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(),
];
return new RetrySettings($settings + $existingSettings);
}
/**
* @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;
}
private static function convertArrayFromSnakeCase($settings)
{
$camelCaseSettings = [];
foreach ($settings as $key => $value) {
$camelCaseKey = str_replace(' ', '', ucwords(str_replace('_', ' ', $key)));
$camelCaseSettings[lcfirst($camelCaseKey)] = $value;
}
return $camelCaseSettings;
}
}
src/Serializer.php 0000644 00000041606 15174274026 0010174 0 ustar 00 \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.help-bin' => \Google\Rpc\Help::class,
'google.rpc.localizedmessage-bin' => \Google\Rpc\LocalizedMessage::class,
];
private $fieldTransformers;
private $messageTypeTransformers;
private $decodeFieldTransformers;
private $decodeMessageTypeTransformers;
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 = []
) {
$this->fieldTransformers = $fieldTransformers;
$this->messageTypeTransformers = $messageTypeTransformers;
$this->decodeFieldTransformers = $decodeFieldTransformers;
$this->decodeMessageTypeTransformers = $decodeMessageTypeTransformers;
}
/**
* Encode protobuf message as a PHP array
*
* @param mixed $message
* @return array
* @throws ValidationException
*/
public function encodeMessage($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, $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)
{
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)
{
return self::getPhpArraySerializer()->encodeMessage($message);
}
/**
* Decode metadata received from gRPC status object
*
* @param array $metadata
* @return array
*/
public static function decodeMetadata($metadata)
{
if (is_null($metadata) || 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' => '',
];
}
} else {
// The metadata contains an unexpected binary type
$decodedValue += [
'data' => '',
];
}
} else {
$decodedValue += [
'data' => $value,
];
}
$result[] = $decodedValue;
}
}
return $result;
}
/**
* Decode an array of Any messages into a printable PHP array.
*
* @param $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' => '',
];
}
}
return $results;
}
/**
* @param FieldDescriptor $field
* @param $data
* @return mixed array
* @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, Descriptor $messageType)
{
$data = [];
$fieldCount = $messageType->getFieldCount();
for ($i = 0; $i < $fieldCount; $i++) {
$field = $messageType->getField($i);
$key = $field->getName();
$getter = $this->getGetter($key);
$v = $message->$getter();
if (is_null($v)) {
continue;
}
// Check and skip unset fields inside oneofs
list($_, $fieldsToOneof) = $this->getDescriptorMaps($messageType);
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 ($field->getLabel() === GPBLabel::REPEATED) {
$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, Descriptor $messageType, $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 $field FieldDescriptor */
$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 ($field->getLabel() === GPBLabel::REPEATED) {
$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 string $name
* @return string Getter function
*/
public static function getGetter($name)
{
return 'get' . ucfirst(self::toCamelCase($name));
}
/**
* @param string $name
* @return string Setter function
*/
public static function getSetter($name)
{
return 'set' . ucfirst(self::toCamelCase($name));
}
/**
* Convert string from camelCase to snake_case
*
* @param string $key
* @return string
*/
public static function toSnakeCase($key)
{
return strtolower(preg_replace(['/([a-z\d])([A-Z])/', '/([^_])([A-Z][a-z])/'], '$1_$2', $key));
}
/**
* Convert string from snake_case to camelCase
*
* @param string $key
* @return string
*/
public static function toCamelCase($key)
{
return lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', $key))));
}
private static function hasBinaryHeaderSuffix($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
src/ServerStream.php 0000644 00000006451 15174274026 0010504 0 ustar 00 call = $serverStreamingCall;
if (array_key_exists('resourcesGetMethod', $streamingDescriptor)) {
$this->resourcesGetMethod = $streamingDescriptor['resourcesGetMethod'];
}
}
/**
* 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|mixed
*/
public function readAll()
{
$resourcesGetMethod = $this->resourcesGetMethod;
if (!is_null($resourcesGetMethod)) {
foreach ($this->call->responses() as $response) {
foreach ($response->$resourcesGetMethod() as $resource) {
yield $resource;
}
}
} else {
foreach ($this->call->responses() as $response) {
yield $response;
}
}
$status = $this->call->getStatus();
if (!($status->code == Code::OK)) {
throw ApiException::createFromStdClass($status);
}
}
/**
* Return the underlying gRPC call object
*
* @return \Grpc\ServerStreamingCall|mixed
*/
public function getServerStreamingCall()
{
return $this->call;
}
}
src/ServiceAddressTrait.php 0000644 00000004725 15174274026 0011776 0 ustar 00 assertEquals($expected, $actual);
}
$this->assertSame(count($expected), count($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);
}
}
}
}
private function getValues($field)
{
return array_values(
is_array($field)
? $field
: iterator_to_array($field)
);
}
}
src/Testing/MessageAwareArrayComparator.php 0000644 00000001561 15174274026 0015067 0 ustar 00 exporter = new MessageAwareExporter();
}
}
src/Testing/MessageAwareExporter.php 0000644 00000002405 15174274026 0013567 0 ustar 00 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 MockStatus|null|\stdClass
* @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 \Google\Protobuf\Internal\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;
}
}
src/Testing/MockClientStreamingCall.php 0000644 00000010364 15174274026 0014173 0 ustar 00 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 \Google\Protobuf\Internal\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;
}
}
src/Testing/MockGrpcTransport.php 0000644 00000006741 15174274026 0013123 0 ustar 00 mockCall = $mockCall;
$opts = ['credentials' => ChannelCredentials::createSsl()];
parent::__construct('', $opts);
}
protected function _simpleRequest(
$method,
$arguments,
$deserialize,
array $metadata = [],
array $options = []
) {
$this->logCall($method, $deserialize, $metadata, $options, $arguments);
return $this->mockCall;
}
protected function _clientStreamRequest(
$method,
$deserialize,
array $metadata = [],
array $options = []
) {
$this->logCall($method, $deserialize, $metadata, $options);
return $this->mockCall;
}
protected function _serverStreamRequest(
$method,
$arguments,
$deserialize,
array $metadata = [],
array $options = []
) {
$this->logCall($method, $deserialize, $metadata, $options, $arguments);
return $this->mockCall;
}
protected function _bidiRequest(
$method,
$deserialize,
array $metadata = [],
array $options = []
) {
$this->logCall($method, $deserialize, $metadata, $options);
return $this->mockCall;
}
private function logCall(
$method,
$deserialize,
$metadata = [],
$options = [],
$arguments = null
) {
$this->requestArguments = [
'method' => $method,
'arguments' => $arguments,
'deserialize' => $deserialize,
'metadata' => $metadata,
'options' => $options,
];
}
public function getRequestArguments()
{
return $this->requestArguments;
}
}
src/Testing/MockRequest.php 0000644 00000003312 15174274026 0011732 0 ustar 00 google.apicore.testing.MockRequest
*/
class MockRequest extends \Google\Protobuf\Internal\Message
{
/**
* Generated from protobuf field string page_token = 1;
*/
private $page_token = '';
/**
* Generated from protobuf field uint64 page_size = 2;
*/
private $page_size = 0;
public function __construct() {
\GPBMetadata\ApiCore\Testing\Mocks::initOnce();
parent::__construct();
}
/**
* Generated from protobuf field string page_token = 1;
* @return string
*/
public function getPageToken()
{
return $this->page_token;
}
/**
* Generated from protobuf field string page_token = 1;
* @param string $var
* @return $this
*/
public function setPageToken($var)
{
GPBUtil::checkString($var, True);
$this->page_token = $var;
return $this;
}
/**
* Generated from protobuf field uint64 page_size = 2;
* @return int|string
*/
public function getPageSize()
{
return $this->page_size;
}
/**
* Generated from protobuf field uint64 page_size = 2;
* @param int|string $var
* @return $this
*/
public function setPageSize($var)
{
GPBUtil::checkUint64($var);
$this->page_size = $var;
return $this;
}
}
src/Testing/MockRequestBody.php 0000644 00000030452 15174274026 0012555 0 ustar 00 google.apicore.testing.MockRequestBody
*/
class MockRequestBody extends \Google\Protobuf\Internal\Message
{
/**
* Generated from protobuf field string name = 1;
*/
private $name = '';
/**
* Generated from protobuf field uint64 number = 2;
*/
private $number = 0;
/**
* Generated from protobuf field repeated string repeated_field = 3;
*/
private $repeated_field;
/**
* Generated from protobuf field .google.apicore.testing.MockRequestBody nested_message = 4;
*/
private $nested_message = null;
/**
* Generated from protobuf field .google.protobuf.BytesValue bytes_value = 5;
*/
private $bytes_value = null;
/**
* Generated from protobuf field .google.protobuf.Duration duration_value = 6;
*/
private $duration_value = null;
/**
* Generated from protobuf field .google.protobuf.FieldMask field_mask = 7;
*/
private $field_mask = null;
/**
* Generated from protobuf field .google.protobuf.Int64Value int64_value = 8;
*/
private $int64_value = null;
/**
* Generated from protobuf field .google.protobuf.ListValue list_value = 9;
*/
private $list_value = null;
/**
* Generated from protobuf field .google.protobuf.StringValue string_value = 10;
*/
private $string_value = null;
/**
* Generated from protobuf field .google.protobuf.Struct struct_value = 11;
*/
private $struct_value = null;
/**
* Generated from protobuf field .google.protobuf.Timestamp timestamp_value = 12;
*/
private $timestamp_value = null;
/**
* Generated from protobuf field .google.protobuf.Value value_value = 13;
*/
private $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\Internal\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 string name = 1;
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Generated from protobuf field string name = 1;
* @param string $var
* @return $this
*/
public function setName($var)
{
GPBUtil::checkString($var, True);
$this->name = $var;
return $this;
}
/**
* Generated from protobuf field uint64 number = 2;
* @return int|string
*/
public function getNumber()
{
return $this->number;
}
/**
* Generated from protobuf field uint64 number = 2;
* @param int|string $var
* @return $this
*/
public function setNumber($var)
{
GPBUtil::checkUint64($var);
$this->number = $var;
return $this;
}
/**
* Generated from protobuf field repeated string repeated_field = 3;
* @return \Google\Protobuf\Internal\RepeatedField
*/
public function getRepeatedField()
{
return $this->repeated_field;
}
/**
* Generated from protobuf field repeated string repeated_field = 3;
* @param string[]|\Google\Protobuf\Internal\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 .google.apicore.testing.MockRequestBody nested_message = 4;
* @return \Google\ApiCore\Testing\MockRequestBody
*/
public function getNestedMessage()
{
return $this->nested_message;
}
/**
* Generated from protobuf field .google.apicore.testing.MockRequestBody nested_message = 4;
* @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 .google.protobuf.BytesValue bytes_value = 5;
* @return \Google\Protobuf\BytesValue
*/
public function getBytesValue()
{
return $this->bytes_value;
}
/**
* Generated from protobuf field .google.protobuf.BytesValue bytes_value = 5;
* @param \Google\Protobuf\BytesValue $var
* @return $this
*/
public function setBytesValue($var)
{
GPBUtil::checkMessage($var, \Google\Protobuf\BytesValue::class);
$this->bytes_value = $var;
return $this;
}
/**
* Generated from protobuf field .google.protobuf.Duration duration_value = 6;
* @return \Google\Protobuf\Duration
*/
public function getDurationValue()
{
return $this->duration_value;
}
/**
* Generated from protobuf field .google.protobuf.Duration duration_value = 6;
* @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 .google.protobuf.FieldMask field_mask = 7;
* @return \Google\Protobuf\FieldMask
*/
public function getFieldMask()
{
return $this->field_mask;
}
/**
* Generated from protobuf field .google.protobuf.FieldMask field_mask = 7;
* @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 .google.protobuf.Int64Value int64_value = 8;
* @return \Google\Protobuf\Int64Value
*/
public function getInt64Value()
{
return $this->int64_value;
}
/**
* Generated from protobuf field .google.protobuf.Int64Value int64_value = 8;
* @param \Google\Protobuf\Int64Value $var
* @return $this
*/
public function setInt64Value($var)
{
GPBUtil::checkMessage($var, \Google\Protobuf\Int64Value::class);
$this->int64_value = $var;
return $this;
}
/**
* Generated from protobuf field .google.protobuf.ListValue list_value = 9;
* @return \Google\Protobuf\ListValue
*/
public function getListValue()
{
return $this->list_value;
}
/**
* Generated from protobuf field .google.protobuf.ListValue list_value = 9;
* @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 .google.protobuf.StringValue string_value = 10;
* @return \Google\Protobuf\StringValue
*/
public function getStringValue()
{
return $this->string_value;
}
/**
* Generated from protobuf field .google.protobuf.StringValue string_value = 10;
* @param \Google\Protobuf\StringValue $var
* @return $this
*/
public function setStringValue($var)
{
GPBUtil::checkMessage($var, \Google\Protobuf\StringValue::class);
$this->string_value = $var;
return $this;
}
/**
* Generated from protobuf field .google.protobuf.Struct struct_value = 11;
* @return \Google\Protobuf\Struct
*/
public function getStructValue()
{
return $this->struct_value;
}
/**
* Generated from protobuf field .google.protobuf.Struct struct_value = 11;
* @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 .google.protobuf.Timestamp timestamp_value = 12;
* @return \Google\Protobuf\Timestamp
*/
public function getTimestampValue()
{
return $this->timestamp_value;
}
/**
* Generated from protobuf field .google.protobuf.Timestamp timestamp_value = 12;
* @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 .google.protobuf.Value value_value = 13;
* @return \Google\Protobuf\Value
*/
public function getValueValue()
{
return $this->value_value;
}
/**
* Generated from protobuf field .google.protobuf.Value value_value = 13;
* @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 string field_1 = 14;
* @return string
*/
public function getField1()
{
return $this->readOneof(14);
}
/**
* Generated from protobuf field string field_1 = 14;
* @param string $var
* @return $this
*/
public function setField1($var)
{
GPBUtil::checkString($var, True);
$this->writeOneof(14, $var);
return $this;
}
/**
* Generated from protobuf field string field_2 = 15;
* @return string
*/
public function getField2()
{
return $this->readOneof(15);
}
/**
* Generated from protobuf field string field_2 = 15;
* @param string $var
* @return $this
*/
public function setField2($var)
{
GPBUtil::checkString($var, True);
$this->writeOneof(15, $var);
return $this;
}
/**
* Generated from protobuf field string field_3 = 16;
* @return string
*/
public function getField3()
{
return $this->readOneof(16);
}
/**
* Generated from protobuf field string field_3 = 16;
* @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");
}
}
src/Testing/MockResponse.php 0000644 00000006020 15174274026 0012077 0 ustar 00 google.apicore.testing.MockResponse
*/
class MockResponse extends \Google\Protobuf\Internal\Message
{
/**
* Generated from protobuf field string name = 1;
*/
private $name = '';
/**
* Generated from protobuf field uint64 number = 2;
*/
private $number = 0;
/**
* Generated from protobuf field repeated string resources_list = 3;
*/
private $resources_list;
/**
* Generated from protobuf field string next_page_token = 4;
*/
private $next_page_token = '';
public function __construct() {
\GPBMetadata\ApiCore\Testing\Mocks::initOnce();
parent::__construct();
}
/**
* Generated from protobuf field string name = 1;
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Generated from protobuf field string name = 1;
* @param string $var
* @return $this
*/
public function setName($var)
{
GPBUtil::checkString($var, True);
$this->name = $var;
return $this;
}
/**
* Generated from protobuf field uint64 number = 2;
* @return int|string
*/
public function getNumber()
{
return $this->number;
}
/**
* Generated from protobuf field uint64 number = 2;
* @param int|string $var
* @return $this
*/
public function setNumber($var)
{
GPBUtil::checkUint64($var);
$this->number = $var;
return $this;
}
/**
* Generated from protobuf field repeated string resources_list = 3;
* @return \Google\Protobuf\Internal\RepeatedField
*/
public function getResourcesList()
{
return $this->resources_list;
}
/**
* Generated from protobuf field repeated string resources_list = 3;
* @param string[]|\Google\Protobuf\Internal\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 string next_page_token = 4;
* @return string
*/
public function getNextPageToken()
{
return $this->next_page_token;
}
/**
* Generated from protobuf field string next_page_token = 4;
* @param string $var
* @return $this
*/
public function setNextPageToken($var)
{
GPBUtil::checkString($var, True);
$this->next_page_token = $var;
return $this;
}
}
src/Testing/MockServerStreamingCall.php 0000644 00000006346 15174274026 0014230 0 ustar 00 responses = $responses;
$this->deserialize = $deserialize;
if (is_null($status)) {
$status = new MockStatus(Code::OK);
}
$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 MockStatus|null|\stdClass
* @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;
}
}
src/Testing/MockStatus.php 0000644 00000003601 15174274026 0011566 0 ustar 00 code = $code;
$this->details = $details;
$this->metadata = $metadata;
}
}
src/Testing/MockStubTrait.php 0000644 00000025335 15174274026 0012234 0 ustar 00 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 = MockStubTrait::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 = MockStubTrait::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 Status $status
*/
public function addResponse($response, $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 Status $status
*/
public function setStreamingStatus($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 $status
* @param callable $deserialize
* @return static An instance of the current class type.
*/
public static function create($responseObject, $status = null, $deserialize = null)
{
$stub = new static($deserialize);
$stub->addResponse($responseObject, $status);
return $stub;
}
/**
* Creates a sequence such that the responses are returned in order.
* @param mixed[] $sequence
* @param callable $deserialize
* @param Status $finalStatus
* @return static An instance of the current class type.
*/
public static function createWithResponseSequence($sequence, $deserialize = null, $finalStatus = null)
{
$stub = new static($deserialize);
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;
}
}
src/Testing/MockTransport.php 0000644 00000010142 15174274026 0012275 0 ustar 00 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 = call_user_func_array(array($this, '_bidiRequest'), $newArgs);
return new BidiStream($response, $call->getDescriptor());
}
public function startClientStreamingCall(Call $call, array $options)
{
$newArgs = ['/' . $call->getMethod(), $this->deserialize, $options, $options];
$response = call_user_func_array(array($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 = call_user_func_array(array($this, '_serverStreamRequest'), $newArgs);
return new ServerStream($response, $call->getDescriptor());
}
public function __call($name, $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
}
}
src/Testing/MockUnaryCall.php 0000644 00000005750 15174274026 0012204 0 ustar 00 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,
];
}
}
src/Testing/ProtobufGPBEmptyComparator.php 0000644 00000004163 15174274026 0014675 0 ustar 00 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'
);
}
}
}
src/Testing/ReceivedRequest.php 0000644 00000005031 15174274026 0012567 0 ustar 00 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'];
}
}
src/Testing/SerializationTrait.php 0000644 00000004620 15174274026 0013314 0 ustar 00 $deserializeFunc($message);
} elseif (is_string($message)) {
$obj->mergeFromString($message);
}
return $obj;
}
// Protobuf-PHP implementation
return call_user_func($deserialize, $message);
}
}
src/Testing/mocks.proto 0000644 00000002231 15174274026 0011157 0 ustar 00 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";
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;
}
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;
}
}
src/Transport/Grpc/ForwardingCall.php 0000644 00000005107 15174274026 0013644 0 ustar 00 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();
}
}
src/Transport/Grpc/ForwardingServerStreamingCall.php 0000644 00000004303 15174274026 0016702 0 ustar 00 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();
}
}
src/Transport/Grpc/ForwardingUnaryCall.php 0000644 00000003643 15174274026 0014666 0 ustar 00 innerCall->wait();
}
}
src/Transport/Grpc/UnaryInterceptorInterface.php 0000644 00000004333 15174274026 0016104 0 ustar 00 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($apiEndpoint, array $config = [])
{
$config += [
'httpHandler' => null,
];
list($baseUri, $port) = self::normalizeServiceAddress($apiEndpoint);
$httpHandler = $config['httpHandler'] ?: self::buildHttpHandlerAsync();
return new GrpcFallbackTransport("$baseUri:$port", $httpHandler);
}
/**
* {@inheritdoc}
*/
public function startUnaryCall(Call $call, array $options)
{
$httpHandler = $this->httpHandler;
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:///$rpc/
$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 = isset($options['transportOptions']['grpcFallbackOptions'])
? $options['transportOptions']['grpcFallbackOptions']
: [];
if (isset($options['timeoutMillis'])) {
$callOptions['timeout'] = $options['timeoutMillis'] / 1000;
}
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;
}
}
}
src/Transport/GrpcTransport.php 0000644 00000022635 15174274026 0012670 0 ustar 00 [],
'channel' => null,
'interceptors' => [],
];
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)) {
$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 {
return new GrpcTransport($host, $stubOpts, $channel, $config['interceptors']);
} catch (Exception $ex) {
throw new ValidationException(
"Failed to build GrpcTransport: " . $ex->getMessage(),
$ex->getCode(),
$ex
);
}
}
/**
* {@inheritdoc}
*/
public function startBidiStreamingCall(Call $call, array $options)
{
return new BidiStream(
$this->_bidiRequest(
'/' . $call->getMethod(),
[$call->getDecodeType(), 'decode'],
isset($options['headers']) ? $options['headers'] : [],
$this->getCallOptions($options)
),
$call->getDescriptor()
);
}
/**
* {@inheritdoc}
*/
public function startClientStreamingCall(Call $call, array $options)
{
return new ClientStream(
$this->_clientStreamRequest(
'/' . $call->getMethod(),
[$call->getDecodeType(), 'decode'],
isset($options['headers']) ? $options['headers'] : [],
$this->getCallOptions($options)
),
$call->getDescriptor()
);
}
/**
* {@inheritdoc}
*/
public function startServerStreamingCall(Call $call, array $options)
{
$message = $call->getMessage();
if (!$message) {
throw new \InvalidArgumentException('A message is required for ServerStreaming calls.');
}
return new ServerStream(
$this->_serverStreamRequest(
'/' . $call->getMethod(),
$message,
[$call->getDecodeType(), 'decode'],
isset($options['headers']) ? $options['headers'] : [],
$this->getCallOptions($options)
),
$call->getDescriptor()
);
}
/**
* {@inheritdoc}
*/
public function startUnaryCall(Call $call, array $options)
{
$unaryCall = $this->_simpleRequest(
'/' . $call->getMethod(),
$call->getMessage(),
[$call->getDecodeType(), 'decode'],
isset($options['headers']) ? $options['headers'] : [],
$this->getCallOptions($options)
);
/** @var Promise $promise */
$promise = new Promise(
function () use ($unaryCall, $options, &$promise) {
list($response, $status) = $unaryCall->wait();
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 getCallOptions(array $options)
{
$callOptions = isset($options['transportOptions']['grpcOptions'])
? $options['transportOptions']['grpcOptions']
: [];
if (isset($options['credentialsWrapper'])) {
$audience = isset($options['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;
}
}
src/Transport/HttpUnaryTransportTrait.php 0000644 00000010076 15174274026 0014733 0 ustar 00 throwUnsupportedException();
}
/**
* {@inheritdoc}
* @throws \BadMethodCallException
*/
public function startServerStreamingCall(Call $call, array $options)
{
$this->throwUnsupportedException();
}
/**
* {@inheritdoc}
* @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 = isset($options['headers'])
? $options['headers']
: [];
// If not already set, add an auth header to the request
if (!isset($headers['Authorization']) && isset($options['credentialsWrapper'])) {
$credentialsWrapper = $options['credentialsWrapper'];
$audience = isset($options['audience'])
? $options['audience']
: null;
$callback = $credentialsWrapper
->getAuthorizationHeaderCallback($audience);
// Prevent unexpected behavior, as the authorization header callback
// uses lowercase "authorization"
unset($headers['authorization']);
$headers += $callback();
}
return $headers;
}
/**
* @return callable
* @throws ValidationException
*/
private static function buildHttpHandlerAsync()
{
try {
return [HttpHandlerFactory::build(), 'async'];
} catch (Exception $ex) {
throw new ValidationException("Failed to build HttpHandler", $ex->getCode(), $ex);
}
}
private function throwUnsupportedException()
{
throw new \BadMethodCallException(
"Streaming calls are not supported while using the {$this->transportName} transport."
);
}
}
src/Transport/RestTransport.php 0000644 00000014072 15174274026 0012706 0 ustar 00 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 $config {
* Config options used to construct the gRPC transport.
*
* @type callable $httpHandler A handler used to deliver PSR-7 requests.
* }
* @return RestTransport
* @throws ValidationException
*/
public static function build($apiEndpoint, $restConfigPath, array $config = [])
{
$config += [
'httpHandler' => null,
];
list($baseUri, $port) = self::normalizeServiceAddress($apiEndpoint);
$requestBuilder = new RequestBuilder("$baseUri:$port", $restConfigPath);
$httpHandler = $config['httpHandler'] ?: self::buildHttpHandlerAsync();
return new RestTransport($requestBuilder, $httpHandler);
}
/**
* {@inheritdoc}
*/
public function startUnaryCall(Call $call, array $options)
{
$headers = self::buildCommonHeaders($options);
// 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;
$return->mergeFromJsonString(
(string) $response->getBody(),
true
);
if (isset($options['metadataCallback'])) {
$metadataCallback = $options['metadataCallback'];
$metadataCallback($response->getHeaders());
}
return $return;
},
function (\Exception $ex) {
if ($ex instanceof RequestException && $ex->hasResponse()) {
throw $this->convertToApiException($ex);
}
throw $ex;
}
);
}
private function getCallOptions(array $options)
{
$callOptions = isset($options['transportOptions']['restOptions'])
? $options['transportOptions']['restOptions']
: [];
if (isset($options['timeoutMillis'])) {
$callOptions['timeout'] = $options['timeoutMillis'] / 1000;
}
return $callOptions;
}
/**
* @param RequestException $ex
* @return ApiException
* @throws ValidationException
*/
private function convertToApiException(RequestException $ex)
{
$res = $ex->getResponse();
$body = (string) $res->getBody();
if ($error = json_decode($body, true)['error']) {
$basicMessage = $error['message'];
$code = ApiStatus::rpcCodeFromStatus($error['status']);
$metadata = isset($error['details']) ? $error['details'] : null;
return ApiException::createFromApiResponse($basicMessage, $code, $metadata);
}
// Use the RPC code instead of the HTTP Status Code.
$code = ApiStatus::rpcCodeFromHttpStatusCode($res->getStatusCode());
return ApiException::createFromApiResponse($body, $code);
}
}
src/Transport/TransportInterface.php 0000644 00000005477 15174274026 0013702 0 ustar 00 &$v) {
if (is_bool($v)) {
$v = $v ? 'true' : 'false';
}
}
return Psr7\uri_for($uri)
->withQuery(
Psr7\build_query($query)
);
}
}
src/ValidationException.php 0000644 00000003321 15174274026 0012024 0 ustar 00 [
'gl-php/' . phpversion() .
' gapic/' .
' gax/' . Version::getApiCoreVersion() .
' grpc/' . phpversion('grpc')
]];
$header = AgentHeader::buildAgentHeader([]);
$this->assertSame($expectedHeader, $header);
}
public function testWithInput()
{
$expectedHeader = [AgentHeader::AGENT_HEADER_KEY => [
'gl-php/4.4.4 gccl/1.1.1 gapic/2.2.2 gax/3.3.3 grpc/5.5.5'
]];
$header = AgentHeader::buildAgentHeader([
'libName' => 'gccl',
'libVersion' => '1.1.1',
'gapicVersion' => '2.2.2',
'apiCoreVersion' => '3.3.3',
'phpVersion' => '4.4.4',
'grpcVersion' => '5.5.5',
]);
$this->assertSame($expectedHeader, $header);
}
public function testWithoutVersionInput()
{
$expectedHeader = [AgentHeader::AGENT_HEADER_KEY => [
'gl-php/' . phpversion() .
' gccl/ gapic/ gax/' . Version::getApiCoreVersion() .
' grpc/' . phpversion('grpc')
]];
$header = AgentHeader::buildAgentHeader([
'libName' => 'gccl',
]);
$this->assertSame($expectedHeader, $header);
}
public function testWithNullVersionInput()
{
$expectedHeader = [AgentHeader::AGENT_HEADER_KEY => [
'gl-php/' . phpversion() .
' gccl/ gapic/ gax/' . Version::getApiCoreVersion() .
' grpc/' . phpversion('grpc')
]];
$header = AgentHeader::buildAgentHeader([
'libName' => 'gccl',
'libVersion' => null,
'gapicVersion' => null,
'apiCoreVersion' => null,
'phpVersion' => null,
'grpcVersion' => null,
]);
$this->assertSame($expectedHeader, $header);
}
public function testGetGapicVersionWithVersionFile()
{
require_once __DIR__ . '/testdata/src/GapicClientStub.php';
$expectedVersion = '1.2.3-dev';
$actualVersion = AgentHeader::readGapicVersionFromFile(\GapicClientStub::class);
$this->assertEquals($expectedVersion, $actualVersion);
}
public function testGetGapicVersionWithNoAvailableVersion()
{
$this->assertEquals('', AgentHeader::readGapicVersionFromFile(__CLASS__));
}
}
tests/Tests/Unit/ApiExceptionTest.php 0000644 00000020475 15174274026 0013710 0 ustar 00 code = Code::OK;
$status->details = 'testWithoutMetadata';
$apiException = ApiException::createFromStdClass($status);
$expectedMessage = json_encode([
'message' => 'testWithoutMetadata',
'code' => Code::OK,
'status' => 'OK',
'details' => []
], JSON_PRETTY_PRINT);
$this->assertSame(Code::OK, $apiException->getCode());
$this->assertSame($expectedMessage, $apiException->getMessage());
$this->assertNull($apiException->getMetadata());
}
/**
* @dataProvider getMetadata
*/
public function testWithMetadata($metadata, $metadataArray)
{
$status = new \stdClass();
$status->code = Code::OK;
$status->details = 'testWithMetadata';
$status->metadata = $metadata;
$apiException = ApiException::createFromStdClass($status);
$expectedMessage = json_encode([
'message' => 'testWithMetadata',
'code' => Code::OK,
'status' => 'OK',
'details' => $metadataArray
], JSON_PRETTY_PRINT);
$this->assertSame(Code::OK, $apiException->getCode());
$this->assertSame($expectedMessage, $apiException->getMessage());
$this->assertSame($metadata, $apiException->getMetadata());
}
public function getMetadata()
{
$retryInfo = new RetryInfo();
$duration = new Duration();
$duration->setSeconds(1);
$duration->setNanos(2);
$retryInfo->setRetryDelay($duration);
$unknownBinData = [
[
'@type' => 'unknown-bin',
'data' => ''
]
];
$asciiData = [
[
'@type' => 'ascii',
'data' => 'ascii-data'
]
];
$retryInfoData = [
[
'@type' => 'google.rpc.retryinfo-bin',
'retryDelay' => [
'seconds' => 1,
'nanos' => 2,
],
]
];
$allKnownTypesData = [
[
'@type' => 'google.rpc.retryinfo-bin',
],
[
'@type' => 'google.rpc.debuginfo-bin',
"stackEntries" => [],
"detail" => ""
],
[
'@type' => 'google.rpc.quotafailure-bin',
'violations' => [],
],
[
'@type' => 'google.rpc.badrequest-bin',
'fieldViolations' => []
],
[
'@type' => 'google.rpc.requestinfo-bin',
'requestId' => '',
'servingData' => '',
],
[
'@type' => 'google.rpc.resourceinfo-bin',
'resourceType' => '',
'resourceName' => '',
'owner' => '',
'description' => '',
],
[
'@type' => 'google.rpc.help-bin',
'links' => [],
],
[
'@type' => 'google.rpc.localizedmessage-bin',
'locale' => '',
'message' => '',
],
];
return [
[['unknown-bin' => ['some-data-that-should-not-appear']], $unknownBinData],
[['ascii' => ['ascii-data']], $asciiData],
[['google.rpc.retryinfo-bin' => [$retryInfo->serializeToString()]], $retryInfoData],
[[
'google.rpc.retryinfo-bin' => [(new RetryInfo())->serializeToString()],
'google.rpc.debuginfo-bin' => [(new DebugInfo())->serializeToString()],
'google.rpc.quotafailure-bin' => [(new QuotaFailure())->serializeToString()],
'google.rpc.badrequest-bin' => [(new BadRequest())->serializeToString()],
'google.rpc.requestinfo-bin' => [(new RequestInfo())->serializeToString()],
'google.rpc.resourceinfo-bin' => [(new ResourceInfo())->serializeToString()],
'google.rpc.help-bin' => [(new Help())->serializeToString()],
'google.rpc.localizedmessage-bin' => [(new LocalizedMessage())->serializeToString()],
], $allKnownTypesData],
];
}
/**
* @dataProvider getMetadata
*/
public function testCreateFromApiResponse($metadata, $metadataArray) {
$basicMessage = 'testWithMetadata';
$code = Code::OK;
$status = 'OK';
$apiException = ApiException::createFromApiResponse($basicMessage, $code, $metadata);
$expectedMessage = json_encode([
'message' => $basicMessage,
'code' => $code,
'status' => $status,
'details' => $metadataArray
], JSON_PRETTY_PRINT);
$this->assertSame(Code::OK, $apiException->getCode());
$this->assertSame($expectedMessage, $apiException->getMessage());
$this->assertSame($metadata, $apiException->getMetadata());
}
/**
* @dataProvider getRpcStatusData
*/
public function testCreateFromRpcStatus($status, $expectedApiException) {
$actualApiException = ApiException::createFromRpcStatus($status);
$this->assertEquals($expectedApiException, $actualApiException);
}
public function getRpcStatusData()
{
$debugInfo = new DebugInfo();
$debugInfo->setDetail("debug detail");
$any = new Any();
$any->pack($debugInfo);
$status = new Status();
$status->setMessage("status string");
$status->setCode(Code::OK);
$status->setDetails([$any]);
$expectedMessage = json_encode([
'message' => $status->getMessage(),
'code' => $status->getCode(),
'status' => 'OK',
'details' => [
[
'stackEntries' => [],
'detail' => 'debug detail',
]
],
], JSON_PRETTY_PRINT);
return [
[
$status,
new ApiException($expectedMessage, Code::OK, 'OK', [
'metadata' => $status->getDetails(),
'basicMessage' => $status->getMessage(),
]
)
]
];
}
}
tests/Tests/Unit/ApiStatusTest.php 0000644 00000010071 15174274026 0013224 0 ustar 00 assertTrue(ApiStatus::isValidStatus($status));
}
/**
* @dataProvider getInvalidStatus
*/
public function testValidateInvalid($status)
{
$this->assertFalse(ApiStatus::isValidStatus($status));
}
public function getValidStatus()
{
return [
['OK'],
['CANCELLED'],
['UNKNOWN'],
['INVALID_ARGUMENT'],
['DEADLINE_EXCEEDED'],
['NOT_FOUND'],
['ALREADY_EXISTS'],
['PERMISSION_DENIED'],
['RESOURCE_EXHAUSTED'],
['FAILED_PRECONDITION'],
['ABORTED'],
['OUT_OF_RANGE'],
['UNIMPLEMENTED'],
['INTERNAL'],
['UNAVAILABLE'],
['DATA_LOSS'],
['UNAUTHENTICATED'],
];
}
public function getInvalidStatus()
{
return [
['UNRECOGNIZED_STATUS'],
[''],
['NONSENSE'],
];
}
/**
* @dataProvider getCodeAndStatus
*/
public function testStatusFromRpcCode($code, $status)
{
$this->assertSame($status, ApiStatus::statusFromRpcCode($code));
}
public function getCodeAndStatus()
{
return [
[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],
[-1, ApiStatus::UNRECOGNIZED_STATUS]
];
}
}
tests/Tests/Unit/ArrayTraitTest.php 0000644 00000011667 15174274026 0013405 0 ustar 00 implementation = new ArrayTraitStub();
}
public function testPluck()
{
$value = '123';
$key = 'key';
$array = [$key => $value];
$actualValue = $this->implementation->call('pluck', [$key, &$array]);
$this->assertEquals($value, $actualValue);
$this->assertEquals([], $array);
}
/**
* @expectedException InvalidArgumentException
*/
public function testPluckThrowsExceptionWithInvalidKey()
{
$array = [];
$this->implementation->call('pluck', ['not_here', &$array]);
}
public function testPluckArray()
{
$keys = ['key1', 'key2'];
$array = [
'key1' => 'test',
'key2' => 'test'
];
$expectedArray = $array;
$actualValues = $this->implementation->call('pluckArray', [$keys, &$array]);
$this->assertEquals($expectedArray, $actualValues);
$this->assertEquals([], $array);
}
public function testIsAssocTrue()
{
$actual = $this->implementation->call('isAssoc', [[
'test' => 1,
'test' => 2
]]);
$this->assertTrue($actual);
}
public function testIsAssocFalse()
{
$actual = $this->implementation->call('isAssoc', [[1, 2, 3]]);
$this->assertFalse($actual);
}
public function testArrayFilterRemoveNull()
{
$input = [
'null' => null,
'false' => false,
'zero' => 0,
'float' => 0.0,
'empty' => '',
'array' => [],
];
$res = $this->implementation->call('arrayFilterRemoveNull', [$input]);
$this->assertFalse(array_key_exists('null', $res));
$this->assertTrue(array_key_exists('false', $res));
$this->assertTrue(array_key_exists('zero', $res));
$this->assertTrue(array_key_exists('float', $res));
$this->assertTrue(array_key_exists('empty', $res));
$this->assertTrue(array_key_exists('array', $res));
}
/**
* @dataProvider subsetArrayData
*/
public function testSubsetArray($keys, $array, $expectedSubset)
{
$actualSubset = $this->implementation->call('subsetArray', [$keys, $array]);
$this->assertSame($expectedSubset, $actualSubset);
}
public function subsetArrayData()
{
return [
[
['one', 2],
[
'one' => 'value-of-one',
2 => 'value-of-two',
'three' => 'value-of-three'
],
[
'one' => 'value-of-one',
2 => 'value-of-two',
],
],
[
['one', 2, 'four'],
[
'one' => 'value-of-one',
2 => 'value-of-two',
'three' => 'value-of-three'
],
[
'one' => 'value-of-one',
2 => 'value-of-two',
],
]
];
}
}
class ArrayTraitStub
{
use ArrayTrait;
public function call($fn, array $args)
{
return call_user_func_array([$this, $fn], $args);
}
}
tests/Tests/Unit/BidiStreamTest.php 0000644 00000026030 15174274026 0013334 0 ustar 00 assertSame($call, $stream->getBidiStreamingCall());
$this->assertSame([], iterator_to_array($stream->closeWriteAndReadAll()));
}
/**
* @expectedException \Google\ApiCore\ApiException
* @expectedExceptionMessage empty failure read
*/
public function testEmptyFailureRead()
{
$call = new MockBidiStreamingCall([], null, new MockStatus(Code::INTERNAL, 'empty failure read'));
$stream = new BidiStream($call);
$this->assertSame($call, $stream->getBidiStreamingCall());
$stream->closeWrite();
$stream->read();
}
/**
* @expectedException \Google\ApiCore\ApiException
* @expectedExceptionMessage empty failure readall
*/
public function testEmptyFailureReadAll()
{
$call = new MockBidiStreamingCall([], null, new MockStatus(Code::INTERNAL, 'empty failure readall'));
$stream = new BidiStream($call);
$this->assertSame($call, $stream->getBidiStreamingCall());
iterator_to_array($stream->closeWriteAndReadAll());
}
/**
* @expectedException \Google\ApiCore\ValidationException
* @expectedExceptionMessage Cannot call read() after streaming call is complete.
*/
public function testReadAfterComplete()
{
$call = new MockBidiStreamingCall([]);
$stream = new BidiStream($call);
$this->assertSame($call, $stream->getBidiStreamingCall());
$stream->closeWrite();
$this->assertNull($stream->read());
$stream->read();
}
/**
* @expectedException \Google\ApiCore\ValidationException
* @expectedExceptionMessage Cannot call write() after streaming call is complete.
*/
public function testWriteAfterComplete()
{
$call = new MockBidiStreamingCall([]);
$stream = new BidiStream($call);
$this->assertSame($call, $stream->getBidiStreamingCall());
$stream->closeWrite();
$this->assertNull($stream->read());
$stream->write('request');
}
/**
* @expectedException \Google\ApiCore\ValidationException
* @expectedExceptionMessage Cannot call write() after calling closeWrite().
*/
public function testWriteAfterCloseWrite()
{
$call = new MockBidiStreamingCall([]);
$stream = new BidiStream($call);
$this->assertSame($call, $stream->getBidiStreamingCall());
$stream->closeWrite();
$stream->write('request');
}
public function testReadStringsSuccess()
{
$responses = ['abc', 'def'];
$call = new MockBidiStreamingCall($responses);
$stream = new BidiStream($call);
$this->assertSame($call, $stream->getBidiStreamingCall());
$this->assertSame($responses, iterator_to_array($stream->closeWriteAndReadAll()));
}
public function testReadObjectsSuccess()
{
$responses = [
$this->createStatus(Code::OK, 'response1'),
$this->createStatus(Code::OK, 'response2')
];
$serializedResponses = [];
foreach ($responses as $response) {
$serializedResponses[] = $response->serializeToString();
}
$call = new MockBidiStreamingCall($serializedResponses, ['\Google\Rpc\Status', 'mergeFromString']);
$stream = new BidiStream($call);
$this->assertSame($call, $stream->getBidiStreamingCall());
$this->assertEquals($responses, iterator_to_array($stream->closeWriteAndReadAll()));
}
public function testReadCloseReadSuccess()
{
$responses = [
$this->createStatus(Code::OK, 'response1'),
$this->createStatus(Code::OK, 'response2')
];
$serializedResponses = [];
foreach ($responses as $response) {
$serializedResponses[] = $response->serializeToString();
}
$call = new MockBidiStreamingCall($serializedResponses, ['\Google\Rpc\Status', 'mergeFromString']);
$stream = new BidiStream($call);
$this->assertSame($call, $stream->getBidiStreamingCall());
$response = $stream->read();
$stream->closeWrite();
$index = 0;
while (!is_null($response)) {
$this->assertEquals($response, $responses[$index]);
$response = $stream->read();
$index++;
}
}
/**
* @expectedException \Google\ApiCore\ApiException
* @expectedExceptionMessage read failure
*/
public function testReadFailure()
{
$responses = ['abc', 'def'];
$call = new MockBidiStreamingCall(
$responses,
null,
new MockStatus(Code::INTERNAL, 'read failure')
);
$stream = new BidiStream($call);
$this->assertSame($call, $stream->getBidiStreamingCall());
$index = 0;
try {
foreach ($stream->closeWriteAndReadAll() as $response) {
$this->assertSame($response, $responses[$index]);
$index++;
}
} finally {
$this->assertSame(2, $index);
}
}
public function testWriteStringsSuccess()
{
$requests = ['request1', 'request2'];
$responses = [];
$call = new MockBidiStreamingCall($responses);
$stream = new BidiStream($call);
$stream->writeAll($requests);
$this->assertSame($call, $stream->getBidiStreamingCall());
$this->assertSame([], iterator_to_array($stream->closeWriteAndReadAll()));
$this->assertEquals($requests, $call->popReceivedCalls());
}
public function testWriteObjectsSuccess()
{
$requests = [
$this->createStatus(Code::OK, 'request1'),
$this->createStatus(Code::OK, 'request2')
];
$responses = [];
$call = new MockBidiStreamingCall($responses, ['\Google\Rpc\Status', 'mergeFromString']);
$stream = new BidiStream($call);
$stream->writeAll($requests);
$this->assertSame($call, $stream->getBidiStreamingCall());
$this->assertSame([], iterator_to_array($stream->closeWriteAndReadAll()));
$this->assertEquals($requests, $call->popReceivedCalls());
}
public function testAlternateReadWriteObjectsSuccess()
{
$requests = [
$this->createStatus(Code::OK, 'request1'),
$this->createStatus(Code::OK, 'request2'),
$this->createStatus(Code::OK, 'request3')
];
$responses = [
$this->createStatus(Code::OK, 'response1'),
$this->createStatus(Code::OK, 'response2'),
$this->createStatus(Code::OK, 'response3'),
$this->createStatus(Code::OK, 'response4')
];
$serializedResponses = [];
foreach ($responses as $response) {
$serializedResponses[] = $response->serializeToString();
}
$call = new MockBidiStreamingCall($serializedResponses, ['\Google\Rpc\Status', 'mergeFromString']);
$stream = new BidiStream($call);
$index = 0;
foreach ($requests as $request) {
$stream->write($request);
$response = $stream->read();
$this->assertEquals($response, $responses[$index]);
$index++;
}
$stream->closeWrite();
$response = $stream->read();
while (!is_null($response)) {
$this->assertEquals($response, $responses[$index]);
$index++;
$response = $stream->read();
}
$this->assertSame($call, $stream->getBidiStreamingCall());
$this->assertEquals($requests, $call->popReceivedCalls());
}
/**
* @expectedException \Google\ApiCore\ApiException
* @expectedExceptionMessage write failure without close
*/
public function testWriteFailureWithoutClose()
{
$request = 'request';
$responses = [null];
$call = new MockBidiStreamingCall(
$responses,
null,
new MockStatus(Code::INTERNAL, 'write failure without close')
);
$stream = new BidiStream($call);
$this->assertSame($call, $stream->getBidiStreamingCall());
$stream->write($request);
try {
$stream->read();
} finally {
$this->assertEquals([$request], $call->popReceivedCalls());
}
}
public function testResourcesSuccess()
{
$resources = ['resource1', 'resource2', 'resource3'];
$repeatedField1 = new RepeatedField(GPBType::STRING);
$repeatedField1[] = 'resource1';
$repeatedField2 = new RepeatedField(GPBType::STRING);
$repeatedField2[] = 'resource2';
$repeatedField2[] = 'resource3';
$responses = [
$this->createMockResponse('nextPageToken1', $repeatedField1),
$this->createMockResponse('nextPageToken1', $repeatedField2)
];
$call = new MockBidiStreamingCall($responses);
$stream = new BidiStream($call, [
'resourcesGetMethod' => 'getResourcesList'
]);
$this->assertSame($call, $stream->getBidiStreamingCall());
$this->assertEquals($resources, iterator_to_array($stream->closeWriteAndReadAll()));
}
}
tests/Tests/Unit/ClientStreamTest.php 0000644 00000013575 15174274026 0013715 0 ustar 00 assertSame($call, $stream->getClientStreamingCall());
$this->assertSame($response, $stream->readResponse());
$this->assertSame([], $call->popReceivedCalls());
}
/**
* @expectedException \Google\ApiCore\ApiException
* @expectedExceptionMessage no writes failure
*/
public function testNoWritesFailure()
{
$response = 'response';
$call = new MockClientStreamingCall(
$response,
null,
new MockStatus(Code::INTERNAL, 'no writes failure')
);
$stream = new ClientStream($call);
$this->assertSame($call, $stream->getClientStreamingCall());
$this->assertSame([], $call->popReceivedCalls());
$stream->readResponse();
}
public function testManualWritesSuccess()
{
$requests = [
$this->createStatus(Code::OK, 'request1'),
$this->createStatus(Code::OK, 'request2')
];
$response = $this->createStatus(Code::OK, 'response');
$call = new MockClientStreamingCall($response->serializeToString(), ['\Google\Rpc\Status', 'mergeFromString']);
$stream = new ClientStream($call);
foreach ($requests as $request) {
$stream->write($request);
}
$this->assertSame($call, $stream->getClientStreamingCall());
$this->assertEquals($response, $stream->readResponse());
$this->assertEquals($requests, $call->popReceivedCalls());
}
/**
* @expectedException \Google\ApiCore\ApiException
* @expectedExceptionMessage manual writes failure
*/
public function testManualWritesFailure()
{
$requests = [
$this->createStatus(Code::OK, 'request1'),
$this->createStatus(Code::OK, 'request2')
];
$response = $this->createStatus(Code::OK, 'response');
$call = new MockClientStreamingCall(
$response->serializeToString(),
['\Google\Rpc\Status', 'mergeFromString'],
new MockStatus(Code::INTERNAL, 'manual writes failure')
);
$stream = new ClientStream($call);
foreach ($requests as $request) {
$stream->write($request);
}
$this->assertSame($call, $stream->getClientStreamingCall());
$this->assertEquals($requests, $call->popReceivedCalls());
$stream->readResponse();
}
public function testWriteAllSuccess()
{
$requests = [
$this->createStatus(Code::OK, 'request1'),
$this->createStatus(Code::OK, 'request2')
];
$response = $this->createStatus(Code::OK, 'response');
$call = new MockClientStreamingCall($response->serializeToString(), ['\Google\Rpc\Status', 'mergeFromString']);
$stream = new ClientStream($call);
$actualResponse = $stream->writeAllAndReadResponse($requests);
$this->assertSame($call, $stream->getClientStreamingCall());
$this->assertEquals($response, $actualResponse);
$this->assertEquals($requests, $call->popReceivedCalls());
}
/**
* @expectedException \Google\ApiCore\ApiException
* @expectedExceptionMessage write all failure
*/
public function testWriteAllFailure()
{
$requests = [
$this->createStatus(Code::OK, 'request1'),
$this->createStatus(Code::OK, 'request2')
];
$response = $this->createStatus(Code::OK, 'response');
$call = new MockClientStreamingCall(
$response->serializeToString(),
['\Google\Rpc\Status', 'mergeFromString'],
new MockStatus(Code::INTERNAL, 'write all failure')
);
$stream = new ClientStream($call);
try {
$stream->writeAllAndReadResponse($requests);
} finally {
$this->assertSame($call, $stream->getClientStreamingCall());
$this->assertEquals($requests, $call->popReceivedCalls());
}
}
}
tests/Tests/Unit/CredentialsWrapperTest.php 0000644 00000031061 15174274026 0015107 0 ustar 00 assertEquals($expectedCredentialsWrapper, $actualCredentialsWrapper);
putenv('GOOGLE_APPLICATION_CREDENTIALS');
}
/**
* @dataProvider buildDataWithKeyFile
*/
public function testBuildWithKeyFile($args, $expectedCredentialsWrapper)
{
$actualCredentialsWrapper = CredentialsWrapper::build($args);
$this->assertEquals($expectedCredentialsWrapper, $actualCredentialsWrapper);
}
public function buildDataWithoutExplicitKeyFile()
{
$scopes = ['myscope'];
$defaultAuthHttpHandler = HttpHandlerFactory::build();
$authHttpHandler = HttpHandlerFactory::build();
$asyncAuthHttpHandler = function ($request, $options) use ($authHttpHandler) {
return $authHttpHandler->async($request, $options)->wait();
};
$defaultAuthCache = new MemoryCacheItemPool();
$authCache = new SysVCacheItemPool();
$authCacheOptions = ['lifetime' => 600];
$quotaProject = 'my-quota-project';
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . __DIR__ . '/testdata/json-key-file.json');
$testData = [
[
[],
new CredentialsWrapper(ApplicationDefaultCredentials::getCredentials(null, $defaultAuthHttpHandler, null, $defaultAuthCache), $defaultAuthHttpHandler),
],
[
['scopes' => $scopes],
new CredentialsWrapper(ApplicationDefaultCredentials::getCredentials($scopes, $defaultAuthHttpHandler, null, $defaultAuthCache), $defaultAuthHttpHandler),
],
[
['scopes' => $scopes, 'authHttpHandler' => $asyncAuthHttpHandler],
new CredentialsWrapper(ApplicationDefaultCredentials::getCredentials($scopes, $asyncAuthHttpHandler, null, $defaultAuthCache), $asyncAuthHttpHandler),
],
[
['enableCaching' => false],
new CredentialsWrapper(ApplicationDefaultCredentials::getCredentials(null, $defaultAuthHttpHandler, null, null), $defaultAuthHttpHandler),
],
[
['authCacheOptions' => $authCacheOptions],
new CredentialsWrapper(ApplicationDefaultCredentials::getCredentials(null, $defaultAuthHttpHandler, $authCacheOptions, $defaultAuthCache), $defaultAuthHttpHandler),
],
[
['authCache' => $authCache],
new CredentialsWrapper(ApplicationDefaultCredentials::getCredentials(null, $defaultAuthHttpHandler, null, $authCache), $defaultAuthHttpHandler),
],
[
['quotaProject' => $quotaProject],
new CredentialsWrapper(ApplicationDefaultCredentials::getCredentials(null, $defaultAuthHttpHandler, null, $defaultAuthCache, $quotaProject), $defaultAuthHttpHandler),
],
];
putenv('GOOGLE_APPLICATION_CREDENTIALS');
return $testData;
}
public function buildDataWithKeyFile()
{
$keyFilePath = __DIR__ . '/testdata/json-key-file.json';
$keyFile = json_decode(file_get_contents($keyFilePath), true);
$scopes = ['myscope'];
$authHttpHandler = function () {};
$defaultAuthCache = new MemoryCacheItemPool();
$authCache = new SysVCacheItemPool();
$authCacheOptions = ['lifetime' => 600];
$quotaProject = 'my-quota-project';
return [
[
['keyFile' => $keyFile],
$this->makeExpectedKeyFileCreds($keyFile, null, $defaultAuthCache, null, null),
],
[
['keyFile' => $keyFilePath],
$this->makeExpectedKeyFileCreds($keyFile, null, $defaultAuthCache, null, null),
],
[
['keyFile' => $keyFile, 'scopes' => $scopes],
$this->makeExpectedKeyFileCreds($keyFile, $scopes, $defaultAuthCache, null, null),
],
[
['keyFile' => $keyFile, 'scopes' => $scopes, 'authHttpHandler' => $authHttpHandler],
$this->makeExpectedKeyFileCreds($keyFile, $scopes, $defaultAuthCache, null, $authHttpHandler),
],
[
['keyFile' => $keyFile, 'enableCaching' => false],
$this->makeExpectedKeyFileCreds($keyFile, null, null, null, null),
],
[
['keyFile' => $keyFile, 'authCacheOptions' => $authCacheOptions],
$this->makeExpectedKeyFileCreds($keyFile, null, $defaultAuthCache, $authCacheOptions, null),
],
[
['keyFile' => $keyFile, 'authCache' => $authCache],
$this->makeExpectedKeyFileCreds($keyFile, null, $authCache, null, null),
],
[
['keyFile' => $keyFile, 'quotaProject' => $quotaProject],
$this->makeExpectedKeyFileCreds(
$keyFile + ['quota_project_id' => $quotaProject],
null,
$defaultAuthCache,
null,
null
),
],
];
}
private function makeExpectedKeyFileCreds($keyFile, $scopes, $cache, $cacheConfig, $httpHandler)
{
$loader = CredentialsLoader::makeCredentials($scopes, $keyFile);
if ($cache) {
$loader = new FetchAuthTokenCache($loader, $cacheConfig, $cache);
}
return new CredentialsWrapper($loader, $httpHandler);
}
/**
* @dataProvider getBearerStringData
*/
public function testGetBearerString($fetcher, $expectedBearerString)
{
$credentialsWrapper = new CredentialsWrapper($fetcher);
$bearerString = $credentialsWrapper->getBearerString();
$this->assertSame($expectedBearerString, $bearerString);
}
public function getBearerStringData()
{
$expiredFetcher = $this->prophesize(FetchAuthTokenInterface::class);
$expiredFetcher->getLastReceivedToken()
->willReturn([
'access_token' => 123,
'expires_at' => time() - 1
]);
$expiredFetcher->fetchAuthToken(Argument::any())
->willReturn([
'access_token' => 456,
'expires_at' => time() + 1000
]);
$unexpiredFetcher = $this->prophesize(FetchAuthTokenInterface::class);
$unexpiredFetcher->getLastReceivedToken()
->willReturn([
'access_token' => 123,
'expires_at' => time() + 100,
]);
$insecureFetcher = $this->prophesize(FetchAuthTokenInterface::class);
$insecureFetcher->getLastReceivedToken()->willReturn(null);
$insecureFetcher->fetchAuthToken(Argument::any())
->willReturn([
'access_token' => '',
]);
$nullFetcher = $this->prophesize(FetchAuthTokenInterface::class);
$nullFetcher->getLastReceivedToken()->willReturn(null);
$nullFetcher->fetchAuthToken(Argument::any())
->willReturn([
'access_token' => null,
]);
return [
[$expiredFetcher->reveal(), 'Bearer 456'],
[$unexpiredFetcher->reveal(), 'Bearer 123'],
[$insecureFetcher->reveal(), ''],
[$nullFetcher->reveal(), '']
];
}
/**
* @dataProvider getAuthorizationHeaderCallbackData
*/
public function testGetAuthorizationHeaderCallback($fetcher, $expectedCallbackResponse)
{
$credentialsWrapper = new CredentialsWrapper($fetcher);
$callback = $credentialsWrapper->getAuthorizationHeaderCallback('audience');
$actualResponse = $callback();
$this->assertSame($expectedCallbackResponse, $actualResponse);
}
public function getAuthorizationHeaderCallbackData()
{
$expiredFetcher = $this->prophesize();
$expiredFetcher->willImplement(FetchAuthTokenInterface::class);
$expiredFetcher->willImplement(UpdateMetadataInterface::class);
$expiredFetcher->getLastReceivedToken()
->willReturn([
'access_token' => 123,
'expires_at' => time() - 1
]);
$expiredFetcher->updateMetadata(Argument::any(), 'audience')
->willReturn(['authorization' => ['Bearer 456']]);
$expiredInvalidFetcher = $this->prophesize(FetchAuthTokenInterface::class);
$expiredInvalidFetcher->getLastReceivedToken()
->willReturn([
'access_token' => 123,
'expires_at' => time() - 1
]);
$expiredInvalidFetcher->fetchAuthToken(Argument::any())
->willReturn(['not-a' => 'valid-token']);
$unexpiredFetcher = $this->prophesize();
$unexpiredFetcher->willImplement(FetchAuthTokenInterface::class);
$unexpiredFetcher->getLastReceivedToken()
->willReturn([
'access_token' => 123,
'expires_at' => time() + 100,
]);
$insecureFetcher = $this->prophesize(FetchAuthTokenInterface::class);
$insecureFetcher->getLastReceivedToken()->willReturn(null);
$insecureFetcher->fetchAuthToken(Argument::any())
->willReturn([
'access_token' => '',
]);
$nullFetcher = $this->prophesize(FetchAuthTokenInterface::class);
$nullFetcher->getLastReceivedToken()->willReturn(null);
$nullFetcher->fetchAuthToken(Argument::any())
->willReturn([
'access_token' => null,
]);
$customFetcher = $this->prophesize();
$customFetcher->willImplement(FetchAuthTokenInterface::class);
$customFetcher->getLastReceivedToken()->willReturn(null);
$customFetcher->fetchAuthToken(Argument::any())
->willReturn([
'access_token' => 123,
'expires_at' => time() + 100,
]);
return [
[$expiredFetcher->reveal(), ['authorization' => ['Bearer 456']]],
[$expiredInvalidFetcher->reveal(), []],
[$unexpiredFetcher->reveal(), ['authorization' => ['Bearer 123']]],
[$insecureFetcher->reveal(), []],
[$nullFetcher->reveal(), []],
[$customFetcher->reveal(), ['authorization' => ['Bearer 123']]],
];
}
}
tests/Tests/Unit/FixedSizeCollectionTest.php 0000644 00000016621 15174274026 0015224 0 ustar 00 createMockRequest('token', 3);
$pageStreamingDescriptor = PageStreamingDescriptor::createFromFields([
'requestPageTokenField' => 'pageToken',
'requestPageSizeField' => 'pageSize',
'responsePageTokenField' => 'nextPageToken',
'resourceField' => 'resourcesList'
]);
$internalCall = $this->createCallWithResponseSequence($responseSequence);
$callable = function () use ($internalCall) {
list($response, $status) = call_user_func_array(
array($internalCall, 'takeAction'),
func_get_args()
);
return $promise = new \GuzzleHttp\Promise\Promise(function () use (&$promise, $response) {
$promise->resolve($response);
});
};
$call = new Call('method', 'decodeType', $mockRequest);
$options = [];
$response = $callable($call, $options)->wait();
return new Page($call, $options, $callable, $pageStreamingDescriptor, $response);
}
public function testFixedCollectionMethods()
{
$responseA = $this->createMockResponse(
'nextPageToken1',
['resource1', 'resource2']
);
$responseB = $this->createMockResponse(
'nextPageToken2',
['resource3', 'resource4', 'resource5']
);
$responseC = $this->createMockResponse(
'nextPageToken3',
['resource6', 'resource7']
);
$responseD = $this->createMockResponse(
'',
['resource8', 'resource9']
);
$page = $this->createPage([
[$responseA, new MockStatus(Code::OK, '')],
[$responseB, new MockStatus(Code::OK, '')],
[$responseC, new MockStatus(Code::OK, '')],
[$responseD, new MockStatus(Code::OK, '')],
]);
$fixedSizeCollection = new FixedSizeCollection($page, 5);
$this->assertEquals($fixedSizeCollection->getCollectionSize(), 5);
$this->assertEquals($fixedSizeCollection->hasNextCollection(), true);
$this->assertEquals($fixedSizeCollection->getNextPageToken(), 'nextPageToken2');
$results = iterator_to_array($fixedSizeCollection);
$this->assertEquals(
$results,
['resource1', 'resource2', 'resource3', 'resource4', 'resource5']
);
$nextCollection = $fixedSizeCollection->getNextCollection();
$this->assertEquals($nextCollection->getCollectionSize(), 4);
$this->assertEquals($nextCollection->hasNextCollection(), false);
$this->assertEquals($nextCollection->getNextPageToken(), '');
$results = iterator_to_array($nextCollection);
$this->assertEquals(
$results,
['resource6', 'resource7', 'resource8', 'resource9']
);
}
public function testIterateCollections()
{
$responseA = $this->createMockResponse(
'nextPageToken1',
['resource1', 'resource2']
);
$responseB = $this->createMockResponse(
'',
['resource3', 'resource4']
);
$page = $this->createPage([
[$responseA, new MockStatus(Code::OK, '')],
[$responseB, new MockStatus(Code::OK, '')],
]);
$collection = new FixedSizeCollection($page, 2);
$results = [];
$iterations = 0;
foreach ($collection->iterateCollections() as $nextCollection) {
$results = array_merge($results, iterator_to_array($nextCollection));
$iterations++;
}
$this->assertEquals(
$results,
['resource1', 'resource2', 'resource3', 'resource4']
);
$this->assertEquals(2, $iterations);
}
/**
* @expectedException LengthException
* @expectedExceptionMessage API returned a number of elements exceeding the specified page size limit
*/
public function testApiReturningMoreElementsThanPageSize()
{
$responseA = $this->createMockResponse(
'nextPageToken1',
['resource1', 'resource2']
);
$responseB = $this->createMockResponse(
'nextPageToken2',
['resource3', 'resource4', 'resource5']
);
$page = $this->createPage([
[$responseA, new MockStatus(Code::OK, '')],
[$responseB, new MockStatus(Code::OK, '')],
]);
$collection = new FixedSizeCollection($page, 3);
$collection->getNextCollection();
}
/**
* @expectedException InvalidArgumentException
* @expectedExceptionMessage collectionSize must be > 0.
*/
public function testEmptyCollectionThrowsException()
{
$collectionSize = 0;
$page = $this->getMockBuilder(Page::class)
->disableOriginalConstructor()
->getMock();
new FixedSizeCollection($page, $collectionSize);
}
/**
* @expectedException InvalidArgumentException
* @expectedExceptionMessage collectionSize must be greater than or equal to the number of elements in initialPage
*/
public function testInvalidPageCount()
{
$collectionSize = 1;
$page = $this->getMockBuilder(Page::class)
->disableOriginalConstructor()
->getMock();
$page->expects($this->exactly(2))
->method('getPageElementCount')
->will($this->returnValue(2));
new FixedSizeCollection($page, $collectionSize);
}
}
tests/Tests/Unit/GapicClientTraitTest.php 0000644 00000115450 15174274026 0014504 0 ustar 00 set('gapicVersionFromFile', null, true);
}
public function testHeadersOverwriteBehavior()
{
$header = AgentHeader::buildAgentHeader([
'libName' => 'gccl',
'libVersion' => '0.0.0',
'gapicVersion' => '0.9.0',
'apiCoreVersion' => '1.0.0',
'phpVersion' => '5.5.0',
'grpcVersion' => '1.0.1'
]);
$headers = [
'x-goog-api-client' => ['this-should-not-be-used'],
'new-header' => ['this-should-be-used']
];
$expectedHeaders = [
'x-goog-api-client' => ['gl-php/5.5.0 gccl/0.0.0 gapic/0.9.0 gax/1.0.0 grpc/1.0.1'],
'new-header' => ['this-should-be-used'],
];
$transport = $this->getMock(TransportInterface::class);
$credentialsWrapper = CredentialsWrapper::build([]);
$transport->expects($this->once())
->method('startUnaryCall')
->with(
$this->isInstanceOf(Call::class),
$this->equalTo([
'headers' => $expectedHeaders,
'credentialsWrapper' => $credentialsWrapper,
])
);
$client = new GapicClientTraitStub();
$client->set('agentHeader', $header);
$client->set('retrySettings', [
'method' => $this->getMockBuilder(RetrySettings::class)
->disableOriginalConstructor()
->getMock()
]
);
$client->set('transport', $transport);
$client->set('credentialsWrapper', $credentialsWrapper);
$client->call('startCall', [
'method',
'decodeType',
['headers' => $headers]
]);
}
public function testConfigureCallConstructionOptionsAcceptsRetryObjectOrArray()
{
$defaultRetrySettings = RetrySettings::constructDefault();
$client = new GapicClientTraitStub();
$client->set('retrySettings', ['method' => $defaultRetrySettings]);
$expectedOptions = [
'retrySettings' => $defaultRetrySettings
->with(['rpcTimeoutMultiplier' => 5])
];
$actualOptionsWithObject = $client->call(
'configureCallConstructionOptions',
[
'method',
[
'retrySettings' => $defaultRetrySettings
->with(['rpcTimeoutMultiplier' => 5])
]
]
);
$actualOptionsWithArray = $client->call(
'configureCallConstructionOptions',
[
'method',
[
'retrySettings' => ['rpcTimeoutMultiplier' => 5]
]
]
);
$this->assertEquals($expectedOptions, $actualOptionsWithObject);
$this->assertEquals($expectedOptions, $actualOptionsWithArray);
}
public function testStartOperationsCall()
{
$header = AgentHeader::buildAgentHeader([]);
$retrySettings = $this->getMockBuilder(RetrySettings::class)
->disableOriginalConstructor()
->getMock();
$longRunningDescriptors = [
'longRunning' => [
'operationReturnType' => 'operationType',
'metadataReturnType' => 'metadataType',
'initialPollDelayMillis' => 100,
'pollDelayMultiplier' => 1.0,
'maxPollDelayMillis' => 200,
'totalPollTimeoutMillis' => 300,
]
];
$expectedPromise = new FulfilledPromise(new Operation());
$transport = $this->getMock(TransportInterface::class);
$transport->expects($this->once())
->method('startUnaryCall')
->will($this->returnValue($expectedPromise));
$credentialsWrapper = CredentialsWrapper::build([]);
$client = new GapicClientTraitStub();
$client->set('transport', $transport);
$client->set('credentialsWrapper', $credentialsWrapper);
$client->set('agentHeader', $header);
$client->set('retrySettings', ['method' => $retrySettings]);
$client->set('descriptors', ['method' => $longRunningDescriptors]);
$message = new MockRequest();
$operationsClient = $this->getMockBuilder(OperationsClient::class)
->disableOriginalConstructor()
->getMock();
$response = $client->call('startOperationsCall', [
'method',
[],
$message,
$operationsClient
])->wait();
$expectedResponse = new OperationResponse(
'',
$operationsClient,
$longRunningDescriptors['longRunning'] + ['lastProtoResponse' => new Operation()]
);
$this->assertEquals($expectedResponse, $response);
}
public function testGetGapicVersionWithVersionFile()
{
require_once __DIR__ . '/testdata/src/GapicClientStub.php';
$version = '1.2.3-dev';
$client = new \GapicClientStub();
$this->assertEquals($version, $client->call('getGapicVersion', [[]]));
}
public function testGetGapicVersionWithNoAvailableVersion()
{
$client = new GapicClientTraitStub();
$this->assertEquals('', $client->call('getGapicVersion', [[]]));
}
public function testGetGapicVersionWithLibVersion()
{
$version = '1.2.3-dev';
$client = new GapicClientTraitStub();
$client->set('gapicVersionFromFile', $version, true);
$options = ['libVersion' => $version];
$this->assertEquals($version, $client->call('getGapicVersion', [
$options
]));
}
/**
* @dataProvider createCredentialsWrapperData
*/
public function testCreateCredentialsWrapper($auth, $authConfig, $expectedCredentialsWrapper)
{
$client = new GapicClientTraitStub();
$actualCredentialsWrapper = $client->call('createCredentialsWrapper', [
$auth,
$authConfig,
]);
$this->assertEquals($expectedCredentialsWrapper, $actualCredentialsWrapper);
}
public function createCredentialsWrapperData()
{
$keyFilePath = __DIR__ . '/testdata/json-key-file.json';
$keyFile = json_decode(file_get_contents($keyFilePath), true);
$fetcher = $this->prophesize(FetchAuthTokenInterface::class)->reveal();
$credentialsWrapper = new CredentialsWrapper($fetcher);
return [
[null, [], CredentialsWrapper::build()],
[$keyFilePath, [], CredentialsWrapper::build(['keyFile' => $keyFile])],
[$keyFile, [], CredentialsWrapper::build(['keyFile' => $keyFile])],
[$fetcher, [], new CredentialsWrapper($fetcher)],
[$credentialsWrapper, [], $credentialsWrapper],
];
}
/**
* @dataProvider createCredentialsWrapperValidationExceptionData
* @expectedException \Google\ApiCore\ValidationException
*/
public function testCreateCredentialsWrapperValidationException($auth, $authConfig)
{
$client = new GapicClientTraitStub();
$client->call('createCredentialsWrapper', [
$auth,
$authConfig,
]);
}
public function createCredentialsWrapperValidationExceptionData()
{
return [
['not a json string', []],
[new \stdClass(), []],
];
}
/**
* @dataProvider createCredentialsWrapperInvalidArgumentExceptionData
* @expectedException \InvalidArgumentException
*/
public function testCreateCredentialsWrapperInvalidArgumentException($auth, $authConfig)
{
$client = new GapicClientTraitStub();
$client->call('createCredentialsWrapper', [
$auth,
$authConfig,
]);
}
public function createCredentialsWrapperInvalidArgumentExceptionData()
{
return [
[['array' => 'without right keys'], []],
];
}
/**
* @dataProvider createTransportData
*/
public function testCreateTransport($apiEndpoint, $transport, $transportConfig, $expectedTransportClass)
{
if ($expectedTransportClass == GrpcTransport::class) {
$this->requiresGrpcExtension();
}
$client = new GapicClientTraitStub();
$transport = $client->call('createTransport', [
$apiEndpoint,
$transport,
$transportConfig
]);
$this->assertEquals($expectedTransportClass, get_class($transport));
}
public function createTransportData()
{
$defaultTransportClass = extension_loaded('grpc')
? GrpcTransport::class
: RestTransport::class;
$apiEndpoint = 'address:443';
$transport = extension_loaded('grpc')
? 'grpc'
: 'rest';
$transportConfig = [
'rest' => [
'restClientConfigPath' => __DIR__ . '/testdata/test_service_rest_client_config.php',
],
];
return [
[$apiEndpoint, $transport, $transportConfig, $defaultTransportClass],
[$apiEndpoint, 'grpc', $transportConfig, GrpcTransport::class],
[$apiEndpoint, 'rest', $transportConfig, RestTransport::class],
[$apiEndpoint, 'grpc-fallback', $transportConfig, GrpcFallbackTransport::class],
];
}
/**
* @dataProvider createTransportDataInvalid
* @expectedException \Google\ApiCore\ValidationException
*/
public function testCreateTransportInvalid($apiEndpoint, $transport, $transportConfig)
{
$client = new GapicClientTraitStub();
$client->call('createTransport', [
$apiEndpoint,
$transport,
$transportConfig
]);
}
public function createTransportDataInvalid()
{
$apiEndpoint = 'address:443';
$transportConfig = [
'rest' => [
'restConfigPath' => __DIR__ . '/testdata/test_service_rest_client_config.php',
],
];
return [
[$apiEndpoint, null, $transportConfig],
[$apiEndpoint, ['transport' => 'weirdstring'], $transportConfig],
[$apiEndpoint, ['transport' => new \stdClass()], $transportConfig],
[$apiEndpoint, ['transport' => 'rest'], []],
];
}
public function testServiceAddressAlias()
{
$client = new GapicClientTraitStub();
$apiEndpoint = 'test.address.com:443';
$updatedOptions = $client->call('buildClientOptions', [
['serviceAddress' => $apiEndpoint]
]);
$client->call('setClientOptions', [$updatedOptions]);
$this->assertEquals($apiEndpoint, $updatedOptions['apiEndpoint']);
$this->assertArrayNotHasKey('serviceAddress', $updatedOptions);
}
/**
* @dataProvider setClientOptionsData
*/
public function testSetClientOptions($options, $expectedProperties)
{
$client = new GapicClientTraitStub();
$updatedOptions = $client->call('buildClientOptions', [$options]);
$client->call('setClientOptions', [$updatedOptions]);
foreach ($expectedProperties as $propertyName => $expectedValue) {
$actualValue = $client->get($propertyName);
$this->assertEquals($expectedValue, $actualValue);
}
}
public function setClientOptionsData()
{
$clientDefaults = GapicClientTraitStub::getClientDefaults();
$expectedRetrySettings = RetrySettings::load(
$clientDefaults['serviceName'],
json_decode(file_get_contents($clientDefaults['clientConfig']), true),
[]
);
$disabledRetrySettings = [];
foreach ($expectedRetrySettings as $method => $retrySettingsItem) {
$disabledRetrySettings[$method] = $retrySettingsItem->with([
'retriesEnabled' => false
]);
}
$expectedProperties = [
'serviceName' => 'test.interface.v1.api',
'agentHeader' => AgentHeader::buildAgentHeader([]),
'retrySettings' => $expectedRetrySettings,
];
return [
[[], $expectedProperties],
[['disableRetries' => true], ['retrySettings' => $disabledRetrySettings] + $expectedProperties],
];
}
/**
* @dataProvider buildClientOptionsProvider
*/
public function testBuildClientOptions($options, $expectedUpdatedOptions)
{
if (!extension_loaded('sysvshm')) {
$this->markTestSkipped('The sysvshm extension must be installed to execute this test.');
}
$client = new GapicClientTraitStub();
$updatedOptions = $client->call('buildClientOptions', [$options]);
$this->assertEquals($expectedUpdatedOptions, $updatedOptions);
}
public function buildClientOptionsProvider()
{
$apiConfig = new ApiConfig();
$apiConfig->mergeFromJsonString(
file_get_contents(__DIR__.'/testdata/test_service_grpc_config.json')
);
$grpcGcpConfig = new Config('test.address.com:443', $apiConfig);
$defaultOptions = [
'apiEndpoint' => 'test.address.com:443',
'serviceName' => 'test.interface.v1.api',
'clientConfig' => __DIR__ . '/testdata/test_service_client_config.json',
'descriptorsConfigPath' => __DIR__.'/testdata/test_service_descriptor_config.php',
'gcpApiConfigPath' => __DIR__.'/testdata/test_service_grpc_config.json',
'disableRetries' => false,
'auth' => null,
'authConfig' => null,
'transport' => null,
'transportConfig' => [
'grpc' => [
'stubOpts' => [
'grpc_call_invoker' => $grpcGcpConfig->callInvoker(),
'grpc.service_config_disable_resolution' => 1
]
],
'rest' => [
'restClientConfigPath' => __DIR__.'/testdata/test_service_rest_client_config.php',
],
'grpc-fallback' => [],
],
'credentials' => null,
'credentialsConfig' => [],
'gapicVersion' => null,
'libName' => null,
'libVersion' => null,
];
$restConfigOptions = $defaultOptions;
$restConfigOptions['transportConfig']['rest'] += [
'customRestConfig' => 'value'
];
$grpcConfigOptions = $defaultOptions;
$grpcConfigOptions['transportConfig']['grpc'] += [
'customGrpcConfig' => 'value'
];
return [
[[], $defaultOptions],
[
[
'transportConfig' => [
'rest' => [
'customRestConfig' => 'value'
]
]
], $restConfigOptions
],
[
[
'transportConfig' => [
'grpc' => [
'customGrpcConfig' => 'value'
]
]
], $grpcConfigOptions
],
];
}
public function testModifyClientOptions()
{
$options = [];
$client = new GapicClientTraitStubExtension();
$updatedOptions = $client->call('buildClientOptions', [$options]);
$this->assertArrayHasKey('addNewOption', $updatedOptions);
$this->assertSame(true, $updatedOptions['disableRetries']);
}
private function buildClientToTestModifyCallMethods()
{
$header = AgentHeader::buildAgentHeader([]);
$retrySettings = $this->getMockBuilder(RetrySettings::class)
->disableOriginalConstructor()
->getMock();
$longRunningDescriptors = [
'longRunning' => [
'operationReturnType' => 'operationType',
'metadataReturnType' => 'metadataType',
]
];
$pageStreamingDescriptors = [
'pageStreaming' => [
'requestPageTokenGetMethod' => 'getPageToken',
'requestPageTokenSetMethod' => 'setPageToken',
'requestPageSizeGetMethod' => 'getPageSize',
'requestPageSizeSetMethod' => 'setPageSize',
'responsePageTokenGetMethod' => 'getNextPageToken',
'resourcesGetMethod' => 'getResources',
],
];
$transport = $this->getMock(TransportInterface::class);
$credentialsWrapper = CredentialsWrapper::build([]);
$client = new GapicClientTraitStubExtension();
$client->set('transport', $transport);
$client->set('credentialsWrapper', $credentialsWrapper);
$client->set('agentHeader', $header);
$client->set('retrySettings', [
'simpleMethod' => $retrySettings,
'longRunningMethod' => $retrySettings,
'pagedMethod' => $retrySettings,
'bidiStreamingMethod' => $retrySettings,
'clientStreamingMethod' => $retrySettings,
'serverStreamingMethod' => $retrySettings,
]);
$client->set('descriptors', [
'longRunningMethod' => $longRunningDescriptors,
'pagedMethod' => $pageStreamingDescriptors,
]);
return [$client, $transport];
}
public function testModifyUnaryCallFromStartCall()
{
list($client, $transport) = $this->buildClientToTestModifyCallMethods();
$transport->expects($this->once())
->method('startUnaryCall')
->with(
$this->isInstanceOf(Call::class),
$this->equalTo([
'transportOptions' => [
'custom' => ['addModifyUnaryCallableOption' => true]
],
'headers' => AgentHeader::buildAgentHeader([]),
'credentialsWrapper' => CredentialsWrapper::build([])
])
)
->willReturn(new FulfilledPromise(new Operation()));
$client->call('startCall', [
'simpleMethod',
'decodeType',
[],
new MockRequest(),
])->wait();
}
public function testModifyUnaryCallFromOperationsCall()
{
list($client, $transport) = $this->buildClientToTestModifyCallMethods();
$transport->expects($this->once())
->method('startUnaryCall')
->with(
$this->isInstanceOf(Call::class),
$this->equalTo([
'transportOptions' => [
'custom' => ['addModifyUnaryCallableOption' => true]
],
'headers' => AgentHeader::buildAgentHeader([]),
'credentialsWrapper' => CredentialsWrapper::build([])
])
)
->willReturn(new FulfilledPromise(new Operation()));
$operationsClient = $this->getMockBuilder(OperationsClient::class)
->disableOriginalConstructor()
->getMock();
$client->call('startOperationsCall', [
'longRunningMethod',
[],
new MockRequest(),
$operationsClient
])->wait();
}
public function testModifyUnaryCallFromGetPagedListResponse()
{
list($client, $transport) = $this->buildClientToTestModifyCallMethods();
$transport->expects($this->once())
->method('startUnaryCall')
->with(
$this->isInstanceOf(Call::class),
$this->equalTo([
'transportOptions' => [
'custom' => ['addModifyUnaryCallableOption' => true]
],
'headers' => AgentHeader::buildAgentHeader([]),
'credentialsWrapper' => CredentialsWrapper::build([])
])
)
->willReturn(new FulfilledPromise(new Operation()));
$client->call('getPagedListResponse', [
'pagedMethod',
[],
'decodeType',
new MockRequest(),
]);
}
/**
* @dataProvider modifyStreamingCallFromStartCallData
*/
public function testModifyStreamingCallFromStartCall($callArgs, $expectedMethod, $expectedResponse)
{
list($client, $transport) = $this->buildClientToTestModifyCallMethods();
$transport->expects($this->once())
->method($expectedMethod)
->with(
$this->isInstanceOf(Call::class),
$this->equalTo([
'transportOptions' => [
'custom' => ['addModifyStreamingCallable' => true]
],
'headers' => AgentHeader::buildAgentHeader([]),
'credentialsWrapper' => CredentialsWrapper::build([])
])
)
->willReturn($expectedResponse);
$client->call('startCall', $callArgs);
}
public function modifyStreamingCallFromStartCallData()
{
return [
[
[
'bidiStreamingMethod',
'',
[],
null,
Call::BIDI_STREAMING_CALL
],
'startBidiStreamingCall',
$this->getMockBuilder(BidiStream::class)
->disableOriginalConstructor()
->getMock()
],
[
[
'clientStreamingMethod',
'',
[],
null,
Call::CLIENT_STREAMING_CALL
],
'startClientStreamingCall',
$this->getMockBuilder(ClientStream::class)
->disableOriginalConstructor()
->getMock()
],
[
[
'serverStreamingMethod',
'',
[],
new MockRequest(),
Call::SERVER_STREAMING_CALL
],
'startServerStreamingCall',
$this->getMockBuilder(ServerStream::class)
->disableOriginalConstructor()
->getMock()
],
];
}
public function testGetTransport()
{
$transport = $this->getMock(TransportInterface::class);
$client = new GapicClientTraitStub();
$client->set('transport', $transport);
$this->assertEquals($transport, $client->call('getTransport'));
}
public function testGetCredentialsWrapper()
{
$credentialsWrapper = $this->getMockBuilder(CredentialsWrapper::class)
->disableOriginalConstructor()
->getMock();
$client = new GapicClientTraitStub();
$client->set('credentialsWrapper', $credentialsWrapper);
$this->assertEquals($credentialsWrapper, $client->call('getCredentialsWrapper'));
}
public function testUserProjectHeaderIsSetWhenProvidingQuotaProject()
{
$quotaProject = 'test-quota-project';
$credentialsWrapper = $this->getMockBuilder(CredentialsWrapper::class)
->disableOriginalConstructor()
->getMock();
$credentialsWrapper->expects($this->once())
->method('getQuotaProject')
->willReturn($quotaProject);
$transport = $this->getMock(TransportInterface::class);
$transport->expects($this->once())
->method('startUnaryCall')
->with(
$this->isInstanceOf(Call::class),
$this->equalTo([
'headers' => AgentHeader::buildAgentHeader([]) + [
'X-Goog-User-Project' => [$quotaProject]
],
'credentialsWrapper' => $credentialsWrapper
])
);
$client = new GapicClientTraitStub();
$updatedOptions = $client->call('buildClientOptions', [
[
'transport' => $transport,
'credentials' => $credentialsWrapper,
]
]);
$client->call('setClientOptions', [$updatedOptions]);
$client->set('retrySettings', [
'method' => $this->getMockBuilder(RetrySettings::class)
->disableOriginalConstructor()
->getMock()
]
);
$client->call('startCall', [
'method',
'decodeType'
]);
}
public function testDefaultScopes()
{
$client = new GapicClientTraitDefaultScopeAndAudienceStub();
// verify scopes are not set by default
$defaultOptions = $client->call('buildClientOptions', [[]]);
$this->assertArrayNotHasKey('scopes', $defaultOptions['credentialsConfig']);
// verify scopes are set when a custom api endpoint is used
$defaultOptions = $client->call('buildClientOptions', [[
'apiEndpoint' => 'www.someotherendpoint.com',
]]);
$this->assertArrayHasKey('scopes', $defaultOptions['credentialsConfig']);
$this->assertEquals(
$client::$serviceScopes,
$defaultOptions['credentialsConfig']['scopes']
);
// verify user-defined scopes override default scopes
$defaultOptions = $client->call('buildClientOptions', [[
'credentialsConfig' => ['scopes' => ['user-scope-1']],
'apiEndpoint' => 'www.someotherendpoint.com',
]]);
$this->assertArrayHasKey('scopes', $defaultOptions['credentialsConfig']);
$this->assertEquals(
['user-scope-1'],
$defaultOptions['credentialsConfig']['scopes']
);
// verify empty default scopes has no effect
$client::$serviceScopes = null;
$defaultOptions = $client->call('buildClientOptions', [[
'apiEndpoint' => 'www.someotherendpoint.com',
]]);
$this->assertArrayNotHasKey('scopes', $defaultOptions['credentialsConfig']);
}
public function testDefaultAudience()
{
$retrySettings = $this->prophesize(RetrySettings::class);
$credentialsWrapper = $this->prophesize(CredentialsWrapper::class)
->reveal();
$transport = $this->prophesize(TransportInterface::class);
$transport
->startUnaryCall(
Argument::any(),
[
'audience' => 'https://service-address/',
'headers' => [],
'credentialsWrapper' => $credentialsWrapper,
]
)
->shouldBeCalledOnce();
$client = new GapicClientTraitDefaultScopeAndAudienceStub();
$client->set('credentialsWrapper', $credentialsWrapper);
$client->set('agentHeader', []);
$client->set(
'retrySettings',
['method.name' => $retrySettings->reveal()]
);
$client->set('transport', $transport->reveal());
$client->call('startCall', ['method.name', 'decodeType']);
$transport
->startUnaryCall(
Argument::any(),
[
'audience' => 'custom-audience',
'headers' => [],
'credentialsWrapper' => $credentialsWrapper,
]
)
->shouldBeCalledOnce();
$client->call('startCall', ['method.name', 'decodeType', [
'audience' => 'custom-audience',
]]);
}
public function testDefaultAudienceWithOperations()
{
$retrySettings = $this->prophesize(RetrySettings::class);
$credentialsWrapper = $this->prophesize(CredentialsWrapper::class)
->reveal();
$transport = $this->prophesize(TransportInterface::class);
$transport
->startUnaryCall(
Argument::any(),
[
'audience' => 'https://service-address/',
'headers' => [],
'credentialsWrapper' => $credentialsWrapper,
]
)
->shouldBeCalledOnce()
->willReturn(new FulfilledPromise(new Operation()));
$longRunningDescriptors = [
'longRunning' => [
'operationReturnType' => 'operationType',
'metadataReturnType' => 'metadataType',
'initialPollDelayMillis' => 100,
'pollDelayMultiplier' => 1.0,
'maxPollDelayMillis' => 200,
'totalPollTimeoutMillis' => 300,
]
];
$client = new GapicClientTraitDefaultScopeAndAudienceStub();
$client->set('credentialsWrapper', $credentialsWrapper);
$client->set('agentHeader', []);
$client->set(
'retrySettings',
['method.name' => $retrySettings->reveal()]
);
$client->set('transport', $transport->reveal());
$client->set('descriptors', ['method.name' => $longRunningDescriptors]);
$operationsClient = $this->getMockBuilder(OperationsClient::class)
->disableOriginalConstructor()
->getMock();
// Test startOperationsCall with default audience
$client->call('startOperationsCall', [
'method.name',
[],
new MockRequest(),
$operationsClient,
])->wait();
}
public function testDefaultAudienceWithPagedList()
{
$retrySettings = $this->prophesize(RetrySettings::class);
$credentialsWrapper = $this->prophesize(CredentialsWrapper::class)
->reveal();
$transport = $this->prophesize(TransportInterface::class);
$transport
->startUnaryCall(
Argument::any(),
[
'audience' => 'https://service-address/',
'headers' => [],
'credentialsWrapper' => $credentialsWrapper,
]
)
->shouldBeCalledOnce()
->willReturn(new FulfilledPromise(new Operation()));
$pageStreamingDescriptors = [
'pageStreaming' => [
'requestPageTokenGetMethod' => 'getPageToken',
'requestPageTokenSetMethod' => 'setPageToken',
'requestPageSizeGetMethod' => 'getPageSize',
'requestPageSizeSetMethod' => 'setPageSize',
'responsePageTokenGetMethod' => 'getNextPageToken',
'resourcesGetMethod' => 'getResources',
],
];
$client = new GapicClientTraitDefaultScopeAndAudienceStub();
$client->set('credentialsWrapper', $credentialsWrapper);
$client->set('agentHeader', []);
$client->set(
'retrySettings',
['method.name' => $retrySettings->reveal()]
);
$client->set('transport', $transport->reveal());
$client->set('descriptors', [
'method.name' => $pageStreamingDescriptors
]);
// Test getPagedListResponse with default audience
$client->call('getPagedListResponse', [
'method.name',
[],
'decodeType',
new MockRequest(),
]);
}
public function testSupportedTransportOverrideWithInvalidTransport()
{
$this->setExpectedException(
ValidationException::class,
'Unexpected transport option "grpc". Supported transports: rest'
);
$client = new GapicClientTraitRestOnly(['transport' => 'grpc']);
}
public function testSupportedTransportOverrideWithDefaultTransport()
{
$client = new GapicClientTraitRestOnly();
$this->assertInstanceOf(RestTransport::class, $client->getTransport());
}
public function testSupportedTransportOverrideWithExplicitTransport()
{
$client = new GapicClientTraitRestOnly(['transport' => 'rest']);
$this->assertInstanceOf(RestTransport::class, $client->getTransport());
}
}
class GapicClientTraitStub
{
use GapicClientTrait;
public static function getClientDefaults()
{
return [
'apiEndpoint' => 'test.address.com:443',
'serviceAddress' => 'test.address.com:443',
'serviceName' => 'test.interface.v1.api',
'clientConfig' => __DIR__ . '/testdata/test_service_client_config.json',
'descriptorsConfigPath' => __DIR__.'/testdata/test_service_descriptor_config.php',
'gcpApiConfigPath' => __DIR__.'/testdata/test_service_grpc_config.json',
'disableRetries' => false,
'auth' => null,
'authConfig' => null,
'transport' => null,
'transportConfig' => [
'rest' => [
'restClientConfigPath' => __DIR__.'/testdata/test_service_rest_client_config.php',
]
],
];
}
public function call($fn, array $args = [])
{
return call_user_func_array([$this, $fn], $args);
}
public function set($name, $val, $static = false)
{
if (!property_exists($this, $name)) {
throw new \InvalidArgumentException("Property not found: $name");
}
if ($static) {
$this::$$name = $val;
} else {
$this->$name = $val;
}
}
public function get($name)
{
if (!property_exists($this, $name)) {
throw new \InvalidArgumentException("Property not found: $name");
}
return $this->$name;
}
}
class GapicClientTraitStubExtension extends GapicClientTraitStub
{
protected function modifyClientOptions(array &$options)
{
$options['disableRetries'] = true;
$options['addNewOption'] = true;
}
protected function modifyUnaryCallable(callable &$callable)
{
$originalCallable = $callable;
$callable = function ($call, $options) use ($originalCallable) {
$options['transportOptions'] = [
'custom' => ['addModifyUnaryCallableOption' => true]
];
return $originalCallable($call, $options);
};
}
protected function modifyStreamingCallable(callable &$callable)
{
$originalCallable = $callable;
$callable = function ($call, $options) use ($originalCallable) {
$options['transportOptions'] = [
'custom' => ['addModifyStreamingCallable' => true]
];
return $originalCallable($call, $options);
};
}
}
class GapicClientTraitDefaultScopeAndAudienceStub
{
use GapicClientTrait;
const SERVICE_ADDRESS = 'service-address';
public static $serviceScopes = [
'default-scope-1',
'default-scope-2',
];
public static function getClientDefaults()
{
return [
'apiEndpoint' => 'test.address.com:443',
'credentialsConfig' => [
'defaultScopes' => self::$serviceScopes,
],
];
}
public function call($fn, array $args = [])
{
return call_user_func_array([$this, $fn], $args);
}
public function set($name, $val, $static = false)
{
if (!property_exists($this, $name)) {
throw new \InvalidArgumentException("Property not found: $name");
}
if ($static) {
$this::$$name = $val;
} else {
$this->$name = $val;
}
}
}
class GapicClientTraitRestOnly
{
use GapicClientTrait;
public function __construct($options = [])
{
$options['apiEndpoint'] = 'api.example.com';
$this->setClientOptions($this->buildClientOptions($options));
}
public static function getClientDefaults()
{
return [
'apiEndpoint' => 'test.address.com:443',
'serviceAddress' => 'test.address.com:443',
'serviceName' => 'test.interface.v1.api',
'clientConfig' => __DIR__ . '/testdata/test_service_client_config.json',
'descriptorsConfigPath' => __DIR__.'/testdata/test_service_descriptor_config.php',
'transportConfig' => [
'rest' => [
'restClientConfigPath' => __DIR__.'/testdata/test_service_rest_client_config.php',
]
],
];
}
public function getTransport()
{
return $this->transport;
}
private static function supportedTransports()
{
return ['rest', 'fake-transport'];
}
private static function defaultTransport()
{
return 'rest';
}
}
tests/Tests/Unit/GeneratedTestTest.php 0000644 00000010755 15174274026 0014056 0 ustar 00 assertProtobufEquals($expected, $actual);
}
/**
* @dataProvider getFailureCases
*/
public function testFailure($expected, $actual)
{
try {
$this->assertProtobufEquals($expected, $actual);
} catch (PHPUnit_Framework_ExpectationFailedException $ex) {
// As expected the assertion failed, silently return
return;
}
// The assertion did not fail, make the test fail
$this->fail('This test did not fail as expected');
}
public function getSuccessCases()
{
$monitoringA = new Monitoring_MonitoringDestination();
$monitoringA->setMonitoredResource("type");
$monitoringB = new Monitoring_MonitoringDestination();
$monitoringB->setMonitoredResource("type");
$emptyRepeatedA = $monitoringA->getMetrics();
$emptyRepeatedB = $monitoringB->getMetrics();
$monitoringC = new Monitoring_MonitoringDestination();
$monitoringD = new Monitoring_MonitoringDestination();
$repeatedC = $monitoringC->getMetrics();
$repeatedC[] = "metric";
$repeatedD = $monitoringD->getMetrics();
$repeatedD[] = "metric";
return [
[[], []],
[[], $emptyRepeatedB],
[$emptyRepeatedA, []],
[$emptyRepeatedA, $emptyRepeatedB],
[[1, 2], [1, 2]],
[["abc", $monitoringA], ["abc", $monitoringB]],
[["metric"], $repeatedD],
[$repeatedC, ["metric"]],
[$repeatedC, $repeatedD],
];
}
public function getFailureCases()
{
$monitoringA = new Monitoring_MonitoringDestination();
$monitoringA->setMonitoredResource("typeA");
$monitoringB = new Monitoring_MonitoringDestination();
$monitoringB->setMonitoredResource("typeB");
$emptyRepeatedA = $monitoringA->getMetrics();
$emptyRepeatedB = $monitoringB->getMetrics();
$monitoringC = new Monitoring_MonitoringDestination();
$monitoringD = new Monitoring_MonitoringDestination();
$repeatedC = $monitoringC->getMetrics();
$repeatedC[] = "metricA";
$repeatedD = $monitoringD->getMetrics();
$repeatedD[] = "metricB";
return [
[[], [1]],
[[1], []],
[[1], [2]],
[[$monitoringA], [$monitoringB]],
[[], $repeatedD],
[$repeatedC, []],
[$emptyRepeatedA, [1]],
[[1], $emptyRepeatedB],
[$emptyRepeatedA, $repeatedD],
[$repeatedC, $emptyRepeatedB],
[$repeatedC, $repeatedD],
];
}
}
tests/Tests/Unit/GrpcSupportTraitTest.php 0000644 00000002576 15174274026 0014616 0 ustar 00 '',
'scopes' => [],
]);
}
/**
* @test
*/
public function getOperationTest()
{
$transport = $this->createTransport();
$client = $this->createClient(['transport' => $transport]);
$this->assertTrue($transport->isExhausted());
// Mock response
$name2 = 'name2-1052831874';
$done = true;
$expectedResponse = new Operation();
$expectedResponse->setName($name2);
$expectedResponse->setDone($done);
$transport->addResponse($expectedResponse);
// Mock request
$name = 'name3373707';
$response = $client->getOperation($name);
$this->assertEquals($expectedResponse, $response);
$actualRequests = $transport->popReceivedCalls();
$this->assertSame(1, count($actualRequests));
$actualFuncCall = $actualRequests[0]->getFuncCall();
$actualRequestObject = $actualRequests[0]->getRequestObject();
$this->assertSame('/google.longrunning.Operations/GetOperation', $actualFuncCall);
$actualValue = $actualRequestObject->getName();
$this->assertProtobufEquals($name, $actualValue);
$this->assertTrue($transport->isExhausted());
}
/**
* @test
*/
public function getOperationExceptionTest()
{
$transport = $this->createTransport();
$client = $this->createClient(['transport' => $transport]);
$this->assertTrue($transport->isExhausted());
$status = new stdClass();
$status->code = Code::DATA_LOSS;
$status->details = 'internal error';
$expectedExceptionMessage = json_encode([
'message' => 'internal error',
'code' => Code::DATA_LOSS,
'status' => 'DATA_LOSS',
'details' => [],
], JSON_PRETTY_PRINT);
$transport->addResponse(null, $status);
// Mock request
$name = 'name3373707';
try {
$client->getOperation($name);
// If the $client method call did not throw, fail the test
$this->fail('Expected an ApiException, but no exception was thrown.');
} catch (ApiException $ex) {
$this->assertEquals($status->code, $ex->getCode());
$this->assertEquals($expectedExceptionMessage, $ex->getMessage());
}
// Call popReceivedCalls to ensure the stub is exhausted
$transport->popReceivedCalls();
$this->assertTrue($transport->isExhausted());
}
/**
* @test
*/
public function listOperationsTest()
{
$transport = $this->createTransport();
$client = $this->createClient(['transport' => $transport]);
$this->assertTrue($transport->isExhausted());
// Mock response
$nextPageToken = '';
$operationsElement = new Operation();
$operations = [$operationsElement];
$expectedResponse = new ListOperationsResponse();
$expectedResponse->setNextPageToken($nextPageToken);
$expectedResponse->setOperations($operations);
$transport->addResponse($expectedResponse);
// Mock request
$name = 'name3373707';
$filter = 'filter-1274492040';
$response = $client->listOperations($name, $filter);
$this->assertEquals($expectedResponse, $response->getPage()->getResponseObject());
$resources = iterator_to_array($response->iterateAllElements());
$this->assertSame(1, count($resources));
$this->assertEquals($expectedResponse->getOperations()[0], $resources[0]);
$actualRequests = $transport->popReceivedCalls();
$this->assertSame(1, count($actualRequests));
$actualFuncCall = $actualRequests[0]->getFuncCall();
$actualRequestObject = $actualRequests[0]->getRequestObject();
$this->assertSame('/google.longrunning.Operations/ListOperations', $actualFuncCall);
$actualValue = $actualRequestObject->getName();
$this->assertProtobufEquals($name, $actualValue);
$actualValue = $actualRequestObject->getFilter();
$this->assertProtobufEquals($filter, $actualValue);
$this->assertTrue($transport->isExhausted());
}
/**
* @test
*/
public function listOperationsExceptionTest()
{
$transport = $this->createTransport();
$client = $this->createClient(['transport' => $transport]);
$this->assertTrue($transport->isExhausted());
$status = new stdClass();
$status->code = Code::DATA_LOSS;
$status->details = 'internal error';
$expectedExceptionMessage = json_encode([
'message' => 'internal error',
'code' => Code::DATA_LOSS,
'status' => 'DATA_LOSS',
'details' => [],
], JSON_PRETTY_PRINT);
$transport->addResponse(null, $status);
// Mock request
$name = 'name3373707';
$filter = 'filter-1274492040';
try {
$client->listOperations($name, $filter);
// If the $client method call did not throw, fail the test
$this->fail('Expected an ApiException, but no exception was thrown.');
} catch (ApiException $ex) {
$this->assertEquals($status->code, $ex->getCode());
$this->assertEquals($expectedExceptionMessage, $ex->getMessage());
}
// Call popReceivedCalls to ensure the stub is exhausted
$transport->popReceivedCalls();
$this->assertTrue($transport->isExhausted());
}
/**
* @test
*/
public function cancelOperationTest()
{
$transport = $this->createTransport();
$client = $this->createClient(['transport' => $transport]);
$this->assertTrue($transport->isExhausted());
// Mock response
$expectedResponse = new GPBEmpty();
$transport->addResponse($expectedResponse);
// Mock request
$name = 'name3373707';
$client->cancelOperation($name);
$actualRequests = $transport->popReceivedCalls();
$this->assertSame(1, count($actualRequests));
$actualFuncCall = $actualRequests[0]->getFuncCall();
$actualRequestObject = $actualRequests[0]->getRequestObject();
$this->assertSame('/google.longrunning.Operations/CancelOperation', $actualFuncCall);
$actualValue = $actualRequestObject->getName();
$this->assertProtobufEquals($name, $actualValue);
$this->assertTrue($transport->isExhausted());
}
/**
* @test
*/
public function cancelOperationExceptionTest()
{
$transport = $this->createTransport();
$client = $this->createClient(['transport' => $transport]);
$this->assertTrue($transport->isExhausted());
$status = new stdClass();
$status->code = Code::DATA_LOSS;
$status->details = 'internal error';
$expectedExceptionMessage = json_encode([
'message' => 'internal error',
'code' => Code::DATA_LOSS,
'status' => 'DATA_LOSS',
'details' => [],
], JSON_PRETTY_PRINT);
$transport->addResponse(null, $status);
// Mock request
$name = 'name3373707';
try {
$client->cancelOperation($name);
// If the $client method call did not throw, fail the test
$this->fail('Expected an ApiException, but no exception was thrown.');
} catch (ApiException $ex) {
$this->assertEquals($status->code, $ex->getCode());
$this->assertEquals($expectedExceptionMessage, $ex->getMessage());
}
// Call popReceivedCalls to ensure the stub is exhausted
$transport->popReceivedCalls();
$this->assertTrue($transport->isExhausted());
}
/**
* @test
*/
public function deleteOperationTest()
{
$transport = $this->createTransport();
$client = $this->createClient(['transport' => $transport]);
$this->assertTrue($transport->isExhausted());
// Mock response
$expectedResponse = new GPBEmpty();
$transport->addResponse($expectedResponse);
// Mock request
$name = 'name3373707';
$client->deleteOperation($name);
$actualRequests = $transport->popReceivedCalls();
$this->assertSame(1, count($actualRequests));
$actualFuncCall = $actualRequests[0]->getFuncCall();
$actualRequestObject = $actualRequests[0]->getRequestObject();
$this->assertSame('/google.longrunning.Operations/DeleteOperation', $actualFuncCall);
$actualValue = $actualRequestObject->getName();
$this->assertProtobufEquals($name, $actualValue);
$this->assertTrue($transport->isExhausted());
}
/**
* @test
*/
public function deleteOperationExceptionTest()
{
$transport = $this->createTransport();
$client = $this->createClient(['transport' => $transport]);
$this->assertTrue($transport->isExhausted());
$status = new stdClass();
$status->code = Code::DATA_LOSS;
$status->details = 'internal error';
$expectedExceptionMessage = json_encode([
'message' => 'internal error',
'code' => Code::DATA_LOSS,
'status' => 'DATA_LOSS',
'details' => [],
], JSON_PRETTY_PRINT);
$transport->addResponse(null, $status);
// Mock request
$name = 'name3373707';
try {
$client->deleteOperation($name);
// If the $client method call did not throw, fail the test
$this->fail('Expected an ApiException, but no exception was thrown.');
} catch (ApiException $ex) {
$this->assertEquals($status->code, $ex->getCode());
$this->assertEquals($expectedExceptionMessage, $ex->getMessage());
}
// Call popReceivedCalls to ensure the stub is exhausted
$transport->popReceivedCalls();
$this->assertTrue($transport->isExhausted());
}
}
tests/Tests/Unit/Middleware/FixedHeaderMiddlewareTest.php 0000644 00000010644 15174274026 0017540 0 ustar 00 getMock(Call::class, [], [], '', false);
$fixedHeader = [
'x-goog-api-client' => ['gl-php/5.5.0 gccl/0.0.0 gapic/0.9.0 gax/1.0.0 grpc/1.0.1']
];
$handlerCalled = false;
$callable = function(Call $call, $options) use ($fixedHeader, &$handlerCalled) {
$this->assertEquals($fixedHeader, $options['headers']);
$handlerCalled = true;
};
$middleware = new FixedHeaderMiddleware($callable, $fixedHeader);
$middleware($call, []);
$this->assertTrue($handlerCalled);
}
public function testCustomHeaderNoOverride()
{
$call = $this->getMock(Call::class, [], [], '', false);
$fixedHeader = [
'my-header' => ['some header string'],
'fixed-only' => ['fixed header only'],
];
$userHeader = [
'my-header' => ['some alternate string'],
'user-only' => ['user only header'],
];
$expectedHeader = [
'my-header' => ['some alternate string'],
'fixed-only' => ['fixed header only'],
'user-only' => ['user only header'],
];
$handlerCalled = false;
$callable = function(Call $call, $options) use ($expectedHeader, &$handlerCalled) {
$this->assertEquals($expectedHeader, $options['headers']);
$handlerCalled = true;
};
$middleware = new FixedHeaderMiddleware($callable, $fixedHeader);
$middleware($call, ['headers' => $userHeader]);
$this->assertTrue($handlerCalled);
}
public function testCustomHeaderOverride()
{
$call = $this->getMock(Call::class, [], [], '', false);
$fixedHeader = [
'my-header' => ['some header string'],
'fixed-only' => ['fixed header only'],
];
$userHeader = [
'my-header' => ['some alternate string'],
'user-only' => ['user only header'],
];
$expectedHeader = [
'my-header' => ['some header string'],
'fixed-only' => ['fixed header only'],
'user-only' => ['user only header'],
];
$handlerCalled = false;
$callable = function(Call $call, $options) use ($expectedHeader, &$handlerCalled) {
$this->assertEquals($expectedHeader, $options['headers']);
$handlerCalled = true;
};
$middleware = new FixedHeaderMiddleware($callable, $fixedHeader, true);
$middleware($call, ['headers' => $userHeader]);
$this->assertTrue($handlerCalled);
}
}
tests/Tests/Unit/Middleware/RetryMiddlewareTest.php 0000644 00000015014 15174274026 0016471 0 ustar 00 getMock(Call::class, [], [], '', false);
$retrySettings = RetrySettings::constructDefault()
->with([
'retriesEnabled' => false,
'retryableCodes' => []
]);
$callCount = 0;
$handler = function(Call $call, $options) use (&$callCount) {
return new Promise(function () use (&$callCount) {
throw new ApiException('Call Count: ' . $callCount += 1, 0, '');
});
};
$middleware = new RetryMiddleware($handler, $retrySettings);
$response = $middleware(
$call,
[]
)->wait();
}
public function testRetryBackoff()
{
$call = $this->getMock(Call::class, [], [], '', false);
$retrySettings = RetrySettings::constructDefault()
->with([
'retriesEnabled' => true,
'retryableCodes' => [ApiStatus::CANCELLED],
]);
$callCount = 0;
$handler = function(Call $call, $options) use (&$callCount) {
$callCount += 1;
return $promise = new Promise(function () use (&$promise, $callCount) {
if ($callCount < 3) {
throw new ApiException('Cancelled!', Code::CANCELLED, ApiStatus::CANCELLED);
}
$promise->resolve('Ok!');
});
};
$middleware = new RetryMiddleware($handler, $retrySettings);
$response = $middleware(
$call,
[]
)->wait();
$this->assertEquals('Ok!', $response);
$this->assertEquals(3, $callCount);
}
/**
* @expectedException Google\ApiCore\ApiException
* @expectedExceptionMessage Retry total timeout exceeded.
*/
public function testRetryTimeoutExceedsMaxTimeout()
{
$call = $this->getMock(Call::class, [], [], '', false);
$retrySettings = RetrySettings::constructDefault()
->with([
'retriesEnabled' => true,
'retryableCodes' => [ApiStatus::CANCELLED],
'totalTimeoutMillis' => 0,
]);
$handler = function(Call $call, $options) {
return new Promise(function () {
throw new ApiException('Cancelled!', Code::CANCELLED, ApiStatus::CANCELLED);
});
};
$middleware = new RetryMiddleware($handler, $retrySettings);
$response = $middleware(
$call,
[]
)->wait();
}
/**
* @expectedException Google\ApiCore\ApiException
* @expectedExceptionMessage Retry total timeout exceeded.
*/
public function testRetryTimeoutExceedsRealTime()
{
$call = $this->getMock(Call::class, [], [], '', false);
$retrySettings = RetrySettings::constructDefault()
->with([
'retriesEnabled' => true,
'retryableCodes' => [ApiStatus::CANCELLED],
'initialRpcTimeoutMillis' => 500,
'totalTimeoutMillis' => 1000,
]);
$handler = function(Call $call, $options) {
return new Promise(function () use ($options) {
// sleep for the duration of the timeout
if (isset($options['timeoutMillis'])) {
usleep($options['timeoutMillis'] * 1000);
}
throw new ApiException('Cancelled!', Code::CANCELLED, ApiStatus::CANCELLED);
});
};
$middleware = new RetryMiddleware($handler, $retrySettings);
$response = $middleware(
$call,
[]
)->wait();
}
public function testTimeoutMillisCallSettingsOverwrite()
{
$handlerCalled = false;
$timeout = 1234;
$handler = function (Call $call, array $options) use (&$handlerCalled, $timeout) {
$handlerCalled = true;
$this->assertEquals($timeout, $options['timeoutMillis']);
return $this->getMock(Promise::class);
};
$retrySettings = RetrySettings::constructDefault()
->with([
'retriesEnabled' => true,
'retryableCodes' => [ApiStatus::CANCELLED],
]);
$middleware = new RetryMiddleware($handler, $retrySettings);
$call = $this->getMock(Call::class, [], [], '', false);
$options = ['timeoutMillis' => $timeout];
$middleware($call, $options);
$this->assertTrue($handlerCalled);
}
}
tests/Tests/Unit/MyMessage.php 0000644 00000001310 15174274026 0012335 0 ustar 00 google.apicore.tests.unit.MyMessage
*/
class MyMessage extends \Google\Protobuf\Internal\Message
{
/**
* Constructor.
*
* @param array $data {
* Optional. Data for populating the Message object.
*
* }
*/
public function __construct($data = NULL) {
\GPBMetadata\Google\ApiCore\Tests\Unit\Example::initOnce();
parent::__construct($data);
}
}
tests/Tests/Unit/OperationResponseTest.php 0000644 00000021506 15174274026 0014773 0 ustar 00 createOperationsClient();
$op = new OperationResponse($opName, $opClient);
$this->assertSame($opName, $op->getName());
$this->assertSame($opClient, $op->getOperationsClient());
}
public function testWithoutResponse()
{
$opName = 'operations/opname';
$opClient = $this->createOperationsClient();
$op = new OperationResponse($opName, $opClient);
$this->assertNull($op->getLastProtoResponse());
$this->assertFalse($op->isDone());
$this->assertNull($op->getResult());
$this->assertNull($op->getError());
$this->assertNull($op->getMetadata());
$this->assertFalse($op->operationSucceeded());
$this->assertFalse($op->operationFailed());
$this->assertEquals([
'operationReturnType' => null,
'metadataReturnType' => null,
'initialPollDelayMillis' => 1000.0,
'pollDelayMultiplier' => 2.0,
'maxPollDelayMillis' => 60000.0,
'totalPollTimeoutMillis' => 0.0,
], $op->getDescriptorOptions());
}
public function testWithResponse()
{
$opName = 'operations/opname';
$opClient = $this->createOperationsClient();
$protoResponse = new Operation();
$op = new OperationResponse($opName, $opClient, [
'lastProtoResponse' => $protoResponse,
]);
$this->assertSame($protoResponse, $op->getLastProtoResponse());
$this->assertFalse($op->isDone());
$this->assertNull($op->getResult());
$this->assertNull($op->getError());
$this->assertNull($op->getMetadata());
$this->assertFalse($op->operationSucceeded());
$this->assertFalse($op->operationFailed());
$this->assertEquals([
'operationReturnType' => null,
'metadataReturnType' => null,
'initialPollDelayMillis' => 1000.0,
'pollDelayMultiplier' => 2.0,
'maxPollDelayMillis' => 60000.0,
'totalPollTimeoutMillis' => 0.0,
], $op->getDescriptorOptions());
$response = $this->createAny($this->createStatus(0, "response"));
$error = $this->createStatus(2, "error");
$metadata = $this->createAny($this->createStatus(0, "metadata"));
$protoResponse->setDone(true);
$protoResponse->setResponse($response);
$protoResponse->setMetadata($metadata);
$this->assertTrue($op->isDone());
$this->assertSame($response, $op->getResult());
$this->assertSame($metadata, $op->getMetadata());
$this->assertTrue($op->operationSucceeded());
$this->assertFalse($op->operationFailed());
$protoResponse->setError($error);
$this->assertNull($op->getResult());
$this->assertSame($error, $op->getError());
$this->assertFalse($op->operationSucceeded());
$this->assertTrue($op->operationFailed());
}
public function testWithOptions()
{
$opName = 'operations/opname';
$opClient = $this->createOperationsClient();
$protoResponse = new Operation();
$op = new OperationResponse($opName, $opClient, [
'operationReturnType' => '\Google\Rpc\Status',
'metadataReturnType' => '\Google\Protobuf\Any',
'lastProtoResponse' => $protoResponse,
]);
$this->assertSame($protoResponse, $op->getLastProtoResponse());
$this->assertFalse($op->isDone());
$this->assertNull($op->getResult());
$this->assertNull($op->getError());
$this->assertNull($op->getMetadata());
$this->assertEquals([
'operationReturnType' => '\Google\Rpc\Status',
'metadataReturnType' => '\Google\Protobuf\Any',
'initialPollDelayMillis' => 1000.0,
'pollDelayMultiplier' => 2.0,
'maxPollDelayMillis' => 60000.0,
'totalPollTimeoutMillis' => 0.0,
], $op->getDescriptorOptions());
$innerResponse = $this->createStatus(0, "response");
$innerMetadata = new Any();
$innerMetadata->setValue("metadata");
$response = $this->createAny($innerResponse);
$metadata = $this->createAny($innerMetadata);
$protoResponse->setDone(true);
$protoResponse->setResponse($response);
$protoResponse->setMetadata($metadata);
$this->assertTrue($op->isDone());
$this->assertEquals($innerResponse, $op->getResult());
$this->assertEquals($innerMetadata, $op->getMetadata());
}
/**
* @dataProvider pollingDataProvider
*/
public function testPolling($op, $pollArgs, $expectedSleeps, $expectedComplete)
{
$op->pollUntilComplete($pollArgs);
$this->assertEquals($op->isDone(), $expectedComplete);
$this->assertEquals($op->getSleeps(), $expectedSleeps);
}
public function pollingDataProvider()
{
$pollingArgs = [
'initialPollDelayMillis' => 10.0,
'pollDelayMultiplier' => 3.0,
'maxPollDelayMillis' => 50.0,
'totalPollTimeoutMillis' => 100.0,
];
return [
[$this->createOperationResponse([], 3), [], [1000.0, 2000.0, 4000.0], true], // Defaults
[$this->createOperationResponse([], 3), $pollingArgs, [10, 30, 50], true], // Args to pollUntilComplete
[$this->createOperationResponse($pollingArgs, 3), [], [10, 30, 50], true], // Args to constructor
[$this->createOperationResponse([], 4), [
'totalPollTimeoutMillis' => 80.0,
] + $pollingArgs, [10, 30, 50], false], // Polling timeout
];
}
private function createOperationResponse($options, $reloadCount)
{
$opName = 'operations/opname';
return new FakeOperationResponse($opName, $this->createOperationClient($reloadCount), $options);
}
private function createOperationClient($reloadCount)
{
$opClient = $this->getMock(
OperationsClient::class,
['getOperation'],
[[
'apiEndpoint' => '',
'scopes' => [],
]]
);
for ($i = 0; $i < $reloadCount - 1; $i++) {
$opClient->expects($this->at($i))
->method('getOperation')
->willReturn(new Operation());
}
$opClient->expects($this->at($reloadCount - 1))
->method('getOperation')
->willReturn((new Operation())->setDone(true));
return $opClient;
}
}
class FakeOperationResponse extends OperationResponse
{
private $currentTime = 0;
public function getSleeps()
{
return $this->sleeps;
}
public function sleepMillis($millis)
{
$this->currentTime += $millis;
$this->sleeps[] = $millis;
}
public function setTimes($times)
{
$this->times = $times;
}
public function getCurrentTimeMillis()
{
return $this->currentTime;
}
}
tests/Tests/Unit/PageStreamingDescriptorTest.php 0000644 00000004163 15174274026 0016101 0 ustar 00 'getNextPageToken',
// Missing field
// 'responsePageTokenField' => 'getNextPageToken',
'resourceField' => 'getResourcesList'
]);
}
}
tests/Tests/Unit/PageTest.php 0000644 00000012542 15174274026 0012170 0 ustar 00 createMockRequest('token');
$pageStreamingDescriptor = PageStreamingDescriptor::createFromFields([
'requestPageTokenField' => 'pageToken',
'responsePageTokenField' => 'nextPageToken',
'resourceField' => 'resourcesList'
]);
$internalCall = $this->createCallWithResponseSequence($responseSequence);
$callable = function () use ($internalCall) {
list($response, $status) = call_user_func_array(
array($internalCall, 'takeAction'),
func_get_args()
);
return $promise = new \GuzzleHttp\Promise\Promise(function () use (&$promise, $response) {
$promise->resolve($response);
});
};
$call = new Call('method', [], $mockRequest);
$options = [];
$response = $callable($call, $options)->wait();
return new Page($call, $options, $callable, $pageStreamingDescriptor, $response);
}
public function testNextPageMethods()
{
$responseA = $this->createMockResponse('nextPageToken1', ['resource1']);
$responseB = $this->createMockResponse('', ['resource2']);
$page = $this->createPage([
[$responseA, new MockStatus(Code::OK, '')],
[$responseB, new MockStatus(Code::OK, '')],
]);
$this->assertEquals($page->hasNextPage(), true);
$this->assertEquals($page->getNextPageToken(), 'nextPageToken1');
$nextPage = $page->getNextPage();
$this->assertEquals($nextPage->hasNextPage(), false);
$this->assertEquals($nextPage->getNextPageToken(), '');
$newRequest = $nextPage->getRequestObject();
$this->assertEquals($newRequest->getPageToken(), 'nextPageToken1');
// Call serializeToJsonString - this will test for bugs in the protobuf
// c extension (if installed) that can cause segmentation faults.
$newRequest->serializeToJsonString();
}
/**
* @expectedException \Google\ApiCore\ValidationException
* @expectedExceptionMessage Could not complete getNextPage operation
*/
public function testNextPageMethodsFailWithNoNextPage()
{
$responseA = $this->createMockResponse('', ['resource1']);
$page = $this->createPage([
[$responseA, new MockStatus(Code::OK, '')],
]);
$this->assertEquals($page->hasNextPage(), false);
$page->getNextPage();
}
/**
* @expectedException \Google\ApiCore\ValidationException
* @expectedExceptionMessage pageSize argument was defined, but the method does not
*/
public function testNextPageMethodsFailWithPageSizeUnsupported()
{
$responseA = $this->createMockResponse('nextPageToken1', ['resource1']);
$responseB = $this->createMockResponse('', ['resource2']);
$page = $this->createPage([
[$responseA, new MockStatus(Code::OK, '')],
[$responseB, new MockStatus(Code::OK, '')],
]);
$page->getNextPage(3);
}
public function testPageElementMethods()
{
$response = $this->createMockResponse(
'nextPageToken1',
['resource1', 'resource2', 'resource3']
);
$page = $this->createPage([
[$response, new MockStatus(Code::OK, '')],
]);
$this->assertEquals($page->getPageElementCount(), 3);
$results = iterator_to_array($page);
$this->assertEquals($results, ['resource1', 'resource2', 'resource3']);
}
}
tests/Tests/Unit/PagedListResponseTest.php 0000644 00000010075 15174274026 0014706 0 ustar 00 createMockRequest('mockToken');
$mockResponse = $this->createMockResponse('nextPageToken1', ['resource1']);
$pageAccessor = $this->makeMockPagedCall($mockRequest, $mockResponse);
$page = $pageAccessor->getPage();
$this->assertEquals($page->getNextPageToken(), 'nextPageToken1');
$this->assertEquals(iterator_to_array($page->getIterator()), ['resource1']);
}
public function testIterateAllElements()
{
$mockRequest = $this->createMockRequest('mockToken');
$mockResponse = $this->createMockResponse('', ['resource1']);
$pageAccessor = $this->makeMockPagedCall($mockRequest, $mockResponse);
$result = iterator_to_array($pageAccessor->iterateAllElements());
$this->assertEquals(['resource1'], $result);
}
public function testIterator()
{
$mockRequest = $this->createMockRequest('mockToken');
$mockResponse = $this->createMockResponse('', ['resource1']);
$pageAccessor = $this->makeMockPagedCall($mockRequest, $mockResponse);
$result = iterator_to_array($pageAccessor);
$this->assertEquals(['resource1'], $result);
}
/**
* @param mixed $mockRequest
* @param mixed $mockResponse
* @param array $options
* @return PagedListResponse
*/
private function makeMockPagedCall($mockRequest, $mockResponse, $options = [])
{
$pageStreamingDescriptor = PageStreamingDescriptor::createFromFields([
'requestPageTokenField' => 'pageToken',
'responsePageTokenField' => 'nextPageToken',
'resourceField' => 'resourcesList'
]);
$callable = function () use ($mockResponse) {
return $promise = new \GuzzleHttp\Promise\Promise(function () use (&$promise, $mockResponse) {
$promise->resolve($mockResponse);
});
};
$call = new Call('method', [], $mockRequest);
$options = [];
$response = $callable($call, $options)->wait();
$page = new Page($call, $options, $callable, $pageStreamingDescriptor, $response);
$pageAccessor = new PagedListResponse($page);
return $pageAccessor;
}
}
tests/Tests/Unit/PathTemplateTest.php 0000644 00000021774 15174274026 0013713 0 ustar 00 match('hello');
}
/**
* @expectedException \Google\ApiCore\ValidationException
* @expectedExceptionMessage Could not match path
*/
public function testFailWhenImpossibleMatch02()
{
$template = new PathTemplate('hello/world');
$template->match('hello/world/fail');
}
/**
* @expectedException \Google\ApiCore\ValidationException
* @expectedExceptionMessage Could not match path
*/
public function testFailMismatchedLiteral()
{
$template = new PathTemplate('hello/world');
$template->match('hello/world2');
}
/**
* @expectedException \Google\ApiCore\ValidationException
* @expectedExceptionMessage Cannot parse
*/
public function testFailWhenMultiplePathWildcards()
{
new PathTemplate('buckets/*/**/**/objects/*');
}
/**
* @expectedException \Google\ApiCore\ValidationException
* @expectedExceptionMessage Unexpected '{'
*/
public function testFailIfInnerBinding()
{
new PathTemplate('buckets/{hello={world}}');
}
/**
* @expectedException \Google\ApiCore\ValidationException
* @expectedExceptionMessage Expected '}'
*/
public function testFailUnexpectedEof()
{
new PathTemplate('a/{hello=world');
}
/**
* @expectedException \Google\ApiCore\ValidationException
* @expectedExceptionMessage Cannot construct PathTemplate from empty string
*/
public function testFailNullString()
{
new PathTemplate(null);
}
/**
* @expectedException \Google\ApiCore\ValidationException
* @expectedExceptionMessage Cannot construct PathTemplate from empty string
*/
public function testFailEmptyString()
{
new PathTemplate("");
}
public function testMatchAtomicResourceName()
{
$template = new PathTemplate('buckets/*/*/objects/*');
$this->assertEquals(
['$0' => 'f', '$1' => 'o', '$2' => 'bar'],
$template->match('buckets/f/o/objects/bar')
);
$template = new PathTemplate('/buckets/{hello}');
$this->assertEquals(
['hello' => 'world'],
$template->match('/buckets/world')
);
$template = new PathTemplate('/buckets/{hello=*}');
$this->assertEquals(
['hello' => 'world'],
$template->match('/buckets/world')
);
}
public function testMatchEscapedChars()
{
$template = new PathTemplate('buckets/*/objects');
$this->assertEquals(
['$0' => 'hello%2F%2Bworld'],
$template->match('buckets/hello%2F%2Bworld/objects')
);
}
/**
* @expectedException \Google\ApiCore\ValidationException
*/
public function testFailMatchWildcardWithColonInMiddle()
{
new PathTemplate('/buckets/*:action/objects');
}
public function testMatchWildcardWithColon()
{
$template = new PathTemplate('/buckets/*:action');
$this->assertEquals(
['$0' => 'foo'],
$template->match('/buckets/foo:action')
);
}
public function testMatchColonInWildcardAndTemplate()
{
$template = new PathTemplate('/buckets/*/*/*/objects/*:action');
$url = $template->render(
['$0' => 'f', '$1' => 'o', '$2' => 'o', '$3' => 'google.com:a-b']
);
$this->assertEquals($url, '/buckets/f/o/o/objects/google.com:a-b:action');
}
public function testMatchUnboundedWildcardWithColon()
{
$template = new PathTemplate('/buckets/*/objects/**:action');
$this->assertEquals(
['$0' => 'foo', '$1' => 'bar/baz'],
$template->match('/buckets/foo/objects/bar/baz:action')
);
}
/**
* @expectedException \Google\ApiCore\ValidationException
*/
public function testFailMatchUnboundedWildcardWithColonInMiddle()
{
new PathTemplate('/buckets/*/objects/**:action/path');
}
public function testMatchTemplateWithUnboundedWildcard()
{
$template = new PathTemplate('buckets/*/objects/**');
$this->assertEquals(
['$0' => 'foo', '$1' => 'bar/baz'],
$template->match('buckets/foo/objects/bar/baz')
);
}
public function testMatchWithUnboundInMiddle()
{
$template = new PathTemplate('bar/**/foo/*');
$this->assertEquals(
['$0' => 'foo/foo', '$1' => 'bar'],
$template->match('bar/foo/foo/foo/bar')
);
}
public function testRenderAtomicResource()
{
$template = new PathTemplate('buckets/*/*/*/objects/*');
$url = $template->render(
['$0' => 'f', '$1' => 'o', '$2' => 'o', '$3' => 'google.com:a-b']
);
$this->assertEquals($url, 'buckets/f/o/o/objects/google.com:a-b');
}
/**
* @expectedException \Google\ApiCore\ValidationException
* @expectedExceptionMessage Error rendering
*/
public function testRenderFailWhenTooFewVariables()
{
$template = new PathTemplate('buckets/*/*/*/objects/*');
$template->render(['$0' => 'f', '$1' => 'l', '$2' => 'o']);
}
public function testRenderWithUnboundInMiddle()
{
$template = new PathTemplate('bar/**/foo/*');
$url = $template->render(['$0' => '1/2', '$1' => '3']);
$this->assertEquals($url, 'bar/1/2/foo/3');
}
public function testToString()
{
$template = new PathTemplate('bar/**/foo/*');
$this->assertEquals((string) $template, 'bar/**/foo/*');
$template = new PathTemplate('buckets/*/objects/*');
$this->assertEquals(
(string) ($template),
'buckets/*/objects/*'
);
$template = new PathTemplate('/buckets/{hello}');
$this->assertEquals((string) ($template), '/buckets/{hello=*}');
$template = new PathTemplate('/buckets/{hello=what}/{world}');
$this->assertEquals(
(string) ($template),
'/buckets/{hello=what}/{world=*}'
);
$template = new PathTemplate('/buckets/helloazAZ09-.~_what');
$this->assertEquals(
(string) ($template),
'/buckets/helloazAZ09-.~_what'
);
}
public function testSubstitutionOddChars()
{
$template = new PathTemplate('projects/{project}/topics/{topic}');
$url = $template->render(
['project' => 'google.com:proj-test', 'topic' => 'some-topic']
);
$this->assertEquals(
$url,
'projects/google.com:proj-test/topics/some-topic'
);
$template = new PathTemplate('projects/{project}/topics/{topic}');
$url = $template->render(
['project' => 'g.,;:~`!@#$%^&()+-', 'topic' => 'sdf<>,.?[]']
);
$this->assertEquals(
$url,
'projects/g.,;:~`!@#$%^&()+-/topics/sdf<>,.?[]'
);
}
}
tests/Tests/Unit/ProtobufBandaidTest.php 0000644 00000002157 15174274026 0014360 0 ustar 00 assertEquals($expected, $actual);
}
public function protobufMessageProvider()
{
$msg1 = new MyMessage();
$msg2 = new Mymessage();
return [
[$msg1, $msg2],
[[$msg1, $msg2], [$msg1, $msg2]]
];
}
}
tests/Tests/Unit/ProtobufExtensionTest.php 0000644 00000004162 15174274026 0015010 0 ustar 00 markTestSkipped('Must have the protobuf extension installed to run this test.');
}
}
public function testForProtobufExtension()
{
// This test should always pass - we have it here so that we can determine whether the
// protobuf extension is installed, based on whether this test passes or is skipped
$this->assertTrue(true);
}
}
tests/Tests/Unit/RequestBuilderTest.php 0000644 00000032755 15174274026 0014263 0 ustar 00 builder = new RequestBuilder(
'www.example.com',
__DIR__ . '/testdata/test_service_rest_client_config.php'
);
}
public function testMethodWithUrlPlaceholder()
{
$message = new MockRequestBody();
$message->setName('message/foo');
$request = $this->builder->build(self::SERVICE_NAME . '/MethodWithUrlPlaceholder', $message);
$uri = $request->getUri();
$this->assertEmpty($uri->getQuery());
$this->assertEmpty((string) $request->getBody());
$this->assertEquals('/v1/message/foo', $uri->getPath());
}
public function testMethodWithBody()
{
$message = new MockRequestBody();
$message->setName('message/foo');
$nestedMessage = new MockRequestBody();
$nestedMessage->setName('nested/foo');
$message->setNestedMessage($nestedMessage);
$request = $this->builder->build(self::SERVICE_NAME . '/MethodWithBodyAndUrlPlaceholder', $message);
$uri = $request->getUri();
$this->assertEmpty($uri->getQuery());
$this->assertEquals('/v1/message/foo', $uri->getPath());
$this->assertEquals(
['name' => 'message/foo', 'nestedMessage' => ['name' => 'nested/foo']],
json_decode($request->getBody(), true)
);
}
public function testMethodWithNestedMessageAsBody()
{
$message = new MockRequestBody();
$message->setName('message/foo');
$nestedMessage = new MockRequestBody();
$nestedMessage->setName('nested/foo');
$message->setNestedMessage($nestedMessage);
$request = $this->builder->build(self::SERVICE_NAME . '/MethodWithNestedMessageAsBody', $message);
$uri = $request->getUri();
$this->assertEmpty($uri->getQuery());
$this->assertEquals('/v1/message/foo', $uri->getPath());
$this->assertEquals(
['name' => 'nested/foo'],
json_decode($request->getBody(), true)
);
}
public function testMethodWithScalarBody()
{
$message = new MockRequestBody();
$message->setName('foo');
$request = $this->builder->build(self::SERVICE_NAME . '/MethodWithScalarBody', $message);
$this->assertEquals(
'"foo"',
(string) $request->getBody()
);
}
public function testMethodWithEmptyMessageInBody()
{
$message = new MockRequestBody();
$nestedMessage = new MockRequestBody();
$message->setNestedMessage($nestedMessage);
$request = $this->builder->build(self::SERVICE_NAME . '/MethodWithBody', $message);
$this->assertEquals(
'{"nestedMessage":{}}',
$request->getBody()
);
}
public function testMethodWithEmptyMessageInNestedMessageBody()
{
$message = new MockRequestBody();
$message->setName('message/foo');
$nestedMessage = new MockRequestBody();
$message->setNestedMessage($nestedMessage);
$emptyMessage = new MockRequestBody();
$nestedMessage->setNestedMessage($emptyMessage);
$request = $this->builder->build(self::SERVICE_NAME . '/MethodWithNestedMessageAsBody', $message);
$this->assertEquals(
'{"nestedMessage":{}}',
$request->getBody()
);
}
public function testMethodWithNestedUrlPlaceholder()
{
$message = new MockRequestBody();
$message->setName('message/foo');
$nestedMessage = new MockRequestBody();
$nestedMessage->setName('nested/foo');
$message->setNestedMessage($nestedMessage);
$request = $this->builder->build(self::SERVICE_NAME . '/MethodWithNestedUrlPlaceholder', $message);
$uri = $request->getUri();
$this->assertEmpty($uri->getQuery());
$this->assertEquals('/v1/nested/foo', $uri->getPath());
$this->assertEquals(
['name' => 'message/foo', 'nestedMessage' => ['name' => 'nested/foo']],
json_decode($request->getBody(), true)
);
}
public function testMethodWithUrlRepeatedField()
{
$message = new MockRequestBody();
$message->setName('message/foo');
$message->setRepeatedField(['bar1', 'bar2']);
$request = $this->builder->build(self::SERVICE_NAME . '/MethodWithUrlPlaceholder', $message);
$uri = $request->getUri();
$this->assertEmpty((string) $request->getBody());
$this->assertEquals('/v1/message/foo', $uri->getPath());
$this->assertEquals('repeatedField=bar1&repeatedField=bar2', $uri->getQuery());
}
public function testMethodWithHeaders()
{
$message = new MockRequestBody();
$message->setName('message/foo');
$request = $this->builder->build(self::SERVICE_NAME . '/MethodWithUrlPlaceholder', $message, [
'header1' => 'value1',
'header2' => 'value2'
]);
$this->assertEquals('value1', $request->getHeaderLine('header1'));
$this->assertEquals('value2', $request->getHeaderLine('header2'));
$this->assertEquals('application/json', $request->getHeaderLine('Content-Type'));
}
public function testMethodWithColon()
{
$message = new MockRequestBody();
$message->setName('message/foo');
$request = $this->builder->build(self::SERVICE_NAME . '/MethodWithColonInUrl', $message);
$uri = $request->getUri();
$this->assertEmpty($uri->getQuery());
$this->assertEquals('/v1/message/foo:action', $uri->getPath());
}
public function testMethodWithMultipleWildcardsAndColonInUrl()
{
$message = new MockRequestBody();
$message->setName('message/foo');
$message->setNumber(10);
$request = $this->builder->build(
self::SERVICE_NAME . '/MethodWithMultipleWildcardsAndColonInUrl',
$message
);
$uri = $request->getUri();
$this->assertEmpty($uri->getQuery());
$this->assertEquals('/v1/message/foo/number/10:action', $uri->getPath());
}
public function testMethodWithSimplePlaceholder()
{
$message = new MockRequestBody();
$message->setName('message-name');
$request = $this->builder->build(
self::SERVICE_NAME . '/MethodWithSimplePlaceholder',
$message
);
$uri = $request->getUri();
$this->assertEquals('/v1/message-name', $uri->getPath());
}
public function testMethodWithAdditionalBindings()
{
$message = new MockRequestBody();
$message->setName('message/foo');
$request = $this->builder->build(self::SERVICE_NAME . '/MethodWithAdditionalBindings', $message);
$this->assertEquals('/v1/message/foo/additional/bindings', $request->getUri()->getPath());
$message->setName('different/format/foo');
$request = $this->builder->build(self::SERVICE_NAME . '/MethodWithAdditionalBindings', $message);
$this->assertEquals('/v1/different/format/foo/additional/bindings', $request->getUri()->getPath());
$nestedMessage = new MockRequestBody();
$nestedMessage->setName('nested/foo');
$message->setNestedMessage($nestedMessage);
$request = $this->builder->build(self::SERVICE_NAME . '/MethodWithAdditionalBindings', $message);
$this->assertEquals('/v2/nested/foo/additional/bindings', $request->getUri()->getPath());
}
public function testMethodWithSpecialJsonMapping()
{
$bytesValue = (new BytesValue)
->setValue('\000');
$durationValue = (new Duration)
->setSeconds(9001)
->setNanos(500000);
$fieldMask = (new FieldMask)
->setPaths(['path1', 'path2']);
$int64Value = (new Int64Value)
->setValue(100);
$listValue = (new ListValue)
->setValues([
(new Value)->setStringValue('val1'),
(new Value)->setStringValue('val2')
]);
$stringValue = (new StringValue)
->setValue('some-value');
$structValue = (new Struct)
->setFields([
'test' => (new Value)->setStringValue('val5')
]);
$timestampValue = (new Timestamp)
->setSeconds(9001);
$valueValue = (new Value)
->setStringValue('some-value');
$message = (new MockRequestBody())
->setBytesValue($bytesValue)
->setDurationValue($durationValue)
->setFieldMask($fieldMask)
->setInt64Value($int64Value)
->setListValue($listValue)
->setStringValue($stringValue)
->setStructValue($structValue)
->setTimestampValue($timestampValue)
->setValueValue($valueValue);
$request = $this->builder->build(self::SERVICE_NAME . '/MethodWithSpecialJsonMapping', $message);
$uri = $request->getUri();
$this->assertContains('listValue=val1&listValue=val2', (string) $uri);
$query = Psr7\parse_query($uri->getQuery());
$this->assertEquals('XDAwMA==', $query['bytesValue']);
// @todo (dwsupplee) Investigate differences in native protobuf implementation
// between v3.7.0 and v3.9.0 - this passed previously with the value
// "9001.000500000s".
if (extension_loaded('protobuf')) {
$this->assertEquals('9001.000500000s', $query['durationValue']);
} else {
$this->assertEquals('9001.000500s', $query['durationValue']);
}
$this->assertEquals('path1,path2', $query['fieldMask']);
$this->assertEquals(100, $query['int64Value']);
$this->assertEquals(['val1', 'val2'], $query['listValue']);
$this->assertEquals('some-value', $query['stringValue']);
$this->assertEquals('val5', $query['structValue.test']);
$this->assertEquals('1970-01-01T02:30:01Z', $query['timestampValue']);
$this->assertEquals('some-value', $query['valueValue']);
}
public function testMethodWithoutPlaceholders()
{
$stringValue = (new StringValue)
->setValue('some-value');
$fieldMask = (new FieldMask)
->setPaths(['path1', 'path2']);
$message = (new MockRequestBody())
->setStringValue($stringValue)
->setFieldMask($fieldMask);
$request = $this->builder->build(self::SERVICE_NAME . '/MethodWithoutPlaceholders', $message);
$query = Psr7\parse_query($request->getUri()->getQuery());
$this->assertEquals('path1,path2', $query['fieldMask']);
$this->assertEquals('some-value', $query['stringValue']);
}
public function testMethodWithComplexMessageInQueryString()
{
$message = (new MockRequestBody())
->setNestedMessage(
(new MockRequestBody)
->setName('some-name')
->setNumber(10)
);
$request = $this->builder->build(self::SERVICE_NAME . '/MethodWithoutPlaceholders', $message);
$query = Psr7\parse_query($request->getUri()->getQuery());
$this->assertEquals('some-name', $query['nestedMessage.name']);
$this->assertEquals(10, $query['nestedMessage.number']);
}
public function testMethodWithOneOfInQueryString()
{
$message = (new MockRequestBody())
->setField1('some-value');
$request = $this->builder->build(self::SERVICE_NAME . '/MethodWithoutPlaceholders', $message);
$query = Psr7\parse_query($request->getUri()->getQuery());
$this->assertEquals('some-value', $query['field1']);
}
/**
* @expectedException \Google\ApiCore\ValidationException
* @expectedExceptionMessage Could not map bindings for test.interface.v1.api/MethodWithAdditionalBindings to any Uri template.
*/
public function testThrowsExceptionWithNonMatchingFormat()
{
$message = new MockRequestBody();
$message->setName('invalid/name/format');
$request = $this->builder->build(self::SERVICE_NAME . '/MethodWithAdditionalBindings', $message);
}
/**
* @expectedException \Google\ApiCore\ValidationException
* @expectedExceptionMessage Failed to build request, as the provided path (myResource/doesntExist) was not found in the configuration.
*/
public function testThrowsExceptionWithNonExistantMethod()
{
$message = new MockRequestBody();
$this->builder->build('myResource/doesntExist', $message);
}
}
tests/Tests/Unit/RequestParamsHeaderDescriptorTest.php 0000644 00000006334 15174274026 0017262 0 ustar 00 ['']];
$agentHeaderDescriptor = new RequestParamsHeaderDescriptor([]);
$header = $agentHeaderDescriptor->getHeader();
$this->assertEquals($expectedHeader, $header);
}
public function testSingleValue()
{
$expectedHeader = [RequestParamsHeaderDescriptor::HEADER_KEY => ['field1=value_1']];
$agentHeaderDescriptor = new RequestParamsHeaderDescriptor(['field1' => 'value_1']);
$header = $agentHeaderDescriptor->getHeader();
$this->assertSame($expectedHeader, $header);
}
public function testMultipleValues()
{
$expectedHeader = [
RequestParamsHeaderDescriptor::HEADER_KEY => ['field1=value_1&field2.field3=value_2']
];
$agentHeaderDescriptor = new RequestParamsHeaderDescriptor([
'field1' => 'value_1',
'field2.field3' => 'value_2'
]);
$header = $agentHeaderDescriptor->getHeader();
$this->assertSame($expectedHeader, $header);
}
public function testNonAsciiCharsAppendsBinToHeaderKey()
{
$val = 'こんにちは';
$expectedHeader = [
RequestParamsHeaderDescriptor::HEADER_KEY . '-bin' => ['field1=' . $val]
];
$agentHeaderDescriptor = new RequestParamsHeaderDescriptor(['field1' => $val]);
$header = $agentHeaderDescriptor->getHeader();
$this->assertSame($expectedHeader, $header);
}
}
tests/Tests/Unit/ResourceTemplate/AbsoluteResourceTemplateTest.php 0000644 00000023327 15174274026 0021564 0 ustar 00 assertEquals($expectedString ?: $path, $template->__toString());
}
public function validPathProvider()
{
return [
["/foo"],
["/*"],
["/**"],
["/{foo}", "/{foo=*}"],
["/{foo=*}"],
["/{foo=**}"],
["/foo/*"],
["/*/foo"],
["/foo/*:bar"],
["/*/foo:bar"],
["/*/*/*/*:foo"],
["/**/*/*:foo"],
["/foo/**/bar/*"],
["/foo/*/bar/**"],
["/foo/helloazAZ09-.~_what"],
];
}
/**
* @dataProvider invalidPathProvider
* @expectedException \Google\ApiCore\ValidationException
* @param string $path
*/
public function testInvalidPaths($path)
{
new AbsoluteResourceTemplate($path);
}
public function invalidPathProvider()
{
return [
[null], // Null path
[""], // Empty path
["foo"], // No leading '/'
["/foo:bar/baz"], // Action containing '/'
["/foo:bar:baz"], // Multiple ':'
["/foo/bar*baz"], // Mixed literal and '*'
["/foo/**/**"], // Multiple '**'
["/foo/**/{var=**}"], // Multiple '**' nested
["/foo/{bizz=**}/{var=**}"],// Multiple '**' nested
["/foo/{bizz=**/**}"], // Multiple '**' nested
["/foo/{bar={baz}}"], // Nested {}
["/foo/{bar=fizz=buzz}"], // Multiple '=' in variable
["/foo/{bar"], // Unmatched '{'
["/foo/{bar}/{bar}"], // Duplicate variable key
["/foo/{bar/baz}"], // Variable containing '/'
["/foo//bar"], // Consecutive '/'
["//bar"], // Consecutive '/'
["/foo/"], // Trailing '/'
];
}
/**
* @param string $pathTemplate
* @param string $path
* @param array $expectedBindings
* @dataProvider matchData
*/
public function testMatch($pathTemplate, $path, $expectedBindings)
{
$template = new AbsoluteResourceTemplate($pathTemplate);
$this->assertEquals(
$expectedBindings,
$template->match($path)
);
}
/**
* @param string $pathTemplate
* @param string $path
* @dataProvider matchData
*/
public function testMatches($pathTemplate, $path)
{
$template = new AbsoluteResourceTemplate($pathTemplate);
$this->assertTrue($template->matches($path));
}
/**
* @param string $pathTemplate
* @param string $expectedPath
* @param array $bindings
* @dataProvider matchData
*/
public function testRender($pathTemplate, $expectedPath, $bindings)
{
$template = new AbsoluteResourceTemplate($pathTemplate);
$this->assertEquals($expectedPath, $template->render($bindings));
}
public function matchData()
{
return [
[
'/buckets/*/*/objects/*',
'/buckets/f/o/objects/bar',
['$0' => 'f', '$1' => 'o', '$2' => 'bar'],
],
[
'/buckets/{hello}',
'/buckets/world',
['hello' => 'world'],
],
[
'/buckets/{hello=*}',
'/buckets/world',
['hello' => 'world'],
],
[
'/buckets/*:action',
'/buckets/foo:action',
['$0' => 'foo'],
],
[
'/buckets/*/*/*/objects/*:action',
'/buckets/f/o/o/objects/google.com:a-b:action',
['$0' => 'f', '$1' => 'o', '$2' => 'o', '$3' => 'google.com:a-b'],
],
[
'/buckets/*/objects/**:action',
'/buckets/foo/objects/bar/baz:action',
['$0' => 'foo', '$1' => 'bar/baz'],
],
[
'/foo/*/{bar=*/rar/*}/**/*:action',
'/foo/fizz/fuzz/rar/bar/bizz/buzz/baz:action',
['$0' => 'fizz', '$1' => 'bizz/buzz', '$2' => 'baz', 'bar' => 'fuzz/rar/bar'],
],
[
'/buckets/*',
'/buckets/{}!@#$%^&*()+=[]\|`~-_',
['$0' => '{}!@#$%^&*()+=[]\|`~-_'],
],
];
}
/**
* @param string $pathTemplate
* @param string $path
* @dataProvider invalidMatchData
* @expectedException \Google\ApiCore\ValidationException
*/
public function testFailMatch($pathTemplate, $path)
{
$template = new AbsoluteResourceTemplate($pathTemplate);
$template->match($path);
}
/**
* @param string $pathTemplate
* @param string $path
* @dataProvider invalidMatchData
*/
public function testFailMatches($pathTemplate, $path)
{
$template = new AbsoluteResourceTemplate($pathTemplate);
$this->assertFalse($template->matches($path));
}
public function invalidMatchData()
{
return [
[
'/buckets/*/*/objects/*',
'/buckets/f/o/objects/bar/far', // Extra '/far'
],
[
'/buckets/*/*/objects/*',
'/buckets/f/o/objects', // Missing final wildcard
],
[
'/foo/*/bar',
'/foo/bar', // Missing middle wildcard
],
[
'/foo/*/bar',
'/foo/fizz/buzz/bar', // Too many segments for middle wildcard
],
[
'/foo/**/bar',
'/foo/bar', // Missing middle wildcard
],
[
'/buckets/*:action',
'/buckets/foo', // Missing action
],
[
'/buckets/*:action',
'/buckets/foo:actingout', // Wrong action
],
[
'/buckets/*:action',
'/buckets/foo:actionstations', // Wrong action
],
[
'/buckets',
'/bouquets', // Wrong literal
],
[
'/buckets',
'/bucketseller', // Wrong literal
],
[
'/foo/*/{bar=*/rar/*}/**/*:action',
'/foo/fizz/fuzz/rat/bar/bizz/buzz/baz:action',
],
];
}
/**
* @param string $pathTemplate
* @param array $bindings
* @dataProvider invalidRenderData
* @expectedException \Google\ApiCore\ValidationException
*/
public function testFailRender($pathTemplate, $bindings)
{
$template = new AbsoluteResourceTemplate($pathTemplate);
$template->render($bindings);
}
public function invalidRenderData()
{
return [
[
'/buckets/*/*/objects/*',
['$0' => 'f', '$2' => 'bar'], // Missing key
],
[
'/buckets/{hello}',
['hellop' => 'world'], // Wrong key
],
[
'/buckets/{hello=*}',
['hello' => 'world/weary'], // Invalid binding
],
[
'/buckets/{hello=*}',
['hello' => ''], // Invalid binding
],
[
'/buckets/*/objects/**:action',
['$0' => 'foo', '$1' => ''], // Invalid binding
],
[
'/foo/*/{bar=*/rar/*}/**/*:action',
['$0' => 'fizz', '$1' => 'bizz/buzz', '$2' => 'baz', 'bar' => 'fuzz/rat/bar'],
// Invalid binding
],
];
}
}
tests/Tests/Unit/ResourceTemplate/ParserTest.php 0000644 00000034504 15174274026 0016035 0 ustar 00 assertEquals($expectedSegments, $actualSegments);
}
public function testParseBasicSegments() {
$this->testParseSegments("foo/bar/baz",
[self::literalSegment("foo"),
self::literalSegment("bar"),
self::literalSegment("baz")]);
$this->testParseSegments("foos/{foo}/bars/{bar}",
[self::literalSegment("foos"),
self::variableSegment("foo", new RelativeResourceTemplate("*")),
self::literalSegment("bars"),
self::variableSegment("bar", new RelativeResourceTemplate("*"))]);
}
public function testParseBasicNonSlashSeparators() {
$this->testParseSegments("foos/{foo}_{oof}",
[self::literalSegment("foos"),
self::variableSegment("foo", new RelativeResourceTemplate("*"), '_'),
self::variableSegment("oof", new RelativeResourceTemplate("*"))]);
$this->testParseSegments("foos/{foo}-{oof}",
[self::literalSegment("foos"),
self::variableSegment("foo", new RelativeResourceTemplate("*"), '-'),
self::variableSegment("oof", new RelativeResourceTemplate("*"))]);
$this->testParseSegments("foos/{foo}~{oof}",
[self::literalSegment("foos"),
self::variableSegment("foo", new RelativeResourceTemplate("*"), '~'),
self::variableSegment("oof", new RelativeResourceTemplate("*"))]);
$this->testParseSegments("foos/{foo}.{oof}",
[self::literalSegment("foos"),
self::variableSegment("foo", new RelativeResourceTemplate("*"), '.'),
self::variableSegment("oof", new RelativeResourceTemplate("*"))]);
}
public function testParseMultipleNonSlashSeparators() {
$this->testParseSegments("foos/{foo}_{oof}-{bar}.{baz}~{car}",
[self::literalSegment("foos"),
self::variableSegment("foo", new RelativeResourceTemplate("*"), '_'),
self::variableSegment("oof", new RelativeResourceTemplate("*"), '-'),
self::variableSegment("bar", new RelativeResourceTemplate("*"), '.'),
self::variableSegment("baz", new RelativeResourceTemplate("*"), '~'),
self::variableSegment("car", new RelativeResourceTemplate("*"))]);
$this->testParseSegments("foos/{foo}.{oof}_{bar}.{car}",
[self::literalSegment("foos"),
self::variableSegment("foo", new RelativeResourceTemplate("*"), '.'),
self::variableSegment("oof", new RelativeResourceTemplate("*"), '_'),
self::variableSegment("bar", new RelativeResourceTemplate("*"), '.'),
self::variableSegment("car", new RelativeResourceTemplate("*"))]);
$this->testParseSegments("foos/{foo}-{oof}.{bar}~{car}",
[self::literalSegment("foos"),
self::variableSegment("foo", new RelativeResourceTemplate("*"), '-'),
self::variableSegment("oof", new RelativeResourceTemplate("*"), '.'),
self::variableSegment("bar", new RelativeResourceTemplate("*"), '~'),
self::variableSegment("car", new RelativeResourceTemplate("*"))]);
}
public function testParseNonSlashSeparatorsWithParents() {
$this->testParseSegments("foos/{foo}_{oof}-{bar}.{baz}~{car}/projects/{project}/locations/{state}~{city}.{cell}",
[self::literalSegment("foos"),
self::variableSegment("foo", new RelativeResourceTemplate("*"), '_'),
self::variableSegment("oof", new RelativeResourceTemplate("*"), '-'),
self::variableSegment("bar", new RelativeResourceTemplate("*"), '.'),
self::variableSegment("baz", new RelativeResourceTemplate("*"), '~'),
self::variableSegment("car", new RelativeResourceTemplate("*")),
self::literalSegment("projects"),
self::variableSegment("project", new RelativeResourceTemplate("*")),
self::literalSegment("locations"),
self::variableSegment("state", new RelativeResourceTemplate("*"), '~'),
self::variableSegment("city", new RelativeResourceTemplate("*"), '.'),
self::variableSegment("cell", new RelativeResourceTemplate("*"))]);
$this->testParseSegments("customers/{customer_id}/userLocationViews/{country_criterion_id}~{is_targeting_location}",
[self::literalSegment("customers"),
self::variableSegment("customer_id", new RelativeResourceTemplate("*")),
self::literalSegment("userLocationViews"),
self::variableSegment("country_criterion_id", new RelativeResourceTemplate("*"), '~'),
self::variableSegment("is_targeting_location", new RelativeResourceTemplate("*"))]);
}
public function validPathProvider()
{
$singlePathTests = [
["foo", [self::literalSegment("foo")]],
["helloazAZ09-.~_what", [self::literalSegment("helloazAZ09-.~_what")]],
["*", [self::wildcardSegment()]],
["**", [self::doubleWildcardSegment()]],
["{foo}", Parser::parseSegments("{foo=*}")],
["{foo=*}", [self::variableSegment("foo", new RelativeResourceTemplate("*"))]],
["{foo=**}", [self::variableSegment("foo", new RelativeResourceTemplate("**"))]],
];
$comboPathPieces = [
["foo", [self::literalSegment("foo")]],
["helloazAZ09-.~_what", [self::literalSegment("helloazAZ09-.~_what")]],
["*", [self::wildcardSegment()]],
["*", [self::wildcardSegment()]],
["**", [self::doubleWildcardSegment()]],
];
// Combine the pieces in $comboPathPieces in every possible order
$comboPathTests = [];
foreach (self::yieldAllSequences($comboPathPieces) as $comboSequence) {
$pathPieces = [];
$segments = [];
foreach ($comboSequence as list($path, $segmentArray)) {
$pathPieces[] = $path;
$segments = array_merge($segments, $segmentArray);
}
$comboPathTests[] = [implode('/', $pathPieces), $segments];
}
return $singlePathTests + $comboPathTests;
}
/**
* @dataProvider sequenceProvider
* @param $sequence
* @param $expectedSequences
*/
public function testYieldAllSequences($sequence, $expectedSequences)
{
$actual = iterator_to_array(self::yieldAllSequences($sequence));
$this->assertEquals($expectedSequences, $actual);
}
public function sequenceProvider()
{
return [
[['a'], [['a']]],
[['a', 'b'], [
['a'],
['a', 'b'],
['b'],
['b', 'a'],
]],
[['a', 'b', 'c'], [
['a'],
['a', 'b'],
['a', 'b', 'c'],
['a', 'c'],
['a', 'c', 'b'],
['b'],
['b', 'a'],
['b', 'a', 'c'],
['b', 'c'],
['b', 'c', 'a'],
['c'],
['c', 'a'],
['c', 'a', 'b'],
['c', 'b'],
['c', 'b', 'a'],
]],
];
}
private static function yieldAllSequences($items)
{
$keys = array_keys($items);
foreach ($keys as $key) {
$itemsCopy = $items;
$value = $itemsCopy[$key];
yield [$value];
unset($itemsCopy[$key]);
foreach (self::yieldAllSequences($itemsCopy) as $subsequence) {
yield array_merge([$value], $subsequence);
}
}
}
/**
* @dataProvider invalidPathProvider
* @expectedException \Google\ApiCore\ValidationException
* @param string $path
*/
public function testParseInvalid($path)
{
Parser::parseSegments($path);
}
public function invalidPathProvider()
{
return [
[null], // Null path
[""], // Empty path
["/foo"], // Leading '/'
["foo:bar"], // Contains ':'
["foo{barbaz"], // Contains '{'
["foo}barbaz"], // Contains '}'
["foo{bar}baz"], // Contains '{' and '}'
["{}"], // Empty var
["{foo#bar}"], // Invalid var
["{foo.bar=baz"], // Unbalanced '{'
["{foo.bar=baz=fizz}"], // Multiple '=' in variable
["{foo.bar=**/**}"], // Invalid resource template
["/foo"], // Leading '/'
["foo//bar"], // Consecutive '/'
["foo/"], // Trailing '/'
];
}
/**
* @param string $literal
* @dataProvider validLiterals
*/
public function testIsValidLiteral($literal)
{
$this->assertTrue($this->invokeStaticMethod(Parser::class, 'isValidLiteral', [$literal]));
}
public function validLiterals()
{
return [
["foo"],
["helloazAZ09-.~_what"],
["5"],
["5five"],
];
}
/**
* @param string $literal
* @dataProvider invalidLiterals
*/
public function testFailIsValidLiteral($literal)
{
$this->assertFalse($this->invokeStaticMethod(Parser::class, 'isValidLiteral', [$literal]));
}
public function invalidLiterals()
{
return [
[null],
[""],
["fo\$o"],
["fo{o"],
["fo}o"],
["fo/o"],
["fo#o"],
["fo%o"],
["fo\\o"],
];
}
/**
* @param string $binding
* @dataProvider validBindings
*/
public function testIsValidBinding($binding)
{
$this->assertTrue($this->invokeStaticMethod(Segment::class, 'isValidBinding', [$binding]));
}
public function validBindings()
{
return array_merge(
$this->validLiterals(),
[
["fo#o"],
["fo%o"],
["fo!o"],
["fo@o"],
["fo#o"],
["fo\$o"],
["fo%o"],
["fo^o"],
["fo&o"],
["fo*o"],
["fo(o"],
["fo)o"],
["fo{o"],
["fo}o"],
["fo+o"],
["fo=o"],
]
);
}
/**
* @param string $binding
* @dataProvider invalidBindings
*/
public function testFailIsValidBinding($binding)
{
$this->assertFalse($this->invokeStaticMethod(Segment::class, 'isValidBinding', [$binding]));
}
public function invalidBindings()
{
return [
[null],
[""],
["fo/o"],
];
}
/**
* @param string $binding
* @dataProvider validDoubleWildcardBindings
*/
public function testIsValidDoubleWildcardBinding($binding)
{
$this->assertTrue($this->invokeStaticMethod(Segment::class, 'isValidDoubleWildcardBinding', [$binding]));
}
public function validDoubleWildcardBindings()
{
return array_merge(
$this->validBindings(),
[
["fo/o"]
]
);
}
/**
* @param string $binding
* @dataProvider invalidDoubleWildcardBindings
*/
public function testFailIsValidDoubleWildcardBinding($binding)
{
$this->assertFalse($this->invokeStaticMethod(Segment::class, 'isValidDoubleWildcardBinding', [$binding]));
}
public function invalidDoubleWildcardBindings()
{
return [
[null],
[""],
];
}
private function invokeStaticMethod($class, $methodName, array $parameters)
{
$reflection = new \ReflectionClass($class);
$method = $reflection->getMethod($methodName);
$method->setAccessible(true);
return $method->invokeArgs(null, $parameters);
}
}
tests/Tests/Unit/ResourceTemplate/RelativeResourceTemplateTest.php 0000644 00000031207 15174274026 0021555 0 ustar 00 assertEquals($expectedString ?: $path, $template->__toString());
}
public function validPathProvider()
{
return [
["foo"],
["5"],
["5five/4/four"],
["*"],
["**"],
["{foo}", "{foo=*}"],
["{foo=*}"],
["{foo=**}"],
["foo/*"],
["*/foo"],
["*/*/*/*"],
["**/*/*"],
["foo/**/bar/*"],
["foo/*/bar/**"],
["foo/helloazAZ09-.~_what"],
];
}
/**
* @dataProvider invalidPathProvider
* @expectedException \Google\ApiCore\ValidationException
* @param string $path
*/
public function testInvalidPaths($path, $expectedExceptionMessage = null)
{
if (isset($expectedExceptionMessage)) {
$this->setExpectedException($this->getExpectedException(), $expectedExceptionMessage);
}
new RelativeResourceTemplate($path);
}
public function invalidPathProvider()
{
return [
[
null, // Null path
"Cannot construct RelativeResourceTemplate from null string"
],
[
"", // Empty path
"Cannot construct RelativeResourceTemplate from empty string"
],
[
"foo:bar/baz", // Action containing '/'
"Error parsing 'foo:bar/baz' at index 7: Unexpected characters in literal segment foo:bar",
],
[
"foo/**/**", // Multiple '**'
"Cannot parse 'foo/**/**': cannot contain more than one path wildcard"
],
[
"foo//bar", // Consecutive '/'
"Error parsing 'foo//bar' at index 4: Unexpected empty segment (consecutive '/'s are invalid)",
],
[
"foo/", // Trailing '/'
"Error parsing 'foo/' at index 3: invalid trailing '/'"
],
["foo:bar:baz"], // Multiple ':'
["foo/bar*baz"], // Mixed literal and '*'
["foo/**/{var=**}"], // Multiple '**' nested
["foo/{bizz=**}/{var=**}"],// Multiple '**' nested
["foo/{bizz=**/**}"], // Multiple '**' nested
["foo/{bar={baz}}"], // Nested {}
["foo/{bar=fizz=buzz}"], // Multiple '=' in variable
["foo/{bar"], // Unmatched '{'
["foo/{bar}/{bar}"], // Duplicate variable key
["foo/{bar/baz}"], // Variable containing '/'
];
}
/**
* @param string $pathTemplate
* @param string $path
* @param array $expectedBindings
* @dataProvider matchData
*/
public function testMatch($pathTemplate, $path, $expectedBindings)
{
$template = new RelativeResourceTemplate($pathTemplate);
$this->assertEquals(
$expectedBindings,
$template->match($path)
);
}
/**
* @param string $pathTemplate
* @param string $path
* @dataProvider matchData
*/
public function testMatches($pathTemplate, $path)
{
$template = new RelativeResourceTemplate($pathTemplate);
$this->assertTrue($template->matches($path));
}
/**
* @param string $pathTemplate
* @param string $expectedPath
* @param array $bindings
* @dataProvider matchData
*/
public function testRender($pathTemplate, $expectedPath, $bindings)
{
$template = new RelativeResourceTemplate($pathTemplate);
$this->assertEquals($expectedPath, $template->render($bindings));
}
public function matchData()
{
return [
[
'buckets/*/*/objects/*',
'buckets/f/o/objects/bar',
['$0' => 'f', '$1' => 'o', '$2' => 'bar'],
],
[
'buckets/{hello}',
'buckets/world',
['hello' => 'world'],
],
[
'buckets/{hello}',
'buckets/5',
['hello' => '5'],
],
[
'buckets/{hello=*}',
'buckets/world',
['hello' => 'world'],
],
[
'buckets/*',
'buckets/foo',
['$0' => 'foo'],
],
[
'buckets/*/*/*/objects/*',
'buckets/f/o/o/objects/google.com:a-b',
['$0' => 'f', '$1' => 'o', '$2' => 'o', '$3' => 'google.com:a-b'],
],
[
'buckets/*/objects/**',
'buckets/foo/objects/bar/baz',
['$0' => 'foo', '$1' => 'bar/baz'],
],
[
'foo/*/{bar=*/rar/*}/**/*',
'foo/fizz/fuzz/rar/bar/bizz/buzz/baz',
['$0' => 'fizz', '$1' => 'bizz/buzz', '$2' => 'baz', 'bar' => 'fuzz/rar/bar'],
],
[
'buckets/*',
'buckets/{}!@#$%^&*()+=[]\|`~-_',
['$0' => '{}!@#$%^&*()+=[]\|`~-_'],
],
[
'foos/{foo}_{oof}',
'foos/imafoo_thisisanoof',
['foo' => 'imafoo', 'oof' => 'thisisanoof'],
],
[
'foos/{foo}.{oof}_{bar}.{car}',
'foos/food.doof_mars.porsche',
['foo' => 'food', 'oof' => 'doof', 'bar' => 'mars', 'car' => 'porsche'],
],
[
'foos/{foo}_{oof}-{bar}.{baz}~{car}/projects/{project}/locations/{state}~{city}.{cell}',
'foos/food_doof-mars.bazz~porsche/projects/someProject/locations/wa~sea.fre3',
['foo' => 'food', 'oof' => 'doof', 'bar' => 'mars', 'baz' => 'bazz', 'car' => 'porsche', 'project' => 'someProject', 'state' => 'wa', 'city' => 'sea', 'cell' => 'fre3'],
],
];
}
/**
* @param string $pathTemplate
* @param string $path
* @dataProvider invalidMatchData
* @expectedException \Google\ApiCore\ValidationException
*/
public function testFailMatch($pathTemplate, $path)
{
$template = new RelativeResourceTemplate($pathTemplate);
$template->match($path);
}
/**
* @param string $pathTemplate
* @param string $path
* @dataProvider invalidMatchData
*/
public function testFailMatches($pathTemplate, $path)
{
$template = new RelativeResourceTemplate($pathTemplate);
$this->assertFalse($template->matches($path));
}
public function invalidMatchData()
{
return [
[
'buckets/*/*/objects/*',
'buckets/f/o/objects/bar/far', // Extra '/far'
],
[
'buckets/*/*/objects/*',
'buckets/f/o/objects', // Missing final wildcard
],
[
'foo/*/bar',
'foo/bar', // Missing middle wildcard
],
[
'foo/*/bar',
'foo/fizz/buzz/bar', // Too many segments for middle wildcard
],
[
'foo/**/bar',
'foo/bar', // Missing middle wildcard
],
[
'buckets',
'bouquets', // Wrong literal
],
[
'buckets',
'bucketseller', // Wrong literal
],
[
'foo/*/{bar=*/rar/*}/**/*',
'foo/fizz/fuzz/rat/bar/bizz/buzz/baz',
],
];
}
/**
* @param string $pathTemplate
* @param array $bindings
* @dataProvider invalidRenderData
* @expectedException \Google\ApiCore\ValidationException
*/
public function testFailRender($pathTemplate, $bindings, $expectedExceptionMessage = null)
{
if (isset($expectedExceptionMessage)) {
$this->setExpectedException($this->getExpectedException(), $expectedExceptionMessage);
}
$template = new RelativeResourceTemplate($pathTemplate);
$template->render($bindings);
}
public function invalidRenderData()
{
return [
[
'buckets/*/*/objects/*',
['$0' => 'f', '$2' => 'bar'], // Missing key
"Error rendering 'buckets/*/*/objects/*': missing required binding '$1' for segment '*'\n" .
"Provided bindings: Array\n" .
"(\n" .
" [$0] => f\n" .
" [$2] => bar\n" .
")\n",
],
[
'buckets/{hello}',
['hellop' => 'world'], // Wrong key
],
[
'buckets/{hello=*}',
['hello' => 'world/weary'], // Invalid binding
],
[
'buckets/{hello=*}',
['hello' => ''], // Invalid binding
"Error rendering 'buckets/{hello=*}': expected binding 'hello' to match segment '{hello=*}', instead got ''\n" .
"Provided bindings: Array\n" .
"(\n" .
" [hello] => \n" .
")\n",
],
[
'buckets/{hello=*}',
['hello' => null], // Invalid binding
"Error rendering 'buckets/{hello=*}': expected binding 'hello' to match segment '{hello=*}', instead got null\n" .
"Provided bindings: Array\n" .
"(\n" .
" [hello] => \n" .
")\n",
],
[
'buckets/*/objects/**',
['$0' => 'foo', '$1' => ''], // Invalid binding
"Error rendering 'buckets/*/objects/**': expected binding '$1' to match segment '**', instead got ''\n" .
"Provided bindings: Array\n" .
"(\n" .
" [$0] => foo\n" .
" [$1] => \n" .
")\n",
],
[
'buckets/*/objects/**',
['$0' => 'foo', '$1' => null], // Invalid binding
"Error rendering 'buckets/*/objects/**': expected binding '$1' to match segment '**', instead got null\n" .
"Provided bindings: Array\n" .
"(\n" .
" [$0] => foo\n" .
" [$1] => \n" .
")\n",
],
[
'foo/*/{bar=*/rar/*}/**/*:action',
['$0' => 'fizz', '$1' => 'bizz/buzz', '$2' => 'baz', 'bar' => 'fuzz/rat/bar'],
// Invalid binding
],
];
}
}
tests/Tests/Unit/RetrySettingsTest.php 0000644 00000027604 15174274026 0014147 0 ustar 00 assertTrue($simpleMethod->retriesEnabled());
$this->assertEquals(40000, $simpleMethod->getNoRetriesRpcTimeoutMillis());
$this->assertEquals(['DEADLINE_EXCEEDED', 'UNAVAILABLE'], $simpleMethod->getRetryableCodes());
$this->assertEquals(100, $simpleMethod->getInitialRetryDelayMillis());
$pageStreamingMethod = $defaultRetrySettings['PageStreamingMethod'];
$this->assertEquals(['INTERNAL'], $pageStreamingMethod->getRetryableCodes());
$timeoutOnlyMethod = $defaultRetrySettings['TimeoutOnlyMethod'];
$this->assertFalse($timeoutOnlyMethod->retriesEnabled());
$this->assertEquals(40000, $timeoutOnlyMethod->getNoRetriesRpcTimeoutMillis());
}
/**
* @expectedException \Google\ApiCore\ValidationException
*/
public function testLoadInvalid()
{
$inputConfig = RetrySettingsTest::buildInvalidInputConfig();
RetrySettings::load(
RetrySettingsTest::SERVICE_NAME,
$inputConfig
);
}
public function testDisableRetries()
{
$inputConfig = RetrySettingsTest::buildInputConfig();
$defaultRetrySettings = RetrySettings::load(
RetrySettingsTest::SERVICE_NAME,
$inputConfig,
true
);
$simpleMethod = $defaultRetrySettings['SimpleMethod'];
$this->assertFalse($simpleMethod->retriesEnabled());
$this->assertEquals(40000, $simpleMethod->getNoRetriesRpcTimeoutMillis());
$pageStreamingMethod = $defaultRetrySettings['PageStreamingMethod'];
$this->assertEquals(['INTERNAL'], $pageStreamingMethod->getRetryableCodes());
$timeoutOnlyMethod = $defaultRetrySettings['TimeoutOnlyMethod'];
$this->assertFalse($timeoutOnlyMethod->retriesEnabled());
$this->assertEquals(40000, $timeoutOnlyMethod->getNoRetriesRpcTimeoutMillis());
}
/**
* @expectedException \Google\ApiCore\ValidationException
*/
public function testRetrySettingsMissingFields()
{
$retrySettings = new RetrySettings([
'initialRetryDelayMillis' => 100,
'retryDelayMultiplier' => 1.3,
// Missing field:
//'maxRetryDelayMillis' => 400,
'initialRpcTimeoutMillis' => 150,
'rpcTimeoutMultiplier' => 2,
'maxRpcTimeoutMillis' => 600,
'totalTimeoutMillis' => 2000
]);
}
/**
* @dataProvider retrySettingsProvider
* @param $settings
* @param $expectedValues
*/
public function testRetrySettings($settings, $expectedValues)
{
$retrySettings = new RetrySettings($settings);
$this->compare($retrySettings, $expectedValues);
}
/**
* @dataProvider withRetrySettingsProvider
* @param $settings
* @param $withSettings
* @param $expectedValues
*/
public function testWith($settings, $withSettings, $expectedValues)
{
$retrySettings = new RetrySettings($settings);
$withRetrySettings = $retrySettings->with($withSettings);
$this->compare($withRetrySettings, $expectedValues);
}
private function compare(RetrySettings $retrySettings, $expectedValues)
{
$this->assertSame(
$expectedValues['initialRetryDelayMillis'],
$retrySettings->getInitialRetryDelayMillis()
);
$this->assertSame(
$expectedValues['retryDelayMultiplier'],
$retrySettings->getRetryDelayMultiplier()
);
$this->assertSame(
$expectedValues['maxRetryDelayMillis'],
$retrySettings->getMaxRetryDelayMillis()
);
$this->assertSame(
$expectedValues['rpcTimeoutMultiplier'],
$retrySettings->getRpcTimeoutMultiplier()
);
$this->assertSame(
$expectedValues['maxRpcTimeoutMillis'],
$retrySettings->getMaxRpcTimeoutMillis()
);
$this->assertSame(
$expectedValues['totalTimeoutMillis'],
$retrySettings->getTotalTimeoutMillis()
);
$this->assertSame(
$expectedValues['retryableCodes'],
$retrySettings->getRetryableCodes()
);
$this->assertSame(
$expectedValues['retriesEnabled'],
$retrySettings->retriesEnabled()
);
$this->assertSame(
$expectedValues['noRetriesRpcTimeoutMillis'],
$retrySettings->getNoRetriesRpcTimeoutMillis()
);
}
public function retrySettingsProvider()
{
$defaultSettings = [
'initialRetryDelayMillis' => 100,
'retryDelayMultiplier' => 1.3,
'maxRetryDelayMillis' => 400,
'initialRpcTimeoutMillis' => 150,
'rpcTimeoutMultiplier' => 2,
'maxRpcTimeoutMillis' => 600,
'totalTimeoutMillis' => 2000,
'retryableCodes' => [1],
];
$defaultExpectedValues = [
'initialRetryDelayMillis' => 100,
'retryDelayMultiplier' => 1.3,
'maxRetryDelayMillis' => 400,
'initialRpcTimeoutMillis' => 150,
'rpcTimeoutMultiplier' => 2,
'maxRpcTimeoutMillis' => 600,
'totalTimeoutMillis' => 2000,
'retryableCodes' => [1],
'noRetriesRpcTimeoutMillis' => 150,
'retriesEnabled' => true
];
return [
[
// Test with retryableCodes, without retriesEnabled or noRetriesRpcTimeoutMillis
$defaultSettings,
$defaultExpectedValues
],
[
// Test with empty retryableCodes, without retriesEnabled or noRetriesRpcTimeoutMillis
[
'retryableCodes' => [],
] + $defaultSettings,
[
'retryableCodes' => [],
'retriesEnabled' => false
] + $defaultExpectedValues
],
[
// Test with retryableCodes, with retriesEnabled=false
[
'retriesEnabled' => false
] + $defaultSettings,
[
'retriesEnabled' => false
] + $defaultExpectedValues
],
[
// Test with empty retryableCodes, with retriesEnabled=true
[
'retryableCodes' => [],
'retriesEnabled' => true
] + $defaultSettings,
[
'retryableCodes' => [],
'retriesEnabled' => true
] + $defaultExpectedValues
],
[
// Test with noRetriesRpcTimeoutMillis
[
'noRetriesRpcTimeoutMillis' => 151,
] + $defaultSettings,
[
'noRetriesRpcTimeoutMillis' => 151,
] + $defaultExpectedValues
]
];
}
public function withRetrySettingsProvider()
{
$defaultSettings = [
'initialRetryDelayMillis' => 1,
'retryDelayMultiplier' => 1,
'maxRetryDelayMillis' => 1,
'initialRpcTimeoutMillis' => 1,
'rpcTimeoutMultiplier' => 1,
'maxRpcTimeoutMillis' => 1,
'totalTimeoutMillis' => 1,
'retryableCodes' => [1],
'noRetriesRpcTimeoutMillis' => 1,
'retriesEnabled' => true,
];
$defaultExpectedValues = [
'initialRetryDelayMillis' => 1,
'retryDelayMultiplier' => 1,
'maxRetryDelayMillis' => 1,
'initialRpcTimeoutMillis' => 1,
'rpcTimeoutMultiplier' => 1,
'maxRpcTimeoutMillis' => 1,
'totalTimeoutMillis' => 1,
'retryableCodes' => [1],
'noRetriesRpcTimeoutMillis' => 1,
'retriesEnabled' => true,
];
return [
[
// Test with no changes
$defaultSettings,
[],
$defaultExpectedValues
],
[
// Test disable retries
$defaultSettings,
[
'retriesEnabled' => false,
],
[
'retriesEnabled' => false,
] + $defaultExpectedValues
],
[
// Test change all settings
$defaultSettings,
[
'initialRetryDelayMillis' => 2,
'retryDelayMultiplier' => 3,
'maxRetryDelayMillis' => 4,
'initialRpcTimeoutMillis' => 5,
'rpcTimeoutMultiplier' => 6,
'maxRpcTimeoutMillis' => 7,
'totalTimeoutMillis' => 8,
'retryableCodes' => [9],
'noRetriesRpcTimeoutMillis' => 10,
'retriesEnabled' => false,
],
[
'initialRetryDelayMillis' => 2,
'retryDelayMultiplier' => 3,
'maxRetryDelayMillis' => 4,
'initialRpcTimeoutMillis' => 5,
'rpcTimeoutMultiplier' => 6,
'maxRpcTimeoutMillis' => 7,
'totalTimeoutMillis' => 8,
'retryableCodes' => [9],
'noRetriesRpcTimeoutMillis' => 10,
'retriesEnabled' => false,
]
]
];
}
}
tests/Tests/Unit/SerializerTest.php 0000644 00000023536 15174274026 0013432 0 ustar 00 encodeMessage($message);
$this->assertEquals($arrayStructure, $serializedMessage);
// Check that $message when encoded and decoded is unchanged
$deserializedMessage = $serializer->decodeMessage(new $klass(), $serializedMessage);
$this->assertEquals($message, $deserializedMessage);
// Check that $arrayStructure when decoded is equal to $message
$deserializedStructure = $serializer->decodeMessage(new $klass(), $arrayStructure);
$this->assertEquals($message, $deserializedStructure);
// Check that $arrayStructure when decoded and encoded is unchanged
$reserializedStructure = $serializer->encodeMessage($deserializedStructure);
$this->assertEquals($arrayStructure, $reserializedStructure);
}
public function testStatusMessage()
{
$details = [new Any()];
$message = new Status();
$message->setMessage("message");
$message->setCode(0);
$message->setDetails($details);
$encodedMessage = [
'message' => 'message',
'code' => 0,
'details' => [
[
'typeUrl' => '',
'value' => '',
],
]
];
$this->verifySerializeAndDeserialize($message, $encodedMessage);
}
public function testHttpRule()
{
$message = new HttpRule();
$encodedMessage = [
'selector' => '',
'body' => '',
'responseBody' => '',
'additionalBindings' => [],
];
$this->verifySerializeAndDeserialize($message, $encodedMessage);
}
public function testHttpRuleSetOneof()
{
$message = new HttpRule();
$message->setPatch('');
$encodedMessage = [
'selector' => '',
'patch' => '',
'body' => '',
'responseBody' => '',
'additionalBindings' => [],
];
$this->verifySerializeAndDeserialize($message, $encodedMessage);
}
public function testHttpRuleSetOneofToValue()
{
$message = new HttpRule();
$message->setPatch('test');
$encodedMessage = [
'selector' => '',
'patch' => 'test',
'body' => '',
'responseBody' => '',
'additionalBindings' => [],
];
$this->verifySerializeAndDeserialize($message, $encodedMessage);
}
public function testFieldMask()
{
$message = new FieldMask();
$encodedMessage = [
'paths' => []
];
$this->verifySerializeAndDeserialize($message, $encodedMessage);
}
public function testProperlyHandlesMessage()
{
$value = 'test';
// Using this class because it contains maps, oneofs and structs
$message = new \Google\Protobuf\Struct();
$innerValue1 = new Value();
$innerValue1->setStringValue($value);
$innerValue2 = new Value();
$innerValue2->setBoolValue(true);
$structValue1 = new Value();
$structValue1->setStringValue(strtoupper($value));
$structValue2 = new Value();
$structValue2->setStringValue($value);
$labels = [
strtoupper($value) => $structValue1,
$value => $structValue2,
];
$innerStruct = new Struct();
$innerStruct->setFields($labels);
$innerValue3 = new Value();
$innerValue3->setStructValue($innerStruct);
$innerValues = [$innerValue1, $innerValue2, $innerValue3];
$listValue = new ListValue();
$listValue->setValues($innerValues);
$fieldValue = new Value();
$fieldValue->setListValue($listValue);
$fields = [
'listField' => $fieldValue,
];
$message->setFields($fields);
$encodedMessage = [
'fields' => [
'listField' => [
'listValue' => [
'values' => [
[
'stringValue' => $value,
],
[
'boolValue' => true,
],
[
'structValue' => [
'fields' => [
strtoupper($value) => [
'stringValue' => strtoupper($value),
],
$value => [
'stringValue' => $value,
]
],
],
]
]
]
]
],
];
$this->verifySerializeAndDeserialize($message, $encodedMessage);
}
public function testSpecialEncodingDecodingByFieldName()
{
$serializer = new Serializer([
'red' => function ($v) {
return $v * 2;
}
], [], [
'red' => function ($v) {
return $v / 2;
}
]);
$data = [
'red' => 0.2,
'green' => 0.3,
'blue' => 0.4,
'alpha' => [
'value' => 1.0
]
];
$color = $serializer->decodeMessage(new Color(), $data);
$this->assertEquals(0.1, $color->getRed(), '', 0.0000001);
$this->assertEquals(0.3, $color->getGreen(), '', 0.0000001);
$this->assertEquals(0.4, $color->getBlue(), '', 0.0000001);
$alpha = $color->getAlpha();
$this->assertEquals(1.0, $alpha->getValue(), '', 0.0000001);
$array = $serializer->encodeMessage($color);
$this->assertEquals($data['red'], $array['red'], '', 0.0000001);
$this->assertEquals($data['green'], $array['green'], '', 0.0000001);
$this->assertEquals($data['blue'], $array['blue'], '', 0.0000001);
$this->assertEquals($data['alpha']['value'], $array['alpha']['value'], '', 0.0000001);
}
public function testSpecialEncodingDecodingByFieldType()
{
$serializer = new Serializer([], [
'google.protobuf.FloatValue' => function ($v) {
return [
'value' => $v['value'] * 2
];
}
], [], [
'google.protobuf.FloatValue' => function ($v) {
return [
'value' => $v['value'] / 2
];
}
]);
$data = [
'red' => 0.2,
'green' => 0.3,
'blue' => 0.4,
'alpha' => [
'value' => 1.0
]
];
$color = $serializer->decodeMessage(new Color(), $data);
$this->assertEquals(0.2, $color->getRed(), '', 0.0000001);
$this->assertEquals(0.3, $color->getGreen(), '', 0.0000001);
$this->assertEquals(0.4, $color->getBlue(), '', 0.0000001);
$alpha = $color->getAlpha();
$this->assertEquals(0.5, $alpha->getValue(), '', 0.0000001);
$array = $serializer->encodeMessage($color);
$this->assertEquals($data['red'], $array['red'], '', 0.0000001);
$this->assertEquals($data['green'], $array['green'], '', 0.0000001);
$this->assertEquals($data['blue'], $array['blue'], '', 0.0000001);
$this->assertEquals($data['alpha']['value'], $array['alpha']['value'], '', 0.0000001);
}
}
tests/Tests/Unit/ServerStreamTest.php 0000644 00000016333 15174274026 0013740 0 ustar 00 assertSame($call, $stream->getServerStreamingCall());
$this->assertSame([], iterator_to_array($stream->readAll()));
}
/**
* @expectedException \Google\ApiCore\ApiException
* @expectedExceptionMessage empty failure
*/
public function testEmptyFailure()
{
$call = new MockServerStreamingCall([], null, new MockStatus(Code::INTERNAL, 'empty failure'));
$stream = new ServerStream($call);
$this->assertSame($call, $stream->getServerStreamingCall());
iterator_to_array($stream->readAll());
}
public function testStringsSuccess()
{
$responses = ['abc', 'def'];
$call = new MockServerStreamingCall($responses);
$stream = new ServerStream($call);
$this->assertSame($call, $stream->getServerStreamingCall());
$this->assertSame($responses, iterator_to_array($stream->readAll()));
}
/**
* @expectedException \Google\ApiCore\ApiException
* @expectedExceptionMessage strings failure
*/
public function testStringsFailure()
{
$responses = ['abc', 'def'];
$call = new MockServerStreamingCall(
$responses,
null,
new MockStatus(Code::INTERNAL, 'strings failure')
);
$stream = new ServerStream($call);
$this->assertSame($call, $stream->getServerStreamingCall());
$index = 0;
try {
foreach ($stream->readAll() as $response) {
$this->assertSame($response, $responses[$index]);
$index++;
}
} finally {
$this->assertSame(2, $index);
}
}
public function testObjectsSuccess()
{
$responses = [
$this->createStatus(Code::OK, 'response1'),
$this->createStatus(Code::OK, 'response2')
];
$serializedResponses = [];
foreach ($responses as $response) {
$serializedResponses[] = $response->serializeToString();
}
$call = new MockServerStreamingCall($serializedResponses, ['\Google\Rpc\Status', 'mergeFromString']);
$stream = new ServerStream($call);
$this->assertSame($call, $stream->getServerStreamingCall());
$this->assertEquals($responses, iterator_to_array($stream->readAll()));
}
/**
* @expectedException \Google\ApiCore\ApiException
* @expectedExceptionMessage objects failure
*/
public function testObjectsFailure()
{
$responses = [
$this->createStatus(Code::OK, 'response1'),
$this->createStatus(Code::OK, 'response2')
];
$serializedResponses = [];
foreach ($responses as $response) {
$serializedResponses[] = $response->serializeToString();
}
$call = new MockServerStreamingCall(
$serializedResponses,
['\Google\Rpc\Status', 'mergeFromString'],
new MockStatus(Code::INTERNAL, 'objects failure')
);
$stream = new ServerStream($call);
$this->assertSame($call, $stream->getServerStreamingCall());
$index = 0;
try {
foreach ($stream->readAll() as $response) {
$this->assertEquals($response, $responses[$index]);
$index++;
}
} finally {
$this->assertSame(2, $index);
}
}
public function testResourcesSuccess()
{
$resources = ['resource1', 'resource2', 'resource3'];
$repeatedField1 = new RepeatedField(GPBType::STRING);
$repeatedField1[] = 'resource1';
$repeatedField2 = new RepeatedField(GPBType::STRING);
$repeatedField2[] = 'resource2';
$repeatedField2[] = 'resource3';
$responses = [
$this->createMockResponse('nextPageToken1', $repeatedField1),
$this->createMockResponse('nextPageToken1', $repeatedField2)
];
$call = new MockServerStreamingCall($responses);
$stream = new ServerStream($call, [
'resourcesGetMethod' => 'getResourcesList'
]);
$this->assertSame($call, $stream->getServerStreamingCall());
$this->assertEquals($resources, iterator_to_array($stream->readAll()));
}
/**
* @expectedException \Google\ApiCore\ApiException
* @expectedExceptionMessage resources failure
*/
public function testResourcesFailure()
{
$resources = ['resource1', 'resource2', 'resource3'];
$responses = [
$this->createMockResponse('nextPageToken1', ['resource1']),
$this->createMockResponse('nextPageToken1', ['resource2', 'resource3'])
];
$call = new MockServerStreamingCall(
$responses,
null,
new MockStatus(Code::INTERNAL, 'resources failure')
);
$stream = new ServerStream($call, [
'resourcesGetMethod' => 'getResourcesList'
]);
$this->assertSame($call, $stream->getServerStreamingCall());
$index = 0;
try {
foreach ($stream->readAll() as $response) {
$this->assertSame($response, $resources[$index]);
$index++;
}
} finally {
$this->assertSame(3, $index);
}
}
}
tests/Tests/Unit/ServiceAddressTraitTest.php 0000644 00000004124 15174274026 0015223 0 ustar 00 assertSame($expectedAddress, $actualAddress);
$this->assertSame($expectedPort, $actualPort);
}
public function normalizeServiceAddressData()
{
return [
["simple.com:123", "simple.com", "123"],
["really.long.and.dotted:456", "really.long.and.dotted", "456"],
["noport.com", "noport.com", self::$defaultPort],
];
}
/**
* @dataProvider normalizeServiceAddressInvalidData
* @expectedException \Google\ApiCore\ValidationException
* @expectedExceptionMessage Invalid apiEndpoint
*/
public function testNormalizeServiceAddressInvalid($serviceAddressString)
{
self::normalizeServiceAddress($serviceAddressString);
}
public function normalizeServiceAddressInvalidData()
{
return [
["too.many:colons:123"],
["too:many:colons"],
];
}
}
tests/Tests/Unit/TestTrait.php 0000644 00000010762 15174274026 0012401 0 ustar 00 setPageToken($token);
}
if ($pageSize) {
$request->setPageSize($pageSize);
}
return $request;
}
public function createMockResponse($pageToken = null, $resourcesList = [])
{
$mockResponse = new MockResponse();
if ($pageToken) {
$mockResponse->setNextPageToken($pageToken);
}
if ($resourcesList) {
$mockResponse->setResourcesList($resourcesList);
}
return $mockResponse;
}
public function createMockCall($response = null, $status = null)
{
$status = $status ?: new MockStatus(Code::OK);
$call = $this->getMockBuilder(UnaryCall::class)
->disableOriginalConstructor()
->getMock();
$call->method('wait')
->will($this->returnValue([$response, $status]));
return $call;
}
public function createCallWithResponseSequence($sequence)
{
foreach ($sequence as $key => $value) {
if (!is_array($value)) {
$sequence[$key] = [$value, null];
}
}
$mockCall = $this->getMockBuilder(MockCall::class)
->setMethods(['takeAction'])
->getMock();
$mockCall->method('takeAction')
->will(call_user_func_array([$this, 'onConsecutiveCalls'], $sequence));
return $mockCall;
}
public function createOperationsClient($transport = null)
{
$this->requiresGrpcExtension();
$client = new OperationsClient([
'apiEndpoint' => '',
'scopes' => [],
'transport' => $transport,
]);
return $client;
}
/**
* @param \Google\Rpc\Code $code
* @param String $message
* @return Status
*/
public function createStatus($code, $message)
{
$status = new Status();
$status->setCode($code);
$status->setMessage($message);
return $status;
}
/**
* @param $value \Google\Protobuf\Internal\Message;
* @return Any
*/
public function createAny($value)
{
$any = new Any();
$any->setValue($value->serializeToString());
return $any;
}
public static function requiresGrpcExtension()
{
if (!extension_loaded('grpc')) {
self::markTestSkipped('Must have the grpc extension installed to run this test.');
}
if (defined('HHVM_VERSION')) {
self::markTestSkipped('gRPC is not supported on HHVM.');
}
}
}
tests/Tests/Unit/Transport/Grpc/ForwardingServerStreamCallTest.php 0000644 00000005456 15174274026 0021452 0 ustar 00 getMockBuilder(ServerStreamingCall::class)
->disableOriginalConstructor()
->getMock();
$serverStreamingCall->expects($this->once())->method('getMetadata');
$serverStreamingCall->expects($this->once())->method('getTrailingMetadata');
$serverStreamingCall->expects($this->once())->method('getPeer');
$serverStreamingCall->expects($this->once())->method('cancel');
$serverStreamingCall->expects($this->once())->method('responses');
$serverStreamingCall->expects($this->once())->method('getStatus');
$forwardingCall = new ForwardingServerStreamingCall($serverStreamingCall);
$forwardingCall->getMetadata();
$forwardingCall->getTrailingMetadata();
$forwardingCall->getPeer();
$forwardingCall->cancel();
$forwardingCall->responses();
$forwardingCall->getStatus();
}
}
tests/Tests/Unit/Transport/Grpc/ForwardingUnaryCallTest.php 0000644 00000005064 15174274026 0020121 0 ustar 00 getMockBuilder(UnaryCall::class)
->disableOriginalConstructor()
->getMock();
$unaryCall->expects($this->once())->method('getMetadata');
$unaryCall->expects($this->once())->method('getTrailingMetadata');
$unaryCall->expects($this->once())->method('getPeer');
$unaryCall->expects($this->once())->method('cancel');
$unaryCall->expects($this->once())->method('wait');
$forwardingCall = new ForwardingUnaryCall($unaryCall);
$forwardingCall->getMetadata();
$forwardingCall->getTrailingMetadata();
$forwardingCall->getPeer();
$forwardingCall->cancel();
$forwardingCall->wait();
}
}
tests/Tests/Unit/Transport/GrpcFallbackTransportTest.php 0000644 00000017231 15174274026 0017540 0 ustar 00 call = new Call(
'Testing123',
MockResponse::class,
new MockRequest()
);
}
private function getTransport(callable $httpHandler)
{
return new GrpcFallbackTransport(
'www.example.com',
$httpHandler
);
}
/**
* @param $apiEndpoint
* @param $requestMessage
* @dataProvider startUnaryCallDataProvider
*/
public function testStartUnaryCall($apiEndpoint, $requestMessage)
{
$expectedRequest = new Request(
'POST',
"https://$apiEndpoint/\$rpc/Testing123",
[
'Content-Type' => 'application/x-protobuf',
'x-goog-api-client' => ['grpc-web'],
],
$requestMessage->serializeToString()
);
$expectedResponse = (new MockResponse())
->setName('hello')
->setNumber(15);
$httpHandler = function (RequestInterface $request, array $options = []) use ($expectedResponse, $expectedRequest) {
$this->assertEquals($expectedRequest, $request);
return Promise\promise_for(
new Response(
200,
[],
$expectedResponse->serializeToString()
)
);
};
$transport = new GrpcFallbackTransport(
$apiEndpoint,
$httpHandler
);
$call = new Call(
'Testing123',
MockResponse::class,
$requestMessage
);
$response = $transport->startUnaryCall($call, [])->wait();
$this->assertEquals($expectedResponse->getName(), $response->getName());
$this->assertEquals($expectedResponse->getNumber(), $response->getNumber());
}
public function startUnaryCallDataProvider()
{
return [
["www.example.com", new MockRequest()],
["www.example.com:443", new MockRequest()],
["www.example.com:447", new MockRequest()],
];
}
/**
* @expectedException \Exception
*/
public function testStartUnaryCallThrowsException()
{
$httpHandler = function (RequestInterface $request, array $options = []) {
return Promise\rejection_for(new \Exception());
};
$this->getTransport($httpHandler)
->startUnaryCall($this->call, [])
->wait();
}
/**
* @expectedException \Google\ApiCore\ApiException
*/
public function testStartUnaryCallThrowsRequestException()
{
$httpHandler = function (RequestInterface $request, array $options = []) {
$status = new Status();
$status->setCode(Code::NOT_FOUND);
$status->setMessage("Ruh-roh");
return Promise\rejection_for(
RequestException::create(
new Request('POST', 'http://www.example.com'),
new Response(
404,
[],
$status->serializeToString()
)
)
);
};
$this->getTransport($httpHandler)
->startUnaryCall($this->call, [])
->wait();
}
/**
* @dataProvider buildDataGrpcFallback
*/
public function testBuildGrpcFallback($apiEndpoint, $config, $expectedTransport)
{
$actualTransport = GrpcFallbackTransport::build($apiEndpoint, $config);
$this->assertEquals($expectedTransport, $actualTransport);
}
public function buildDataGrpcFallback()
{
$uri = "address.com";
$apiEndpoint = "$uri:443";
$httpHandler = [HttpHandlerFactory::build(), 'async'];
return [
[
$apiEndpoint,
['httpHandler' => $httpHandler],
new GrpcFallbackTransport($apiEndpoint, $httpHandler)
],
[
$apiEndpoint,
[],
new GrpcFallbackTransport($apiEndpoint, $httpHandler),
],
];
}
/**
* @dataProvider buildInvalidData
* @expectedException \Google\ApiCore\ValidationException
* @param $apiEndpoint
* @param $args
* @throws \Google\ApiCore\ValidationException
*/
public function testBuildInvalid($apiEndpoint, $args)
{
GrpcFallbackTransport::build($apiEndpoint, $args);
}
public function buildInvalidData()
{
return [
[
"addresswithtoo:many:segments",
[],
],
];
}
/**
* @expectedException \Google\ApiCore\ApiException
* @expectedExceptionMessage This is an HTML response<\/body><\/html>
* @expectedExceptionCode 5
*/
public function testNonBinaryProtobufResponseException()
{
$httpHandler = function (RequestInterface $request, array $options = []) {
return Promise\rejection_for(
RequestException::create(
new Request('POST', 'http://www.example.com'),
new Response(
404,
[],
"This is an HTML response"
)
)
);
};
$this->getTransport($httpHandler)
->startUnaryCall($this->call, [])
->wait();
}
}
tests/Tests/Unit/Transport/GrpcTransportTest.php 0000644 00000046436 15174274026 0016131 0 ustar 00 requiresGrpcExtension();
}
private function callCredentialsCallback(MockGrpcTransport $transport)
{
$mockCall = new Call('method', '', null);
$options = [];
$response = $transport->startUnaryCall($mockCall, $options)->wait();
$args = $transport->getRequestArguments();
return call_user_func($args['options']['call_credentials_callback']);
}
public function testClientStreamingSuccessObject()
{
$response = new Status();
$response->setCode(Code::OK);
$response->setMessage('response');
$status = new stdClass;
$status->code = Code::OK;
$clientStreamingCall = $this->getMockBuilder(ClientStreamingCall::class)
->disableOriginalConstructor()
->getMock();
$clientStreamingCall->method('write');
$clientStreamingCall->method('wait')
->will($this->returnValue([$response, $status]));
$transport = new MockGrpcTransport($clientStreamingCall);
$stream = $transport->startClientStreamingCall(
new Call('method', null),
[]
);
/* @var $stream \Google\ApiCore\ClientStream */
$actualResponse = $stream->writeAllAndReadResponse([]);
$this->assertEquals($response, $actualResponse);
}
/**
* @expectedException \Google\ApiCore\ApiException
* @expectedExceptionMessage client streaming failure
*/
public function testClientStreamingFailure()
{
$request = "request";
$response = "response";
$status = new stdClass;
$status->code = Code::INTERNAL;
$status->details = 'client streaming failure';
$clientStreamingCall = $this->getMockBuilder(ClientStreamingCall::class)
->disableOriginalConstructor()
->getMock();
$clientStreamingCall->method('wait')
->will($this->returnValue([$response, $status]));
$transport = new MockGrpcTransport($clientStreamingCall);
$stream = $transport->startClientStreamingCall(
new Call('takeAction', null),
[]
);
$stream->readResponse();
}
public function testServerStreamingSuccess()
{
$response = "response";
$status = new stdClass;
$status->code = Code::OK;
$message = $this->createMockRequest();
$serverStreamingCall = $this->getMockBuilder(\Grpc\ServerStreamingCall::class)
->disableOriginalConstructor()
->getMock();
$serverStreamingCall->method('responses')
->will($this->returnValue([$response]));
$serverStreamingCall->method('getStatus')
->will($this->returnValue($status));
$transport = new MockGrpcTransport($serverStreamingCall);
/* @var $stream \Google\ApiCore\ServerStream */
$stream = $transport->startServerStreamingCall(
new Call('takeAction', null, $message),
[]
);
$actualResponsesArray = [];
foreach ($stream->readAll() as $actualResponse) {
$actualResponsesArray[] = $actualResponse;
}
$this->assertEquals([$response], $actualResponsesArray);
}
public function testServerStreamingSuccessResources()
{
$responses = ['resource1', 'resource2'];
$repeatedField = new RepeatedField(GPBType::STRING);
foreach ($responses as $response) {
$repeatedField[] = $response;
}
$response = $this->createMockResponse('nextPageToken', $repeatedField);
$status = new stdClass;
$status->code = Code::OK;
$message = $this->createMockRequest();
$call = $this->getMockBuilder(\Grpc\ServerStreamingCall::class)
->disableOriginalConstructor()
->getMock();
$call->method('responses')
->will($this->returnValue([$response]));
$call->method('getStatus')
->will($this->returnValue($status));
$transport = new MockGrpcTransport($call);
$call = new Call('takeAction',
null,
$message,
['resourcesGetMethod' => 'getResourcesList']
);
$options = [];
/* @var $stream \Google\ApiCore\ServerStream */
$stream = $transport->startServerStreamingCall(
$call,
$options
);
$actualResponsesArray = [];
foreach ($stream->readAll() as $actualResponse) {
$actualResponsesArray[] = $actualResponse;
}
$this->assertEquals($responses, $actualResponsesArray);
}
/**
* @expectedException \Google\ApiCore\ApiException
* @expectedExceptionMessage server streaming failure
*/
public function testServerStreamingFailure()
{
$status = new stdClass;
$status->code = Code::INTERNAL;
$status->details = 'server streaming failure';
$message = $this->createMockRequest();
$serverStreamingCall = $this->getMockBuilder(\Grpc\ServerStreamingCall::class)
->disableOriginalConstructor()
->getMock();
$serverStreamingCall->method('responses')
->will($this->returnValue(['response1']));
$serverStreamingCall->method('getStatus')
->will($this->returnValue($status));
$transport = new MockGrpcTransport($serverStreamingCall);
/* @var $stream \Google\ApiCore\ServerStream */
$stream = $transport->startServerStreamingCall(
new Call('takeAction', null, $message),
[]
);
foreach ($stream->readAll() as $actualResponse) {
// for loop to trigger generator and API exception
}
}
public function testBidiStreamingSuccessSimple()
{
$response = "response";
$status = new stdClass;
$status->code = Code::OK;
$bidiStreamingCall = $this->getMockBuilder(\Grpc\BidiStreamingCall::class)
->disableOriginalConstructor()
->getMock();
$bidiStreamingCall->method('read')
->will($this->onConsecutiveCalls($response, null));
$bidiStreamingCall->method('getStatus')
->will($this->returnValue($status));
$transport = new MockGrpcTransport($bidiStreamingCall);
/* @var $stream \Google\ApiCore\BidiStream */
$stream = $transport->startBidiStreamingCall(
new Call('takeAction', null),
[]
);
$actualResponsesArray = [];
foreach ($stream->closeWriteAndReadAll() as $actualResponse) {
$actualResponsesArray[] = $actualResponse;
}
$this->assertEquals([$response], $actualResponsesArray);
}
public function testBidiStreamingSuccessObject()
{
$response = new Status();
$response->setCode(Code::OK);
$response->setMessage('response');
$status = new stdClass;
$status->code = Code::OK;
$bidiStreamingCall = $this->getMockBuilder(\Grpc\BidiStreamingCall::class)
->disableOriginalConstructor()
->getMock();
$bidiStreamingCall->method('read')
->will($this->onConsecutiveCalls($response, null));
$bidiStreamingCall->method('getStatus')
->will($this->returnValue($status));
$transport = new MockGrpcTransport($bidiStreamingCall);
/* @var $stream \Google\ApiCore\BidiStream */
$stream = $transport->startBidiStreamingCall(
new Call('takeAction', null),
[]
);
$actualResponsesArray = [];
foreach ($stream->closeWriteAndReadAll() as $actualResponse) {
$actualResponsesArray[] = $actualResponse;
}
$this->assertEquals([$response], $actualResponsesArray);
}
public function testBidiStreamingSuccessResources()
{
$responses = ['resource1', 'resource2'];
$repeatedField = new RepeatedField(GPBType::STRING);
foreach ($responses as $response) {
$repeatedField[] = $response;
}
$response = $this->createMockResponse('nextPageToken', $repeatedField);
$status = new stdClass;
$status->code = Code::OK;
$bidiStreamingCall = $this->getMockBuilder(\Grpc\BidiStreamingCall::class)
->disableOriginalConstructor()
->getMock();
$bidiStreamingCall->method('read')
->will($this->onConsecutiveCalls($response, null));
$bidiStreamingCall->method('getStatus')
->will($this->returnValue($status));
$transport = new MockGrpcTransport($bidiStreamingCall);
$call = new Call(
'takeAction',
null,
null,
['resourcesGetMethod' => 'getResourcesList']
);
/* @var $stream \Google\ApiCore\BidiStream */
$stream = $transport->startBidiStreamingCall(
$call,
[]
);
$actualResponsesArray = [];
foreach ($stream->closeWriteAndReadAll() as $actualResponse) {
$actualResponsesArray[] = $actualResponse;
}
$this->assertEquals($responses, $actualResponsesArray);
}
/**
* @expectedException \Google\ApiCore\ApiException
* @expectedExceptionMessage bidi failure
*/
public function testBidiStreamingFailure()
{
$response = "response";
$status = new stdClass;
$status->code = Code::INTERNAL;
$status->details = 'bidi failure';
$bidiStreamingCall = $this->getMockBuilder(\Grpc\BidiStreamingCall::class)
->disableOriginalConstructor()
->getMock();
$bidiStreamingCall->method('read')
->will($this->onConsecutiveCalls($response, null));
$bidiStreamingCall->method('getStatus')
->will($this->returnValue($status));
$transport = new MockGrpcTransport($bidiStreamingCall);
/* @var $stream \Google\ApiCore\BidiStream */
$stream = $transport->startBidiStreamingCall(
new Call('takeAction', null),
[]
);
foreach ($stream->closeWriteAndReadAll() as $actualResponse) {
// for loop to trigger generator and API exception
}
}
public function testAudienceOption()
{
$message = $this->createMockRequest();
$call = $this->prophesize(Call::class);
$call->getMessage()->willReturn($message);
$call->getMethod()->shouldBeCalled();
$call->getDecodeType()->shouldBeCalled();
$credentialsWrapper = $this->prophesize(CredentialsWrapper::class);
$credentialsWrapper->getAuthorizationHeaderCallback('an-audience')
->shouldBeCalledOnce();
$hostname = '';
$opts = ['credentials' => ChannelCredentials::createInsecure()];
$transport = new GrpcTransport($hostname, $opts);
$options = [
'audience' => 'an-audience',
'credentialsWrapper' => $credentialsWrapper->reveal(),
];
$transport->startUnaryCall($call->reveal(), $options);
}
/**
* @dataProvider buildDataGrpc
*/
public function testBuildGrpc($apiEndpoint, $config, $expectedTransportProvider)
{
$expectedTransport = $expectedTransportProvider();
$actualTransport = GrpcTransport::build($apiEndpoint, $config);
$this->assertEquals($expectedTransport, $actualTransport);
}
public function buildDataGrpc()
{
$uri = "address.com";
$apiEndpoint = "$uri:447";
$apiEndpointDefaultPort = "$uri:443";
return [
[
$apiEndpoint,
[],
function () use ($apiEndpoint) {
return new GrpcTransport(
$apiEndpoint,
[
'credentials' => null,
],
null
);
},
],
[
$uri,
[],
function () use ($apiEndpointDefaultPort) {
return new GrpcTransport(
$apiEndpointDefaultPort,
[
'credentials' => null,
],
null
);
},
],
];
}
/**
* @dataProvider buildInvalidData
* @expectedException \Google\ApiCore\ValidationException
*/
public function testBuildInvalid($apiEndpoint, $args)
{
GrpcTransport::build($apiEndpoint, $args);
}
public function buildInvalidData()
{
return [
[
"addresswithtoo:many:segments",
[],
],
[
'example.com',
[
'channel' => 'not a channel',
]
]
];
}
/**
* @dataProvider interceptorDataProvider
*/
public function testExperimentalInterceptors($callType, $interceptor)
{
$transport = new GrpcTransport(
'example.com',
[
'credentials' => ChannelCredentials::createInsecure()
],
null,
[$interceptor]
);
$r = new \ReflectionProperty(BaseStub::class, 'call_invoker');
$r->setAccessible(true);
$r->setValue(
$transport,
new MockCallInvoker(
$this->buildMockCallForInterceptor($callType)
)
);
$call = new Call(
'method1',
'',
new MockRequest()
);
if ($callType === UnaryCall::class) {
$transport->startUnaryCall($call, [
'transportOptions' => [
'grpcOptions' => [
'call-option' => 'call-option-value'
]
]
]);
return;
}
$transport->startServerStreamingCall($call, [
'transportOptions' => [
'grpcOptions' => [
'call-option' => 'call-option-value'
]
]
]);
}
public function interceptorDataProvider()
{
return [
[
UnaryCall::class,
new TestUnaryInterceptor()
],
[
UnaryCall::class,
new TestInterceptor()
],
[
ServerStreamingCall::class,
new TestInterceptor()
]
];
}
private function buildMockCallForInterceptor($callType)
{
$mockCall = $this->getMockBuilder($callType)
->disableOriginalConstructor()
->getMock();
$mockCall->method('start')
->with(
$this->isInstanceOf(Message::class),
$this->equalTo([]),
$this->equalTo([
'call-option' => 'call-option-value',
'test-interceptor-insert' => 'inserted-value'
])
);
if ($callType === UnaryCall::class) {
$mockCall->method('wait')
->will($this->returnValue([
null,
Code::OK
]));
}
return $mockCall;
}
}
class MockCallInvoker implements CallInvoker
{
public function __construct($mockCall)
{
$this->mockCall = $mockCall;
}
public function createChannelFactory($hostname, $opts)
{
// no-op
}
public function UnaryCall($channel, $method, $deserialize, $options)
{
return $this->mockCall;
}
public function ServerStreamingCall($channel, $method, $deserialize, $options)
{
return $this->mockCall;
}
public function ClientStreamingCall($channel, $method, $deserialize, $options)
{
// no-op
}
public function BidiStreamingCall($channel, $method, $deserialize, $options)
{
// no-op
}
}
class TestInterceptor extends Interceptor
{
public function interceptUnaryUnary(
$method,
$argument,
$deserialize,
array $metadata = [],
array $options = [],
$continuation
) {
$options['test-interceptor-insert'] = 'inserted-value';
return $continuation($method, $argument, $deserialize, $metadata, $options);
}
public function interceptUnaryStream(
$method,
$argument,
$deserialize,
array $metadata = [],
array $options = [],
$continuation
) {
$options['test-interceptor-insert'] = 'inserted-value';
return $continuation($method, $argument, $deserialize, $metadata, $options);
}
}
class TestUnaryInterceptor implements UnaryInterceptorInterface
{
public function interceptUnaryUnary(
$method,
$argument,
$deserialize,
array $metadata,
array $options,
callable $continuation
) {
$options['test-interceptor-insert'] = 'inserted-value';
return $continuation($method, $argument, $deserialize, $metadata, $options);
}
}
tests/Tests/Unit/Transport/RestTransportTest.php 0000644 00000020643 15174274026 0016143 0 ustar 00 call = new Call(
'Testing123',
MockResponse::class,
new MockRequest()
);
}
private function getTransport(callable $httpHandler, $apiEndpoint = 'http://www.example.com')
{
$request = new Request('POST', $apiEndpoint);
$requestBuilder = $this->getMockBuilder(RequestBuilder::class)
->disableOriginalConstructor()
->getMock();
$requestBuilder->method('build')
->willReturn($request);
return new RestTransport(
$requestBuilder,
$httpHandler
);
}
/**
* @param $apiEndpoint
* @dataProvider startUnaryCallDataProvider
*/
public function testStartUnaryCall($apiEndpoint)
{
$expectedRequest = new Request(
'POST',
"$apiEndpoint",
[],
""
);
$body = ['name' => 'hello', 'number' => 15];
$httpHandler = function (RequestInterface $request, array $options = []) use ($body, $expectedRequest) {
$this->assertEquals($expectedRequest, $request);
return Promise\promise_for(
new Response(
200,
[],
json_encode($body)
)
);
};
$response = $this->getTransport($httpHandler, $apiEndpoint)
->startUnaryCall($this->call, [])
->wait();
$this->assertEquals($body['name'], $response->getName());
$this->assertEquals($body['number'], $response->getNumber());
}
public function startUnaryCallDataProvider()
{
return [
["www.example.com"],
["www.example.com:443"],
["www.example.com:447"],
];
}
/**
* @expectedException \Exception
*/
public function testStartUnaryCallThrowsException()
{
$httpHandler = function (RequestInterface $request, array $options = []) {
return Promise\rejection_for(new \Exception());
};
$this->getTransport($httpHandler)
->startUnaryCall($this->call, [])
->wait();
}
/**
* @expectedException \Google\ApiCore\ApiException
*/
public function testStartUnaryCallThrowsRequestException()
{
$httpHandler = function (RequestInterface $request, array $options = []) {
return Promise\rejection_for(
RequestException::create(
new Request('POST', 'http://www.example.com'),
new Response(
404,
[],
json_encode([
'error' => [
'status' => 'NOT_FOUND',
'message' => 'Ruh-roh.'
]
])
)
)
);
};
$this->getTransport($httpHandler)
->startUnaryCall($this->call, [])
->wait();
}
/**
* @dataProvider buildDataRest
*/
public function testBuildRest($apiEndpoint, $restConfigPath, $config, $expectedTransport)
{
$actualTransport = RestTransport::build($apiEndpoint, $restConfigPath, $config);
$this->assertEquals($expectedTransport, $actualTransport);
}
public function buildDataRest()
{
$uri = "address.com";
$apiEndpoint = "$uri:443";
$restConfigPath = __DIR__ . '/../testdata/test_service_rest_client_config.php';
$requestBuilder = new RequestBuilder($apiEndpoint, $restConfigPath);
$httpHandler = [HttpHandlerFactory::build(), 'async'];
return [
[
$apiEndpoint,
$restConfigPath,
['httpHandler' => $httpHandler],
new RestTransport($requestBuilder, $httpHandler)
],
[
$apiEndpoint,
$restConfigPath,
[],
new RestTransport($requestBuilder, $httpHandler),
],
];
}
/**
* @dataProvider buildInvalidData
* @expectedException \Google\ApiCore\ValidationException
*/
public function testBuildInvalid($apiEndpoint, $restConfigPath, $args)
{
RestTransport::build($apiEndpoint, $restConfigPath, $args);
}
public function buildInvalidData()
{
$restConfigPath = __DIR__ . '/../testdata/test_service_rest_client_config.php';
return [
[
"addresswithtoo:many:segments",
$restConfigPath,
[],
],
[
"address.com",
"badpath",
[],
],
];
}
/**
* @expectedException \Google\ApiCore\ApiException
* @expectedExceptionMessage This is an HTML response<\/body><\/html>
* @expectedExceptionCode 5
*/
public function testNonJsonResponseException()
{
$httpHandler = function (RequestInterface $request, array $options = []) {
return Promise\rejection_for(
RequestException::create(
new Request('POST', 'http://www.example.com'),
new Response(
404,
[],
"This is an HTML response"
)
)
);
};
$this->getTransport($httpHandler)
->startUnaryCall($this->call, [])
->wait();
}
public function testAudienceOption()
{
$credentialsWrapper = $this->prophesize(CredentialsWrapper::class);
$credentialsWrapper->getAuthorizationHeaderCallback('an-audience')
->shouldBeCalledOnce()
->willReturn(function() { return []; });
$options = [
'audience' => 'an-audience',
'credentialsWrapper' => $credentialsWrapper->reveal(),
];
$httpHandler = function (RequestInterface $request, array $options = []) {
return Promise\promise_for(new Response(200, [], '{}'));
};
$this->getTransport($httpHandler)
->startUnaryCall($this->call, $options)
->wait();
}
}
tests/Tests/Unit/UriTraitTest.php 0000644 00000004602 15174274026 0013055 0 ustar 00 implementation = $this->getObjectForTrait(UriTrait::class);
}
/**
* @dataProvider queryProvider
*/
public function testBuildsUriWithQuery($expectedQuery, $query)
{
$baseUri = 'http://www.example.com';
$uri = $this->implementation->buildUriWithQuery($baseUri, $query);
$this->assertEquals($baseUri . $expectedQuery, (string) $uri);
}
public function queryProvider()
{
return [
['?narf=yes', ['narf' => 'yes']],
['?narf=true', ['narf' => true]],
['?narf=false', ['narf' => false]],
['?narf=0', ['narf' => '0']]
];
}
}
tests/Tests/Unit/ValidationTraitTest.php 0000644 00000004100 15174274026 0014401 0 ustar 00 stub = new ValidationTraitStub;
}
/**
* @expectedException \Google\ApiCore\ValidationException
* @expectedExceptionMessage Missing required argument
*/
public function testValidateMissingRequiredKey()
{
$input = [
'foo' => 1,
'bar' => 2
];
$this->stub->validate($input, ['bar', 'baz']);
}
public function testValidateValidArray()
{
$input = [
'foo' => 1,
'bar' => 2
];
$arr = $this->stub->validate($input, ['foo', 'bar']);
$this->assertEquals($input, $arr);
}
/**
* @expectedException \Google\ApiCore\ValidationException
* @expectedExceptionMessage Missing required argument
*/
public function testValidateNotNullWithNullRequiredKey()
{
$input = [
'foo' => 1,
'bar' => null
];
$this->stub->validateNotNull($input, ['foo', 'bar']);
}
public function testValidateValidArrayWithNotNull()
{
$input = [
'foo' => 1,
'bar' => 2
];
$arr = $this->stub->validateNotNull($input, ['foo', 'bar']);
$this->assertEquals($input, $arr);
}
}
class ValidationTraitStub
{
use ValidationTrait;
}
tests/Tests/Unit/VersionTest.php 0000644 00000003537 15174274026 0012745 0 ustar 00 assertStringMatchesFormat("%d.%d.%d", $actualVersion);
}
}
tests/Tests/Unit/testdata/VERSION 0000644 00000000011 15174274026 0012610 0 ustar 00 1.2.3-dev tests/Tests/Unit/testdata/json-key-file.json 0000644 00000000240 15174274026 0015113 0 ustar 00 {
"type": "authorized_user",
"client_id": "example@example.com",
"client_secret": "example",
"refresh_token": "abc",
"project_id": "example_project"
} tests/Tests/Unit/testdata/src/GapicClientStub.php 0000644 00000003704 15174274026 0016074 0 ustar 00 [
'test.interface.v1.api' => [
'PageStreamingMethod' => [
'pageStreaming' => [
'requestPageTokenGetMethod' => 'getPageToken',
'requestPageTokenSetMethod' => 'setPageToken',
'requestPageSizeGetMethod' => 'getPageSize',
'requestPageSizeSetMethod' => 'setPageSize',
'responsePageTokenGetMethod' => 'getNextPageToken',
],
],
],
],
];
tests/Tests/Unit/testdata/test_service_grpc_config.json 0000644 00000000415 15174274026 0017502 0 ustar 00 {
"channelPool": {
"maxSize": 10,
"maxConcurrentStreamsLowWatermark": 1
},
"method": [
{
"name": [
"/google.test.v1.Test/TestMethod"
],
"affinity": {
"command": "BIND",
"affinityKey": "key"
}
}
]
} tests/Tests/Unit/testdata/test_service_invalid_client_config.json 0000644 00000001574 15174274026 0021542 0 ustar 00 {
"interfaces": {
"test.interface.v1.api": {
"retry_codes": {
"idempotent": [
"DEADLINE_EXCEEDED",
"UNAVAILABLE"
],
"non_idempotent": ["INTERNAL"]
},
"retry_params": {
"default": {
"initial_retry_delay_millis": 100,
"retry_delay_multiplier": 1.2,
"initial_rpc_timeout_millis": 300,
"rpc_timeout_multiplier": 1.3,
"max_rpc_timeout_millis": 3000,
"total_timeout_millis": 30000
}
},
"methods": {
"SimpleMethod": {
"timeout_millis": 40000,
"retry_codes_name": "idempotent",
"retry_params_name": "default"
},
"PageStreamingMethod": {
"timeout_millis": 40000,
"retry_codes_name": "non_idempotent",
"retry_params_name": "default"
}
}
}
}
} tests/Tests/Unit/testdata/test_service_rest_client_config.php 0000644 00000011310 15174274026 0020674 0 ustar 00 [
'test.interface.v1.api' => [
'MethodWithBody' => [
'method' => 'post',
'uriTemplate' => '/v1/foo',
'body' => '*',
],
'MethodWithUrlPlaceholder' => [
'method' => 'get',
'uriTemplate' => '/v1/{name=message/**}',
'placeholders' => [
'name' => [
'getters' => [
'getName'
]
],
],
],
'MethodWithBodyAndUrlPlaceholder' => [
'method' => 'post',
'uriTemplate' => '/v1/{name=message/**}',
'body' => '*',
'placeholders' => [
'name' => [
'getters' => [
'getName'
]
],
],
],
'MethodWithNestedMessageAsBody' => [
'method' => 'post',
'uriTemplate' => '/v1/{name=message/**}',
'body' => 'nested_message',
'placeholders' => [
'name' => [
'getters' => [
'getName'
]
],
],
],
'MethodWithScalarBody' => [
'method' => 'post',
'uriTemplate' => '/v1/foo',
'body' => 'name',
],
'MethodWithNestedUrlPlaceholder' => [
'method' => 'get',
'uriTemplate' => '/v1/{nested_message=nested/**}',
'body' => '*',
'placeholders' => [
'nested_message' => [
'getters' => [
'getNestedMessage',
'getName'
]
]
],
],
'MethodWithColonInUrl' => [
'method' => 'get',
'uriTemplate' => '/v1/{name=message/*}:action',
'placeholders' => [
'name' => [
'getters' => [
'getName',
],
]
],
],
'MethodWithMultipleWildcardsAndColonInUrl' => [
'method' => 'get',
'uriTemplate' => '/v1/{name=message/*}/number/{number=*}:action',
'placeholders' => [
'name' => [
'getters' => [
'getName',
]
],
'number' => [
'getters' => [
'getNumber',
]
]
],
],
'MethodWithSimplePlaceholder' => [
'method' => 'get',
'uriTemplate' => '/v1/{name}',
'placeholders' => [
'name' => [
'getters' => [
'getName',
],
]
]
],
'MethodWithAdditionalBindings' => [
'method' => 'post',
'uriTemplate' => '/v1/{name=message/**}/additional/bindings',
'body' => '*',
'additionalBindings' => [
[
'method' => 'post',
'uriTemplate' => '/v2/{nested_message=nested/**}/additional/bindings',
'body' => '*'
],
[
'method' => 'post',
'uriTemplate' => '/v1/{name=different/format/**}/additional/bindings',
'body' => '*'
],
],
'placeholders' => [
'name' => [
'getters' => [
'getName'
]
],
'nested_message' => [
'getters' => [
'getNestedMessage',
'getName'
]
]
],
],
'MethodWithSpecialJsonMapping' => [
'method' => 'get',
'uriTemplate' => '/v1/special/mapping'
],
'MethodWithoutPlaceholders' => [
'method' => 'get',
'uriTemplate' => '/v1/fixedurl',
],
],
],
];
tests/bootstrap.php 0000644 00000004267 15174274026 0010455 0 ustar 00 register(new MessageAwareArrayComparator());
\SebastianBergmann\Comparator\Factory::getInstance()->register(new ProtobufMessageComparator());
\SebastianBergmann\Comparator\Factory::getInstance()->register(new ProtobufGPBEmptyComparator());