diff --git a/.github/run-package-tests.sh b/.github/run-package-tests.sh
index a02cfa1dbe1b..c28adbfbfaa5 100644
--- a/.github/run-package-tests.sh
+++ b/.github/run-package-tests.sh
@@ -63,6 +63,7 @@ for DIR in ${DIRS}; do
cp "${DIR}/composer.json" "${DIR}/composer-local.json"
# Update composer to use local packages
PACKAGE_DEPENDENCIES=(
+ "Gax,gax"
"CommonProtos,common-protos,4.100"
"BigQuery,cloud-bigquery"
"Core,cloud-core"
diff --git a/.github/workflows/conformance-tests-gax-showcase.yaml b/.github/workflows/conformance-tests-gax-showcase.yaml
new file mode 100644
index 000000000000..19eb221179c7
--- /dev/null
+++ b/.github/workflows/conformance-tests-gax-showcase.yaml
@@ -0,0 +1,38 @@
+name: Run GAX Conformance Tests With Showcase
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+permissions:
+ contents: read
+jobs:
+ gapic-showcase:
+ runs-on: ubuntu-latest
+ env:
+ GAPIC_SHOWCASE_VERSION: 0.36.2
+ OS: linux
+ ARCH: amd64
+ name: GAPIC Showcase Conformance Tests
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v6
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: '8.1'
+ extensions: grpc
+
+ - name: Install and run GAPIC Showcase
+ run: |
+ curl -L https://github.com/googleapis/gapic-showcase/releases/download/v${GAPIC_SHOWCASE_VERSION}/gapic-showcase-${GAPIC_SHOWCASE_VERSION}-${OS}-${ARCH}.tar.gz | tar -zx
+ ./gapic-showcase run &
+
+ - name: Install dependencies
+ run: |
+ composer update --prefer-dist --no-interaction --no-suggest -d Gax/
+
+ - name: Run PHPUnit
+ run: Gax/vendor/bin/phpunit -c Gax/phpunit-conformance.xml.dist
+
diff --git a/.github/workflows/unit-tests.yaml b/.github/workflows/unit-tests.yaml
index 2760e8c5a1f0..40833bb83ce2 100644
--- a/.github/workflows/unit-tests.yaml
+++ b/.github/workflows/unit-tests.yaml
@@ -13,13 +13,17 @@ jobs:
matrix:
platform: [ubuntu-latest, windows-latest]
php: [ "8.1", "8.2", "8.3", "8.4", "8.5" ]
- extensions: ["grpc-1.54.0"]
+ extensions: ["grpc-1.78.0"]
type: ["Unit Test"]
include:
- platform: "ubuntu-latest"
php: "8.1"
- extensions: "protobuf,grpc-1.54.0"
+ extensions: "protobuf,grpc"
type: "Unit Test + protobuf extension"
+ - platform: "ubuntu-latest"
+ php: "8.1"
+ extensions: ""
+ type: "Unit Test (no gRPC)"
name: PHP ${{ matrix.php }} ${{ matrix.type }} (${{ matrix.platform }})
runs-on: ${{ matrix.platform }}
continue-on-error: true
@@ -30,7 +34,7 @@ jobs:
uses: shivammathur/cache-extensions@v1
with:
php-version: ${{ matrix.php }}
- extensions: sodium,${{ matrix.extensions }}
+ extensions: sodium,sysvshm,${{ matrix.extensions }}
key: cache-key-1 # increment to bust the cache
- name: Cache extensions
uses: actions/cache@v5
@@ -42,7 +46,7 @@ jobs:
uses: shivammathur/setup-php@verbose
with:
php-version: ${{ matrix.php }}
- extensions: sodium,${{ matrix.extensions }}
+ extensions: sodium,sysvshm,${{ matrix.extensions }}
- name: Install Dependencies
uses: nick-invision/retry@v4
with:
diff --git a/.kokoro/docs/publish.sh b/.kokoro/docs/publish.sh
index 3e249b569bbc..c8174582c863 100755
--- a/.kokoro/docs/publish.sh
+++ b/.kokoro/docs/publish.sh
@@ -42,15 +42,6 @@ do
$VERBOSITY_FLAG
done
-# Add GAX repo
-GAX_DIR=$PROJECT_DIR/dev/vendor/google/gax
-$PROJECT_DIR/dev/google-cloud docfx \
- --path $GAX_DIR \
- --out gax-out \
- --metadata-version $(cat $GAX_DIR/VERSION) \
- $STAGING_FLAG \
- $VERBOSITY_FLAG
-
# Add Auth repo
AUTH_DIR=$PROJECT_DIR/dev/vendor/google/auth
$PROJECT_DIR/dev/google-cloud docfx \
diff --git a/.repo-metadata-full.json b/.repo-metadata-full.json
index eaa99a006ff3..a507457df4da 100644
--- a/.repo-metadata-full.json
+++ b/.repo-metadata-full.json
@@ -789,6 +789,14 @@
"library_type": "GAPIC_AUTO",
"api_shortname": "cloudfunctions"
},
+ "Gax": {
+ "language": "php",
+ "distribution_name": "google/gax",
+ "release_level": "stable",
+ "client_documentation": "https://cloud.google.com/php/docs/reference/gax/latest",
+ "library_type": "CORE",
+ "api_shortname": ""
+ },
"GSuiteAddOns": {
"language": "php",
"distribution_name": "google/cloud-gsuite-addons",
diff --git a/CommonProtos/renovate.json b/CommonProtos/renovate.json
deleted file mode 100644
index 6d8121323111..000000000000
--- a/CommonProtos/renovate.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "extends": [
- "config:base",
- ":preserveSemverRanges",
- ":disableDependencyDashboard"
- ]
-}
diff --git a/Core/src/Testing/Snippet/SnippetTestCase.php b/Core/src/Testing/Snippet/SnippetTestCase.php
index 3ed8de6d07a1..2e2fa742dd08 100644
--- a/Core/src/Testing/Snippet/SnippetTestCase.php
+++ b/Core/src/Testing/Snippet/SnippetTestCase.php
@@ -18,6 +18,7 @@
namespace Google\Cloud\Core\Testing\Snippet;
use Google\Cloud\Core\Testing\CheckForClassTrait;
+use Google\Cloud\Core\Testing\GrpcTestTrait;
use Google\Cloud\Core\Testing\Snippet\Container;
use Google\Cloud\Core\Testing\Snippet\Parser\Snippet;
use PHPUnit\Framework\TestCase;
@@ -35,6 +36,7 @@ class SnippetTestCase extends TestCase
const PROJECT = 'my-awesome-project';
use CheckForClassTrait;
+ use GrpcTestTrait;
private static $coverage;
private static $parser;
diff --git a/Core/tests/Snippet/Iam/IamTest.php b/Core/tests/Snippet/Iam/IamTest.php
index 24283448fe3a..05d6c64cc989 100644
--- a/Core/tests/Snippet/Iam/IamTest.php
+++ b/Core/tests/Snippet/Iam/IamTest.php
@@ -54,6 +54,7 @@ public function testClass()
$this->checkAndSkipTest([
SpannerClient::class,
]);
+ $this->checkAndSkipGrpcTests();
$res = $snippet->invoke('iam');
$this->assertInstanceOf(IamManager::class, $res->returnVal());
diff --git a/Gax/.gitattributes b/Gax/.gitattributes
new file mode 100644
index 000000000000..0acb7092cef2
--- /dev/null
+++ b/Gax/.gitattributes
@@ -0,0 +1,8 @@
+.gitattributes export-ignore
+.gitignore export-ignore
+.github export-ignore
+dev export-ignore
+tests export-ignore
+codecov.yml export-ignore
+ruleset.xml export-ignore
+.travis.yml export-ignore
diff --git a/Gax/.github/pull_request_template.md b/Gax/.github/pull_request_template.md
new file mode 100644
index 000000000000..a3a7331405fe
--- /dev/null
+++ b/Gax/.github/pull_request_template.md
@@ -0,0 +1,24 @@
+**PLEASE READ THIS ENTIRE MESSAGE**
+
+Hello, and thank you for your contribution! Please note that this repository is
+a read-only split of `googleapis/google-cloud-php`. As such, we are
+unable to accept pull requests to this repository.
+
+We welcome your pull request and would be happy to consider it for inclusion in
+our library if you follow these steps:
+
+* Clone the parent client library repository:
+
+```sh
+$ git clone git@github.com:googleapis/google-cloud-php.git
+```
+
+* Move your changes into the correct location in that library. Library code
+belongs in `Grafeas/src`, and tests in `Grafeas/tests`.
+
+* Push the changes in a new branch to a fork, and open a new pull request
+[here](https://github.com/googleapis/google-cloud-php).
+
+Thanks again, and we look forward to seeing your proposed change!
+
+The Google Cloud PHP team
diff --git a/Gax/.github/workflows/tests.yml b/Gax/.github/workflows/tests.yml
new file mode 100644
index 000000000000..7de768f304dd
--- /dev/null
+++ b/Gax/.github/workflows/tests.yml
@@ -0,0 +1,43 @@
+name: Test Suite
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+permissions:
+ contents: read
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ php: [ "8.1", "8.2", "8.3", "8.4" ]
+ extensions: [""]
+ tools: [""]
+ composerflags: [""]
+ include:
+ - php: "8.1"
+ extensions: "protobuf,grpc"
+ tools: "pecl"
+ - php: "8.4"
+ extensions: "protobuf,grpc"
+ tools: "pecl"
+ - php: "8.1"
+ composerflags: "--prefer-lowest"
+ name: "PHP ${{ matrix.php }} Unit Test ${{ matrix.extensions }}${{ matrix.composerflags }}"
+ steps:
+ - uses: actions/checkout@v6
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ tools: ${{ matrix.tools }}
+ extensions: bcmath,${{ matrix.extensions }}
+ - name: Install composer dependencies
+ uses: nick-invision/retry@v3
+ with:
+ timeout_minutes: 10
+ max_attempts: 3
+ command: composer update ${{ matrix.composerflags }}
+ - name: Run script
+ run: vendor/bin/phpunit
diff --git a/Gax/.gitignore b/Gax/.gitignore
new file mode 100644
index 000000000000..94e81fe0df3a
--- /dev/null
+++ b/Gax/.gitignore
@@ -0,0 +1,22 @@
+.DS_Store
+phpunit.xml
+composer.phar
+doctum.phar
+composer.lock
+composer-test.lock
+vendor/
+build/artifacts/
+artifacts/
+.idea
+.metadata
+.phpunit.result.cache
+coverage.xml
+src/Jison/Parser.js
+*.iml
+
+# Vim
+*.sw*
+*.vim
+
+# VS Code
+.vscode
diff --git a/Gax/.php-cs-fixer.cache b/Gax/.php-cs-fixer.cache
new file mode 100644
index 000000000000..40d8fff137ca
--- /dev/null
+++ b/Gax/.php-cs-fixer.cache
@@ -0,0 +1 @@
+{"php":"8.1.34","version":"3.93.1:v3.93.1#b3546ab487c0762c39f308dc1ec0ea2c461fc21a","indent":" ","lineEnding":"\n","rules":{"blank_line_after_namespace":true,"braces_position":{"allow_single_line_anonymous_functions":false},"class_definition":true,"constant_case":true,"control_structure_braces":true,"control_structure_continuation_position":true,"elseif":true,"function_declaration":{"closure_fn_spacing":"one"},"indentation_type":true,"line_ending":true,"lowercase_keywords":true,"method_argument_space":{"after_heredoc":false,"attribute_placement":"ignore","on_multiline":"ensure_fully_multiline"},"modifier_keywords":{"elements":["method","property"]},"no_break_comment":true,"no_closing_tag":true,"no_multiple_statements_per_line":true,"no_space_around_double_colon":true,"no_spaces_after_function_name":true,"no_trailing_whitespace":true,"no_trailing_whitespace_in_comment":true,"single_blank_line_at_eof":true,"single_class_element_per_statement":{"elements":["property"]},"single_import_per_statement":true,"single_line_after_imports":true,"single_space_around_construct":true,"spaces_inside_parentheses":true,"statement_indentation":true,"switch_case_semicolon_to_colon":true,"switch_case_space":true,"encoding":true,"full_opening_tag":true,"array_syntax":{"syntax":"short"},"concat_space":{"spacing":"one"},"new_with_parentheses":true,"no_unused_imports":true,"ordered_imports":true,"return_type_declaration":{"space_before":"none"},"single_quote":true,"cast_spaces":true,"whitespace_after_comma_in_array":true,"no_whitespace_in_blank_line":true,"binary_operator_spaces":{"default":"at_least_single_space"},"no_extra_blank_lines":true,"nullable_type_declaration_for_default_null_value":true},"ruleCustomisationPolicyVersion":"null-policy","hashes":{"src\/Serializer.php":"4fa9973f8b299147054921c41481ac94","src\/Middleware\/RequestAutoPopulationMiddleware.php":"ef1665f98d178c5a53cf2b8bd2188f6e","src\/Middleware\/OptionsFilterMiddleware.php":"6c9f54dd6977318b60aeb0ca75a1f5ff","src\/Middleware\/MiddlewareInterface.php":"e7c0cacbb192c15be0f6ee051756ae76","src\/Middleware\/OperationsMiddleware.php":"322cb37874ffbf254b3496af5b30fef7","src\/Middleware\/ResponseMetadataMiddleware.php":"92dbc8ee03c5a4fe679d2b152fffdc1b","src\/Middleware\/TransportCallMiddleware.php":"61627b99fb8318ee4182aabdd56c609d","src\/Middleware\/CredentialsWrapperMiddleware.php":"1d8e5dc253b7e568604fb77fe9b02cb6","src\/Middleware\/RetryMiddleware.php":"94cd80b28d43637a0fe94f79d1c13240","src\/Middleware\/PagedMiddleware.php":"aeed36993b035b13dfa0d04d4e30eefb","src\/Middleware\/FixedHeaderMiddleware.php":"3718ecf055d8b7c2232b3d9135757454","src\/Options\/TransportOptions\/RestTransportOptions.php":"90145a73ca8ca9b5c795f9381a821cf0","src\/Options\/TransportOptions\/GrpcTransportOptions.php":"ecb68b51eafa53d3e8d1ce1ce842498b","src\/Options\/TransportOptions\/GrpcFallbackTransportOptions.php":"8603033f1d9f1c9605aa9f3f924403ce","src\/Options\/ClientOptions.php":"4346d49e9bcb9654c4346433292ec56d","src\/Options\/CallOptions.php":"c2e68aabd01c55d400f4d7c71d0c30fe","src\/Options\/OptionsInterface.php":"6ff36b26d16042e3a2b2563a466c7fc5","src\/Options\/OptionsTrait.php":"7d9fc3a500b693e81036f005ebc5f0c7","src\/Options\/TransportOptions.php":"054d286f195906ff5415803a414b44f2","src\/ServerStreamingCallInterface.php":"3423c03f796eb841fad926b8365ba7cc","src\/GapicClientTrait.php":"d4c62f47c8a7a49622087395b3065008","src\/UriTrait.php":"d3f45f17ef641dd53b90ad503b10208d","src\/ClientStream.php":"fd8a0a85cc33990d18a9c9fe0c72ae73","src\/Transport\/TransportInterface.php":"c0e7dec8a9fe54258651f7b650f60de1","src\/Transport\/RestTransport.php":"bec96cdb662974b60230381a8d5806d9","src\/Transport\/GrpcTransport.php":"646d4799753df2617544896c71abfeb7","src\/Transport\/Grpc\/ForwardingUnaryCall.php":"c493c8f8978fae1e19d35f169b3e05e4","src\/Transport\/Grpc\/ForwardingCall.php":"8ff1fa74ee1998da538fa1e14a0b9ea7","src\/Transport\/Grpc\/UnaryInterceptorInterface.php":"4661cef3479cd8ce6ded3821dad200f6","src\/Transport\/Grpc\/ServerStreamingCallWrapper.php":"07e773c7857d462be84bca50dbc15102","src\/Transport\/Grpc\/ForwardingServerStreamingCall.php":"d78e44cde284dd15a262ffa74a2f7feb","src\/Transport\/GrpcFallbackTransport.php":"26cf71fae8766fc689a7f755bb701da6","src\/Transport\/HttpUnaryTransportTrait.php":"78fbfe5fc1de4e452bc30c6b374d79d7","src\/Transport\/Rest\/JsonStreamDecoder.php":"ae04ca22ae03a55d38f5a1ed01f7821c","src\/Transport\/Rest\/RestServerStreamingCall.php":"067ae6acb20fc4aa9d78ecf9d4c05d6d","src\/AgentHeader.php":"85000f95d8873162df4e12ab4b83908d","src\/ArrayTrait.php":"0c520595d0aa799d1fb6ed15f0055417","src\/PageStreamingDescriptor.php":"dfaab31b799839863b9b1ac9cca4fcde","src\/GPBType.php":"56034406c12308b8f660dbda3e679502","src\/FixedSizeCollection.php":"4d15cdb87a85effc178c52a1b7d25b7e","src\/RetrySettings.php":"d938b09a13eb46ffc6c325889828878f","src\/ResourceTemplate\/Segment.php":"5dc7d3f73d768a84db37a00a7360ad76","src\/ResourceTemplate\/Parser.php":"65376edaf13bcfa620f34e16f35a19f6","src\/ResourceTemplate\/ResourceTemplateInterface.php":"09598e10967eb8b967481b48cdbe2d83","src\/ResourceTemplate\/AbsoluteResourceTemplate.php":"53f6c4ffc39308c56b06984b1c43dad4","src\/ResourceTemplate\/RelativeResourceTemplate.php":"fb29d25f7e6ddf9270b8abae291d6f96","src\/PagedListResponse.php":"f0344c017ffc23fd1de1d1d93d908989","src\/ApiKeyHeaderCredentials.php":"027aa68218ed4b55f16247dc374661ef","src\/OperationResponse.php":"34bed3a387486da63c79c2acf6cc8ea4","src\/ResourceHelperTrait.php":"6d63ac9fe3734809086c633068f8ac94","src\/PathTemplate.php":"97d31591ba1fd7feb2c7d7b41602be02","src\/Testing\/MockClientStreamingCall.php":"f3e6f7ac104df825f4b0e8e902cd8dc8","src\/Testing\/MessageAwareArrayComparator.php":"2786c2f5ed689468d9176eaad3674de9","src\/Testing\/MessageAwareExporter.php":"24fafea2aad4a2cb907481eae5845e25","src\/Testing\/MockStatus.php":"dc7fc1f83feb8ffef0a97a13b78458fa","src\/Testing\/MockTransport.php":"8f5c55ecd305685cb6b7faad114e961f","src\/Testing\/MockResponse.php":"f07de318dcadb36a0f69d1fd57dbf673","src\/Testing\/MockBidiStreamingCall.php":"67eddf951ae8e0056de3059e80936c12","src\/Testing\/ProtobufMessageComparator.php":"f7adbbb842a79408198291103d637741","src\/Testing\/ProtobufGPBEmptyComparator.php":"34de0c00c7b9229fbed4d16161a73825","src\/Testing\/MockServerStreamingCall.php":"a49f19eea45c1bbc8b2945190a7d5e20","src\/Testing\/MockRequest.php":"8950022a0af6b4b5ece38fe1b0acdac1","src\/Testing\/GeneratedTest.php":"70811c343971de74a8a039e53871402d","src\/Testing\/MockGrpcTransport.php":"5cff7b1c62ba07f19ef952220d4fb054","src\/Testing\/MockRequestBody.php":"0e7bcfba70d89d0fe1b53cc6c941da9d","src\/Testing\/SerializationTrait.php":"10a7bc945b994dad90799da5feda5354","src\/Testing\/MockStubTrait.php":"db524dc9d25c6b8f955c5f5bd2f0d0ad","src\/Testing\/MockUnaryCall.php":"4dd1a61ffa4ae21357f6031f061420ca","src\/Testing\/ReceivedRequest.php":"112c3808832cdf2c1007abd1c2fdd9f7","src\/InsecureRequestBuilder.php":"734ac824a7c14493cb4557f0a62f8a5c","src\/ValidationException.php":"a9e4b81b41f373b4eeaaaede287c656c","src\/ApiException.php":"79fae796067d60334e01364c4d72382d","src\/GPBLabel.php":"fe5dd68d55df44596727fd56cfaed437","src\/RequestBuilder.php":"6e156ad536b9cb8236d34c07bdd498bf","src\/ValidationTrait.php":"fdb5f26062976059a9d7636114078ebf","src\/Version.php":"917ecf244bcff088b39d4aa52da20ab7","src\/CredentialsWrapper.php":"fb4dc0b7c43a4ddfebadf64b91f29142","src\/PollingTrait.php":"6b17a6295d029080b72ec4fc61752051","src\/InsecureCredentialsWrapper.php":"67b785509ed11d536186014a44d57662","src\/KnownTypes.php":"e7b677ea5db340f2b5b38a5f0bbc6eaa","src\/Call.php":"931b0e4e9748ac80b59fdd14fbcf4a21","src\/Page.php":"a359f9f1c4c9ef20b76edf0ff4d131c8","src\/ServerStream.php":"88faa82373635ad3d756975225f79539","src\/ServiceAddressTrait.php":"b8d6727ceb444184f85af59cf7fde7f0","src\/RequestParamsHeaderDescriptor.php":"75ad97744c135e149bf89cb5b7afe1be","src\/ApiStatus.php":"cef0047eb7170d746f670e809951cc69","src\/ClientOptionsTrait.php":"35f9524e1c2db3b987de6bea1b0625a1","src\/GrpcSupportTrait.php":"3c999c31b879650fc23b2f1ff4fa1c51","src\/BidiStream.php":"c53825f61eeeaf83a8439bc5c1f06ef3","src\/HeaderCredentialsInterface.php":"02fe972ca60d9b950804f83c2c0a422e"}}
\ No newline at end of file
diff --git a/Gax/CHANGELOG.md b/Gax/CHANGELOG.md
new file mode 100644
index 000000000000..1e3bd0b95fca
--- /dev/null
+++ b/Gax/CHANGELOG.md
@@ -0,0 +1,428 @@
+# Changelog
+
+## [1.42.1](https://github.com/googleapis/gax-php/compare/v1.42.0...v1.42.1) (2026-03-12)
+
+
+### Bug Fixes
+
+* **deps:** Update dependency google/protobuf to v5 ([#655](https://github.com/googleapis/gax-php/issues/655)) ([1d89f50](https://github.com/googleapis/gax-php/commit/1d89f50510794e220fb036042612336717e8fedd))
+* Suppress unpack exceptions ([#656](https://github.com/googleapis/gax-php/issues/656)) ([989cf37](https://github.com/googleapis/gax-php/commit/989cf3710c6285b3eb71b6fc0c83d2b50508f456))
+
+## [1.42.0](https://github.com/googleapis/gax-php/compare/v1.41.0...v1.42.0) (2026-01-22)
+
+
+### Features
+
+* Support grpc-status-details-bin for ApiException::getErrorDetails ([#614](https://github.com/googleapis/gax-php/issues/614)) ([c44ad0c](https://github.com/googleapis/gax-php/commit/c44ad0c068586b635a3f6b325e157c116f68065e))
+
+## [1.41.0](https://github.com/googleapis/gax-php/compare/v1.40.1...v1.41.0) (2026-01-16)
+
+
+### Features
+
+* Add ApiException::getErrorDetails() for retrieval of additional exception info ([#611](https://github.com/googleapis/gax-php/issues/611)) ([be7402a](https://github.com/googleapis/gax-php/commit/be7402a94d1c4201e1ca03cdd3bab6c01adb0c24))
+
+## [1.40.1](https://github.com/googleapis/gax-php/compare/v1.40.0...v1.40.1) (2026-01-16)
+
+
+### Bug Fixes
+
+* Use self:: instead of $this-> for static validation methods in GapicClientTrait validation ([#643](https://github.com/googleapis/gax-php/issues/643)) ([5ba2b20](https://github.com/googleapis/gax-php/commit/5ba2b200c16b78b10e3dddf5ac6a2c5cd7eb8641))
+
+## [1.40.0](https://github.com/googleapis/gax-php/compare/v1.39.0...v1.40.0) (2025-12-04)
+
+
+### Features
+
+* Add TransportCallMiddleware ([#640](https://github.com/googleapis/gax-php/issues/640)) ([a0f9d37](https://github.com/googleapis/gax-php/commit/a0f9d3740d62f6a776ac701631aa734046ceeb77))
+
+## [1.39.0](https://github.com/googleapis/gax-php/compare/v1.38.2...v1.39.0) (2025-12-02)
+
+
+### Features
+
+* Add GapicClientTrait::prependMiddleware ([#638](https://github.com/googleapis/gax-php/issues/638)) ([d46c06d](https://github.com/googleapis/gax-php/commit/d46c06d3bb551d9f7848bceebcfd78f80ec7890f))
+
+## [1.38.2](https://github.com/googleapis/gax-php/compare/v1.38.1...v1.38.2) (2025-11-14)
+
+
+### Bug Fixes
+
+* Don't override ApiException::__toString ([#388](https://github.com/googleapis/gax-php/issues/388)) ([db7cd2e](https://github.com/googleapis/gax-php/commit/db7cd2e55219463aa0f7d0bcc989f35d008d174b))
+
+## [1.38.1](https://github.com/googleapis/gax-php/compare/v1.38.0...v1.38.1) (2025-11-06)
+
+
+### Bug Fixes
+
+* Add return type to `offsetGet` ([#633](https://github.com/googleapis/gax-php/issues/633)) ([b77c12d](https://github.com/googleapis/gax-php/commit/b77c12dc959e8434fcd1f7f08cedaa84cdfb00a4))
+
+## [1.38.0](https://github.com/googleapis/gax-php/compare/v1.37.0...v1.38.0) (2025-09-17)
+
+
+### Features
+
+* Add the rpcName to the BIDI stream opening request ([#630](https://github.com/googleapis/gax-php/issues/630)) ([9c61d8f](https://github.com/googleapis/gax-php/commit/9c61d8f2bd09731d5f22c22eb81895eaf4db2031))
+* Make options classes fluid ([#618](https://github.com/googleapis/gax-php/issues/618)) ([427b46e](https://github.com/googleapis/gax-php/commit/427b46e91b3881fd0da361b5b351c6dda47e640a))
+
+
+### Bug Fixes
+
+* Update protobuf RepeatedField to new namespace ([#624](https://github.com/googleapis/gax-php/issues/624)) ([3558cc4](https://github.com/googleapis/gax-php/commit/3558cc49139861fa411c77b33f457467ec8daa97))
+
+## [1.37.0](https://github.com/googleapis/gax-php/compare/v1.36.1...v1.37.0) (2025-09-10)
+
+
+### Features
+
+* Support client options in ClientOptionsTrait::buildClientOptions ([#621](https://github.com/googleapis/gax-php/issues/621)) ([68e2336](https://github.com/googleapis/gax-php/commit/68e23369657b1740fffe480f96d9d7b04e3e38c2))
+
+
+### Bug Fixes
+
+* Ensure compute request build parameters have the operation ID last ([#625](https://github.com/googleapis/gax-php/issues/625)) ([f90ab28](https://github.com/googleapis/gax-php/commit/f90ab28cea6bbbd00f2a652a6d77babb69b2ada8))
+
+## [1.36.1](https://github.com/googleapis/gax-php/compare/v1.36.0...v1.36.1) (2025-05-20)
+
+
+### Bug Fixes
+
+* Protobuf 4.31 deprecations ([#616](https://github.com/googleapis/gax-php/issues/616)) ([b06048b](https://github.com/googleapis/gax-php/commit/b06048be5c29a2534ba1c908642c69798e145d99))
+
+## [1.36.0](https://github.com/googleapis/gax-php/compare/v1.35.1...v1.36.0) (2024-12-11)
+
+
+### Features
+
+* Add logging to the supported transports ([#585](https://github.com/googleapis/gax-php/issues/585)) ([819a677](https://github.com/googleapis/gax-php/commit/819a677e0d89d75662b30a1dbdd45f6a610d9f0c))
+
+## [1.35.1](https://github.com/googleapis/gax-php/compare/v1.35.0...v1.35.1) (2024-12-04)
+
+
+### Bug Fixes
+
+* Ensure hasEmulator client option is passed to createTransport ([#594](https://github.com/googleapis/gax-php/issues/594)) ([13bbe8a](https://github.com/googleapis/gax-php/commit/13bbe8a2e6df2bfd6c5febba735113f1abcba201))
+* Remove implicit null, add php 8.4 ([#599](https://github.com/googleapis/gax-php/issues/599)) ([af0a0e7](https://github.com/googleapis/gax-php/commit/af0a0e708dfbea46de627965db0f63114fcfb67f))
+
+## [1.35.0](https://github.com/googleapis/gax-php/compare/v1.34.1...v1.35.0) (2024-11-06)
+
+
+### Features
+
+* Add `InsecureRequestBuilder` for emulator ([#582](https://github.com/googleapis/gax-php/issues/582)) ([cc1d047](https://github.com/googleapis/gax-php/commit/cc1d0472a1caf31bb3ecd98da1d6b8588f95b63a))
+* **docs:** Use doctum shared workflow for reference docs ([#578](https://github.com/googleapis/gax-php/issues/578)) ([021763f](https://github.com/googleapis/gax-php/commit/021763f255acaffda6ebe34a9d1a01c2bd187326))
+* Support for API Key client option ([#351](https://github.com/googleapis/gax-php/issues/351)) ([ab7f04f](https://github.com/googleapis/gax-php/commit/ab7f04fd8c9f7ed33a58155ae6b9e740f365ac2a))
+
+
+### Bug Fixes
+
+* **tests:** Skip docs tests from forks ([#591](https://github.com/googleapis/gax-php/issues/591)) ([35ae9f7](https://github.com/googleapis/gax-php/commit/35ae9f708d3ef937308d266e3a012296ce8606fc))
+
+## [1.34.1](https://github.com/googleapis/gax-php/compare/v1.34.0...v1.34.1) (2024-08-13)
+
+
+### Bug Fixes
+
+* Disable universe domain check for MDS ([#575](https://github.com/googleapis/gax-php/issues/575)) ([a47a469](https://github.com/googleapis/gax-php/commit/a47a469d9ef76613c5d320539646323a5e7b978d))
+
+## [1.34.0](https://github.com/googleapis/gax-php/compare/v1.33.0...v1.34.0) (2024-05-29)
+
+
+### Features
+
+* Support new surface operations clients ([#569](https://github.com/googleapis/gax-php/issues/569)) ([fa06e73](https://github.com/googleapis/gax-php/commit/fa06e738fc63a3b9f70a26e6d20f30c582ef1870))
+
+## [1.33.0](https://github.com/googleapis/gax-php/compare/v1.32.0...v1.33.0) (2024-05-10)
+
+
+### Features
+
+* Support V2 OperationsClient in OperationResponse ([#564](https://github.com/googleapis/gax-php/issues/564)) ([7f8bb13](https://github.com/googleapis/gax-php/commit/7f8bb13f78463b1e7f2289ce5514763992806e5e))
+
+## [1.32.0](https://github.com/googleapis/gax-php/compare/v1.31.0...v1.32.0) (2024-04-24)
+
+
+### Features
+
+* Add a custom encoder in Serializer ([#554](https://github.com/googleapis/gax-php/issues/554)) ([be28b5a](https://github.com/googleapis/gax-php/commit/be28b5a859b674a3d398bdaab7ed86b93dd7a593))
+
+## [1.31.0](https://github.com/googleapis/gax-php/compare/v1.30.1...v1.31.0) (2024-04-22)
+
+
+### Features
+
+* Add the api header to the GapicClientTrait ([#553](https://github.com/googleapis/gax-php/issues/553)) ([cc102eb](https://github.com/googleapis/gax-php/commit/cc102ebdfd63019b1e6bcd51515be2a2cb13534d))
+
+
+### Bug Fixes
+
+* Add caching and micro optimizations in Serializer ([5a5d8a7](https://github.com/googleapis/gax-php/commit/5a5d8a763d8e2d470a6d960b788e7d2a938cd64f))
+* Pass auth http handler to update metadata call ([#557](https://github.com/googleapis/gax-php/issues/557)) ([6e04a50](https://github.com/googleapis/gax-php/commit/6e04a50d013f5686ec5e66c457b9b440b9bcde9e))
+
+## [1.30.0](https://github.com/googleapis/gax-php/compare/v1.29.1...v1.30.0) (2024-02-28)
+
+
+### Features
+
+* Auto Populate fields configured for auto population in Rpc Request Message ([#543](https://github.com/googleapis/gax-php/issues/543)) ([99d6b89](https://github.com/googleapis/gax-php/commit/99d6b899ddf55d51fab976844c1e0f8fe9918a52))
+* Make the default authHttpHandler in CredentialsWrapper null ([#544](https://github.com/googleapis/gax-php/issues/544)) ([2a25eea](https://github.com/googleapis/gax-php/commit/2a25eeacadf2f783f64b4eca4f94e067ddef3eaa))
+
+## [1.29.1](https://github.com/googleapis/gax-php/compare/v1.29.0...v1.29.1) (2024-02-26)
+
+
+### Bug Fixes
+
+* Allow InsecureCredentialsWrapper::getAuthorizationHeaderCallback to return null ([#541](https://github.com/googleapis/gax-php/issues/541)) ([676f4f7](https://github.com/googleapis/gax-php/commit/676f4f7e3d8925d8aba00285616fdf89440b45f9))
+
+## [1.29.0](https://github.com/googleapis/gax-php/compare/v1.28.1...v1.29.0) (2024-02-26)
+
+
+### Features
+
+* Add InsecureCredentialsWrapper for Emulator connection ([#538](https://github.com/googleapis/gax-php/issues/538)) ([b5dbeaf](https://github.com/googleapis/gax-php/commit/b5dbeaf33594b300a0c678ffc6a6946b09fce7dd))
+
+## [1.28.1](https://github.com/googleapis/gax-php/compare/v1.28.0...v1.28.1) (2024-02-20)
+
+
+### Bug Fixes
+
+* Universe domain check for grpc transport ([#534](https://github.com/googleapis/gax-php/issues/534)) ([1026d8a](https://github.com/googleapis/gax-php/commit/1026d8aec73e0aad8949a86ee7575e3edb3d56be))
+
+## [1.28.0](https://github.com/googleapis/gax-php/compare/v1.27.2...v1.28.0) (2024-02-15)
+
+
+### Features
+
+* Allow setting of universe domain in environment variable ([#520](https://github.com/googleapis/gax-php/issues/520)) ([6e6603b](https://github.com/googleapis/gax-php/commit/6e6603b03285f3f8d1072776cd206720e3990f50))
+
+## [1.27.2](https://github.com/googleapis/gax-php/compare/v1.27.1...v1.27.2) (2024-02-14)
+
+
+### Bug Fixes
+
+* Typo in TransportOptions option name ([#530](https://github.com/googleapis/gax-php/issues/530)) ([6914fe0](https://github.com/googleapis/gax-php/commit/6914fe04554867bd827be6596fafc751a3d7621a))
+
+## [1.27.1](https://github.com/googleapis/gax-php/compare/v1.27.0...v1.27.1) (2024-02-14)
+
+
+### Bug Fixes
+
+* Issues in Options classes ([#528](https://github.com/googleapis/gax-php/issues/528)) ([aa9ba3a](https://github.com/googleapis/gax-php/commit/aa9ba3a6bac9324ad894d9677da0e897497ebab2))
+
+## [1.27.0](https://github.com/googleapis/gax-php/compare/v1.26.3...v1.27.0) (2024-02-07)
+
+
+### Features
+
+* Create ClientOptionsTrait ([#527](https://github.com/googleapis/gax-php/issues/527)) ([cfe2c60](https://github.com/googleapis/gax-php/commit/cfe2c60a36233f74259c96a6799d8492ed7c45d0))
+* Implement ProjectIdProviderInterface in CredentialsWrapper ([#523](https://github.com/googleapis/gax-php/issues/523)) ([b56a463](https://github.com/googleapis/gax-php/commit/b56a4635abfeeec08895202da8218e9ba915413e))
+* Update ArrayTrait to be consistent with Core ([#526](https://github.com/googleapis/gax-php/issues/526)) ([8e44185](https://github.com/googleapis/gax-php/commit/8e44185dd6f8f8f9ef5b136776cba61ec7a8b8f6))
+
+
+### Bug Fixes
+
+* Correct exception type for Guzzle promise ([#521](https://github.com/googleapis/gax-php/issues/521)) ([7129373](https://github.com/googleapis/gax-php/commit/712937339c134e1d92cab5fa736cfe1bbcd7f343))
+
+## [1.26.3](https://github.com/googleapis/gax-php/compare/v1.26.2...v1.26.3) (2024-01-18)
+
+
+### Bug Fixes
+
+* CallOptions should use transportOptions ([#513](https://github.com/googleapis/gax-php/issues/513)) ([2d45ee1](https://github.com/googleapis/gax-php/commit/2d45ee187cdc3619b30c51b653b508718baf3af4))
+
+## [1.26.2](https://github.com/googleapis/gax-php/compare/v1.26.1...v1.26.2) (2024-01-09)
+
+
+### Bug Fixes
+
+* Ensure modifyClientOptions is called for new surface clients ([#515](https://github.com/googleapis/gax-php/issues/515)) ([68231b8](https://github.com/googleapis/gax-php/commit/68231b896dec8efb86f8986aefba3d247d2a2d1c))
+
+## [1.26.1](https://github.com/googleapis/gax-php/compare/v1.26.0...v1.26.1) (2024-01-04)
+
+
+### Bug Fixes
+
+* Widen google/longrunning version ([#511](https://github.com/googleapis/gax-php/issues/511)) ([b93096d](https://github.com/googleapis/gax-php/commit/b93096d0e10bde14c50480ea9f0423c292fbd5a6))
+
+## [1.26.0](https://github.com/googleapis/gax-php/compare/v1.25.0...v1.26.0) (2024-01-03)
+
+
+### Features
+
+* Add support for universe domain ([#502](https://github.com/googleapis/gax-php/issues/502)) ([5a26fac](https://github.com/googleapis/gax-php/commit/5a26facad5c2e5c30945987c422bb78a3fffb9b1))
+* Interface and methods for middleware stack ([#473](https://github.com/googleapis/gax-php/issues/473)) ([766da7b](https://github.com/googleapis/gax-php/commit/766da7b369409ec1b29376b533e7f22ee7f745f4))
+
+
+### Bug Fixes
+
+* Accept throwable for retry settings ([#509](https://github.com/googleapis/gax-php/issues/509)) ([5af9c3c](https://github.com/googleapis/gax-php/commit/5af9c3c650419c8f1a590783e954cd11dc1f0d56))
+
+## [1.25.0](https://github.com/googleapis/gax-php/compare/v1.24.0...v1.25.0) (2023-11-02)
+
+
+### Features
+
+* Add custom retries ([#489](https://github.com/googleapis/gax-php/issues/489)) ([ef0789b](https://github.com/googleapis/gax-php/commit/ef0789b73ef28d79a08c354d1361a9ccc6206088))
+
+## [1.24.0](https://github.com/googleapis/gax-php/compare/v1.23.0...v1.24.0) (2023-10-10)
+
+
+### Features
+
+* Ensure NewClientSurface works for consoldiated v2 clients ([#493](https://github.com/googleapis/gax-php/issues/493)) ([cb8706e](https://github.com/googleapis/gax-php/commit/cb8706ef9211a1e43f733d2c8f272a330c2fa792))
+
+## [1.23.0](https://github.com/googleapis/gax-php/compare/v1.22.1...v1.23.0) (2023-09-14)
+
+
+### Features
+
+* Typesafety for new surface client options ([#450](https://github.com/googleapis/gax-php/issues/450)) ([21550c5](https://github.com/googleapis/gax-php/commit/21550c5bf07f178f2043b0630f3ac34fcc3a05e0))
+
+## [1.22.1](https://github.com/googleapis/gax-php/compare/v1.22.0...v1.22.1) (2023-08-04)
+
+
+### Bug Fixes
+
+* Deprecation notice while GapicClientTrait->setClientOptions ([#483](https://github.com/googleapis/gax-php/issues/483)) ([1c66d34](https://github.com/googleapis/gax-php/commit/1c66d3445dca4d43831a2f4e26e59b9bd1cb76dd))
+
+## [1.22.0](https://github.com/googleapis/gax-php/compare/v1.21.1...v1.22.0) (2023-07-31)
+
+
+### Features
+
+* Sets api headers for "gcloud-php-new" and "gcloud-php-legacy" surface versions ([#470](https://github.com/googleapis/gax-php/issues/470)) ([2d8ccff](https://github.com/googleapis/gax-php/commit/2d8ccff419a076ee2fe9d3dc7ecd5509c74afb4c))
+
+## [1.21.1](https://github.com/googleapis/gax-php/compare/v1.21.0...v1.21.1) (2023-06-28)
+
+
+### Bug Fixes
+
+* Revert "chore: remove unnecessary api endpoint check" ([#476](https://github.com/googleapis/gax-php/issues/476)) ([13e773f](https://github.com/googleapis/gax-php/commit/13e773f5b09f9a99b8425835815746d37e9c1da3))
+
+## [1.21.0](https://github.com/googleapis/gax-php/compare/v1.20.2...v1.21.0) (2023-06-09)
+
+
+### Features
+
+* Support guzzle/promises:v2 ([753eae9](https://github.com/googleapis/gax-php/commit/753eae9acf638f3356f8149acf84444eb399a699))
+
+## [1.20.2](https://github.com/googleapis/gax-php/compare/v1.20.1...v1.20.2) (2023-05-12)
+
+
+### Bug Fixes
+
+* Ensure timeout set by RetryMiddleware is int not float ([#462](https://github.com/googleapis/gax-php/issues/462)) ([9d4c7fa](https://github.com/googleapis/gax-php/commit/9d4c7fa89445c63ec0bf4745ed9d98fd185ef51f))
+
+## [1.20.1](https://github.com/googleapis/gax-php/compare/v1.20.0...v1.20.1) (2023-05-12)
+
+
+### Bug Fixes
+
+* Default value for error message in createFromRequestException ([#463](https://github.com/googleapis/gax-php/issues/463)) ([7552d22](https://github.com/googleapis/gax-php/commit/7552d22241c2f488606e9546efdd6edea356ee9a))
+
+## [1.20.0](https://github.com/googleapis/gax-php/compare/v1.19.1...v1.20.0) (2023-05-01)
+
+
+### Features
+
+* **deps:** Support google/common-protos 4.0 ([af1db80](https://github.com/googleapis/gax-php/commit/af1db80c22307597f0dfcb9fafa86caf466588ba))
+* **deps:** Support google/grpc-gcp 0.3 ([18edc2c](https://github.com/googleapis/gax-php/commit/18edc2ce6a1a615e3ea7c00ede313c32cec4b799))
+
+## [1.19.1](https://github.com/googleapis/gax-php/compare/v1.19.0...v1.19.1) (2023-03-16)
+
+
+### Bug Fixes
+
+* Simplify ResourceHelperTrait registration ([#447](https://github.com/googleapis/gax-php/issues/447)) ([4949dc0](https://github.com/googleapis/gax-php/commit/4949dc0c4cd5e58af7933a1d2ecab90832c0b036))
+
+## [1.19.0](https://github.com/googleapis/gax-php/compare/v1.18.2...v1.19.0) (2023-01-27)
+
+
+### Features
+
+* Ensure cache is used in calls to ADC::onGCE ([#441](https://github.com/googleapis/gax-php/issues/441)) ([64a4184](https://github.com/googleapis/gax-php/commit/64a4184ab69d13104d269b15a55d4b8b2515b5a6))
+
+## [1.18.2](https://github.com/googleapis/gax-php/compare/v1.18.1...v1.18.2) (2023-01-06)
+
+
+### Bug Fixes
+
+* Ensure metadata return type is loaded into descriptor pool ([#439](https://github.com/googleapis/gax-php/issues/439)) ([a40cf8d](https://github.com/googleapis/gax-php/commit/a40cf8d87ac9aa45d18239456e2e4c96653f1a6c))
+* Implicit conversion from float to int warning ([#438](https://github.com/googleapis/gax-php/issues/438)) ([1cb62ad](https://github.com/googleapis/gax-php/commit/1cb62ad3d92ace0518017abc972e912b339f1b56))
+
+## [1.18.1](https://github.com/googleapis/gax-php/compare/v1.18.0...v1.18.1) (2022-12-06)
+
+
+### Bug Fixes
+
+* Message parameters in required querystring ([#430](https://github.com/googleapis/gax-php/issues/430)) ([77bc5e1](https://github.com/googleapis/gax-php/commit/77bc5e1cb8f347601d9237bf5164cf8b8ad8aa0f))
+
+## [1.18.0](https://github.com/googleapis/gax-php/compare/v1.17.0...v1.18.0) (2022-12-05)
+
+
+### Features
+
+* Add ResourceHelperTrait ([#428](https://github.com/googleapis/gax-php/issues/428)) ([0439efa](https://github.com/googleapis/gax-php/commit/0439efa926865be5fea25b699469b0c1f8c1c768))
+
+## [1.17.0](https://github.com/googleapis/gax-php/compare/v1.16.4...v1.17.0) (2022-09-08)
+
+
+### Features
+
+* Add startAsyncCall support ([#418](https://github.com/googleapis/gax-php/issues/418)) ([fc90693](https://github.com/googleapis/gax-php/commit/fc9069373c329183e07f8d174084c305b2308209))
+
+## [1.16.4](https://github.com/googleapis/gax-php/compare/v1.16.3...v1.16.4) (2022-08-25)
+
+
+### Bug Fixes
+
+* use interfaceOverride instead of param ([#419](https://github.com/googleapis/gax-php/issues/419)) ([9dd5bc9](https://github.com/googleapis/gax-php/commit/9dd5bc91c4becfd2a0832288ab2406c3d224618e))
+
+## [1.16.3](https://github.com/googleapis/gax-php/compare/v1.16.2...v1.16.3) (2022-08-23)
+
+
+### Bug Fixes
+
+* add eager threshold ([#416](https://github.com/googleapis/gax-php/issues/416)) ([99eb172](https://github.com/googleapis/gax-php/commit/99eb172280f301b117fde9dcc92079ca03aa28bd))
+
+## [1.16.2](https://github.com/googleapis/gax-php/compare/v1.16.1...v1.16.2) (2022-08-16)
+
+
+### Bug Fixes
+
+* use responseType for custom operations ([#413](https://github.com/googleapis/gax-php/issues/413)) ([b643adf](https://github.com/googleapis/gax-php/commit/b643adfc44dd9fe82b0919e5b34edd00c7cdbb1f))
+
+## [1.16.1](https://github.com/googleapis/gax-php/compare/v1.16.0...v1.16.1) (2022-08-11)
+
+
+### Bug Fixes
+
+* remove typehint from extended method ([#411](https://github.com/googleapis/gax-php/issues/411)) ([fb37f73](https://github.com/googleapis/gax-php/commit/fb37f7365e888465d84fca304ca83360ddbae6c3))
+
+## [1.16.0](https://github.com/googleapis/gax-php/compare/v1.15.0...v1.16.0) (2022-08-10)
+
+
+### Features
+
+* additional typehinting ([#403](https://github.com/googleapis/gax-php/issues/403)) ([6597a07](https://github.com/googleapis/gax-php/commit/6597a07019665d91e07ea0a016c7d99c8a099cd2))
+* drop support for PHP 5.6 ([#397](https://github.com/googleapis/gax-php/issues/397)) ([b888b24](https://github.com/googleapis/gax-php/commit/b888b24e0e223784e22dbbbe27fe0284cdcdfc35))
+* introduce startApiCall ([#406](https://github.com/googleapis/gax-php/issues/406)) ([1cfeb62](https://github.com/googleapis/gax-php/commit/1cfeb628070c9c6e57b2dde854b0a973a888a2bc))
+
+
+### Bug Fixes
+
+* **deps:** update dependency google/longrunning to ^0.2 ([#407](https://github.com/googleapis/gax-php/issues/407)) ([54d4f32](https://github.com/googleapis/gax-php/commit/54d4f32ba5464d1f5da33e1c99a020174cae367c))
+
+## [1.15.0](https://github.com/googleapis/gax-php/compare/v1.14.0...v1.15.0) (2022-08-02)
+
+
+### Features
+
+* move LongRunning classes to a standalone package ([#401](https://github.com/googleapis/gax-php/issues/401)) ([1747125](https://github.com/googleapis/gax-php/commit/1747125c84dcc6d42390de7e78d2e326884e1073))
+
+## [1.14.0](https://github.com/googleapis/gax-php/compare/v1.13.0...v1.14.0) (2022-07-26)
+
+
+### Features
+
+* support requesting numeric enum rest encoding ([#395](https://github.com/googleapis/gax-php/issues/395)) ([0d74a48](https://github.com/googleapis/gax-php/commit/0d74a4877c5198cfaf534c4e55d7e418b50bc6ab))
diff --git a/Gax/CODE_OF_CONDUCT.md b/Gax/CODE_OF_CONDUCT.md
new file mode 100644
index 000000000000..46b2a08ea6d1
--- /dev/null
+++ b/Gax/CODE_OF_CONDUCT.md
@@ -0,0 +1,43 @@
+# 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/)
diff --git a/Gax/CONTRIBUTING.md b/Gax/CONTRIBUTING.md
new file mode 100644
index 000000000000..2827b7d3fa27
--- /dev/null
+++ b/Gax/CONTRIBUTING.md
@@ -0,0 +1,27 @@
+Want to contribute? Great! First, read this page (including the small print at the end).
+
+### Before you contribute
+Before we can use your code, you must sign the
+[Google Individual Contributor License Agreement]
+(https://cla.developers.google.com/about/google-individual)
+(CLA), which you can do online. The CLA is necessary mainly because you own the
+copyright to your changes, even after your contribution becomes part of our
+codebase, so we need your permission to use and distribute your code. We also
+need to be sure of various other things—for instance that you'll tell us if you
+know that your code infringes on other people's patents. You don't have to sign
+the CLA until after you've submitted your code for review and a member has
+approved it, but you must do it before we can put your code into our codebase.
+Before you start working on a larger contribution, you should get in touch with
+us first through the issue tracker with your idea so that we can help out and
+possibly guide you. Coordinating up front makes it much easier to avoid
+frustration later on.
+
+### Code reviews
+All submissions, including submissions by project members, require review. We
+use Github pull requests for this purpose.
+
+### The small print
+Contributions made by corporations are covered by a different agreement than
+the one above, the
+[Software Grant and Corporate Contributor License Agreement]
+(https://cla.developers.google.com/about/google-corporate).
diff --git a/Gax/LICENSE b/Gax/LICENSE
new file mode 100644
index 000000000000..1839ff93ec3d
--- /dev/null
+++ b/Gax/LICENSE
@@ -0,0 +1,25 @@
+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.
diff --git a/Gax/README.md b/Gax/README.md
new file mode 100644
index 000000000000..6a6c0176069d
--- /dev/null
+++ b/Gax/README.md
@@ -0,0 +1,97 @@
+# Google API Core for PHP
+
+
+
+- [Documentation](https://cloud.google.com/php/docs/reference/gax/latest)
+
+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 8.1 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/main/.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
+ > composer install
+ ```
+
+3. Run tests.
+
+ ```sh
+ > composer test
+ ```
+
+4. Updating dependencies after changing `composer.json`:
+
+ ```sh
+ > composer update
+ `
+ ```
+
+5. Formatting source:
+
+ ```sh
+ > composer cs-lint
+ > composer cs-fix
+ ```
+
+## License
+
+BSD - See [LICENSE][] for more information.
+
+[LICENSE]: https://github.com/googleapis/gax-php/blob/main/LICENSE
diff --git a/Gax/SECURITY.md b/Gax/SECURITY.md
new file mode 100644
index 000000000000..8b58ae9c01ae
--- /dev/null
+++ b/Gax/SECURITY.md
@@ -0,0 +1,7 @@
+# Security Policy
+
+To report a security issue, please use [g.co/vulnz](https://g.co/vulnz).
+
+The Google Security Team will respond within 5 working days of your report on g.co/vulnz.
+
+We use g.co/vulnz for our intake, and do coordination and disclosure here using GitHub Security Advisory to privately discuss and fix the issue.
diff --git a/Gax/VERSION b/Gax/VERSION
new file mode 100644
index 000000000000..e640847f99cd
--- /dev/null
+++ b/Gax/VERSION
@@ -0,0 +1 @@
+1.42.1
diff --git a/Gax/codecov.yml b/Gax/codecov.yml
new file mode 100644
index 000000000000..972f1e6bd07d
--- /dev/null
+++ b/Gax/codecov.yml
@@ -0,0 +1,3 @@
+ignore:
+ - "metadata"
+ - "src/Testing"
diff --git a/Gax/composer.json b/Gax/composer.json
new file mode 100644
index 000000000000..22f80e811c07
--- /dev/null
+++ b/Gax/composer.json
@@ -0,0 +1,45 @@
+{
+ "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": "^8.1",
+ "google/auth": "^1.49",
+ "google/grpc-gcp": "^0.4",
+ "grpc/grpc": "^1.13",
+ "google/protobuf": "^4.31||^5.34",
+ "guzzlehttp/promises": "^2.0",
+ "guzzlehttp/psr7": "^2.0",
+ "google/common-protos": "^4.4",
+ "google/longrunning": "~0.4",
+ "ramsey/uuid": "^4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.6",
+ "phpspec/prophecy-phpunit": "^2.1",
+ "phpstan/phpstan": "^2.0",
+ "google/cloud-tools": "^0.16.1"
+ },
+ "conflict": {
+ "ext-protobuf": "<4.31.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "Google\\ApiCore\\": "src",
+ "GPBMetadata\\ApiCore\\": "metadata/ApiCore"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Google\\ApiCore\\Tests\\": "tests",
+ "Google\\Showcase\\": "tests/Conformance/src",
+ "GPBMetadata\\Google\\Showcase\\": "tests/Conformance/metadata"
+ }
+ },
+ "scripts": {
+ "regenerate-test-protos": "dev/sh/regenerate-test-protos.sh"
+ }
+}
diff --git a/Gax/dev/sh/build-protobuf-docs.sh b/Gax/dev/sh/build-protobuf-docs.sh
new file mode 100644
index 000000000000..1e0213024ec7
--- /dev/null
+++ b/Gax/dev/sh/build-protobuf-docs.sh
@@ -0,0 +1,57 @@
+#!/bin/bash
+
+# Script to build docs for PHP Protobuf. To be run locally and submitted
+# manually when an update is desired.
+# The Protobuf team does not maintain the reference documentation for PHP at
+# this time and rely on the Core Client Libraries team to do so.
+# Docs are deployed to developers.google.com, but soon will be deployed to
+# protobuf.dev.
+# @see https://developers.google.com/protocol-buffers/docs/reference/overview
+
+# This script expects to be invoked from the gax-php root. It requires a git tag
+# of a valid protobuf-php releas eas the first argument.
+# @see https://github.com/protocolbuffers/protobuf-php/tags
+
+# Once ran, copy the "api-docs" directory to the protocolbuffers.github.io repo:
+# https://github.com/protocolbuffers/protocolbuffers.github.io/tree/main/content/reference/php/api-docs
+
+set -ev
+
+if [ "$#" -ne 1 ]; then
+ echo "Usage: $0 TAG"
+ exit 1
+fi
+GIT_TAG_NAME=$1
+ROOT_DIR=$(pwd)
+DOC_OUTPUT_DIR=${ROOT_DIR}/api-docs
+DOCTUM_EXECUTABLE=${ROOT_DIR}/doctum.phar
+
+function downloadDoctum() {
+ # Download the latest (5.1.x) release
+ # You can fetch the latest (5.1.x) version code here:
+ # https://doctum.long-term.support/releases/5.1/VERSION
+ rm -f "${DOCTUM_EXECUTABLE}"
+ rm -f "${DOCTUM_EXECUTABLE}.sha256"
+ curl -# https://doctum.long-term.support/releases/5.5/doctum.phar -o "${DOCTUM_EXECUTABLE}"
+ curl -# https://doctum.long-term.support/releases/5.5/doctum.phar.sha256 -o "${DOCTUM_EXECUTABLE}.sha256"
+ sha256sum --strict --check "${DOCTUM_EXECUTABLE}.sha256"
+ rm -f "${DOCTUM_EXECUTABLE}.sha256"
+}
+
+function buildDocs() {
+ DOCTUM_CONFIG=${ROOT_DIR}/dev/src/Docs/doctum-protobuf-config.php
+ PROTOBUF_DOCS_VERSION=${GIT_TAG_NAME} php ${DOCTUM_EXECUTABLE} update ${DOCTUM_CONFIG} -v
+}
+# ensure we have the correct version of protobuf
+composer update google/protobuf:$GIT_TAG_NAME
+# download doctum.phar
+downloadDoctum
+# build the docs
+buildDocs ${GIT_TAG_NAME}
+
+# Construct the base index file to redirect to the Protobuf namespace
+UPDATED_INDEX_FILE=$(cat << EndOfMessage
+
+EndOfMessage
+)
+echo ${UPDATED_INDEX_FILE} > ${DOC_OUTPUT_DIR}/index.html
diff --git a/Gax/dev/sh/regenerate-test-protos.sh b/Gax/dev/sh/regenerate-test-protos.sh
new file mode 100755
index 000000000000..b22338f889c7
--- /dev/null
+++ b/Gax/dev/sh/regenerate-test-protos.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+# Run this script whenever changes are made to mocks.proto to regenerate the
+# PHP protobuf message classes.
+#
+# This script expected to be invoked from the gax-php root using:
+# $ composer regenerate-test-protos
+
+echo ${pwd}
+cd src
+protoc --php_out . ./Testing/mocks.proto
+cp -r ./GPBMetadata/* ../metadata/
+cp -r ./Google/ApiCore/* ./
+rm -r ./GPBMetadata
+rm -r ./Google
diff --git a/Gax/dev/sh/test-composer-conflict.sh b/Gax/dev/sh/test-composer-conflict.sh
new file mode 100755
index 000000000000..b4f5434f470d
--- /dev/null
+++ b/Gax/dev/sh/test-composer-conflict.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+# Try to run `composer install`, with the expectation that it will FAIL!
+# This is to test that the 'conflict' clause of the composer.json file
+# is correctly blocking installation when an incompatible protobuf
+# extension is present.
+
+if composer install ; then
+ echo "Expected 'composer install' to fail, but it succeeded!"
+ exit 1
+else
+ echo "'composer install' failed, as expected"
+fi
+
diff --git a/Gax/list1.txt b/Gax/list1.txt
new file mode 100644
index 000000000000..fb9064309571
--- /dev/null
+++ b/Gax/list1.txt
@@ -0,0 +1,89 @@
+src/Serializer.php
+src/Middleware/RequestAutoPopulationMiddleware.php
+src/Middleware/OptionsFilterMiddleware.php
+src/Middleware/MiddlewareInterface.php
+src/Middleware/OperationsMiddleware.php
+src/Middleware/ResponseMetadataMiddleware.php
+src/Middleware/TransportCallMiddleware.php
+src/Middleware/CredentialsWrapperMiddleware.php
+src/Middleware/RetryMiddleware.php
+src/Middleware/PagedMiddleware.php
+src/Middleware/FixedHeaderMiddleware.php
+src/Options/TransportOptions/RestTransportOptions.php
+src/Options/TransportOptions/GrpcTransportOptions.php
+src/Options/TransportOptions/GrpcFallbackTransportOptions.php
+src/Options/ClientOptions.php
+src/Options/CallOptions.php
+src/Options/OptionsInterface.php
+src/Options/OptionsTrait.php
+src/Options/TransportOptions.php
+src/ServerStreamingCallInterface.php
+src/GapicClientTrait.php
+src/UriTrait.php
+src/ClientStream.php
+src/Transport/TransportInterface.php
+src/Transport/RestTransport.php
+src/Transport/GrpcTransport.php
+src/Transport/Grpc/ForwardingUnaryCall.php
+src/Transport/Grpc/ForwardingCall.php
+src/Transport/Grpc/ServerStreamingCallWrapper.php
+src/Transport/Grpc/ForwardingServerStreamingCall.php
+src/Transport/GrpcFallbackTransport.php
+src/Transport/HttpUnaryTransportTrait.php
+src/Transport/Rest/JsonStreamDecoder.php
+src/Transport/Rest/RestServerStreamingCall.php
+src/AgentHeader.php
+src/ArrayTrait.php
+src/PageStreamingDescriptor.php
+src/GPBType.php
+src/FixedSizeCollection.php
+src/RetrySettings.php
+src/ResourceTemplate/Segment.php
+src/ResourceTemplate/Parser.php
+src/ResourceTemplate/ResourceTemplateInterface.php
+src/ResourceTemplate/AbsoluteResourceTemplate.php
+src/ResourceTemplate/RelativeResourceTemplate.php
+src/PagedListResponse.php
+src/ApiKeyHeaderCredentials.php
+src/OperationResponse.php
+src/ResourceHelperTrait.php
+src/PathTemplate.php
+src/Testing/MockClientStreamingCall.php
+src/Testing/MessageAwareArrayComparator.php
+src/Testing/MessageAwareExporter.php
+src/Testing/MockStatus.php
+src/Testing/MockTransport.php
+src/Testing/MockResponse.php
+src/Testing/MockBidiStreamingCall.php
+src/Testing/ProtobufMessageComparator.php
+src/Testing/ProtobufGPBEmptyComparator.php
+src/Testing/MockServerStreamingCall.php
+src/Testing/MockRequest.php
+src/Testing/GeneratedTest.php
+src/Testing/MockGrpcTransport.php
+src/Testing/MockRequestBody.php
+src/Testing/SerializationTrait.php
+src/Testing/MockStubTrait.php
+src/Testing/MockUnaryCall.php
+src/Testing/ReceivedRequest.php
+src/InsecureRequestBuilder.php
+src/ValidationException.php
+src/ApiException.php
+src/GPBLabel.php
+src/RequestBuilder.php
+src/ApiEndpointTrait.php
+src/ValidationTrait.php
+src/Version.php
+src/CredentialsWrapper.php
+src/PollingTrait.php
+src/InsecureCredentialsWrapper.php
+src/KnownTypes.php
+src/Call.php
+src/Page.php
+src/ServerStream.php
+src/RequestParamsHeaderDescriptor.php
+src/ApiStatus.php
+src/ClientOptionsTrait.php
+src/GrpcSupportTrait.php
+src/BidiStream.php
+src/HeaderCredentialsInterface.php
diff --git a/Gax/metadata/ApiCore/Testing/Mocks.php b/Gax/metadata/ApiCore/Testing/Mocks.php
new file mode 100644
index 000000000000..5dcb11f0c8c9
Binary files /dev/null and b/Gax/metadata/ApiCore/Testing/Mocks.php differ
diff --git a/Gax/metadata/README.md b/Gax/metadata/README.md
new file mode 100644
index 000000000000..1bd41550eeaf
--- /dev/null
+++ b/Gax/metadata/README.md
@@ -0,0 +1,6 @@
+# Google Protobuf Metadata Classes
+
+## WARNING!
+
+These classes are not intended for direct use - they exist only to support
+the generated protobuf classes in src/
diff --git a/Gax/phpstan.neon.dist b/Gax/phpstan.neon.dist
new file mode 100644
index 000000000000..111e50d4e049
--- /dev/null
+++ b/Gax/phpstan.neon.dist
@@ -0,0 +1,7 @@
+parameters:
+ treatPhpDocTypesAsCertain: false
+ level: 5
+ paths:
+ - src
+ ignoreErrors:
+ - identifier: trait.unused
diff --git a/Gax/phpunit-conformance.xml.dist b/Gax/phpunit-conformance.xml.dist
new file mode 100644
index 000000000000..f2faf425a79c
--- /dev/null
+++ b/Gax/phpunit-conformance.xml.dist
@@ -0,0 +1,16 @@
+
+
+
+
+ tests/Conformance
+
+
+
+
+ src
+
+ src/V[!a-zA-Z]*
+
+
+
+
diff --git a/Gax/phpunit.xml.dist b/Gax/phpunit.xml.dist
new file mode 100644
index 000000000000..4a40d836621c
--- /dev/null
+++ b/Gax/phpunit.xml.dist
@@ -0,0 +1,13 @@
+
+
+
+
+ src/ApiCore
+
+
+
+
+ tests/Unit
+
+
+
diff --git a/Gax/src/AgentHeader.php b/Gax/src/AgentHeader.php
new file mode 100644
index 000000000000..4fda2b0e9a98
--- /dev/null
+++ b/Gax/src/AgentHeader.php
@@ -0,0 +1,132 @@
+ $value) {
+ $metricsList[] = $key . '/' . $value;
+ }
+ return [self::AGENT_HEADER_KEY => [implode(' ', $metricsList)]];
+ }
+
+ /**
+ * Reads the gapic version string from a VERSION file. In order to determine the file
+ * location, this method follows this procedure:
+ * - accepts a class name $callingClass
+ * - identifies the file defining that class
+ * - searches up the directory structure for the 'src' directory
+ * - looks in the directory above 'src' for a file named VERSION
+ *
+ * @param string $callingClass
+ * @return string the gapic version
+ * @throws \ReflectionException
+ */
+ public static function readGapicVersionFromFile(string $callingClass)
+ {
+ $callingClassFile = (new \ReflectionClass($callingClass))->getFileName();
+ $versionFile = substr(
+ $callingClassFile,
+ 0,
+ strrpos($callingClassFile, DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR)
+ ) . DIRECTORY_SEPARATOR . 'VERSION';
+
+ return Version::readVersionFile($versionFile);
+ }
+}
diff --git a/Gax/src/ApiException.php b/Gax/src/ApiException.php
new file mode 100644
index 000000000000..89843149e332
--- /dev/null
+++ b/Gax/src/ApiException.php
@@ -0,0 +1,396 @@
+ null,
+ 'metadata' => null,
+ 'basicMessage' => $message,
+ ];
+ parent::__construct($message, $code, $optionalArgs['previous']);
+ $this->status = $status;
+ $this->metadata = $optionalArgs['metadata'];
+ $this->basicMessage = $optionalArgs['basicMessage'];
+ if ($this->metadata) {
+ $this->decodedMetadataErrorInfo = self::decodeMetadataErrorInfo($this->metadata);
+ }
+ $this->protobufErrors = $protobufErrors;
+ }
+
+ public function getStatus()
+ {
+ return $this->status;
+ }
+
+ /**
+ * Returns null if metadata does not contain error info, or returns containsErrorInfo() array
+ * if the metadata does contain error info.
+ * @param array $metadata
+ * @return array $details {
+ * @type string|null $reason
+ * @type string|null $domain
+ * @type array|null $errorInfoMetadata
+ * }
+ */
+ private static function decodeMetadataErrorInfo(array $metadata)
+ {
+ $details = [];
+ // ApiExceptions created from RPC status have metadata that is an array of objects.
+ if (is_object(reset($metadata))) {
+ $metadataRpcStatus = Serializer::decodeAnyMessages($metadata);
+ $details = self::containsErrorInfo($metadataRpcStatus);
+ } elseif (self::containsErrorInfo($metadata)) {
+ $details = self::containsErrorInfo($metadata);
+ } else {
+ // For GRPC-based responses, the $metadata needs to be decoded.
+ $metadataGrpc = Serializer::decodeMetadata($metadata);
+ $details = self::containsErrorInfo($metadataGrpc);
+ }
+ return $details;
+ }
+
+ /**
+ * Returns the `reason` in ErrorInfo for an exception, or null if there is no ErrorInfo.
+ * @return string|null $reason
+ */
+ public function getReason()
+ {
+ return ($this->decodedMetadataErrorInfo) ? $this->decodedMetadataErrorInfo['reason'] : null;
+ }
+
+ /**
+ * Returns the `domain` in ErrorInfo for an exception, or null if there is no ErrorInfo.
+ * @return string|null $domain
+ */
+ public function getDomain()
+ {
+ return ($this->decodedMetadataErrorInfo) ? $this->decodedMetadataErrorInfo['domain'] : null;
+ }
+
+ /**
+ * Returns the `metadata` in ErrorInfo for an exception, or null if there is no ErrorInfo.
+ * @return array|null $errorInfoMetadata
+ */
+ public function getErrorInfoMetadata()
+ {
+ return ($this->decodedMetadataErrorInfo) ? $this->decodedMetadataErrorInfo['errorInfoMetadata'] : null;
+ }
+
+ /**
+ * Returns the unserialized errors
+ * @return array
+ */
+ public function getErrorDetails(): array
+ {
+ return $this->protobufErrors;
+ }
+
+ /**
+ * @param stdClass $status
+ * @return ApiException
+ */
+ public static function createFromStdClass(stdClass $status)
+ {
+ $metadata = property_exists($status, 'metadata') ? $status->metadata : null;
+ $errors = [];
+
+ return self::create(
+ $status->details,
+ $status->code,
+ $metadata,
+ Serializer::decodeMetadata((array) $metadata, $errors),
+ $errors,
+ );
+ }
+
+ /**
+ * @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
+ ) {
+ $errors = [];
+ return self::create(
+ $basicMessage,
+ $rpcCode,
+ $metadata,
+ Serializer::decodeMetadata((array) $metadata, $errors),
+ $errors,
+ $previous
+ );
+ }
+
+ /**
+ * For REST-based responses, the metadata does not need to be decoded.
+ *
+ * @param string $basicMessage
+ * @param int $rpcCode
+ * @param array|null $metadata
+ * @param Exception $previous
+ * @return ApiException
+ */
+ public static function createFromRestApiResponse(
+ $basicMessage,
+ $rpcCode,
+ ?array $metadata = null,
+ ?Exception $previous = null
+ ) {
+ return self::create(
+ $basicMessage,
+ $rpcCode,
+ $metadata,
+ is_null($metadata) ? [] : $metadata,
+ self::decodeMetadataToProtobufErrors($metadata ?? []),
+ $previous
+ );
+ }
+
+ /**
+ * Checks if decoded metadata includes errorInfo message.
+ * If errorInfo is set, it will always contain `reason`, `domain`, and `metadata` keys.
+ * @param array $decodedMetadata
+ * @return array {
+ * @type string $reason
+ * @type string $domain
+ * @type array $errorInfoMetadata
+ * }
+ */
+ private static function containsErrorInfo(array $decodedMetadata)
+ {
+ if (empty($decodedMetadata)) {
+ return [];
+ }
+ foreach ($decodedMetadata as $value) {
+ $isErrorInfoArray = isset($value['reason']) && isset($value['domain']) && isset($value['metadata']);
+ if ($isErrorInfoArray) {
+ return [
+ 'reason' => $value['reason'],
+ 'domain' => $value['domain'],
+ 'errorInfoMetadata' => $value['metadata'],
+ ];
+ }
+ }
+ return [];
+ }
+
+ /**
+ * Construct an ApiException with a useful message, including decoded metadata.
+ * If the decoded metadata includes an errorInfo message, then the domain, reason,
+ * and metadata fields from that message are hoisted directly into the error.
+ *
+ * @param string $basicMessage
+ * @param int $rpcCode
+ * @param iterable|null $metadata
+ * @param array $decodedMetadata
+ * @param array|null $protobufErrors
+ * @param Exception|null $previous
+ * @return ApiException
+ */
+ private static function create(
+ string $basicMessage,
+ int $rpcCode,
+ $metadata,
+ array $decodedMetadata,
+ ?array $protobufErrors = null,
+ ?Exception $previous = null
+ ) {
+ $containsErrorInfo = self::containsErrorInfo($decodedMetadata);
+ $rpcStatus = ApiStatus::statusFromRpcCode($rpcCode);
+ $messageData = [
+ 'message' => $basicMessage,
+ 'code' => $rpcCode,
+ 'status' => $rpcStatus,
+ 'details' => $decodedMetadata
+ ];
+ if ($containsErrorInfo) {
+ $messageData = array_merge($containsErrorInfo, $messageData);
+ }
+
+ $message = json_encode($messageData, JSON_PRETTY_PRINT);
+
+ if ($metadata instanceof RepeatedField) {
+ $metadata = iterator_to_array($metadata);
+ }
+
+ return new ApiException(
+ $message,
+ $rpcCode,
+ $rpcStatus,
+ [
+ 'previous' => $previous,
+ 'metadata' => $metadata,
+ 'basicMessage' => $basicMessage,
+ ],
+ $protobufErrors ?? []
+ );
+ }
+
+ /**
+ * Encodes decoded metadata to the Protobuf error type
+ *
+ * @param array $metadata
+ * @return array
+ */
+ private static function decodeMetadataToProtobufErrors(array $metadata): array
+ {
+ $result = [];
+ Serializer::loadKnownMetadataTypes();
+
+ foreach ($metadata as $error) {
+ $message = null;
+
+ if (!isset($error['@type'])) {
+ continue;
+ }
+
+ $type = $error['@type'];
+
+ if (!isset(KnownTypes::TYPE_URLS[$type])) {
+ continue;
+ }
+
+ $class = KnownTypes::TYPE_URLS[$type];
+ $message = new $class();
+ $jsonMessage = json_encode(array_diff_key($error, ['@type' => true]));
+ $message->mergeFromJsonString($jsonMessage);
+ $result[] = $message;
+ }
+
+ return $result;
+ }
+
+ /**
+ * @param Status $status
+ * @return ApiException
+ */
+ public static function createFromRpcStatus(Status $status)
+ {
+ return self::create(
+ $status->getMessage(),
+ $status->getCode(),
+ $status->getDetails(),
+ Serializer::decodeAnyMessages($status->getDetails())
+ );
+ }
+
+ /**
+ * Creates an ApiException from a GuzzleHttp RequestException.
+ *
+ * @param RequestException $ex
+ * @param boolean $isStream
+ * @return ApiException
+ * @throws ValidationException
+ */
+ public static function createFromRequestException(RequestException $ex, bool $isStream = false)
+ {
+ $res = $ex->getResponse();
+ $body = (string) $res->getBody();
+ $decoded = json_decode($body, true);
+
+ // A streaming response body will return one error in an array. Parse
+ // that first (and only) error message, if provided.
+ if ($isStream && isset($decoded[0])) {
+ $decoded = $decoded[0];
+ }
+
+ if (isset($decoded['error']) && $decoded['error']) {
+ $error = $decoded['error'];
+ $basicMessage = $error['message'] ?? '';
+ $code = isset($error['status'])
+ ? ApiStatus::rpcCodeFromStatus($error['status'])
+ : $ex->getCode();
+ $metadata = $error['details'] ?? null;
+ return static::createFromRestApiResponse($basicMessage, $code, $metadata);
+ }
+ // Use the RPC code instead of the HTTP Status Code.
+ $code = ApiStatus::rpcCodeFromHttpStatusCode($res->getStatusCode());
+ return static::createFromApiResponse($body, $code);
+ }
+
+ /**
+ * @return null|string
+ */
+ public function getBasicMessage()
+ {
+ return $this->basicMessage;
+ }
+
+ /**
+ * @return mixed[]
+ */
+ public function getMetadata()
+ {
+ return $this->metadata;
+ }
+}
diff --git a/Gax/src/ApiKeyHeaderCredentials.php b/Gax/src/ApiKeyHeaderCredentials.php
new file mode 100644
index 000000000000..f4471d6e94f1
--- /dev/null
+++ b/Gax/src/ApiKeyHeaderCredentials.php
@@ -0,0 +1,95 @@
+apiKey = $apiKey;
+ $this->quotaProject = $quotaProject;
+ }
+
+ /**
+ * @return string|null The quota project associated with the credentials.
+ */
+ public function getQuotaProject(): ?string
+ {
+ return $this->quotaProject;
+ }
+
+ /**
+ * @param string|null $unusedAudience audiences are not supported for API keys.
+ *
+ * @return callable|null Callable function that returns the API key header.
+ */
+ public function getAuthorizationHeaderCallback(?string $unusedAudience = null): ?callable
+ {
+ $apiKey = $this->apiKey;
+
+ // NOTE: changes to this function should be treated carefully and tested thoroughly. It will
+ // be passed into the gRPC c extension, and changes have the potential to trigger very
+ // difficult-to-diagnose segmentation faults.
+ return function () use ($apiKey) {
+ return ['x-goog-api-key' => [$apiKey]];
+ };
+ }
+
+ /**
+ * Verify that the expected universe domain matches the universe domain from the credentials.
+ *
+ * @throws ValidationException if the universe domain does not match.
+ */
+ public function checkUniverseDomain(): void
+ {
+ // This is a no-op, as API keys are not tied to a universe domain. As a result, the
+ // potential for leaking API keys to the GDU is higher, and it's recommended to specify
+ // the "universeDomain" option with the GAPIC client.
+ }
+}
diff --git a/Gax/src/ApiStatus.php b/Gax/src/ApiStatus.php
new file mode 100644
index 000000000000..aa6adcfc131f
--- /dev/null
+++ b/Gax/src/ApiStatus.php
@@ -0,0 +1,171 @@
+ Code::OK,
+ ApiStatus::CANCELLED => Code::CANCELLED,
+ ApiStatus::UNKNOWN => Code::UNKNOWN,
+ ApiStatus::INVALID_ARGUMENT => Code::INVALID_ARGUMENT,
+ ApiStatus::DEADLINE_EXCEEDED => Code::DEADLINE_EXCEEDED,
+ ApiStatus::NOT_FOUND => Code::NOT_FOUND,
+ ApiStatus::ALREADY_EXISTS => Code::ALREADY_EXISTS,
+ ApiStatus::PERMISSION_DENIED => Code::PERMISSION_DENIED,
+ ApiStatus::RESOURCE_EXHAUSTED => Code::RESOURCE_EXHAUSTED,
+ ApiStatus::FAILED_PRECONDITION => Code::FAILED_PRECONDITION,
+ ApiStatus::ABORTED => Code::ABORTED,
+ ApiStatus::OUT_OF_RANGE => Code::OUT_OF_RANGE,
+ ApiStatus::UNIMPLEMENTED => Code::UNIMPLEMENTED,
+ ApiStatus::INTERNAL => Code::INTERNAL,
+ ApiStatus::UNAVAILABLE => Code::UNAVAILABLE,
+ ApiStatus::DATA_LOSS => Code::DATA_LOSS,
+ ApiStatus::UNAUTHENTICATED => Code::UNAUTHENTICATED,
+ ];
+ private static $codeToApiStatusMap = [
+ Code::OK => ApiStatus::OK,
+ Code::CANCELLED => ApiStatus::CANCELLED,
+ Code::UNKNOWN => ApiStatus::UNKNOWN,
+ Code::INVALID_ARGUMENT => ApiStatus::INVALID_ARGUMENT,
+ Code::DEADLINE_EXCEEDED => ApiStatus::DEADLINE_EXCEEDED,
+ Code::NOT_FOUND => ApiStatus::NOT_FOUND,
+ Code::ALREADY_EXISTS => ApiStatus::ALREADY_EXISTS,
+ Code::PERMISSION_DENIED => ApiStatus::PERMISSION_DENIED,
+ Code::RESOURCE_EXHAUSTED => ApiStatus::RESOURCE_EXHAUSTED,
+ Code::FAILED_PRECONDITION => ApiStatus::FAILED_PRECONDITION,
+ Code::ABORTED => ApiStatus::ABORTED,
+ Code::OUT_OF_RANGE => ApiStatus::OUT_OF_RANGE,
+ Code::UNIMPLEMENTED => ApiStatus::UNIMPLEMENTED,
+ Code::INTERNAL => ApiStatus::INTERNAL,
+ Code::UNAVAILABLE => ApiStatus::UNAVAILABLE,
+ Code::DATA_LOSS => ApiStatus::DATA_LOSS,
+ Code::UNAUTHENTICATED => ApiStatus::UNAUTHENTICATED,
+ ];
+ private static $httpStatusCodeToRpcCodeMap = [
+ 400 => Code::INVALID_ARGUMENT,
+ 401 => Code::UNAUTHENTICATED,
+ 403 => Code::PERMISSION_DENIED,
+ 404 => Code::NOT_FOUND,
+ 409 => Code::ABORTED,
+ 416 => Code::OUT_OF_RANGE,
+ 429 => Code::RESOURCE_EXHAUSTED,
+ 499 => Code::CANCELLED,
+ 501 => Code::UNIMPLEMENTED,
+ 503 => Code::UNAVAILABLE,
+ 504 => Code::DEADLINE_EXCEEDED,
+ ];
+
+ /**
+ * @param string $status
+ * @return bool
+ */
+ public static function isValidStatus(string $status)
+ {
+ return array_key_exists($status, self::$apiStatusToCodeMap);
+ }
+
+ /**
+ * @param int $code
+ * @return string
+ */
+ public static function statusFromRpcCode(int $code)
+ {
+ if (array_key_exists($code, self::$codeToApiStatusMap)) {
+ return self::$codeToApiStatusMap[$code];
+ }
+ return ApiStatus::UNRECOGNIZED_STATUS;
+ }
+
+ /**
+ * @param string $status
+ * @return int
+ */
+ public static function rpcCodeFromStatus(string $status)
+ {
+ if (array_key_exists($status, self::$apiStatusToCodeMap)) {
+ return self::$apiStatusToCodeMap[$status];
+ }
+ return ApiStatus::UNRECOGNIZED_CODE;
+ }
+
+ /**
+ * Maps HTTP status codes to Google\Rpc\Code codes.
+ * Some codes are left out because they map to multiple gRPC codes (e.g. 500).
+ *
+ * @param int $httpStatusCode
+ * @return int
+ */
+ public static function rpcCodeFromHttpStatusCode(int $httpStatusCode)
+ {
+ if (array_key_exists($httpStatusCode, self::$httpStatusCodeToRpcCodeMap)) {
+ return self::$httpStatusCodeToRpcCodeMap[$httpStatusCode];
+ }
+ // All 2xx
+ if ($httpStatusCode >= 200 && $httpStatusCode < 300) {
+ return Code::OK;
+ }
+ // All 4xx
+ if ($httpStatusCode >= 400 && $httpStatusCode < 500) {
+ return Code::FAILED_PRECONDITION;
+ }
+ // All 5xx
+ if ($httpStatusCode >= 500 && $httpStatusCode < 600) {
+ return Code::INTERNAL;
+ }
+ // Everything else (We cannot change this to Code::UNKNOWN because it would break BC)
+ return ApiStatus::UNRECOGNIZED_CODE;
+ }
+}
diff --git a/Gax/src/ArrayTrait.php b/Gax/src/ArrayTrait.php
new file mode 100644
index 000000000000..02638be0c246
--- /dev/null
+++ b/Gax/src/ArrayTrait.php
@@ -0,0 +1,156 @@
+pluck($key, $arr, false);
+ }
+ }
+
+ return $values;
+ }
+
+ /**
+ * Determine whether given array is associative.
+ *
+ * @param array $arr
+ * @return bool
+ */
+ private function isAssoc(array $arr)
+ {
+ return array_keys($arr) !== range(0, count($arr) - 1);
+ }
+
+ /**
+ * Just like array_filter(), but preserves falsey values except null.
+ *
+ * @param array $arr
+ * @return array
+ */
+ private function arrayFilterRemoveNull(array $arr)
+ {
+ return array_filter($arr, function ($element) {
+ if (!is_null($element)) {
+ return true;
+ }
+
+ return false;
+ });
+ }
+
+ /**
+ * Return a subset of an array, like pluckArray, without modifying the original array.
+ *
+ * @param array $keys
+ * @param array $arr
+ * @return array
+ */
+ private function subsetArray(array $keys, array $arr)
+ {
+ return array_intersect_key(
+ $arr,
+ array_flip($keys)
+ );
+ }
+
+ /**
+ * A method, similar to PHP's `array_merge_recursive`, with two differences.
+ *
+ * 1. Keys in $array2 take precedence over keys in $array1.
+ * 2. Non-array keys found in both inputs are not transformed into an array
+ * and appended. Rather, the value in $array2 is used.
+ *
+ * @param array $array1
+ * @param array $array2
+ * @return array
+ */
+ private function arrayMergeRecursive(array $array1, array $array2)
+ {
+ foreach ($array2 as $key => $value) {
+ if (array_key_exists($key, $array1) && is_array($array1[$key]) && is_array($value)) {
+ $array1[$key] = ($this->isAssoc($array1[$key]) && $this->isAssoc($value))
+ ? $this->arrayMergeRecursive($array1[$key], $value)
+ : array_merge($array1[$key], $value);
+ } else {
+ $array1[$key] = $value;
+ }
+ }
+
+ return $array1;
+ }
+}
diff --git a/Gax/src/BidiStream.php b/Gax/src/BidiStream.php
new file mode 100644
index 000000000000..e4d6718b25e6
--- /dev/null
+++ b/Gax/src/BidiStream.php
@@ -0,0 +1,217 @@
+call = $bidiStreamingCall;
+ if (array_key_exists('resourcesGetMethod', $streamingDescriptor)) {
+ $this->resourcesGetMethod = $streamingDescriptor['resourcesGetMethod'];
+ }
+ $this->logger = $logger;
+ }
+
+ /**
+ * Write request to the server.
+ *
+ * @param mixed $request The request to write
+ * @throws ValidationException
+ */
+ public function write($request)
+ {
+ if ($this->isComplete) {
+ throw new ValidationException('Cannot call write() after streaming call is complete.');
+ }
+ if ($this->writesClosed) {
+ throw new ValidationException('Cannot call write() after calling closeWrite().');
+ }
+
+ if ($this->logger && $request instanceof Message) {
+ $logEvent = new RpcLogEvent();
+
+ $logEvent->headers = null;
+ $logEvent->payload = $request->serializeToJsonString();
+ $logEvent->processId = (int) getmypid();
+ $logEvent->requestId = crc32((string) spl_object_id($this) . getmypid());
+
+ $this->logRequest($logEvent);
+ }
+
+ $this->call->write($request);
+ }
+
+ /**
+ * Write all requests in $requests.
+ *
+ * @param iterable $requests An Iterable of request objects to write to the server
+ *
+ * @throws ValidationException
+ */
+ public function writeAll($requests = [])
+ {
+ foreach ($requests as $request) {
+ $this->write($request);
+ }
+ }
+
+ /**
+ * Inform the server that no more requests will be written. The write() function cannot be
+ * called after closeWrite() is called.
+ * @throws ValidationException
+ */
+ public function closeWrite()
+ {
+ if ($this->isComplete) {
+ throw new ValidationException(
+ 'Cannot call closeWrite() after streaming call is complete.'
+ );
+ }
+ if (!$this->writesClosed) {
+ $this->call->writesDone();
+ $this->writesClosed = true;
+ }
+ }
+
+ /**
+ * Read the next response from the server. Returns null if the streaming call completed
+ * successfully. Throws an ApiException if the streaming call failed.
+ *
+ * @throws ValidationException
+ * @throws ApiException
+ * @return mixed
+ */
+ public function read()
+ {
+ if ($this->isComplete) {
+ throw new ValidationException('Cannot call read() after streaming call is complete.');
+ }
+ $resourcesGetMethod = $this->resourcesGetMethod;
+ if (!is_null($resourcesGetMethod)) {
+ if (count($this->pendingResources) === 0) {
+ $response = $this->call->read();
+ if (!is_null($response)) {
+ $pendingResources = [];
+ foreach ($response->$resourcesGetMethod() as $resource) {
+ $pendingResources[] = $resource;
+ }
+ $this->pendingResources = array_reverse($pendingResources);
+ }
+ }
+ $result = array_pop($this->pendingResources);
+ } else {
+ $result = $this->call->read();
+ }
+ if (is_null($result)) {
+ $status = $this->call->getStatus();
+ $this->isComplete = true;
+ if (!($status->code == Code::OK)) {
+ throw ApiException::createFromStdClass($status);
+ }
+ }
+
+ if ($this->logger) {
+ $responseEvent = new RpcLogEvent();
+
+ $responseEvent->headers = $this->call->getMetadata();
+ $responseEvent->status = $status->code ?? null;
+ $responseEvent->processId = (int) getmypid();
+ $responseEvent->requestId = crc32((string) spl_object_id($this) . getmypid());
+
+ if ($result instanceof Message) {
+ $responseEvent->payload = $result->serializeToJsonString();
+ }
+
+ $this->logResponse($responseEvent);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Call closeWrite(), and read all responses from the server, until the streaming call is
+ * completed. Throws an ApiException if the streaming call failed.
+ *
+ * @throws ValidationException
+ * @throws ApiException
+ * @return \Generator|mixed[]
+ */
+ public function closeWriteAndReadAll()
+ {
+ $this->closeWrite();
+ $response = $this->read();
+ while (!is_null($response)) {
+ yield $response;
+ $response = $this->read();
+ }
+ }
+
+ /**
+ * Return the underlying gRPC call object
+ *
+ * @return \Grpc\BidiStreamingCall|mixed
+ */
+ public function getBidiStreamingCall()
+ {
+ return $this->call;
+ }
+}
diff --git a/Gax/src/Call.php b/Gax/src/Call.php
new file mode 100644
index 000000000000..53c5d32bb2c4
--- /dev/null
+++ b/Gax/src/Call.php
@@ -0,0 +1,131 @@
+method = $method;
+ $this->decodeType = $decodeType;
+ $this->message = $message;
+ $this->descriptor = $descriptor;
+ $this->callType = $callType;
+ }
+
+ /**
+ * @return string
+ */
+ public function getMethod()
+ {
+ return $this->method;
+ }
+
+ /**
+ * @return int
+ */
+ public function getCallType()
+ {
+ return $this->callType;
+ }
+
+ /**
+ * @return string
+ */
+ public function getDecodeType()
+ {
+ return $this->decodeType;
+ }
+
+ /**
+ * @return mixed|Message
+ */
+ public function getMessage()
+ {
+ return $this->message;
+ }
+
+ /**
+ * @return array|null
+ */
+ public function getDescriptor()
+ {
+ return $this->descriptor;
+ }
+
+ /**
+ * @param mixed|Message $message
+ * @return Call
+ */
+ public function withMessage($message)
+ {
+ // @phpstan-ignore-next-line
+ return new static(
+ $this->method,
+ $this->decodeType,
+ $message,
+ $this->descriptor,
+ $this->callType
+ );
+ }
+}
diff --git a/Gax/src/ClientOptionsTrait.php b/Gax/src/ClientOptionsTrait.php
new file mode 100644
index 000000000000..f5fbafdd4d23
--- /dev/null
+++ b/Gax/src/ClientOptionsTrait.php
@@ -0,0 +1,399 @@
+mergeFromJsonString(file_get_contents($confPath));
+ $config = new Config($hostName, $apiConfig);
+ return $config;
+ }
+
+ /**
+ * Get default options. This function should be "overridden" by clients using late static
+ * binding to provide default options to the client.
+ *
+ * @return array
+ * @access private
+ */
+ private static function getClientDefaults()
+ {
+ return [];
+ }
+
+ /**
+ * Resolve client options based on the client's default
+ * ({@see ClientOptionsTrait::getClientDefault}) and the default for all
+ * Google APIs.
+ *
+ * 1. Set default client option values
+ * 2. Set default logger (and log user-supplied configuration options)
+ * 3. Set default transport configuration
+ * 4. Call "modifyClientOptions" (for backwards compatibility)
+ * 5. Use "defaultScopes" when custom endpoint is supplied
+ * 6. Load mTLS from the environment if configured
+ * 7. Resolve endpoint based on universe domain template when possible
+ * 8. Load sysvshm grpc config when possible
+ */
+ private function buildClientOptions(array|ClientOptions $options)
+ {
+ if ($options instanceof ClientOptions) {
+ $options = $options->toArray();
+ }
+
+ // Build $defaultOptions starting from top level
+ // variables, then going into deeper nesting, so that
+ // we will not encounter missing keys
+ $defaultOptions = self::getClientDefaults();
+ $defaultOptions += [
+ 'disableRetries' => false,
+ 'credentials' => null,
+ 'credentialsConfig' => [],
+ 'transport' => null,
+ 'transportConfig' => [],
+ 'gapicVersion' => self::getGapicVersion($options),
+ 'libName' => null,
+ 'libVersion' => null,
+ 'apiEndpoint' => null,
+ 'clientCertSource' => null,
+ 'universeDomain' => null,
+ 'logger' => null,
+ ];
+
+ $supportedTransports = $this->supportedTransports();
+ foreach ($supportedTransports as $transportName) {
+ if (!array_key_exists($transportName, $defaultOptions['transportConfig'])) {
+ $defaultOptions['transportConfig'][$transportName] = [];
+ }
+ }
+ if (in_array('grpc', $supportedTransports)) {
+ $defaultOptions['transportConfig']['grpc'] = [
+ 'stubOpts' => ['grpc.service_config_disable_resolution' => 1]
+ ];
+ }
+
+ // Keep track of the API Endpoint
+ $apiEndpoint = $options['apiEndpoint'] ?? null;
+
+ // Keep track of the original user supplied options for logging the configuration
+ $clientSuppliedOptions = $options;
+
+ // Merge defaults into $options starting from top level
+ // variables, then going into deeper nesting, so that
+ // we will not encounter missing keys
+ $options += $defaultOptions;
+
+ // If logger is explicitly set to false, logging is disabled
+ if (is_null($options['logger'])) {
+ $options['logger'] = ApplicationDefaultCredentials::getDefaultLogger();
+ }
+
+ if ($options['logger'] !== null
+ && $options['logger'] !== false
+ && !$options['logger'] instanceof LoggerInterface
+ ) {
+ throw new ValidationException(
+ 'The "logger" option in the options array should be PSR-3 LoggerInterface compatible'
+ );
+ }
+
+ // Log the user supplied configuration.
+ $this->logConfiguration($options['logger'], $clientSuppliedOptions);
+
+ if (isset($options['logger'])) {
+ $options['credentialsConfig']['authHttpHandler'] = HttpHandlerFactory::build(
+ logger: $options['logger']
+ );
+ }
+
+ $options['credentialsConfig'] += $defaultOptions['credentialsConfig'];
+ $options['transportConfig'] += $defaultOptions['transportConfig']; // @phpstan-ignore-line
+ if (isset($options['transportConfig']['grpc'])) {
+ $options['transportConfig']['grpc'] += $defaultOptions['transportConfig']['grpc'];
+ $options['transportConfig']['grpc']['stubOpts'] += $defaultOptions['transportConfig']['grpc']['stubOpts'];
+ $options['transportConfig']['grpc']['logger'] = $options['logger'] ?? null;
+ }
+ if (isset($options['transportConfig']['rest'])) {
+ $options['transportConfig']['rest'] += $defaultOptions['transportConfig']['rest'];
+ $options['transportConfig']['rest']['logger'] = $options['logger'] ?? null;
+ }
+ if (isset($options['transportConfig']['grpc-fallback'])) {
+ $options['transportConfig']['grpc-fallback']['logger'] = $options['logger'] ?? null;
+ }
+
+ // These calls do not apply to "New Surface" clients.
+ if ($this->isBackwardsCompatibilityMode()) {
+ $preModifiedOptions = $options;
+ $this->modifyClientOptions($options);
+ // NOTE: this is required to ensure backwards compatiblity with $options['apiEndpoint']
+ if ($options['apiEndpoint'] !== $preModifiedOptions['apiEndpoint']) {
+ $apiEndpoint = $options['apiEndpoint'];
+ }
+
+ // serviceAddress is now deprecated and acts as an alias for apiEndpoint
+ if (isset($options['serviceAddress'])) {
+ $apiEndpoint = $this->pluck('serviceAddress', $options, false);
+ }
+ } else {
+ // Ads is using this method in their new surface clients, so we need to call it.
+ // However, this method is not used anywhere else for the new surface clients
+ // @TODO: Remove this in GAX V2
+ $this->modifyClientOptions($options);
+ }
+ // If an API endpoint is different form the default, ensure the "audience" does not conflict
+ // with the custom endpoint by setting "user defined" scopes.
+ if ($apiEndpoint
+ && $apiEndpoint != $defaultOptions['apiEndpoint']
+ && empty($options['credentialsConfig']['scopes'])
+ && !empty($options['credentialsConfig']['defaultScopes'])
+ ) {
+ $options['credentialsConfig']['scopes'] = $options['credentialsConfig']['defaultScopes'];
+ }
+
+ // mTLS: detect and load the default clientCertSource if the environment variable
+ // "GOOGLE_API_USE_CLIENT_CERTIFICATE" is true, and the cert source is available
+ if (empty($options['clientCertSource']) && CredentialsLoader::shouldLoadClientCertSource()) {
+ if ($defaultCertSource = CredentialsLoader::getDefaultClientCertSource()) {
+ $options['clientCertSource'] = function () use ($defaultCertSource) {
+ $cert = call_user_func($defaultCertSource);
+
+ // the key and the cert are returned in one string
+ return [$cert, $cert];
+ };
+ }
+ }
+
+ // mTLS: If no apiEndpoint has been supplied by the user, and either
+ // GOOGLE_API_USE_MTLS_ENDPOINT tells us to, or mTLS is available, use the mTLS endpoint.
+ if (is_null($apiEndpoint) && $this->shouldUseMtlsEndpoint($options)) {
+ $apiEndpoint = self::determineMtlsEndpoint($options['apiEndpoint']);
+ }
+
+ // If the user has not supplied a universe domain, use the environment variable if set.
+ // Otherwise, use the default ("googleapis.com").
+ $options['universeDomain'] ??= getenv('GOOGLE_CLOUD_UNIVERSE_DOMAIN')
+ ?: GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN;
+
+ // mTLS: It is not valid to configure mTLS outside of "googleapis.com" (yet)
+ if (isset($options['clientCertSource'])
+ && $options['universeDomain'] !== GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN
+ ) {
+ throw new ValidationException(
+ 'mTLS is not supported outside the "googleapis.com" universe'
+ );
+ }
+
+ if (is_null($apiEndpoint)) {
+ if (defined('self::SERVICE_ADDRESS_TEMPLATE')) {
+ // Derive the endpoint from the service address template and the universe domain
+ $apiEndpoint = str_replace(
+ 'UNIVERSE_DOMAIN',
+ $options['universeDomain'],
+ self::SERVICE_ADDRESS_TEMPLATE
+ );
+ } else {
+ // For older clients, the service address template does not exist. Use the default
+ // endpoint instead.
+ $apiEndpoint = $defaultOptions['apiEndpoint'];
+ }
+ }
+
+ if (extension_loaded('sysvshm')
+ && isset($options['gcpApiConfigPath'])
+ && file_exists($options['gcpApiConfigPath'])
+ && !empty($apiEndpoint)
+ ) {
+ $grpcGcpConfig = self::initGrpcGcpConfig(
+ $apiEndpoint,
+ $options['gcpApiConfigPath']
+ );
+
+ if (!array_key_exists('stubOpts', $options['transportConfig']['grpc'])) {
+ $options['transportConfig']['grpc']['stubOpts'] = [];
+ }
+
+ $options['transportConfig']['grpc']['stubOpts'] += [
+ 'grpc_call_invoker' => $grpcGcpConfig->callInvoker()
+ ];
+ }
+
+ $options['apiEndpoint'] = $apiEndpoint;
+
+ return $options;
+ }
+
+ private function shouldUseMtlsEndpoint(array $options)
+ {
+ $mtlsEndpointEnvVar = getenv('GOOGLE_API_USE_MTLS_ENDPOINT');
+ if ('always' === $mtlsEndpointEnvVar) {
+ return true;
+ }
+ if ('never' === $mtlsEndpointEnvVar) {
+ return false;
+ }
+ // For all other cases, assume "auto" and return true if clientCertSource exists
+ return !empty($options['clientCertSource']);
+ }
+
+ private static function determineMtlsEndpoint(string $apiEndpoint)
+ {
+ $parts = explode('.', $apiEndpoint);
+ if (count($parts) < 3) {
+ return $apiEndpoint; // invalid endpoint!
+ }
+ return sprintf('%s.mtls.%s', array_shift($parts), implode('.', $parts));
+ }
+
+ /**
+ * @param mixed $credentials
+ * @param array $credentialsConfig
+ * @return CredentialsWrapper
+ * @throws ValidationException
+ */
+ private function createCredentialsWrapper($credentials, array $credentialsConfig, string $universeDomain)
+ {
+ if (is_null($credentials)) {
+ // If the user has explicitly set the apiKey option, use Api Key credentials
+ return CredentialsWrapper::build($credentialsConfig, $universeDomain);
+ }
+
+ if (is_string($credentials) || is_array($credentials)) {
+ return CredentialsWrapper::build(['keyFile' => $credentials] + $credentialsConfig, $universeDomain);
+ }
+
+ if ($credentials instanceof FetchAuthTokenInterface) {
+ $authHttpHandler = $credentialsConfig['authHttpHandler'] ?? null;
+ return new CredentialsWrapper($credentials, $authHttpHandler, $universeDomain);
+ }
+
+ if ($credentials instanceof CredentialsWrapper) {
+ return $credentials;
+ }
+
+ throw new ValidationException(sprintf(
+ 'Unexpected value in $auth option, got: %s',
+ print_r($credentials, true)
+ ));
+ }
+
+ /**
+ * This defaults to all three transports, which One-Platform supports.
+ * Discovery clients should define this function and only return ['rest'].
+ */
+ private static function supportedTransports()
+ {
+ return ['grpc', 'grpc-fallback', 'rest'];
+ }
+
+ // Gapic Client Extension Points
+ // The methods below provide extension points that can be used to customize client
+ // functionality. These extension points are currently considered
+ // private and may change at any time.
+
+ /**
+ * Modify options passed to the client before calling setClientOptions.
+ *
+ * @param array $options
+ * @access private
+ * @internal
+ */
+ protected function modifyClientOptions(array &$options)
+ {
+ // Do nothing - this method exists to allow option modification by partial veneers.
+ }
+
+ /**
+ * @internal
+ */
+ private function isBackwardsCompatibilityMode(): bool
+ {
+ return false;
+ }
+
+ /**
+ * @param null|false|LoggerInterface $logger
+ * @param string $options
+ */
+ private function logConfiguration(null|false|LoggerInterface $logger, array $options): void
+ {
+ if (!$logger) {
+ return;
+ }
+
+ $configurationLog = [
+ 'timestamp' => date(DATE_RFC3339),
+ 'severity' => strtoupper(LogLevel::DEBUG),
+ 'processId' => getmypid(),
+ 'jsonPayload' => [
+ 'serviceName' => self::SERVICE_NAME, // @phpstan-ignore-line
+ 'clientConfiguration' => $options,
+ ]
+ ];
+
+ $logger->debug(json_encode($configurationLog));
+ }
+}
diff --git a/Gax/src/ClientStream.php b/Gax/src/ClientStream.php
new file mode 100644
index 000000000000..6c249551ca54
--- /dev/null
+++ b/Gax/src/ClientStream.php
@@ -0,0 +1,143 @@
+call = $clientStreamingCall;
+ $this->logger = $logger;
+ }
+
+ /**
+ * Write request to the server.
+ *
+ * @param mixed $request The request to write
+ */
+ public function write($request)
+ {
+ // In some cases, $request can be a string
+ if ($this->logger && $request instanceof Message) {
+ $requestEvent = new RpcLogEvent();
+
+ $requestEvent->payload = $request->serializeToJsonString();
+ $requestEvent->processId = (int) getmypid();
+ $requestEvent->requestId = crc32((string) spl_object_id($this) . getmypid());
+
+ $this->logRequest($requestEvent);
+ }
+
+ $this->call->write($request);
+ }
+
+ /**
+ * Read the response from the server, completing the streaming call.
+ *
+ * @throws ApiException
+ * @return mixed The response object from the server
+ */
+ public function readResponse()
+ {
+ list($response, $status) = $this->call->wait();
+ if ($status->code == Code::OK) {
+ if ($this->logger) {
+ $responseEvent = new RpcLogEvent();
+
+ $responseEvent->headers = $status->metadata;
+ $responseEvent->status = $status->code;
+ $responseEvent->processId = (int) getmypid();
+ $responseEvent->requestId = crc32((string) spl_object_id($this) . getmypid());
+
+ if ($response instanceof Message) {
+ $response->serializeToJsonString();
+ }
+
+ $this->logResponse($responseEvent);
+ }
+
+ return $response;
+ } else {
+ throw ApiException::createFromStdClass($status);
+ }
+ }
+
+ /**
+ * Write all data in $dataArray and read the response from the server, completing the streaming
+ * call.
+ *
+ * @param mixed[] $requests An iterator of request objects to write to the server
+ * @return mixed The response object from the server
+ */
+ public function writeAllAndReadResponse(array $requests)
+ {
+ foreach ($requests as $request) {
+ $this->write($request);
+ }
+ return $this->readResponse();
+ }
+
+ /**
+ * Return the underlying gRPC call object
+ *
+ * @return \Grpc\ClientStreamingCall|mixed
+ */
+ public function getClientStreamingCall()
+ {
+ return $this->call;
+ }
+}
diff --git a/Gax/src/CredentialsWrapper.php b/Gax/src/CredentialsWrapper.php
new file mode 100644
index 000000000000..d6e90892b5a3
--- /dev/null
+++ b/Gax/src/CredentialsWrapper.php
@@ -0,0 +1,359 @@
+credentialsFetcher = $credentialsFetcher;
+ $this->authHttpHandler = $authHttpHandler;
+ if (empty($universeDomain)) {
+ throw new ValidationException('The universe domain cannot be empty');
+ }
+ $this->universeDomain = $universeDomain;
+ }
+
+ /**
+ * Factory method to create a CredentialsWrapper from an array of options.
+ *
+ * @param array $args {
+ * An array of optional arguments.
+ *
+ * @type string|array $keyFile
+ * Credentials to be used. Accepts either a path to a credentials file, or a decoded
+ * credentials file as a PHP array. If this is not specified, application default
+ * credentials will be used.
+ * @type string[] $scopes
+ * A string array of scopes to use when acquiring credentials.
+ * @type callable $authHttpHandler
+ * A handler used to deliver PSR-7 requests specifically
+ * for authentication. Should match a signature of
+ * `function (RequestInterface $request, array $options) : ResponseInterface`.
+ * @type bool $enableCaching
+ * Enable caching of access tokens. Defaults to true.
+ * @type CacheItemPoolInterface $authCache
+ * A cache for storing access tokens. Defaults to a simple in memory implementation.
+ * @type array $authCacheOptions
+ * Cache configuration options.
+ * @type string $quotaProject
+ * Specifies a user project to bill for access charges associated with the request.
+ * @type string[] $defaultScopes
+ * A string array of default scopes to use when acquiring
+ * credentials.
+ * @type bool $useJwtAccessWithScope
+ * Ensures service account credentials use JWT Access (also known as self-signed
+ * JWTs), even when user-defined scopes are supplied.
+ * }
+ * @param string $universeDomain The expected universe of the credentials. Defaults to
+ * "googleapis.com"
+ * @return CredentialsWrapper
+ * @throws ValidationException
+ */
+ public static function build(
+ array $args = [],
+ string $universeDomain = GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN
+ ) {
+ $args += [
+ 'keyFile' => null,
+ 'scopes' => null,
+ 'authHttpHandler' => null,
+ 'enableCaching' => true,
+ 'authCache' => null,
+ 'authCacheOptions' => [],
+ 'quotaProject' => null,
+ 'defaultScopes' => null,
+ 'useJwtAccessWithScope' => true,
+ ];
+
+ $keyFile = $args['keyFile'];
+
+ if (is_null($keyFile)) {
+ $loader = self::buildApplicationDefaultCredentials(
+ $args['scopes'],
+ $args['authHttpHandler'],
+ $args['authCacheOptions'],
+ $args['authCache'],
+ $args['quotaProject'],
+ $args['defaultScopes']
+ );
+ if ($loader instanceof FetchAuthTokenCache) {
+ $loader = $loader->getFetcher();
+ }
+ } else {
+ if (is_string($keyFile)) {
+ if (!file_exists($keyFile)) {
+ throw new ValidationException("Could not find keyfile: $keyFile");
+ }
+ $keyFile = json_decode(file_get_contents($keyFile), true);
+ }
+
+ if (isset($args['quotaProject'])) {
+ $keyFile['quota_project_id'] = $args['quotaProject'];
+ }
+
+ $loader = CredentialsLoader::makeCredentials(
+ $args['scopes'],
+ $keyFile,
+ $args['defaultScopes']
+ );
+ }
+
+ if ($loader instanceof ServiceAccountCredentials && $args['useJwtAccessWithScope']) {
+ // Ensures the ServiceAccountCredentials uses JWT Access, also known
+ // as self-signed JWTs, even when user-defined scopes are supplied.
+ $loader->useJwtAccessWithScope();
+ }
+
+ if ($args['enableCaching']) {
+ $authCache = $args['authCache'] ?: new MemoryCacheItemPool();
+ $loader = new FetchAuthTokenCache(
+ $loader,
+ $args['authCacheOptions'],
+ $authCache
+ );
+ }
+
+ return new CredentialsWrapper($loader, $args['authHttpHandler'], $universeDomain);
+ }
+
+ /**
+ * @return string|null The quota project associated with the credentials.
+ */
+ public function getQuotaProject(): ?string
+ {
+ if ($this->credentialsFetcher instanceof GetQuotaProjectInterface) {
+ return $this->credentialsFetcher->getQuotaProject();
+ }
+ return null;
+ }
+
+ public function getProjectId(?callable $httpHandler = null): ?string
+ {
+ // Ensure that FetchAuthTokenCache does not throw an exception
+ if ($this->credentialsFetcher instanceof FetchAuthTokenCache
+ && !$this->credentialsFetcher->getFetcher() instanceof ProjectIdProviderInterface) {
+ return null;
+ }
+
+ if ($this->credentialsFetcher instanceof ProjectIdProviderInterface) {
+ return $this->credentialsFetcher->getProjectId($httpHandler);
+ }
+ return null;
+ }
+
+ /**
+ * @deprecated
+ * @return string Bearer string containing access token.
+ */
+ public function getBearerString()
+ {
+ $token = $this->credentialsFetcher->getLastReceivedToken();
+ if (self::isExpired($token)) {
+ $this->checkUniverseDomain();
+
+ $token = $this->credentialsFetcher->fetchAuthToken($this->authHttpHandler);
+ if (!self::isValid($token)) {
+ return '';
+ }
+ }
+ return empty($token['access_token']) ? '' : 'Bearer ' . $token['access_token'];
+ }
+
+ /**
+ * @param string $audience optional audience for self-signed JWTs.
+ * @return callable Callable function that returns an authorization header.
+ */
+ public function getAuthorizationHeaderCallback($audience = null): ?callable
+ {
+ // NOTE: changes to this function should be treated carefully and tested thoroughly. It will
+ // be passed into the gRPC c extension, and changes have the potential to trigger very
+ // difficult-to-diagnose segmentation faults.
+ return function () use ($audience) {
+ $token = $this->credentialsFetcher->getLastReceivedToken();
+ if (self::isExpired($token)) {
+ $this->checkUniverseDomain();
+
+ // Call updateMetadata to take advantage of self-signed JWTs
+ if ($this->credentialsFetcher instanceof UpdateMetadataInterface) {
+ return $this->credentialsFetcher->updateMetadata([], $audience, $this->authHttpHandler);
+ }
+
+ // In case a custom fetcher is provided (unlikely) which doesn't
+ // implement UpdateMetadataInterface
+ $token = $this->credentialsFetcher->fetchAuthToken($this->authHttpHandler);
+ if (!self::isValid($token)) {
+ return [];
+ }
+ }
+ $tokenString = $token['access_token'];
+ if (!empty($tokenString)) {
+ return ['authorization' => ["Bearer $tokenString"]];
+ }
+ return [];
+ };
+ }
+
+ /**
+ * Verify that the expected universe domain matches the universe domain from the credentials.
+ *
+ * @throws ValidationException if the universe domain does not match.
+ */
+ public function checkUniverseDomain(): void
+ {
+ if (false === $this->hasCheckedUniverse && $this->shouldCheckUniverseDomain()) {
+ $credentialsUniverse = $this->credentialsFetcher instanceof GetUniverseDomainInterface
+ ? $this->credentialsFetcher->getUniverseDomain()
+ : GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN;
+ if ($credentialsUniverse !== $this->universeDomain) {
+ throw new ValidationException(sprintf(
+ 'The configured universe domain (%s) does not match the credential universe domain (%s)',
+ $this->universeDomain,
+ $credentialsUniverse
+ ));
+ }
+ $this->hasCheckedUniverse = true;
+ }
+ }
+
+ /**
+ * Skip universe domain check for Metadata server (e.g. GCE) credentials.
+ *
+ * @return bool
+ */
+ private function shouldCheckUniverseDomain(): bool
+ {
+ $fetcher = $this->credentialsFetcher instanceof FetchAuthTokenCache
+ ? $this->credentialsFetcher->getFetcher()
+ : $this->credentialsFetcher;
+
+ if ($fetcher instanceof GCECredentials) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * @param array $scopes
+ * @param callable $authHttpHandler
+ * @param array $authCacheOptions
+ * @param CacheItemPoolInterface $authCache
+ * @param string $quotaProject
+ * @param array $defaultScopes
+ * @return FetchAuthTokenInterface
+ * @throws ValidationException
+ */
+ private static function buildApplicationDefaultCredentials(
+ ?array $scopes = null,
+ ?callable $authHttpHandler = null,
+ ?array $authCacheOptions = null,
+ ?CacheItemPoolInterface $authCache = null,
+ $quotaProject = null,
+ ?array $defaultScopes = null
+ ) {
+ try {
+ return ApplicationDefaultCredentials::getCredentials(
+ $scopes,
+ $authHttpHandler,
+ $authCacheOptions,
+ $authCache,
+ $quotaProject,
+ $defaultScopes
+ );
+ } catch (DomainException $ex) {
+ throw new ValidationException('Could not construct ApplicationDefaultCredentials', $ex->getCode(), $ex);
+ }
+ }
+
+ /**
+ * @param mixed $token
+ */
+ private static function isValid($token)
+ {
+ return is_array($token)
+ && array_key_exists('access_token', $token);
+ }
+
+ /**
+ * @param mixed $token
+ */
+ private static function isExpired($token)
+ {
+ return !(self::isValid($token)
+ && array_key_exists('expires_at', $token)
+ && $token['expires_at'] > time() + self::$eagerRefreshThresholdSeconds);
+ }
+}
diff --git a/Gax/src/FixedSizeCollection.php b/Gax/src/FixedSizeCollection.php
new file mode 100644
index 000000000000..03ec835c1368
--- /dev/null
+++ b/Gax/src/FixedSizeCollection.php
@@ -0,0 +1,190 @@
+ 0. collectionSize: $collectionSize"
+ );
+ }
+ if ($collectionSize < $initialPage->getPageElementCount()) {
+ $ipc = $initialPage->getPageElementCount();
+ throw new InvalidArgumentException(
+ 'collectionSize must be greater than or equal to the number of ' .
+ "elements in initialPage. collectionSize: $collectionSize, " .
+ "initialPage size: $ipc"
+ );
+ }
+ $this->collectionSize = $collectionSize;
+
+ $this->pageList = FixedSizeCollection::createPageArray($initialPage, $collectionSize);
+ }
+
+ /**
+ * Returns the number of elements in the collection. This will be
+ * equal to the collectionSize parameter used at construction
+ * unless there are no elements remaining to be retrieved.
+ *
+ * @return int
+ */
+ public function getCollectionSize()
+ {
+ $size = 0;
+ foreach ($this->pageList as $page) {
+ $size += $page->getPageElementCount();
+ }
+ return $size;
+ }
+
+ /**
+ * Returns true if there are more elements that can be retrieved
+ * from the API.
+ *
+ * @return bool
+ */
+ public function hasNextCollection()
+ {
+ return $this->getLastPage()->hasNextPage();
+ }
+
+ /**
+ * Returns a page token that can be passed into the API list
+ * method to retrieve additional elements.
+ *
+ * @return string
+ */
+ public function getNextPageToken()
+ {
+ return $this->getLastPage()->getNextPageToken();
+ }
+
+ /**
+ * Retrieves the next FixedSizeCollection using one or more API calls.
+ *
+ * @return FixedSizeCollection
+ */
+ public function getNextCollection()
+ {
+ $lastPage = $this->getLastPage();
+ $nextPage = $lastPage->getNextPage($this->collectionSize);
+ return new FixedSizeCollection($nextPage, $this->collectionSize);
+ }
+
+ /**
+ * Returns an iterator over the elements of the collection.
+ *
+ * @return Generator
+ */
+ #[\ReturnTypeWillChange]
+ public function getIterator()
+ {
+ foreach ($this->pageList as $page) {
+ foreach ($page as $element) {
+ yield $element;
+ }
+ }
+ }
+
+ /**
+ * Returns an iterator over FixedSizeCollections, starting with this
+ * and making API calls as required until all of the elements have
+ * been retrieved.
+ *
+ * @return Generator|FixedSizeCollection[]
+ */
+ public function iterateCollections()
+ {
+ $currentCollection = $this;
+ yield $this;
+ while ($currentCollection->hasNextCollection()) {
+ $currentCollection = $currentCollection->getNextCollection();
+ yield $currentCollection;
+ }
+ }
+
+ private function getLastPage()
+ {
+ $pageList = $this->pageList;
+ // Get last element in array...
+ $lastPage = end($pageList);
+ reset($pageList);
+ return $lastPage;
+ }
+
+ /**
+ * @param Page $initialPage
+ * @param int $collectionSize
+ * @return Page[]
+ */
+ private static function createPageArray(Page $initialPage, int $collectionSize)
+ {
+ $pageList = [$initialPage];
+ $currentPage = $initialPage;
+ $itemCount = $currentPage->getPageElementCount();
+ while ($itemCount < $collectionSize && $currentPage->hasNextPage()) {
+ $remainingCount = $collectionSize - $itemCount;
+ $currentPage = $currentPage->getNextPage($remainingCount);
+ $rxElementCount = $currentPage->getPageElementCount();
+ if ($rxElementCount > $remainingCount) {
+ throw new LengthException('API returned a number of elements ' .
+ 'exceeding the specified page size limit. page size: ' .
+ "$remainingCount, elements received: $rxElementCount");
+ }
+ array_push($pageList, $currentPage);
+ $itemCount += $rxElementCount;
+ }
+ return $pageList;
+ }
+}
diff --git a/Gax/src/GPBLabel.php b/Gax/src/GPBLabel.php
new file mode 100644
index 000000000000..18208cd64862
--- /dev/null
+++ b/Gax/src/GPBLabel.php
@@ -0,0 +1,43 @@
+ $prependMiddlewareCallables */
+ private array $prependMiddlewareCallables = [];
+ /** @var array $middlewareCallables */
+ private array $middlewareCallables = [];
+ private array $transportCallMethods = [
+ Call::UNARY_CALL => 'startUnaryCall',
+ Call::BIDI_STREAMING_CALL => 'startBidiStreamingCall',
+ Call::CLIENT_STREAMING_CALL => 'startClientStreamingCall',
+ Call::SERVER_STREAMING_CALL => 'startServerStreamingCall',
+ ];
+ private bool $backwardsCompatibilityMode;
+
+ /**
+ * Add a middleware to the call stack by providing a callable which will be
+ * invoked at the start of each call, and will return an instance of
+ * {@see MiddlewareInterface} when invoked.
+ *
+ * The callable must have the following method signature:
+ *
+ * callable(MiddlewareInterface): MiddlewareInterface
+ *
+ * An implementation may look something like this:
+ * ```
+ * $client->addMiddleware(function (MiddlewareInterface $handler) {
+ * return new class ($handler) implements MiddlewareInterface {
+ * public function __construct(private MiddlewareInterface $handler) {
+ * }
+ *
+ * public function __invoke(Call $call, array $options) {
+ * // modify call and options (pre-request)
+ * $response = ($this->handler)($call, $options);
+ * // modify the response (post-request)
+ * return $response;
+ * }
+ * };
+ * });
+ * ```
+ *
+ * @param callable $middlewareCallable A callable which returns an instance
+ * of {@see MiddlewareInterface} when invoked with a
+ * MiddlewareInterface instance as its first argument.
+ * @return void
+ */
+ public function addMiddleware(callable $middlewareCallable): void
+ {
+ $this->middlewareCallables[] = $middlewareCallable;
+ }
+
+ /**
+ * Prepend a middleware to the call stack by providing a callable which will be
+ * invoked at the end of each call, and will return an instance of
+ * {@see MiddlewareInterface} when invoked.
+ *
+ * The callable must have the following method signature:
+ *
+ * callable(MiddlewareInterface): MiddlewareInterface
+ *
+ * An implementation may look something like this:
+ * ```
+ * $client->prependMiddleware(function (MiddlewareInterface $handler) {
+ * return new class ($handler) implements MiddlewareInterface {
+ * public function __construct(private MiddlewareInterface $handler) {
+ * }
+ *
+ * public function __invoke(Call $call, array $options) {
+ * // modify call and options (pre-request)
+ * $response = ($this->handler)($call, $options);
+ * // modify the response (post-request)
+ * return $response;
+ * }
+ * };
+ * });
+ * ```
+ *
+ * @param callable $middlewareCallable A callable which returns an instance
+ * of {@see MiddlewareInterface} when invoked with a
+ * MiddlewareInterface instance as its first argument.
+ * @return void
+ */
+ public function prependMiddleware(callable $middlewareCallable): void
+ {
+ $this->prependMiddlewareCallables[] = $middlewareCallable;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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 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.
+ * @type callable $clientCertSource
+ * A callable which returns the client cert as a string.
+ * }
+ * @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);
+ }
+ self::validateNotNull($options, [
+ 'apiEndpoint',
+ 'serviceName',
+ 'descriptorsConfigPath',
+ 'clientConfig',
+ 'disableRetries',
+ 'credentialsConfig',
+ 'transportConfig',
+ ]);
+ self::traitValidate($options, [
+ 'credentials',
+ 'transport',
+ 'gapicVersion',
+ 'libName',
+ 'libVersion',
+ ]);
+
+ // "hasEmulator" is not a supported Client Option, but is used
+ // internally to determine if the client is running in emulator mode.
+ // Therefore, we need to remove it from the $options array before
+ // creating the ClientOptions.
+ $hasEmulator = $this->pluck('hasEmulator', $options, false) ?? false;
+ if ($this->isBackwardsCompatibilityMode()) {
+ if (is_string($options['clientConfig'])) {
+ // perform validation for V1 surfaces which is done in the
+ // ClientOptions class for v2 surfaces.
+ $options['clientConfig'] = json_decode(
+ file_get_contents($options['clientConfig']),
+ true
+ );
+ self::validateFileExists($options['descriptorsConfigPath']);
+ }
+ } else {
+ // cast to ClientOptions for new surfaces only
+ $options = new ClientOptions($options);
+ }
+ $this->serviceName = $options['serviceName'];
+ $this->retrySettings = RetrySettings::load(
+ $this->serviceName,
+ $options['clientConfig'],
+ $options['disableRetries']
+ );
+
+ $headerInfo = [
+ 'libName' => $options['libName'],
+ 'libVersion' => $options['libVersion'],
+ 'gapicVersion' => $options['gapicVersion'],
+ ];
+ // Edge case: If the client has the gRPC extension installed, but is
+ // a REST-only library, then the grpcVersion header should not be set.
+ if ($this->transport instanceof GrpcTransport) {
+ $headerInfo['grpcVersion'] = phpversion('grpc');
+ } elseif ($this->transport instanceof RestTransport
+ || $this->transport instanceof GrpcFallbackTransport) {
+ $headerInfo['restVersion'] = Version::getApiCoreVersion();
+ }
+ $this->agentHeader = AgentHeader::buildAgentHeader($headerInfo);
+
+ // Set "client_library_name" depending on client library surface being used
+ $userAgentHeader = sprintf(
+ 'gcloud-php-%s/%s',
+ $this->isBackwardsCompatibilityMode() ? 'legacy' : 'new',
+ $options['gapicVersion']
+ );
+ $this->agentHeader['User-Agent'] = [$userAgentHeader];
+
+ self::validateFileExists($options['descriptorsConfigPath']);
+
+ $descriptors = require($options['descriptorsConfigPath']);
+ $this->descriptors = $descriptors['interfaces'][$this->serviceName];
+
+ if (isset($options['apiKey'], $options['credentials'])) {
+ throw new ValidationException(
+ 'API Keys and Credentials are mutually exclusive authentication methods and cannot be used together.'
+ );
+ }
+ // Set the credentialsWrapper
+ if (isset($options['apiKey'])) {
+ $this->credentialsWrapper = new ApiKeyHeaderCredentials(
+ $options['apiKey'],
+ $options['credentialsConfig']['quotaProject'] ?? null
+ );
+ } else {
+ $this->credentialsWrapper = $this->createCredentialsWrapper(
+ $options['credentials'],
+ $options['credentialsConfig'],
+ $options['universeDomain']
+ );
+ }
+
+ $transport = $options['transport'] ?: self::defaultTransport();
+ $this->transport = $transport instanceof TransportInterface
+ ? $transport
+ : $this->createTransport(
+ $options['apiEndpoint'],
+ $transport,
+ $options['transportConfig'],
+ $options['clientCertSource'],
+ $hasEmulator
+ );
+ }
+
+ /**
+ * @param string $apiEndpoint
+ * @param string $transport
+ * @param TransportOptions|array $transportConfig
+ * @param callable $clientCertSource
+ * @param bool $hasEmulator
+ * @return TransportInterface
+ * @throws ValidationException
+ */
+ private function createTransport(
+ string $apiEndpoint,
+ $transport,
+ $transportConfig,
+ ?callable $clientCertSource = null,
+ bool $hasEmulator = false
+ ) {
+ 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 = $transportConfig[$transport] ?? [];
+ if (is_array($configForSpecifiedTransport)) {
+ $configForSpecifiedTransport['clientCertSource'] = $clientCertSource;
+ } else {
+ $configForSpecifiedTransport->setClientCertSource($clientCertSource);
+ $configForSpecifiedTransport = $configForSpecifiedTransport->toArray();
+ }
+ switch ($transport) {
+ case 'grpc':
+ // Setting the user agent for gRPC requires special handling
+ if (isset($this->agentHeader['User-Agent'])) {
+ if ($configForSpecifiedTransport['stubOpts']['grpc.primary_user_agent'] ??= '') {
+ $configForSpecifiedTransport['stubOpts']['grpc.primary_user_agent'] .= ' ';
+ }
+ $configForSpecifiedTransport['stubOpts']['grpc.primary_user_agent'] .=
+ $this->agentHeader['User-Agent'][0];
+ }
+ 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'];
+ $configForSpecifiedTransport['hasEmulator'] = $hasEmulator;
+
+ 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);
+
+ // User-supplied operations client
+ if ($operationsClient = $this->pluck('operationsClient', $options, false)) {
+ return $operationsClient;
+ }
+
+ // operationsClientClass option
+ $operationsClientClass = $this->pluck('operationsClientClass', $options, false)
+ ?: OperationsCLient::class;
+ return new $operationsClientClass($options);
+ }
+
+ /**
+ * @return string
+ */
+ private static function defaultTransport()
+ {
+ return self::getGrpcDependencyStatus()
+ ? 'grpc'
+ : 'rest';
+ }
+
+ private function validateCallConfig(string $methodName)
+ {
+ // Ensure a method descriptor exists for the target method.
+ if (!isset($this->descriptors[$methodName])) {
+ throw new ValidationException("Requested method '$methodName' does not exist in descriptor configuration.");
+ }
+ $methodDescriptors = $this->descriptors[$methodName];
+
+ // Ensure required descriptor configuration exists.
+ if (!isset($methodDescriptors['callType'])) {
+ throw new ValidationException("Requested method '$methodName' does not have a callType " .
+ 'in descriptor configuration.');
+ }
+ $callType = $methodDescriptors['callType'];
+
+ // Validate various callType specific configurations.
+ if ($callType == Call::LONGRUNNING_CALL) {
+ if (!isset($methodDescriptors['longRunning'])) {
+ throw new ValidationException("Requested method '$methodName' does not have a longRunning config " .
+ 'in descriptor configuration.');
+ }
+ // @TODO: check if the client implements `OperationsClientInterface` instead
+ if (!method_exists($this, 'getOperationsClient')) {
+ throw new ValidationException('Client missing required getOperationsClient ' .
+ "for longrunning call '$methodName'");
+ }
+ } elseif ($callType == Call::PAGINATED_CALL) {
+ if (!isset($methodDescriptors['pageStreaming'])) {
+ throw new ValidationException("Requested method '$methodName' with callType PAGINATED_CALL does not " .
+ 'have a pageStreaming in descriptor configuration.');
+ }
+ }
+
+ // LRO are either Standard LRO response type or custom, which are handled by
+ // startOperationCall, so no need to validate responseType for those callType.
+ if ($callType != Call::LONGRUNNING_CALL) {
+ if (!isset($methodDescriptors['responseType'])) {
+ throw new ValidationException("Requested method '$methodName' does not have a responseType " .
+ 'in descriptor configuration.');
+ }
+ }
+
+ return $methodDescriptors;
+ }
+
+ /**
+ * @param string $methodName
+ * @param Message $request
+ * @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.
+ * }
+ *
+ * @experimental
+ *
+ * @return PromiseInterface
+ */
+ private function startAsyncCall(
+ string $methodName,
+ Message $request,
+ array $optionalArgs = []
+ ) {
+ // Convert method name to the UpperCamelCase of RPC names from lowerCamelCase of GAPIC method names
+ // in order to find the method in the descriptor config.
+ $methodName = ucfirst($methodName);
+ $methodDescriptors = $this->validateCallConfig($methodName);
+
+ $callType = $methodDescriptors['callType'];
+
+ switch ($callType) {
+ case Call::PAGINATED_CALL:
+ return $this->getPagedListResponseAsync(
+ $methodName,
+ $optionalArgs,
+ $methodDescriptors['responseType'],
+ $request,
+ $methodDescriptors['interfaceOverride'] ?? $this->serviceName
+ );
+ case Call::SERVER_STREAMING_CALL:
+ case Call::CLIENT_STREAMING_CALL:
+ case Call::BIDI_STREAMING_CALL:
+ throw new ValidationException("Call type '$callType' of requested method " .
+ "'$methodName' is not supported for async execution.");
+ }
+
+ return $this->startApiCall($methodName, $request, $optionalArgs);
+ }
+
+ /**
+ * @param string $methodName
+ * @param Message $request
+ * @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.
+ * }
+ *
+ * @experimental
+ *
+ * @return PromiseInterface|PagedListResponse|BidiStream|ClientStream|ServerStream
+ */
+ private function startApiCall(
+ string $methodName,
+ ?Message $request = null,
+ array $optionalArgs = []
+ ) {
+ $methodDescriptors = $this->validateCallConfig($methodName);
+ $callType = $methodDescriptors['callType'];
+
+ // Prepare request-based headers, merge with user-provided headers,
+ // which take precedence.
+ $headerParams = $methodDescriptors['headerParams'] ?? [];
+ $requestHeaders = $this->buildRequestParamsHeader($headerParams, $request);
+ $optionalArgs['headers'] = array_merge($requestHeaders, $optionalArgs['headers'] ?? []);
+
+ // Default the interface name, if not set, to the client's protobuf service name.
+ $interfaceName = $methodDescriptors['interfaceOverride'] ?? $this->serviceName;
+
+ // Handle call based on call type configured in the method descriptor config.
+ if ($callType == Call::LONGRUNNING_CALL) {
+ return $this->startOperationsCall(
+ $methodName,
+ $optionalArgs,
+ $request,
+ $this->getOperationsClient(),
+ $interfaceName,
+ // Custom operations will define their own operation response type, whereas standard
+ // LRO defaults to the same type.
+ $methodDescriptors['responseType'] ?? null
+ );
+ }
+
+ // Fully-qualified name of the response message PHP class.
+ $decodeType = $methodDescriptors['responseType'];
+
+ if ($callType == Call::PAGINATED_CALL) {
+ return $this->getPagedListResponse($methodName, $optionalArgs, $decodeType, $request, $interfaceName);
+ }
+
+ // Unary, and all Streaming types handled by startCall.
+ return $this->startCall($methodName, $decodeType, $optionalArgs, $request, $callType, $interfaceName);
+ }
+
+ /**
+ * @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(
+ string $methodName,
+ string $decodeType,
+ array $optionalArgs = [],
+ ?Message $request = null,
+ int $callType = Call::UNARY_CALL,
+ ?string $interfaceName = null
+ ) {
+ $optionalArgs = $this->configureCallOptions($optionalArgs);
+ $callStack = $this->createCallStack(
+ $this->configureCallConstructionOptions($methodName, $optionalArgs)
+ );
+
+ $descriptor = $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.
+ * @type array $autoPopulationSettings Settings for
+ * auto population of particular request fields if unset.
+ * }
+ *
+ * @return callable
+ */
+ private function createCallStack(array $callConstructionOptions)
+ {
+ $fixedHeaders = $this->agentHeader;
+ if ($quotaProject = $this->credentialsWrapper->getQuotaProject()) {
+ $fixedHeaders += [
+ 'X-Goog-User-Project' => [$quotaProject]
+ ];
+ }
+
+ if (isset($this->apiVersion)) {
+ $fixedHeaders += [
+ 'X-Goog-Api-Version' => [$this->apiVersion]
+ ];
+ }
+
+ $callStack = new TransportCallMiddleware($this->transport, $this->transportCallMethods);
+
+ foreach ($this->prependMiddlewareCallables as $fn) {
+ /** @var MiddlewareInterface $callStack */
+ $callStack = $fn($callStack);
+ }
+
+ $callStack = new CredentialsWrapperMiddleware($callStack, $this->credentialsWrapper);
+ $callStack = new FixedHeaderMiddleware($callStack, $fixedHeaders, true);
+ $callStack = new RetryMiddleware($callStack, $callConstructionOptions['retrySettings']);
+ $callStack = new RequestAutoPopulationMiddleware(
+ $callStack,
+ $callConstructionOptions['autoPopulationSettings'],
+ );
+ $callStack = new OptionsFilterMiddleware($callStack, [
+ 'headers',
+ 'timeoutMillis',
+ 'transportOptions',
+ 'metadataCallback',
+ 'audience',
+ 'metadataReturnType'
+ ]);
+
+ foreach (\array_reverse($this->middlewareCallables) as $fn) {
+ /** @var MiddlewareInterface $callStack */
+ $callStack = $fn($callStack);
+ }
+
+ 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(string $methodName, array $optionalArgs)
+ {
+ $retrySettings = $this->retrySettings[$methodName];
+ $autoPopulatedFields = $this->descriptors[$methodName]['autoPopulatedFields'] ?? [];
+ // 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,
+ 'autoPopulationSettings' => $autoPopulatedFields,
+ ];
+ }
+
+ /**
+ * @return array
+ */
+ private function configureCallOptions(array $optionalArgs): array
+ {
+ if ($this->isBackwardsCompatibilityMode()) {
+ return $optionalArgs;
+ }
+ // cast to CallOptions for new surfaces only
+ return (new CallOptions($optionalArgs))->toArray();
+ }
+
+ /**
+ * @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|object $client
+ * @param string $interfaceName
+ * @param string $operationClass If provided, will be used instead of the default
+ * operation response class of {@see \Google\LongRunning\Operation}.
+ *
+ * @return PromiseInterface
+ */
+ private function startOperationsCall(
+ string $methodName,
+ array $optionalArgs,
+ Message $request,
+ $client,
+ ?string $interfaceName = null,
+ ?string $operationClass = null
+ ) {
+ $optionalArgs = $this->configureCallOptions($optionalArgs);
+ $callStack = $this->createCallStack(
+ $this->configureCallConstructionOptions($methodName, $optionalArgs)
+ );
+ $descriptor = $this->descriptors[$methodName]['longRunning'];
+ $metadataReturnType = null;
+
+ // Call the methods supplied in "additionalArgumentMethods" on the request Message object
+ // to build the "additionalOperationArguments" option for the operation response.
+ if (isset($descriptor['additionalArgumentMethods'])) {
+ $additionalArgs = [];
+ foreach ($descriptor['additionalArgumentMethods'] as $additionalArgsMethodName) {
+ $additionalArgs[] = $request->$additionalArgsMethodName();
+ }
+ $descriptor['additionalOperationArguments'] = $additionalArgs;
+ unset($descriptor['additionalArgumentMethods']);
+ }
+
+ if (isset($descriptor['metadataReturnType'])) {
+ $metadataReturnType = $descriptor['metadataReturnType'];
+ }
+
+ $callStack = new OperationsMiddleware($callStack, $client, $descriptor);
+
+ $call = new Call(
+ $this->buildMethod($interfaceName, $methodName),
+ $operationClass ?: Operation::class,
+ $request,
+ [],
+ Call::UNARY_CALL
+ );
+
+ $this->modifyUnaryCallable($callStack);
+ return $callStack($call, $optionalArgs + array_filter([
+ 'metadataReturnType' => $metadataReturnType,
+ 'audience' => self::getDefaultAudience()
+ ]));
+ }
+
+ /**
+ * @param string $methodName
+ * @param array $optionalArgs
+ * @param string $decodeType
+ * @param Message $request
+ * @param string $interfaceName
+ *
+ * @return PagedListResponse
+ */
+ private function getPagedListResponse(
+ string $methodName,
+ array $optionalArgs,
+ string $decodeType,
+ Message $request,
+ ?string $interfaceName = null
+ ) {
+ return $this->getPagedListResponseAsync(
+ $methodName,
+ $optionalArgs,
+ $decodeType,
+ $request,
+ $interfaceName
+ )->wait();
+ }
+
+ /**
+ * @param string $methodName
+ * @param array $optionalArgs
+ * @param string $decodeType
+ * @param Message $request
+ * @param string $interfaceName
+ *
+ * @return PromiseInterface
+ */
+ private function getPagedListResponseAsync(
+ string $methodName,
+ array $optionalArgs,
+ string $decodeType,
+ Message $request,
+ ?string $interfaceName = null
+ ) {
+ $optionalArgs = $this->configureCallOptions($optionalArgs);
+ $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()
+ ]));
+ }
+
+ /**
+ * @param string $interfaceName
+ * @param string $methodName
+ *
+ * @return string
+ */
+ private function buildMethod(?string $interfaceName = null, ?string $methodName = null)
+ {
+ return sprintf(
+ '%s/%s',
+ $interfaceName ?: $this->serviceName,
+ $methodName
+ );
+ }
+
+ /**
+ * @param array $headerParams
+ * @param Message|null $request
+ *
+ * @return array
+ */
+ private function buildRequestParamsHeader(array $headerParams, ?Message $request = null)
+ {
+ $headers = [];
+
+ // No request message means no request-based headers.
+ if (!$request) {
+ return $headers;
+ }
+
+ foreach ($headerParams as $headerParam) {
+ $msg = $request;
+ $value = null;
+ foreach ($headerParam['fieldAccessors'] as $accessor) {
+ $value = $msg->$accessor();
+
+ // In case the field in question is nested in another message,
+ // skip the header param when the nested message field is unset.
+ $msg = $value;
+ if (is_null($msg)) {
+ break;
+ }
+ }
+
+ $keyName = $headerParam['keyName'];
+
+ // If there are value pattern matchers configured and the target
+ // field was set, evaluate the matchers in the order that they were
+ // annotated in with last one matching wins.
+ $original = $value;
+ $matchers = isset($headerParam['matchers']) && !is_null($value) ?
+ $headerParam['matchers'] :
+ [];
+ foreach ($matchers as $matcher) {
+ $matches = [];
+ if (preg_match($matcher, $original, $matches)) {
+ $value = $matches[$keyName];
+ }
+ }
+
+ // If there are no matches or the target field was unset, skip this
+ // header param.
+ if (!$value) {
+ continue;
+ }
+
+ $headers[$keyName] = $value;
+ }
+
+ $requestParams = new RequestParamsHeaderDescriptor($headers);
+
+ return $requestParams->getHeader();
+ }
+
+ /**
+ * 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 . '/'; // @phpstan-ignore-line
+ }
+
+ /**
+ * 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.
+ }
+
+ /**
+ * @internal
+ */
+ private function isBackwardsCompatibilityMode(): bool
+ {
+ return $this->backwardsCompatibilityMode
+ ?? $this->backwardsCompatibilityMode = substr(__CLASS__, -11) === 'GapicClient';
+ }
+}
diff --git a/Gax/src/GrpcSupportTrait.php b/Gax/src/GrpcSupportTrait.php
new file mode 100644
index 000000000000..85251eeeebc4
--- /dev/null
+++ b/Gax/src/GrpcSupportTrait.php
@@ -0,0 +1,63 @@
+baseUri,
+ $path
+ ));
+ if ($queryParams) {
+ $uri = $this->buildUriWithQuery(
+ $uri,
+ $queryParams
+ );
+ }
+ return $uri;
+ }
+}
diff --git a/Gax/src/KnownTypes.php b/Gax/src/KnownTypes.php
new file mode 100644
index 000000000000..8d55402d4650
--- /dev/null
+++ b/Gax/src/KnownTypes.php
@@ -0,0 +1,87 @@
+ \Google\Rpc\RetryInfo::class,
+ 'google.rpc.debuginfo-bin' => \Google\Rpc\DebugInfo::class,
+ 'google.rpc.quotafailure-bin' => \Google\Rpc\QuotaFailure::class,
+ 'google.rpc.badrequest-bin' => \Google\Rpc\BadRequest::class,
+ 'google.rpc.requestinfo-bin' => \Google\Rpc\RequestInfo::class,
+ 'google.rpc.resourceinfo-bin' => \Google\Rpc\ResourceInfo::class,
+ 'google.rpc.errorinfo-bin' => \Google\Rpc\ErrorInfo::class,
+ 'google.rpc.help-bin' => \Google\Rpc\Help::class,
+ 'google.rpc.localizedmessage-bin' => \Google\Rpc\LocalizedMessage::class,
+ 'google.rpc.preconditionfailure-bin' => \Google\Rpc\PreconditionFailure::class,
+ ];
+
+ public const TYPE_URLS = [
+ 'type.googleapis.com/google.rpc.RetryInfo' => \Google\Rpc\RetryInfo::class,
+ 'type.googleapis.com/google.rpc.DebugInfo' => \Google\Rpc\DebugInfo::class,
+ 'type.googleapis.com/google.rpc.QuotaFailure' => \Google\Rpc\QuotaFailure::class,
+ 'type.googleapis.com/google.rpc.BadRequest' => \Google\Rpc\BadRequest::class,
+ 'type.googleapis.com/google.rpc.RequestInfo' => \Google\Rpc\RequestInfo::class,
+ 'type.googleapis.com/google.rpc.ResourceInfo' => \Google\Rpc\ResourceInfo::class,
+ 'type.googleapis.com/google.rpc.ErrorInfo' => \Google\Rpc\ErrorInfo::class,
+ 'type.googleapis.com/google.rpc.Help' => \Google\Rpc\Help::class,
+ 'type.googleapis.com/google.rpc.LocalizedMessage' => \Google\Rpc\LocalizedMessage::class,
+ 'type.googleapis.com/google.rpc.PreconditionFailure' => \Google\Rpc\PreconditionFailure::class,
+ ];
+
+ public static function allKnownTypes(): array
+ {
+ return array_values(self::TYPE_URLS);
+ }
+
+ public static function addKnownTypesToDescriptorPool()
+ {
+ if (self::$initialized) {
+ return;
+ }
+
+ // adds all the above protobuf classes to the descriptor pool
+ \GPBMetadata\Google\Rpc\ErrorDetails::initOnce();
+ self::$initialized = true;
+ }
+}
diff --git a/Gax/src/Middleware/CredentialsWrapperMiddleware.php b/Gax/src/Middleware/CredentialsWrapperMiddleware.php
new file mode 100644
index 000000000000..086df0ef0252
--- /dev/null
+++ b/Gax/src/Middleware/CredentialsWrapperMiddleware.php
@@ -0,0 +1,67 @@
+nextHandler = $nextHandler;
+ $this->credentialsWrapper = $credentialsWrapper;
+ }
+
+ public function __invoke(Call $call, array $options)
+ {
+ $next = $this->nextHandler;
+ return $next(
+ $call,
+ $options + ['credentialsWrapper' => $this->credentialsWrapper]
+ );
+ }
+}
diff --git a/Gax/src/Middleware/FixedHeaderMiddleware.php b/Gax/src/Middleware/FixedHeaderMiddleware.php
new file mode 100644
index 000000000000..f3204b5483ce
--- /dev/null
+++ b/Gax/src/Middleware/FixedHeaderMiddleware.php
@@ -0,0 +1,74 @@
+nextHandler = $nextHandler;
+ $this->headers = $headers;
+ $this->overrideUserHeaders = $overrideUserHeaders;
+ }
+
+ public function __invoke(Call $call, array $options)
+ {
+ $userHeaders = $options['headers'] ?? [];
+ if ($this->overrideUserHeaders) {
+ $options['headers'] = $this->headers + $userHeaders;
+ } else {
+ $options['headers'] = $userHeaders + $this->headers;
+ }
+
+ $next = $this->nextHandler;
+ return $next(
+ $call,
+ $options
+ );
+ }
+}
diff --git a/Gax/src/Middleware/MiddlewareInterface.php b/Gax/src/Middleware/MiddlewareInterface.php
new file mode 100644
index 000000000000..224006ffe351
--- /dev/null
+++ b/Gax/src/Middleware/MiddlewareInterface.php
@@ -0,0 +1,91 @@
+handler = $handler;
+ * }
+ * public function __invoke(Call $call, array $options)
+ * {
+ * echo "Logging info about the call: " . $call->getMethod();
+ * return ($this->handler)($call, $options);
+ * }
+ * }
+ * ```
+ *
+ * Next, add the middleware to any class implementing `GapicClientTrait` by passing in a
+ * callable which returns the new middleware:
+ *
+ * ```
+ * $client = new ExampleGoogleApiServiceClient();
+ * $client->addMiddleware(function (MiddlewareInterface $handler) {
+ * return new MyTestMiddleware($handler);
+ * });
+ * ```
+ */
+interface MiddlewareInterface
+{
+ /**
+ * Modify or observe the API call request and response.
+ * The returned value must include the result of the next MiddlewareInterface invocation in the
+ * chain.
+ *
+ * @param Call $call
+ * @param array $options
+ * @return PromiseInterface|ClientStream|ServerStream|BidiStream
+ */
+ public function __invoke(Call $call, array $options);
+}
diff --git a/Gax/src/Middleware/OperationsMiddleware.php b/Gax/src/Middleware/OperationsMiddleware.php
new file mode 100644
index 000000000000..e7f9baca6985
--- /dev/null
+++ b/Gax/src/Middleware/OperationsMiddleware.php
@@ -0,0 +1,75 @@
+nextHandler = $nextHandler;
+ $this->operationsClient = $operationsClient;
+ $this->descriptor = $descriptor;
+ }
+
+ public function __invoke(Call $call, array $options)
+ {
+ $next = $this->nextHandler;
+ return $next(
+ $call,
+ $options
+ )->then(function (Message $response) {
+ $options = $this->descriptor + [
+ 'lastProtoResponse' => $response
+ ];
+ $operationNameMethod = $options['operationNameMethod'] ?? 'getName';
+ $operationName = call_user_func([$response, $operationNameMethod]);
+ return new OperationResponse($operationName, $this->operationsClient, $options);
+ });
+ }
+}
diff --git a/Gax/src/Middleware/OptionsFilterMiddleware.php b/Gax/src/Middleware/OptionsFilterMiddleware.php
new file mode 100644
index 000000000000..d5027fe4b948
--- /dev/null
+++ b/Gax/src/Middleware/OptionsFilterMiddleware.php
@@ -0,0 +1,67 @@
+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
+ );
+ }
+}
diff --git a/Gax/src/Middleware/PagedMiddleware.php b/Gax/src/Middleware/PagedMiddleware.php
new file mode 100644
index 000000000000..b396281e94cd
--- /dev/null
+++ b/Gax/src/Middleware/PagedMiddleware.php
@@ -0,0 +1,80 @@
+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);
+ }
+ );
+ }
+}
diff --git a/Gax/src/Middleware/RequestAutoPopulationMiddleware.php b/Gax/src/Middleware/RequestAutoPopulationMiddleware.php
new file mode 100644
index 000000000000..4811664eae44
--- /dev/null
+++ b/Gax/src/Middleware/RequestAutoPopulationMiddleware.php
@@ -0,0 +1,104 @@
+ */
+ private $autoPopulationSettings;
+
+ public function __construct(
+ callable $nextHandler,
+ array $autoPopulationSettings
+ ) {
+ $this->nextHandler = $nextHandler;
+ $this->autoPopulationSettings = $autoPopulationSettings;
+ }
+
+ /**
+ * @param Call $call
+ * @param array $options
+ *
+ * @return PromiseInterface
+ */
+ public function __invoke(Call $call, array $options)
+ {
+ $next = $this->nextHandler;
+
+ if (empty($this->autoPopulationSettings)) {
+ return $next($call, $options);
+ }
+
+ $request = $call->getMessage();
+ foreach ($this->autoPopulationSettings as $fieldName => $valueType) {
+ $getFieldName = 'get' . ucwords($fieldName);
+ // We use a getter instead of a hazzer here because there's no need to
+ // differentiate between isset and an empty default value. Even if a
+ // field is explicitly set to an empty string, we want to autopopulate it.
+ if (empty($request->$getFieldName())) {
+ $setFieldName = 'set' . ucwords($fieldName);
+ switch ($valueType) {
+ case Format::UUID4:
+ $request->$setFieldName(Uuid::uuid4()->toString());
+ break;
+ default:
+ throw new \UnexpectedValueException(sprintf(
+ 'Value type %s::%s not supported for auto population of the field %s',
+ Format::class,
+ Format::name($valueType),
+ $fieldName
+ ));
+ }
+ }
+ }
+ $call = $call->withMessage($request);
+ return $next(
+ $call,
+ $options
+ );
+ }
+}
diff --git a/Gax/src/Middleware/ResponseMetadataMiddleware.php b/Gax/src/Middleware/ResponseMetadataMiddleware.php
new file mode 100644
index 000000000000..c9dbdef218dd
--- /dev/null
+++ b/Gax/src/Middleware/ResponseMetadataMiddleware.php
@@ -0,0 +1,73 @@
+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, []];
+ }
+ }
+ );
+ }
+}
diff --git a/Gax/src/Middleware/RetryMiddleware.php b/Gax/src/Middleware/RetryMiddleware.php
new file mode 100644
index 000000000000..a86e120d63e0
--- /dev/null
+++ b/Gax/src/Middleware/RetryMiddleware.php
@@ -0,0 +1,201 @@
+nextHandler = $nextHandler;
+ $this->retrySettings = $retrySettings;
+ $this->deadlineMs = $deadlineMs;
+ $this->retryAttempts = $retryAttempts;
+ }
+
+ /**
+ * @param Call $call
+ * @param array $options
+ *
+ * @return PromiseInterface
+ */
+ public function __invoke(Call $call, array $options)
+ {
+ $nextHandler = $this->nextHandler;
+
+ if (!isset($options['timeoutMillis'])) {
+ // default to "noRetriesRpcTimeoutMillis" when retries are disabled, otherwise use "initialRpcTimeoutMillis"
+ if (!$this->retrySettings->retriesEnabled() && $this->retrySettings->getNoRetriesRpcTimeoutMillis() > 0) {
+ $options['timeoutMillis'] = $this->retrySettings->getNoRetriesRpcTimeoutMillis();
+ } elseif ($this->retrySettings->getInitialRpcTimeoutMillis() > 0) {
+ $options['timeoutMillis'] = $this->retrySettings->getInitialRpcTimeoutMillis();
+ }
+ }
+
+ // Setting the retry attempt for logging
+ if ($this->retryAttempts > 0) {
+ $options['retryAttempt'] = $this->retryAttempts;
+ }
+
+ // Call the handler immediately if retry settings are disabled.
+ if (!$this->retrySettings->retriesEnabled()) {
+ return $nextHandler($call, $options);
+ }
+
+ return $nextHandler($call, $options)->then(null, function ($e) use ($call, $options) {
+ $retryFunction = $this->getRetryFunction();
+
+ // If the number of retries has surpassed the max allowed retries
+ // then throw the exception as we normally would.
+ // If the maxRetries is set to 0, then we don't check this condition.
+ if (0 !== $this->retrySettings->getMaxRetries()
+ && $this->retryAttempts >= $this->retrySettings->getMaxRetries()
+ ) {
+ throw $e;
+ }
+ // If the retry function returns false then throw the
+ // exception as we normally would.
+ if (!$retryFunction($e, $options)) {
+ throw $e;
+ }
+
+ // Retry function returned true, so we attempt another retry
+ return $this->retry($call, $options, $e->getStatus());
+ });
+ }
+
+ /**
+ * @param Call $call
+ * @param array $options
+ * @param string $status
+ *
+ * @return PromiseInterface
+ * @throws ApiException
+ */
+ private function retry(Call $call, array $options, string $status)
+ {
+ $delayMult = $this->retrySettings->getRetryDelayMultiplier();
+ $maxDelayMs = $this->retrySettings->getMaxRetryDelayMillis();
+ $timeoutMult = $this->retrySettings->getRpcTimeoutMultiplier();
+ $maxTimeoutMs = $this->retrySettings->getMaxRpcTimeoutMillis();
+ $totalTimeoutMs = $this->retrySettings->getTotalTimeoutMillis();
+
+ $delayMs = $this->retrySettings->getInitialRetryDelayMillis();
+ $timeoutMs = $options['timeoutMillis'];
+ $currentTimeMs = $this->getCurrentTimeMs();
+ $deadlineMs = $this->deadlineMs ?: $currentTimeMs + $totalTimeoutMs;
+
+ if ($currentTimeMs >= $deadlineMs) {
+ throw new ApiException(
+ 'Retry total timeout exceeded.',
+ \Google\Rpc\Code::DEADLINE_EXCEEDED,
+ ApiStatus::DEADLINE_EXCEEDED
+ );
+ }
+
+ $delayMs = min($delayMs * $delayMult, $maxDelayMs);
+ $timeoutMs = (int) min(
+ $timeoutMs * $timeoutMult,
+ $maxTimeoutMs,
+ $deadlineMs - $this->getCurrentTimeMs()
+ );
+
+ $nextHandler = new RetryMiddleware(
+ $this->nextHandler,
+ $this->retrySettings->with([
+ 'initialRetryDelayMillis' => $delayMs,
+ ]),
+ $deadlineMs,
+ $this->retryAttempts + 1
+ );
+
+ // Set the timeout for the call
+ $options['timeoutMillis'] = $timeoutMs;
+
+ return $nextHandler(
+ $call,
+ $options
+ );
+ }
+
+ protected function getCurrentTimeMs()
+ {
+ return microtime(true) * 1000.0;
+ }
+
+ /**
+ * This is the default retry behaviour.
+ */
+ private function getRetryFunction()
+ {
+ return $this->retrySettings->getRetryFunction() ??
+ function (\Throwable $e, array $options): bool {
+ // This is the default retry behaviour, i.e. we don't retry an ApiException
+ // and for other exception types, we only retry when the error code is in
+ // the list of retryable error codes.
+ if (!$e instanceof ApiException) {
+ return false;
+ }
+
+ if (!in_array($e->getStatus(), $this->retrySettings->getRetryableCodes())) {
+ return false;
+ }
+
+ return true;
+ };
+ }
+}
diff --git a/Gax/src/Middleware/TransportCallMiddleware.php b/Gax/src/Middleware/TransportCallMiddleware.php
new file mode 100644
index 000000000000..1306f78e11d0
--- /dev/null
+++ b/Gax/src/Middleware/TransportCallMiddleware.php
@@ -0,0 +1,60 @@
+transportCallMethods[$call->getCallType()];
+ return $this->transport->$startCallMethod($call, $options);
+ }
+}
diff --git a/Gax/src/OperationResponse.php b/Gax/src/OperationResponse.php
new file mode 100644
index 000000000000..9dfeb0a73129
--- /dev/null
+++ b/Gax/src/OperationResponse.php
@@ -0,0 +1,539 @@
+ self::DEFAULT_POLLING_INTERVAL,
+ 'pollDelayMultiplier' => self::DEFAULT_POLLING_MULTIPLIER,
+ 'maxPollDelayMillis' => self::DEFAULT_MAX_POLLING_INTERVAL,
+ 'totalPollTimeoutMillis' => self::DEFAULT_MAX_POLLING_DURATION,
+ ];
+
+ private ?object $lastProtoResponse;
+ private bool $deleted = false;
+
+ private array $additionalArgs;
+ private string $getOperationMethod;
+ private ?string $cancelOperationMethod;
+ private ?string $deleteOperationMethod;
+ private string $getOperationRequest;
+ private ?string $cancelOperationRequest;
+ private ?string $deleteOperationRequest;
+ private string $operationStatusMethod;
+ /** @var mixed */
+ private $operationStatusDoneValue;
+ private ?string $operationErrorCodeMethod;
+ private ?string $operationErrorMessageMethod;
+
+ /**
+ * OperationResponse constructor.
+ *
+ * @param string $operationName
+ * @param object|null $operationsClient
+ * @param array $options {
+ * Optional. Options for configuring the operation response object.
+ *
+ * @type string $operationReturnType The return type of the longrunning operation.
+ * @type string $metadataReturnType The type of the metadata returned in the operation response.
+ * @type int $initialPollDelayMillis The initial polling interval to use, in milliseconds.
+ * @type int $pollDelayMultiplier Multiplier applied to the polling interval on each retry.
+ * @type int $maxPollDelayMillis The maximum polling interval to use, in milliseconds.
+ * @type int $totalPollTimeoutMillis The maximum amount of time to continue polling.
+ * @type object $lastProtoResponse A response already received from the server.
+ * @type string $getOperationMethod The method on $operationsClient to get the operation.
+ * @type string $cancelOperationMethod The method on $operationsClient to cancel the operation.
+ * @type string $deleteOperationMethod The method on $operationsClient to delete the operation.
+ * @type string $operationStatusMethod The method on the operation to get the status.
+ * @type string $operationStatusDoneValue The method on the operation to determine if the status is done.
+ * @type array $additionalOperationArguments Additional arguments to pass to $operationsClient methods.
+ * @type string $operationErrorCodeMethod The method on the operation to get the error code
+ * @type string $operationErrorMessageMethod The method on the operation to get the error status
+ * }
+ */
+ public function __construct(string $operationName, $operationsClient, array $options = [])
+ {
+ $this->operationName = $operationName;
+ $this->operationsClient = $operationsClient;
+ $options += [
+ 'operationReturnType' => null,
+ 'metadataReturnType' => null,
+ 'lastProtoResponse' => null,
+ 'getOperationMethod' => 'getOperation',
+ 'cancelOperationMethod' => 'cancelOperation',
+ 'deleteOperationMethod' => 'deleteOperation',
+ 'operationStatusMethod' => 'getDone',
+ 'operationStatusDoneValue' => true,
+ 'additionalOperationArguments' => [],
+ 'operationErrorCodeMethod' => null,
+ 'operationErrorMessageMethod' => null,
+ 'getOperationRequest' => GetOperationRequest::class,
+ 'cancelOperationRequest' => CancelOperationRequest::class,
+ 'deleteOperationRequest' => DeleteOperationRequest::class,
+ ];
+ $this->operationReturnType = $options['operationReturnType'];
+ $this->metadataReturnType = $options['metadataReturnType'];
+ $this->lastProtoResponse = $options['lastProtoResponse'];
+ $this->getOperationMethod = $options['getOperationMethod'];
+ $this->cancelOperationMethod = $options['cancelOperationMethod'];
+ $this->deleteOperationMethod = $options['deleteOperationMethod'];
+ $this->additionalArgs = $options['additionalOperationArguments'];
+ $this->operationStatusMethod = $options['operationStatusMethod'];
+ $this->operationStatusDoneValue = $options['operationStatusDoneValue'];
+ $this->operationErrorCodeMethod = $options['operationErrorCodeMethod'];
+ $this->operationErrorMessageMethod = $options['operationErrorMessageMethod'];
+ $this->getOperationRequest = $options['getOperationRequest'];
+ $this->cancelOperationRequest = $options['cancelOperationRequest'];
+ $this->deleteOperationRequest = $options['deleteOperationRequest'];
+
+ if (isset($options['initialPollDelayMillis'])) {
+ $this->defaultPollSettings['initialPollDelayMillis'] = $options['initialPollDelayMillis'];
+ }
+ if (isset($options['pollDelayMultiplier'])) {
+ $this->defaultPollSettings['pollDelayMultiplier'] = $options['pollDelayMultiplier'];
+ }
+ if (isset($options['maxPollDelayMillis'])) {
+ $this->defaultPollSettings['maxPollDelayMillis'] = $options['maxPollDelayMillis'];
+ }
+ if (isset($options['totalPollTimeoutMillis'])) {
+ $this->defaultPollSettings['totalPollTimeoutMillis'] = $options['totalPollTimeoutMillis'];
+ }
+ }
+
+ /**
+ * Check whether the operation has completed.
+ *
+ * @return bool
+ */
+ public function isDone()
+ {
+ if (!$this->hasProtoResponse()) {
+ return false;
+ }
+
+ $status = call_user_func([$this->lastProtoResponse, $this->operationStatusMethod]);
+ if (is_null($status)) {
+ return false;
+ }
+
+ return $status === $this->operationStatusDoneValue;
+ }
+
+ /**
+ * Check whether the operation completed successfully. If the operation is not complete, or if the operation
+ * failed, return false.
+ *
+ * @return bool
+ */
+ public function operationSucceeded()
+ {
+ if (!$this->hasProtoResponse()) {
+ return false;
+ }
+
+ if (!$this->canHaveResult()) {
+ // For Operations which do not have a result, we consider a successful
+ // operation when the operation has completed without errors.
+ return $this->isDone() && !$this->hasErrors();
+ }
+
+ return !is_null($this->getResult());
+ }
+
+ /**
+ * Check whether the operation failed. If the operation is not complete, or if the operation
+ * succeeded, return false.
+ *
+ * @return bool
+ */
+ public function operationFailed()
+ {
+ return $this->hasErrors();
+ }
+
+ /**
+ * Get the formatted name of the operation
+ *
+ * @return string The formatted name of the operation
+ */
+ public function getName()
+ {
+ return $this->operationName;
+ }
+
+ /**
+ * Poll the server in a loop until the operation is complete.
+ *
+ * Return true if the operation completed, otherwise return false. If the
+ * $options['totalPollTimeoutMillis'] setting is not set (or set <= 0) then
+ * pollUntilComplete will continue polling until the operation completes,
+ * and therefore will always return true.
+ *
+ * @param array $options {
+ * Options for configuring the polling behaviour.
+ *
+ * @type int $initialPollDelayMillis The initial polling interval to use, in milliseconds.
+ * @type int $pollDelayMultiplier Multiplier applied to the polling interval on each retry.
+ * @type int $maxPollDelayMillis The maximum polling interval to use, in milliseconds.
+ * @type int $totalPollTimeoutMillis The maximum amount of time to continue polling, in milliseconds.
+ * }
+ * @throws ApiException If an API call fails.
+ * @throws ValidationException
+ * @return bool Indicates if the operation completed.
+ */
+ public function pollUntilComplete(array $options = [])
+ {
+ if ($this->isDone()) {
+ return true;
+ }
+
+ $pollSettings = array_merge($this->defaultPollSettings, $options);
+ return $this->poll(function () {
+ $this->reload();
+ return $this->isDone();
+ }, $pollSettings);
+ }
+
+ /**
+ * Reload the status of the operation with a request to the service.
+ *
+ * @throws ApiException If the API call fails.
+ * @throws ValidationException If called on a deleted operation.
+ */
+ public function reload()
+ {
+ if ($this->deleted) {
+ throw new ValidationException('Cannot call reload() on a deleted operation');
+ }
+
+ $requestClass = $this->isNewSurfaceOperationsClient() ? $this->getOperationRequest : null;
+ $this->lastProtoResponse = $this->operationsCall($this->getOperationMethod, $requestClass);
+ }
+
+ /**
+ * Return the result of the operation. If operationSucceeded() is false,
+ * return null.
+ *
+ * @return T|null
+ */
+ public function getResult()
+ {
+ if (!$this->hasProtoResponse()) {
+ return null;
+ }
+
+ if (!$this->canHaveResult()) {
+ return null;
+ }
+
+ if (!$this->isDone()) {
+ return null;
+ }
+
+ /** @var Any|null $anyResponse */
+ $anyResponse = $this->lastProtoResponse->getResponse();
+ if (is_null($anyResponse)) {
+ return null;
+ }
+ if (is_null($this->operationReturnType)) {
+ return $anyResponse;
+ }
+ $operationReturnType = $this->operationReturnType;
+ /** @var Message $response */
+ $response = new $operationReturnType();
+ $response->mergeFromString($anyResponse->getValue());
+ return $response;
+ }
+
+ /**
+ * If the operation failed, return the status. If operationFailed() is false, return null.
+ *
+ * @return Status|null The status of the operation in case of failure, or null if
+ * operationFailed() is false.
+ */
+ public function getError()
+ {
+ if (!$this->hasProtoResponse() || !$this->isDone()) {
+ return null;
+ }
+
+ if ($this->operationErrorCodeMethod || $this->operationErrorMessageMethod) {
+ $errorCode = $this->operationErrorCodeMethod
+ ? call_user_func([$this->lastProtoResponse, $this->operationErrorCodeMethod])
+ : null;
+ $errorMessage = $this->operationErrorMessageMethod
+ ? call_user_func([$this->lastProtoResponse, $this->operationErrorMessageMethod])
+ : null;
+ return (new Status())
+ ->setCode(ApiStatus::rpcCodeFromHttpStatusCode($errorCode))
+ ->setMessage($errorMessage);
+ }
+
+ if (method_exists($this->lastProtoResponse, 'getError')) {
+ return $this->lastProtoResponse->getError();
+ }
+
+ return null;
+ }
+
+ /**
+ * Get an array containing the values of 'operationReturnType', 'metadataReturnType', and
+ * the polling options `initialPollDelayMillis`, `pollDelayMultiplier`, `maxPollDelayMillis`,
+ * and `totalPollTimeoutMillis`. The array can be passed as the $options argument to the
+ * constructor when creating another OperationResponse object.
+ *
+ * @return array
+ */
+ public function getDescriptorOptions()
+ {
+ return [
+ 'operationReturnType' => $this->operationReturnType,
+ 'metadataReturnType' => $this->metadataReturnType,
+ ] + $this->defaultPollSettings;
+ }
+
+ /**
+ * @return Operation|mixed|null The last Operation object received from the server.
+ */
+ public function getLastProtoResponse()
+ {
+ return $this->lastProtoResponse;
+ }
+
+ /**
+ * @return object The OperationsClient object used to make
+ * requests to the operations API.
+ */
+ public function getOperationsClient()
+ {
+ return $this->operationsClient;
+ }
+
+ /**
+ * Cancel the long-running operation.
+ *
+ * For operations of type Google\LongRunning\Operation, this method starts
+ * asynchronous cancellation on a long-running operation. The server
+ * makes a best effort to cancel the operation, but success is not
+ * guaranteed. If the server doesn't support this method, it will throw an
+ * ApiException with code \Google\Rpc\Code::UNIMPLEMENTED. Clients can continue
+ * to use reload and pollUntilComplete methods to check whether the cancellation
+ * succeeded or whether the operation completed despite cancellation.
+ * On successful cancellation, the operation is not deleted; instead, it becomes
+ * an operation with a getError() value with a \Google\Rpc\Status code of 1,
+ * corresponding to \Google\Rpc\Code::CANCELLED.
+ *
+ * @throws ApiException If the API call fails.
+ * @throws LogicException If the API call method has not been configured
+ */
+ public function cancel()
+ {
+ if (is_null($this->cancelOperationMethod)) {
+ throw new LogicException('The cancel operation is not supported by this API');
+ }
+
+ $requestClass = $this->isNewSurfaceOperationsClient() ? $this->cancelOperationRequest : null;
+ $this->operationsCall($this->cancelOperationMethod, $requestClass);
+ }
+
+ /**
+ * Delete the long-running operation.
+ *
+ * For operations of type Google\LongRunning\Operation, this method
+ * indicates that the client is no longer interested in the operation result.
+ * It does not cancel the operation. If the server doesn't support this method,
+ * it will throw an ApiException with code \Google\Rpc\Code::UNIMPLEMENTED.
+ *
+ * @throws ApiException If the API call fails.
+ * @throws LogicException If the API call method has not been configured
+ */
+ public function delete()
+ {
+ if (is_null($this->deleteOperationMethod)) {
+ throw new LogicException('The delete operation is not supported by this API');
+ }
+
+ $requestClass = $this->isNewSurfaceOperationsClient() ? $this->deleteOperationRequest : null;
+ $this->operationsCall($this->deleteOperationMethod, $requestClass);
+ $this->deleted = true;
+ }
+
+ /**
+ * Get the metadata returned with the last proto response. If a metadata type was provided, then
+ * the return value will be of that type - otherwise, the return value will be of type Any. If
+ * no metadata object is available, returns null.
+ *
+ * @return mixed The metadata returned from the server in the last response.
+ */
+ public function getMetadata()
+ {
+ if (!$this->hasProtoResponse()) {
+ return null;
+ }
+
+ if (!method_exists($this->lastProtoResponse, 'getMetadata')) {
+ // The call to getMetadata is only for OnePlatform LROs, and is not
+ // supported by other LRO GAPIC clients (e.g. Compute)
+ return null;
+ }
+
+ /** @var Any|null $any */
+ $any = $this->lastProtoResponse->getMetadata();
+ if (is_null($this->metadataReturnType)) {
+ return $any;
+ }
+ if (is_null($any)) {
+ return null;
+ }
+ // @TODO: This is probably not doing anything and can be removed in the next release.
+ if (is_null($any->getValue())) {
+ return null;
+ }
+ $metadataReturnType = $this->metadataReturnType;
+ /** @var Message $metadata */
+ $metadata = new $metadataReturnType();
+ $metadata->mergeFromString($any->getValue());
+ return $metadata;
+ }
+
+ /**
+ * Call the operations client to perform an operation.
+ *
+ * @param string $method The method to call on the operations client.
+ * @param string|null $requestClass The request class to use for the call.
+ * Will be null for legacy operations clients.
+ */
+ private function operationsCall(string $method, ?string $requestClass)
+ {
+ // V1 GAPIC clients have an empty $requestClass
+ if (empty($requestClass)) {
+ if ($this->additionalArgs) {
+ return $this->operationsClient->$method(
+ $this->getName(),
+ ...array_values($this->additionalArgs)
+ );
+ }
+ return $this->operationsClient->$method($this->getName());
+ }
+
+ if (!method_exists($requestClass, 'build')) {
+ throw new LogicException('Request class must support the static build method');
+ }
+ // In V2 of Compute, the Request "build" methods contain the operation ID last instead
+ // of first. Compute is the only API which uses $additionalArgs, so switching the order
+ // will not break anything.
+ $request = $requestClass::build(...array_merge(
+ array_values($this->additionalArgs),
+ [$this->getName()]
+ ));
+ return $this->operationsClient->$method($request);
+ }
+
+ private function canHaveResult()
+ {
+ // The call to getResponse is only for OnePlatform LROs, and is not
+ // supported by other LRO GAPIC clients (e.g. Compute)
+ return method_exists($this->lastProtoResponse, 'getResponse');
+ }
+
+ private function hasErrors()
+ {
+ if (!$this->hasProtoResponse()) {
+ return false;
+ }
+
+ if (method_exists($this->lastProtoResponse, 'getError')) {
+ return !empty($this->lastProtoResponse->getError());
+ }
+
+ if ($this->operationErrorCodeMethod) {
+ $errorCode = call_user_func([$this->lastProtoResponse, $this->operationErrorCodeMethod]);
+ return !empty($errorCode);
+ }
+
+ // This should never happen unless an API is misconfigured
+ throw new LogicException('Unable to determine operation error status for this service');
+ }
+
+ private function hasProtoResponse()
+ {
+ return !is_null($this->lastProtoResponse);
+ }
+
+ private function isNewSurfaceOperationsClient(): bool
+ {
+ return !$this->operationsClient instanceof LegacyOperationsClient
+ && false !== strpos(get_class($this->operationsClient), self::NEW_CLIENT_NAMESPACE);
+ }
+}
diff --git a/Gax/src/Options/CallOptions.php b/Gax/src/Options/CallOptions.php
new file mode 100644
index 000000000000..bae69ddd0568
--- /dev/null
+++ b/Gax/src/Options/CallOptions.php
@@ -0,0 +1,160 @@
+> $headers
+ * Key-value array containing headers.
+ * @type int $timeoutMillis
+ * The timeout in milliseconds for the call.
+ * @type array $transportOptions
+ * Transport-specific call options. See {@see CallOptions::setTransportOptions}.
+ * @type RetrySettings|array $retrySettings
+ * A retry settings override for the call. If $retrySettings is an
+ * array, the settings will be merged with the method's default
+ * retry settings. If $retrySettings is a RetrySettings object,
+ * that object will be used instead of the method defaults.
+ * }
+ */
+ public function __construct(array $options)
+ {
+ $this->fromArray($options);
+ }
+
+ /**
+ * Sets the array of options as class properites.
+ *
+ * @param array $arr See the constructor for the list of supported options.
+ */
+ private function fromArray(array $arr): void
+ {
+ $this->setHeaders($arr['headers'] ?? []);
+ $this->setTimeoutMillis($arr['timeoutMillis'] ?? null);
+ $this->setTransportOptions($arr['transportOptions'] ?? []);
+ $this->setRetrySettings($arr['retrySettings'] ?? null);
+ }
+
+ /**
+ * @param array $headers
+ */
+ public function setHeaders(array $headers): self
+ {
+ $this->headers = $headers;
+
+ return $this;
+ }
+
+ /**
+ * @param int|null $timeoutMillis
+ */
+ public function setTimeoutMillis(?int $timeoutMillis): self
+ {
+ $this->timeoutMillis = $timeoutMillis;
+
+ return $this;
+ }
+
+ /**
+ * @param array $transportOptions {
+ * Transport-specific call-time options.
+ *
+ * @type array $grpcOptions
+ * Key-value pairs for gRPC-specific options passed as the `$options` argument to {@see \Grpc\BaseStub}
+ * request methods. Current options are `call_credentials_callback` and `timeout`.
+ * **NOTE**: This library sets `call_credentials_callback` using {@see CredentialsWrapper}, and `timeout`
+ * using the `timeoutMillis` call option, so these options are not very useful.
+ * @type array $grpcFallbackOptions
+ * Key-value pairs for gRPC fallback specific options passed as the `$options` argument to the
+ * `$httpHandler` callable. By default these are passed to {@see \GuzzleHttp\Client} as request options.
+ * See {@link https://docs.guzzlephp.org/en/stable/request-options.html}.
+ * @type array $restOptions
+ * Key-value pairs for REST-specific options passed as the `$options` argument to the `$httpHandler`
+ * callable. By default these are passed to {@see \GuzzleHttp\Client} as request options.
+ * See {@link https://docs.guzzlephp.org/en/stable/request-options.html}.
+ * }
+ */
+ public function setTransportOptions(array $transportOptions): self
+ {
+ $this->transportOptions = $transportOptions;
+
+ return $this;
+ }
+
+ /**
+ * @deprecated use CallOptions::setTransportOptions
+ */
+ public function setTransportSpecificOptions(array $transportSpecificOptions): self
+ {
+ $this->setTransportOptions($transportSpecificOptions);
+
+ return $this;
+ }
+
+ /**
+ * @param RetrySettings|array|null $retrySettings
+ *
+ * @return $this
+ */
+ public function setRetrySettings($retrySettings): self
+ {
+ $this->retrySettings = $retrySettings;
+
+ return $this;
+ }
+}
diff --git a/Gax/src/Options/ClientOptions.php b/Gax/src/Options/ClientOptions.php
new file mode 100644
index 000000000000..b9677fe20399
--- /dev/null
+++ b/Gax/src/Options/ClientOptions.php
@@ -0,0 +1,421 @@
+ '/path/to/my/credentials.json'
+ * ]);
+ * $secretManager = new SecretManagerClient($options->toArray());
+ * ```
+ *
+ * Note: It's possible to pass an associative array to the API clients as well,
+ * as ClientOptions will still be used internally for validation.
+ */
+class ClientOptions implements ArrayAccess, OptionsInterface
+{
+ use OptionsTrait;
+
+ private ?string $apiEndpoint;
+
+ private bool $disableRetries;
+
+ private array $clientConfig;
+
+ /** @var string|array|FetchAuthTokenInterface|CredentialsWrapper|null */
+ private $credentials;
+
+ private array $credentialsConfig;
+
+ /** @var string|TransportInterface|null $transport */
+ private $transport;
+
+ private TransportOptions $transportConfig;
+
+ private ?string $versionFile;
+
+ private ?string $descriptorsConfigPath;
+
+ private ?string $serviceName;
+
+ private ?string $libName;
+
+ private ?string $libVersion;
+
+ private ?string $gapicVersion;
+
+ private ?Closure $clientCertSource;
+
+ private ?string $universeDomain;
+
+ private ?string $apiKey;
+
+ private null|false|LoggerInterface $logger;
+
+ /**
+ * @param array $options {
+ * @type string $apiEndpoint
+ * The address of the API remote host, for example "example.googleapis.com. May also
+ * include the port, for example "example.googleapis.com:443"
+ * @type bool $disableRetries
+ * Determines whether or not retries defined by the client configuration should be
+ * disabled. Defaults to `false`.
+ * @type string|array $clientConfig
+ * Client method configuration, including retry settings. This option can be either a
+ * path to a JSON file, or a PHP array containing the decoded JSON data.
+ * By default this settings points to the default client config file, which is provided
+ * in the resources folder.
+ * @type FetchAuthTokenInterface|CredentialsWrapper $credentials
+ * This option should only be used with a pre-constructed \Google\Auth\FetchAuthTokenInterface
+ * object or \Google\ApiCore\CredentialsWrapper object. Note that when one of these objects
+ * are provided, any settings in $authConfig will be ignored.
+ * **Important**: If you are providing a path to a credentials file, or a decoded credentials
+ * file as a PHP array, this usage is now DEPRECATED. Providing an unvalidated credential
+ * configuration to Google APIs can compromise the security of your systems and data. It is now
+ * recommended to create the credentials explicitly:
+ * ```
+ * use Google\Auth\Credentials\ServiceAccountCredentials;
+ * use Google\ApiCore\Options\ClientOptions;
+ * $creds = new ServiceAccountCredentials($scopes, $json);
+ * $options = new ClientOptions(['credentials' => $creds]);
+ * ```
+ * For more information
+ * {@see https://cloud.google.com/docs/authentication/external/externally-sourced-credentials}
+ * @type array $credentialsConfig
+ * Options used to configure credentials, including auth token caching, for the client.
+ * For a full list of supporting configuration options, see
+ * \Google\ApiCore\CredentialsWrapper::build.
+ * @type string|TransportInterface|null $transport
+ * The transport used for executing network requests. May be either the string `rest`,
+ * `grpc`, or 'grpc-fallback'. Defaults to `grpc` if gRPC support is detected on the system.
+ * *Advanced usage*: Additionally, it is possible to pass in an already instantiated
+ * TransportInterface object. Note that when this objects is provided, any settings in
+ * $transportConfig, and any `$apiEndpoint` setting, will be ignored.
+ * @type array $transportConfig
+ * Configuration options that will be used to construct the transport. Options for
+ * each supported transport type should be passed in a key for that transport. For
+ * example:
+ * $transportConfig = [
+ * 'grpc' => [...],
+ * 'rest' => [...],
+ * 'grpc-fallback' => [...],
+ * ];
+ * See the GrpcTransport::build and RestTransport::build
+ * methods for the supported options.
+ * @type string $versionFile
+ * The path to a file which contains the current version of the client.
+ * @type string $descriptorsConfigPath
+ * The path to a descriptor configuration file.
+ * @type string $serviceName
+ * The name of the service.
+ * @type string $libName
+ * The name of the client application.
+ * @type string $libVersion
+ * The version of the client application.
+ * @type string $gapicVersion
+ * The code generator version of the GAPIC library.
+ * @type callable $clientCertSource
+ * A callable which returns the client cert as a string.
+ * @type string $universeDomain
+ * The default service domain for a given Cloud universe.
+ * @type string $apiKey
+ * The API key to be used for the client.
+ * @type null|false|LoggerInterface
+ * A PSR-3 compliant logger.
+ * }
+ */
+ public function __construct(array $options)
+ {
+ $this->fromArray($options);
+ }
+
+ /**
+ * Sets the array of options as class properites.
+ *
+ * @param array $arr See the constructor for the list of supported options.
+ */
+ private function fromArray(array $arr): void
+ {
+ $this->setApiEndpoint($arr['apiEndpoint'] ?? null);
+ $this->setDisableRetries($arr['disableRetries'] ?? false);
+ $this->setClientConfig($arr['clientConfig'] ?? []);
+ $this->setCredentials($arr['credentials'] ?? null);
+ $this->setCredentialsConfig($arr['credentialsConfig'] ?? []);
+ $this->setTransport($arr['transport'] ?? null);
+ $this->setTransportConfig(new TransportOptions($arr['transportConfig'] ?? []));
+ $this->setVersionFile($arr['versionFile'] ?? null);
+ $this->setDescriptorsConfigPath($arr['descriptorsConfigPath'] ?? null);
+ $this->setServiceName($arr['serviceName'] ?? null);
+ $this->setLibName($arr['libName'] ?? null);
+ $this->setLibVersion($arr['libVersion'] ?? null);
+ $this->setGapicVersion($arr['gapicVersion'] ?? null);
+ $this->setClientCertSource($arr['clientCertSource'] ?? null);
+ $this->setUniverseDomain($arr['universeDomain'] ?? null);
+ $this->setApiKey($arr['apiKey'] ?? null);
+ $this->setLogger($arr['logger'] ?? null);
+ }
+
+ /**
+ * @param ?string $apiEndpoint
+ *
+ * @return $this
+ */
+ public function setApiEndpoint(?string $apiEndpoint): self
+ {
+ $this->apiEndpoint = $apiEndpoint;
+
+ return $this;
+ }
+
+ /**
+ * @param bool $disableRetries
+ *
+ * @return $this
+ */
+ public function setDisableRetries(bool $disableRetries): self
+ {
+ $this->disableRetries = $disableRetries;
+
+ return $this;
+ }
+
+ /**
+ * @param string|array $clientConfig
+ *
+ * @return $this
+ * @throws InvalidArgumentException
+ */
+ public function setClientConfig($clientConfig): self
+ {
+ if (is_string($clientConfig)) {
+ $this->clientConfig = json_decode(file_get_contents($clientConfig), true);
+ } elseif (is_array($clientConfig)) {
+ $this->clientConfig = $clientConfig;
+ } else {
+ throw new InvalidArgumentException('Invalid client config');
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param string|array|FetchAuthTokenInterface|CredentialsWrapper|null $credentials
+ *
+ * @return $this
+ */
+ public function setCredentials($credentials): self
+ {
+ $this->credentials = $credentials;
+
+ return $this;
+ }
+
+ /**
+ * @param array $credentialsConfig
+ *
+ * @return $this
+ */
+ public function setCredentialsConfig(array $credentialsConfig): self
+ {
+ $this->credentialsConfig = $credentialsConfig;
+
+ return $this;
+ }
+
+ /**
+ * @param string|TransportInterface|null $transport
+ *
+ * @return $this
+ */
+ public function setTransport($transport): self
+ {
+ $this->transport = $transport;
+
+ return $this;
+ }
+
+ /**
+ * @param TransportOptions $transportConfig
+ *
+ * @return $this
+ */
+ public function setTransportConfig(TransportOptions $transportConfig): self
+ {
+ $this->transportConfig = $transportConfig;
+
+ return $this;
+ }
+
+ /**
+ * @param ?string $versionFile
+ *
+ * @return $this
+ */
+ public function setVersionFile(?string $versionFile): self
+ {
+ $this->versionFile = $versionFile;
+
+ return $this;
+ }
+
+ /**
+ * @param ?string $descriptorsConfigPath
+ *
+ * @return $this
+ */
+ private function setDescriptorsConfigPath(?string $descriptorsConfigPath): self
+ {
+ if (!is_null($descriptorsConfigPath)) {
+ self::validateFileExists($descriptorsConfigPath);
+ }
+ $this->descriptorsConfigPath = $descriptorsConfigPath;
+
+ return $this;
+ }
+
+ /**
+ * @param ?string $serviceName
+ *
+ * @return $this
+ */
+ public function setServiceName(?string $serviceName): self
+ {
+ $this->serviceName = $serviceName;
+
+ return $this;
+ }
+
+ /**
+ * @param ?string $libName
+ *
+ * @return $this
+ */
+ public function setLibName(?string $libName): self
+ {
+ $this->libName = $libName;
+
+ return $this;
+ }
+
+ /**
+ * @param ?string $libVersion
+ *
+ * @return $this
+ */
+ public function setLibVersion(?string $libVersion): self
+ {
+ $this->libVersion = $libVersion;
+
+ return $this;
+ }
+
+ /**
+ * @param ?string $gapicVersion
+ *
+ * @return $this
+ */
+ public function setGapicVersion(?string $gapicVersion): self
+ {
+ $this->gapicVersion = $gapicVersion;
+
+ return $this;
+ }
+
+ /**
+ * @param ?callable $clientCertSource
+ *
+ * @return $this
+ */
+ public function setClientCertSource(?callable $clientCertSource): self
+ {
+ if (!is_null($clientCertSource)) {
+ $clientCertSource = Closure::fromCallable($clientCertSource);
+ }
+ $this->clientCertSource = $clientCertSource;
+
+ return $this;
+ }
+
+ /**
+ * @param string $universeDomain
+ *
+ * @return $this
+ */
+ public function setUniverseDomain(?string $universeDomain): self
+ {
+ $this->universeDomain = $universeDomain;
+
+ return $this;
+ }
+
+ /**
+ * @param string $apiKey
+ *
+ * @return $this
+ */
+ public function setApiKey(?string $apiKey): self
+ {
+ $this->apiKey = $apiKey;
+
+ return $this;
+ }
+
+ /**
+ * @param null|false|LoggerInterface $logger
+ *
+ * @return $this
+ */
+ public function setLogger(null|false|LoggerInterface $logger): self
+ {
+ $this->logger = $logger;
+
+ return $this;
+ }
+}
diff --git a/Gax/src/Options/OptionsInterface.php b/Gax/src/Options/OptionsInterface.php
new file mode 100644
index 000000000000..684d27e65cbd
--- /dev/null
+++ b/Gax/src/Options/OptionsInterface.php
@@ -0,0 +1,38 @@
+$offset);
+ }
+
+ /**
+ * @return mixed
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetGet($offset)
+ {
+ return $this->$offset;
+ }
+
+ /**
+ * @throws BadMethodCallException
+ */
+ public function offsetSet($offset, $value): void
+ {
+ throw new BadMethodCallException('Cannot set options through array access. Use the setters instead');
+ }
+
+ /**
+ * @throws BadMethodCallException
+ */
+ public function offsetUnset($offset): void
+ {
+ throw new BadMethodCallException('Cannot unset options through array access. Use the setters instead');
+ }
+
+ public function toArray(): array
+ {
+ $arr = [];
+ foreach (get_object_vars($this) as $key => $value) {
+ $arr[$key] = $value instanceof OptionsInterface
+ ? $value->toArray()
+ : $value;
+ }
+ return $arr;
+ }
+}
diff --git a/Gax/src/Options/TransportOptions.php b/Gax/src/Options/TransportOptions.php
new file mode 100644
index 000000000000..3334d6f6c0ec
--- /dev/null
+++ b/Gax/src/Options/TransportOptions.php
@@ -0,0 +1,111 @@
+fromArray($options);
+ }
+
+ /**
+ * Sets the array of options as class properites.
+ *
+ * @param array $arr See the constructor for the list of supported options.
+ */
+ private function fromArray(array $arr): void
+ {
+ $this->setGrpc(new GrpcTransportOptions($arr['grpc'] ?? []));
+ $this->setGrpcFallback(new GrpcFallbackTransportOptions($arr['grpc-fallback'] ?? []));
+ $this->setRest(new RestTransportOptions($arr['rest'] ?? []));
+ }
+
+ /**
+ * @param GrpcTransportOptions $grpc
+ *
+ * @return $this
+ */
+ public function setGrpc(GrpcTransportOptions $grpc): self
+ {
+ $this->grpc = $grpc;
+
+ return $this;
+ }
+
+ /**
+ * @param GrpcFallbackTransportOptions $grpcFallback
+ *
+ * @return $this
+ */
+ public function setGrpcFallback(GrpcFallbackTransportOptions $grpcFallback): self
+ {
+ $this->grpcFallback = $grpcFallback;
+
+ return $this;
+ }
+
+ /**
+ * @param RestTransportOptions $rest
+ *
+ * @return $this
+ */
+ public function setRest(RestTransportOptions $rest): self
+ {
+ $this->rest = $rest;
+
+ return $this;
+ }
+}
diff --git a/Gax/src/Options/TransportOptions/GrpcFallbackTransportOptions.php b/Gax/src/Options/TransportOptions/GrpcFallbackTransportOptions.php
new file mode 100644
index 000000000000..9f94b235ea26
--- /dev/null
+++ b/Gax/src/Options/TransportOptions/GrpcFallbackTransportOptions.php
@@ -0,0 +1,125 @@
+fromArray($options);
+ }
+
+ /**
+ * Sets the array of options as class properites.
+ *
+ * @param array $arr See the constructor for the list of supported options.
+ */
+ private function fromArray(array $arr): void
+ {
+ $this->setClientCertSource($arr['clientCertSource'] ?? null);
+ $this->setHttpHandler($arr['httpHandler'] ?? null);
+ $this->setLogger($arr['logger'] ?? null);
+ }
+
+ /**
+ * @param ?callable $httpHandler
+ *
+ * @return $this
+ */
+ public function setHttpHandler(?callable $httpHandler): self
+ {
+ if (!is_null($httpHandler)) {
+ $httpHandler = Closure::fromCallable($httpHandler);
+ }
+ $this->httpHandler = $httpHandler;
+
+ return $this;
+ }
+
+ /**
+ * @param ?callable $clientCertSource
+ *
+ * @return $this
+ */
+ public function setClientCertSource(?callable $clientCertSource): self
+ {
+ if (!is_null($clientCertSource)) {
+ $clientCertSource = Closure::fromCallable($clientCertSource);
+ }
+ $this->clientCertSource = $clientCertSource;
+
+ return $this;
+ }
+
+ /**
+ * @param null|false|LoggerInterface $logger
+ *
+ * @return $this
+ */
+ public function setLogger(null|false|LoggerInterface $logger): self
+ {
+ $this->logger = $logger;
+
+ return $this;
+ }
+}
diff --git a/Gax/src/Options/TransportOptions/GrpcTransportOptions.php b/Gax/src/Options/TransportOptions/GrpcTransportOptions.php
new file mode 100644
index 000000000000..051b57bbea04
--- /dev/null
+++ b/Gax/src/Options/TransportOptions/GrpcTransportOptions.php
@@ -0,0 +1,165 @@
+fromArray($options);
+ }
+
+ /**
+ * Sets the array of options as class properites.
+ *
+ * @param array $arr See the constructor for the list of supported options.
+ */
+ private function fromArray(array $arr): void
+ {
+ $this->setStubOpts($arr['stubOpts'] ?? []);
+ $this->setChannel($arr['channel'] ?? null);
+ $this->setInterceptors($arr['interceptors'] ?? []);
+ $this->setClientCertSource($arr['clientCertSource'] ?? null);
+ $this->setLogger($arr['logger'] ?? null);
+ }
+
+ /**
+ * @param array $stubOpts
+ *
+ * @return $this
+ */
+ public function setStubOpts(array $stubOpts): self
+ {
+ $this->stubOpts = $stubOpts;
+
+ return $this;
+ }
+
+ /**
+ * @param ?Channel $channel
+ *
+ * @return $this
+ */
+ public function setChannel(?Channel $channel): self
+ {
+ $this->channel = $channel;
+
+ return $this;
+ }
+
+ /**
+ * @param Interceptor[]|UnaryInterceptorInterface[] $interceptors
+ *
+ * @return $this
+ */
+ public function setInterceptors(array $interceptors): self
+ {
+ $this->interceptors = $interceptors;
+
+ return $this;
+ }
+
+ /**
+ * @param ?callable $clientCertSource
+ *
+ * @return $this
+ */
+ public function setClientCertSource(?callable $clientCertSource): self
+ {
+ if (!is_null($clientCertSource)) {
+ $clientCertSource = Closure::fromCallable($clientCertSource);
+ }
+ $this->clientCertSource = $clientCertSource;
+
+ return $this;
+ }
+
+ /**
+ * @param null|false|LoggerInterface $logger
+ *
+ * @return $this
+ */
+ public function setLogger(null|false|LoggerInterface $logger): self
+ {
+ $this->logger = $logger;
+
+ return $this;
+ }
+}
diff --git a/Gax/src/Options/TransportOptions/RestTransportOptions.php b/Gax/src/Options/TransportOptions/RestTransportOptions.php
new file mode 100644
index 000000000000..ea55e2e97c70
--- /dev/null
+++ b/Gax/src/Options/TransportOptions/RestTransportOptions.php
@@ -0,0 +1,142 @@
+fromArray($options);
+ }
+
+ /**
+ * Sets the array of options as class properites.
+ *
+ * @param array $arr See the constructor for the list of supported options.
+ */
+ private function fromArray(array $arr): void
+ {
+ $this->setHttpHandler($arr['httpHandler'] ?? null);
+ $this->setClientCertSource($arr['clientCertSource'] ?? null);
+ $this->setRestClientConfigPath($arr['restClientConfigPath'] ?? null);
+ $this->setLogger($arr['logger'] ?? null);
+ }
+
+ /**
+ * @param ?callable $httpHandler
+ *
+ * @return $this
+ */
+ public function setHttpHandler(?callable $httpHandler): self
+ {
+ if (!is_null($httpHandler)) {
+ $httpHandler = Closure::fromCallable($httpHandler);
+ }
+ $this->httpHandler = $httpHandler;
+
+ return $this;
+ }
+
+ /**
+ * @param ?callable $clientCertSource
+ *
+ * @return $this
+ */
+ public function setClientCertSource(?callable $clientCertSource): self
+ {
+ if (!is_null($clientCertSource)) {
+ $clientCertSource = Closure::fromCallable($clientCertSource);
+ }
+ $this->clientCertSource = $clientCertSource;
+
+ return $this;
+ }
+
+ /**
+ * @param ?string $restClientConfigPath
+ *
+ * @return $this
+ */
+ public function setRestClientConfigPath(?string $restClientConfigPath): self
+ {
+ $this->restClientConfigPath = $restClientConfigPath;
+
+ return $this;
+ }
+
+ /**
+ * @param null|false|LoggerInterface $logger
+ *
+ * @return $this
+ */
+ public function setLogger(null|false|LoggerInterface $logger): self
+ {
+ $this->logger = $logger;
+
+ return $this;
+ }
+}
diff --git a/Gax/src/Page.php b/Gax/src/Page.php
new file mode 100644
index 000000000000..996cadf39da9
--- /dev/null
+++ b/Gax/src/Page.php
@@ -0,0 +1,271 @@
+call = $call;
+ $this->options = $options;
+ $this->callable = $callable;
+ $this->pageStreamingDescriptor = $pageStreamingDescriptor;
+ $this->response = $response;
+
+ $requestPageTokenGetMethod = $this->pageStreamingDescriptor->getRequestPageTokenGetMethod();
+ $this->pageToken = $this->call->getMessage()->$requestPageTokenGetMethod();
+ }
+
+ /**
+ * Returns true if there are more pages that can be retrieved from the
+ * API.
+ *
+ * @return bool
+ */
+ public function hasNextPage()
+ {
+ return strcmp($this->getNextPageToken(), Page::FINAL_PAGE_TOKEN) != 0;
+ }
+
+ /**
+ * Returns the next page token from the response.
+ *
+ * @return string
+ */
+ public function getNextPageToken()
+ {
+ $responsePageTokenGetMethod = $this->pageStreamingDescriptor->getResponsePageTokenGetMethod();
+ return $this->getResponseObject()->$responsePageTokenGetMethod();
+ }
+
+ /**
+ * Retrieves the next Page object using the next page token.
+ *
+ * @param int|null $pageSize
+ * @throws ValidationException if there are no pages remaining, or if pageSize is supplied but
+ * is not supported by the API
+ * @throws ApiException if the call to fetch the next page fails.
+ * @return Page
+ */
+ public function getNextPage(?int $pageSize = null)
+ {
+ if (!$this->hasNextPage()) {
+ throw new ValidationException(
+ 'Could not complete getNextPage operation: ' .
+ 'there are no more pages to retrieve.'
+ );
+ }
+
+ $oldRequest = $this->getRequestObject();
+ $requestClass = get_class($oldRequest);
+ $newRequest = new $requestClass();
+ $newRequest->mergeFrom($oldRequest);
+
+ $requestPageTokenSetMethod = $this->pageStreamingDescriptor->getRequestPageTokenSetMethod();
+ $newRequest->$requestPageTokenSetMethod($this->getNextPageToken());
+
+ if (isset($pageSize)) {
+ if (!$this->pageStreamingDescriptor->requestHasPageSizeField()) {
+ throw new ValidationException(
+ 'pageSize argument was defined, but the method does not ' .
+ 'support a page size parameter in the optional array argument'
+ );
+ }
+ $requestPageSizeSetMethod = $this->pageStreamingDescriptor->getRequestPageSizeSetMethod();
+ $newRequest->$requestPageSizeSetMethod($pageSize);
+ }
+ $this->call = $this->call->withMessage($newRequest);
+
+ $callable = $this->callable;
+ $response = $callable(
+ $this->call,
+ $this->options
+ )->wait();
+
+ return new Page(
+ $this->call,
+ $this->options,
+ $this->callable,
+ $this->pageStreamingDescriptor,
+ $response
+ );
+ }
+
+ /**
+ * Return the number of elements in the response.
+ *
+ * @return int
+ */
+ public function getPageElementCount()
+ {
+ $resourcesGetMethod = $this->pageStreamingDescriptor->getResourcesGetMethod();
+ return count($this->getResponseObject()->$resourcesGetMethod());
+ }
+
+ /**
+ * Return an iterator over the elements in the response.
+ *
+ * @return Generator
+ */
+ #[\ReturnTypeWillChange]
+ public function getIterator()
+ {
+ $resourcesGetMethod = $this->pageStreamingDescriptor->getResourcesGetMethod();
+ $items = $this->getResponseObject()->$resourcesGetMethod();
+ foreach ($items as $key => $element) {
+ if ($items instanceof MapField) {
+ yield $key => $element;
+ } else {
+ yield $element;
+ }
+ }
+ }
+
+ /**
+ * Return an iterator over Page objects, beginning with this object.
+ * Additional Page objects are retrieved lazily via API calls until
+ * all elements have been retrieved.
+ *
+ * @return Generator|array
+ * @throws ValidationException
+ * @throws ApiException
+ */
+ public function iteratePages()
+ {
+ $currentPage = $this;
+ yield $this;
+ while ($currentPage->hasNextPage()) {
+ $currentPage = $currentPage->getNextPage();
+ yield $currentPage;
+ }
+ }
+
+ /**
+ * Gets the request object used to generate the Page.
+ *
+ * @return mixed|Message
+ */
+ public function getRequestObject()
+ {
+ return $this->call->getMessage();
+ }
+
+ /**
+ * Gets the API response object.
+ *
+ * @return mixed|Message
+ */
+ public function getResponseObject()
+ {
+ return $this->response;
+ }
+
+ /**
+ * Returns a collection of elements with a fixed size set by
+ * the collectionSize parameter. The collection will only contain
+ * fewer than collectionSize elements if there are no more
+ * pages to be retrieved from the server.
+ *
+ * NOTE: it is an error to call this method if an optional parameter
+ * to set the page size is not supported or has not been set in the
+ * API call that was used to create this page. It is also an error
+ * if the collectionSize parameter is less than the page size that
+ * has been set.
+ *
+ * @param int $collectionSize
+ * @throws ValidationException if a FixedSizeCollection of the specified size cannot be constructed
+ * @return FixedSizeCollection
+ */
+ public function expandToFixedSizeCollection($collectionSize)
+ {
+ if (!$this->pageStreamingDescriptor->requestHasPageSizeField()) {
+ throw new ValidationException(
+ 'FixedSizeCollection is not supported for this method, because ' .
+ 'the method does not support an optional argument to set the ' .
+ 'page size.'
+ );
+ }
+ $request = $this->getRequestObject();
+ $pageSizeGetMethod = $this->pageStreamingDescriptor->getRequestPageSizeGetMethod();
+ $pageSize = $request->$pageSizeGetMethod();
+ if (is_null($pageSize)) {
+ throw new ValidationException(
+ 'Error while expanding Page to FixedSizeCollection: No page size ' .
+ 'parameter found. The page size parameter must be set in the API ' .
+ 'optional arguments array, and must be less than the collectionSize ' .
+ 'parameter, in order to create a FixedSizeCollection object.'
+ );
+ }
+ if ($pageSize > $collectionSize) {
+ throw new ValidationException(
+ 'Error while expanding Page to FixedSizeCollection: collectionSize ' .
+ 'parameter is less than the page size optional argument specified in ' .
+ "the API call. collectionSize: $collectionSize, page size: $pageSize"
+ );
+ }
+ return new FixedSizeCollection($this, $collectionSize);
+ }
+}
diff --git a/Gax/src/PageStreamingDescriptor.php b/Gax/src/PageStreamingDescriptor.php
new file mode 100644
index 000000000000..351eaf7fc3ec
--- /dev/null
+++ b/Gax/src/PageStreamingDescriptor.php
@@ -0,0 +1,178 @@
+descriptor = $descriptor;
+ }
+
+ /**
+ * @param array $fields {
+ * Required.
+ *
+ * @type string $requestPageTokenField the page token field in the request object.
+ * @type string $responsePageTokenField the page token field in the response object.
+ * @type string $resourceField the resource field in the response object.
+ *
+ * Optional.
+ * @type string $requestPageSizeField the page size field in the request object.
+ * }
+ * @return PageStreamingDescriptor
+ */
+ public static function createFromFields(array $fields)
+ {
+ $requestPageToken = $fields['requestPageTokenField'];
+ $responsePageToken = $fields['responsePageTokenField'];
+ $resources = $fields['resourceField'];
+
+ $descriptor = [
+ 'requestPageTokenGetMethod' => PageStreamingDescriptor::getMethod($requestPageToken),
+ 'requestPageTokenSetMethod' => PageStreamingDescriptor::setMethod($requestPageToken),
+ 'responsePageTokenGetMethod' => PageStreamingDescriptor::getMethod($responsePageToken),
+ 'resourcesGetMethod' => PageStreamingDescriptor::getMethod($resources),
+ ];
+
+ if (isset($fields['requestPageSizeField'])) {
+ $requestPageSize = $fields['requestPageSizeField'];
+ $descriptor['requestPageSizeGetMethod'] = PageStreamingDescriptor::getMethod($requestPageSize);
+ $descriptor['requestPageSizeSetMethod'] = PageStreamingDescriptor::setMethod($requestPageSize);
+ }
+
+ return new PageStreamingDescriptor($descriptor);
+ }
+
+ private static function getMethod(string $field)
+ {
+ return 'get' . ucfirst($field);
+ }
+
+ private static function setMethod(string $field)
+ {
+ return 'set' . ucfirst($field);
+ }
+
+ /**
+ * @return string The page token get method on the request object
+ */
+ public function getRequestPageTokenGetMethod()
+ {
+ return $this->descriptor['requestPageTokenGetMethod'];
+ }
+
+ /**
+ * @return string The page size get method on the request object
+ */
+ public function getRequestPageSizeGetMethod()
+ {
+ return $this->descriptor['requestPageSizeGetMethod'];
+ }
+
+ /**
+ * @return bool True if the request object has a page size field
+ */
+ public function requestHasPageSizeField()
+ {
+ return array_key_exists('requestPageSizeGetMethod', $this->descriptor);
+ }
+
+ /**
+ * @return string The page token get method on the response object
+ */
+ public function getResponsePageTokenGetMethod()
+ {
+ return $this->descriptor['responsePageTokenGetMethod'];
+ }
+
+ /**
+ * @return string The resources get method on the response object
+ */
+ public function getResourcesGetMethod()
+ {
+ return $this->descriptor['resourcesGetMethod'];
+ }
+
+ /**
+ * @return string The page token set method on the request object
+ */
+ public function getRequestPageTokenSetMethod()
+ {
+ return $this->descriptor['requestPageTokenSetMethod'];
+ }
+
+ /**
+ * @return string The page size set method on the request object
+ */
+ public function getRequestPageSizeSetMethod()
+ {
+ return $this->descriptor['requestPageSizeSetMethod'];
+ }
+
+ private static function validate(array $descriptor)
+ {
+ $requiredFields = [
+ 'requestPageTokenGetMethod',
+ 'requestPageTokenSetMethod',
+ 'responsePageTokenGetMethod',
+ 'resourcesGetMethod',
+ ];
+ foreach ($requiredFields as $field) {
+ if (empty($descriptor[$field])) {
+ throw new InvalidArgumentException(
+ "$field is required for PageStreamingDescriptor"
+ );
+ }
+ }
+ }
+}
diff --git a/Gax/src/PagedListResponse.php b/Gax/src/PagedListResponse.php
new file mode 100644
index 000000000000..62e6511eebef
--- /dev/null
+++ b/Gax/src/PagedListResponse.php
@@ -0,0 +1,197 @@
+getList(...);
+ * foreach ($pagedListResponse as $element) {
+ * // doSomethingWith($element);
+ * }
+ * ```
+ *
+ * Example of iterating over each page of elements:
+ * ```
+ * $pagedListResponse = $client->getList(...);
+ * foreach ($pagedListResponse->iteratePages() as $page) {
+ * foreach ($page as $element) {
+ * // doSomethingWith($element);
+ * }
+ * }
+ * ```
+ *
+ * Example of accessing the current page, and manually iterating
+ * over pages:
+ * ```
+ * $pagedListResponse = $client->getList(...);
+ * $page = $pagedListResponse->getPage();
+ * // doSomethingWith($page);
+ * while ($page->hasNextPage()) {
+ * $page = $page->getNextPage();
+ * // doSomethingWith($page);
+ * }
+ * ```
+ */
+class PagedListResponse implements IteratorAggregate
+{
+ private $firstPage;
+
+ /**
+ * PagedListResponse constructor.
+ *
+ * @param Page $firstPage A page containing response details.
+ */
+ public function __construct(
+ Page $firstPage
+ ) {
+ $this->firstPage = $firstPage;
+ }
+
+ /**
+ * Returns an iterator over the full list of elements. If the
+ * API response contains a (non-empty) next page token, then
+ * the PagedListResponse object will make calls to the underlying
+ * API to retrieve additional elements as required.
+ *
+ * NOTE: The result of this method is the same as getIterator().
+ * Prefer using getIterator(), or iterate directly on the
+ * PagedListResponse object.
+ *
+ * @return Generator
+ * @throws ValidationException
+ */
+ public function iterateAllElements()
+ {
+ return $this->getIterator();
+ }
+
+ /**
+ * Returns an iterator over the full list of elements. If the
+ * API response contains a (non-empty) next page token, then
+ * the PagedListResponse object will make calls to the underlying
+ * API to retrieve additional elements as required.
+ *
+ * @return Generator
+ * @throws ValidationException
+ */
+ #[\ReturnTypeWillChange]
+ public function getIterator()
+ {
+ foreach ($this->iteratePages() as $page) {
+ foreach ($page as $key => $element) {
+ yield $key => $element;
+ }
+ }
+ }
+
+ /**
+ * Return the current page of results.
+ *
+ * @return Page
+ */
+ public function getPage()
+ {
+ return $this->firstPage;
+ }
+
+ /**
+ * Returns an iterator over pages of results. The pages are
+ * retrieved lazily from the underlying API.
+ *
+ * @return Page[]
+ * @throws ValidationException
+ */
+ public function iteratePages()
+ {
+ return $this->getPage()->iteratePages();
+ }
+
+ /**
+ * Returns a collection of elements with a fixed size set by
+ * the collectionSize parameter. The collection will only contain
+ * fewer than collectionSize elements if there are no more
+ * pages to be retrieved from the server.
+ *
+ * NOTE: it is an error to call this method if an optional parameter
+ * to set the page size is not supported or has not been set in the
+ * original API call. It is also an error if the collectionSize parameter
+ * is less than the page size that has been set.
+ *
+ * @param int $collectionSize
+ * @throws ValidationException if a FixedSizeCollection of the specified size cannot be constructed
+ * @return FixedSizeCollection
+ */
+ public function expandToFixedSizeCollection(int $collectionSize)
+ {
+ return $this->getPage()->expandToFixedSizeCollection($collectionSize);
+ }
+
+ /**
+ * Returns an iterator over fixed size collections of results.
+ * The collections are retrieved lazily from the underlying API.
+ *
+ * Each collection will have collectionSize elements, with the
+ * exception of the final collection which may contain fewer
+ * elements.
+ *
+ * NOTE: it is an error to call this method if an optional parameter
+ * to set the page size is not supported or has not been set in the
+ * original API call. It is also an error if the collectionSize parameter
+ * is less than the page size that has been set.
+ *
+ * @param int $collectionSize
+ * @throws ValidationException if a FixedSizeCollection of the specified size cannot be constructed
+ * @return Generator|FixedSizeCollection[]
+ */
+ public function iterateFixedSizeCollections(int $collectionSize)
+ {
+ return $this->expandToFixedSizeCollection($collectionSize)->iterateCollections();
+ }
+}
diff --git a/Gax/src/PathTemplate.php b/Gax/src/PathTemplate.php
new file mode 100644
index 000000000000..4556acf077b3
--- /dev/null
+++ b/Gax/src/PathTemplate.php
@@ -0,0 +1,113 @@
+resourceTemplate = new AbsoluteResourceTemplate($path);
+ } else {
+ $this->resourceTemplate = new RelativeResourceTemplate($path);
+ }
+ }
+
+ /**
+ * @return string A string representation of the path template
+ */
+ public function __toString()
+ {
+ return $this->resourceTemplate->__toString();
+ }
+
+ /**
+ * Renders a path template using the provided bindings.
+ *
+ * @param array $bindings An array matching var names to binding strings.
+ * @throws ValidationException if a key isn't provided or if a sub-template
+ * can't be parsed.
+ * @return string A rendered representation of this path template.
+ */
+ public function render(array $bindings)
+ {
+ return $this->resourceTemplate->render($bindings);
+ }
+
+ /**
+ * Check if $path matches a resource string.
+ *
+ * @param string $path A resource string.
+ * @return bool
+ */
+ public function matches(string $path)
+ {
+ return $this->resourceTemplate->matches($path);
+ }
+
+ /**
+ * Matches a fully qualified path template string.
+ *
+ * @param string $path A fully qualified path template string.
+ * @throws ValidationException if path can't be matched to the template.
+ * @return array Array matching var names to binding values.
+ */
+ public function match(string $path)
+ {
+ return $this->resourceTemplate->match($path);
+ }
+}
diff --git a/Gax/src/PollingTrait.php b/Gax/src/PollingTrait.php
new file mode 100644
index 000000000000..526b01255f68
--- /dev/null
+++ b/Gax/src/PollingTrait.php
@@ -0,0 +1,97 @@
+ 0.0;
+ $endTime = $this->getCurrentTimeMillis() + $totalPollTimeoutMillis;
+
+ while (true) {
+ if ($hasTotalPollTimeout && $this->getCurrentTimeMillis() > $endTime) {
+ return false;
+ }
+ $this->sleepMillis($currentPollDelayMillis);
+ if ($pollCallable()) {
+ return true;
+ }
+ $currentPollDelayMillis = (int) min([
+ $currentPollDelayMillis * $pollDelayMultiplier,
+ $maxPollDelayMillis
+ ]);
+ }
+ }
+
+ /**
+ * Protected to allow overriding for tests
+ *
+ * @return float Current time in milliseconds
+ */
+ protected function getCurrentTimeMillis()
+ {
+ return microtime(true) * 1000.0;
+ }
+
+ /**
+ * Protected to allow overriding for tests
+ *
+ * @param int $millis
+ */
+ protected function sleepMillis(int $millis)
+ {
+ usleep($millis * 1000);
+ }
+}
diff --git a/Gax/src/RequestBuilder.php b/Gax/src/RequestBuilder.php
new file mode 100644
index 000000000000..b0d589dc42a0
--- /dev/null
+++ b/Gax/src/RequestBuilder.php
@@ -0,0 +1,290 @@
+baseUri = $baseUri;
+ $this->restConfig = require($restConfigPath);
+ }
+
+ /**
+ * @param string $path
+ * @return bool
+ */
+ public function pathExists(string $path)
+ {
+ list($interface, $method) = explode('/', $path);
+ return isset($this->restConfig['interfaces'][$interface][$method]);
+ }
+
+ /**
+ * @param string $path
+ * @param Message $message
+ * @param array $headers
+ * @return RequestInterface
+ * @throws ValidationException
+ */
+ public function build(string $path, Message $message, array $headers = [])
+ {
+ list($interface, $method) = explode('/', $path);
+
+ if (!isset($this->restConfig['interfaces'][$interface][$method])) {
+ throw new ValidationException(
+ "Failed to build request, as the provided path ($path) was not found in the configuration."
+ );
+ }
+
+ $numericEnums = isset($this->restConfig['numericEnums']) && $this->restConfig['numericEnums'];
+ $methodConfig = $this->restConfig['interfaces'][$interface][$method] + [
+ 'placeholders' => [],
+ 'body' => null,
+ 'additionalBindings' => null,
+ ];
+ $bindings = $this->buildBindings($methodConfig['placeholders'], $message);
+ $uriTemplateConfigs = $this->getConfigsForUriTemplates($methodConfig);
+
+ foreach ($uriTemplateConfigs as $config) {
+ $pathTemplate = $this->tryRenderPathTemplate($config['uriTemplate'], $bindings);
+
+ if ($pathTemplate) {
+ // We found a valid uriTemplate - now build and return the Request
+
+ list($body, $queryParams) = $this->constructBodyAndQueryParameters($message, $config);
+
+ // Request enum fields will be encoded as numbers rather than strings (in the response).
+ if ($numericEnums) {
+ $queryParams['$alt'] = 'json;enum-encoding=int';
+ }
+
+ $uri = $this->buildUri($pathTemplate, $queryParams);
+
+ return new Request(
+ $config['method'],
+ $uri,
+ ['Content-Type' => 'application/json'] + $headers,
+ $body
+ );
+ }
+ }
+
+ // No valid uriTemplate found - construct an exception
+ $uriTemplates = [];
+ foreach ($uriTemplateConfigs as $config) {
+ $uriTemplates[] = $config['uriTemplate'];
+ }
+
+ throw new ValidationException("Could not map bindings for $path to any Uri template.\n" .
+ 'Bindings: ' . print_r($bindings, true) .
+ 'UriTemplates: ' . print_r($uriTemplates, true));
+ }
+
+ /**
+ * Create a list of all possible configs using the additionalBindings
+ *
+ * @param array $config
+ * @return array[] An array of configs
+ */
+ private function getConfigsForUriTemplates(array $config)
+ {
+ $configs = [$config];
+
+ if ($config['additionalBindings']) {
+ foreach ($config['additionalBindings'] as $additionalBinding) {
+ $configs[] = $additionalBinding + $config;
+ }
+ }
+
+ return $configs;
+ }
+
+ /**
+ * @param Message $message
+ * @param array $config
+ * @return array Tuple [$body, $queryParams]
+ */
+ private function constructBodyAndQueryParameters(Message $message, array $config)
+ {
+ $messageDataJson = $message->serializeToJsonString();
+
+ if ($config['body'] === '*') {
+ return [$messageDataJson, []];
+ }
+
+ $body = null;
+ $queryParams = [];
+ $messageData = json_decode($messageDataJson, true);
+ foreach ($messageData as $name => $value) {
+ if (array_key_exists($name, $config['placeholders'])) {
+ continue;
+ }
+
+ if (Serializer::toSnakeCase($name) === $config['body']) {
+ if (($bodyMessage = $message->{"get$name"}()) instanceof Message) {
+ $body = $bodyMessage->serializeToJsonString();
+ } else {
+ $body = json_encode($value);
+ }
+ continue;
+ }
+
+ if (is_array($value) && $this->isAssoc($value)) {
+ foreach ($value as $key => $value2) {
+ $queryParams[$name . '.' . $key] = $value2;
+ }
+ } else {
+ $queryParams[$name] = $value;
+ }
+ }
+
+ // Ensures required query params with default values are always sent
+ // over the wire.
+ if (isset($config['queryParams'])) {
+ foreach ($config['queryParams'] as $requiredQueryParam) {
+ $requiredQueryParam = Serializer::toCamelCase($requiredQueryParam);
+ if (!array_key_exists($requiredQueryParam, $queryParams)) {
+ $getter = Serializer::getGetter($requiredQueryParam);
+ $queryParamValue = $message->$getter();
+ if ($queryParamValue instanceof Message) {
+ // Decode message for the query parameter.
+ $queryParamValue = json_decode($queryParamValue->serializeToJsonString(), true);
+ }
+ if (is_array($queryParamValue)) {
+ // If the message has properties, add them as nested querystring values.
+ // NOTE: This only supports nesting at one level of depth.
+ foreach ($queryParamValue as $key => $value) {
+ $queryParams[$requiredQueryParam . '.' . $key] = $value;
+ }
+ } else {
+ $queryParams[$requiredQueryParam] = $queryParamValue;
+ }
+ }
+ }
+ }
+
+ return [$body, $queryParams];
+ }
+
+ /**
+ * @param array $placeholders
+ * @param Message $message
+ * @return array Bindings from path template fields to values from message
+ */
+ private function buildBindings(array $placeholders, Message $message)
+ {
+ $bindings = [];
+ foreach ($placeholders as $placeholder => $metadata) {
+ $value = array_reduce(
+ $metadata['getters'],
+ function (?Message $result = null, $getter = null) {
+ if ($result && $getter) {
+ return $result->$getter();
+ }
+ },
+ $message
+ );
+
+ $bindings[$placeholder] = $value;
+ }
+ return $bindings;
+ }
+
+ /**
+ * Try to render the resource name. The rendered resource name will always contain a leading '/'
+ *
+ * @param string $uriTemplate
+ * @param array $bindings
+ * @return null|string
+ * @throws ValidationException
+ */
+ private function tryRenderPathTemplate(string $uriTemplate, array $bindings)
+ {
+ $template = new AbsoluteResourceTemplate($uriTemplate);
+
+ try {
+ return $template->render($bindings);
+ } catch (ValidationException $e) {
+ return null;
+ }
+ }
+
+ /**
+ * @param string $path
+ * @param array $queryParams
+ * @return UriInterface
+ */
+ protected function buildUri(string $path, array $queryParams)
+ {
+ $uri = Utils::uriFor(
+ sprintf(
+ 'https://%s%s',
+ $this->baseUri,
+ $path
+ )
+ );
+ if ($queryParams) {
+ $uri = $this->buildUriWithQuery(
+ $uri,
+ $queryParams
+ );
+ }
+ return $uri;
+ }
+}
diff --git a/Gax/src/RequestParamsHeaderDescriptor.php b/Gax/src/RequestParamsHeaderDescriptor.php
new file mode 100644
index 000000000000..73e9af3317c8
--- /dev/null
+++ b/Gax/src/RequestParamsHeaderDescriptor.php
@@ -0,0 +1,78 @@
+ value].
+ */
+ public function __construct(array $requestParams)
+ {
+ $headerKey = self::HEADER_KEY;
+
+ $headerValue = '';
+ foreach ($requestParams as $key => $value) {
+ if ('' !== $headerValue) {
+ $headerValue .= '&';
+ }
+
+ $headerValue .= $key . '=' . urlencode(strval($value));
+ }
+
+ $this->header = [$headerKey => [$headerValue]];
+ }
+
+ /**
+ * Returns an associative array that contains request params header metadata.
+ *
+ * @return array
+ */
+ public function getHeader()
+ {
+ return $this->header;
+ }
+}
diff --git a/Gax/src/ResourceHelperTrait.php b/Gax/src/ResourceHelperTrait.php
new file mode 100644
index 000000000000..7f168413b338
--- /dev/null
+++ b/Gax/src/ResourceHelperTrait.php
@@ -0,0 +1,108 @@
+ $template) {
+ self::$templateMap[$name] = new PathTemplate($template);
+ }
+ }
+
+ private static function getPathTemplate(string $key)
+ {
+ // TODO: Add nullable return type reference once PHP 7.1 is minimum.
+ if (is_null(self::$templateMap)) {
+ self::registerPathTemplates();
+ }
+ return self::$templateMap[$key] ?? null;
+ }
+
+ private static function parseFormattedName(string $formattedName, ?string $template = null): array
+ {
+ if (is_null(self::$templateMap)) {
+ self::registerPathTemplates();
+ }
+ if ($template) {
+ if (!isset(self::$templateMap[$template])) {
+ throw new ValidationException("Template name $template does not exist");
+ }
+
+ return self::$templateMap[$template]->match($formattedName);
+ }
+
+ foreach (self::$templateMap as $templateName => $pathTemplate) {
+ try {
+ return $pathTemplate->match($formattedName);
+ } catch (ValidationException $ex) {
+ // Swallow the exception to continue trying other path templates
+ }
+ }
+
+ throw new ValidationException("Input did not match any known format. Input: $formattedName");
+ }
+}
diff --git a/Gax/src/ResourceTemplate/AbsoluteResourceTemplate.php b/Gax/src/ResourceTemplate/AbsoluteResourceTemplate.php
new file mode 100644
index 000000000000..7f2425f2eaf6
--- /dev/null
+++ b/Gax/src/ResourceTemplate/AbsoluteResourceTemplate.php
@@ -0,0 +1,146 @@
+").
+ *
+ * Examples:
+ * /projects
+ * /projects/{project}
+ * /foo/{bar=**}/fizz/*:action
+ *
+ * Templates use the syntax of the API platform; see
+ * https://github.com/googleapis/api-common-protos/blob/master/google/api/http.proto
+ * for details. A template consists of a sequence of literals, wildcards, and variable bindings,
+ * where each binding can have a sub-path. A string representation can be parsed into an
+ * instance of AbsoluteResourceTemplate, which can then be used to perform matching and instantiation.
+ *
+ * @internal
+ */
+class AbsoluteResourceTemplate implements ResourceTemplateInterface
+{
+ private RelativeResourceTemplate $resourceTemplate;
+ /** @var string */
+ private $verb;
+
+ /**
+ * AbsoluteResourceTemplate constructor.
+ * @param string $path
+ * @throws ValidationException
+ */
+ public function __construct(string $path)
+ {
+ if (empty($path)) {
+ throw new ValidationException('Cannot construct AbsoluteResourceTemplate from empty string');
+ }
+ if ($path[0] !== '/') {
+ throw new ValidationException(
+ "Could not construct AbsoluteResourceTemplate from '$path': must begin with '/'"
+ );
+ }
+ $verbSeparatorPos = $this->verbSeparatorPos($path);
+ $this->resourceTemplate = new RelativeResourceTemplate(substr($path, 1, $verbSeparatorPos - 1));
+ $this->verb = substr($path, $verbSeparatorPos + 1);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function __toString()
+ {
+ return sprintf('/%s%s', $this->resourceTemplate, $this->renderVerb());
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function render(array $bindings)
+ {
+ return sprintf('/%s%s', $this->resourceTemplate->render($bindings), $this->renderVerb());
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function matches(string $path)
+ {
+ try {
+ $this->match($path);
+ return true;
+ } catch (ValidationException $ex) {
+ return false;
+ }
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function match(string $path)
+ {
+ if (empty($path)) {
+ throw $this->matchException($path, 'path cannot be empty');
+ }
+ if ($path[0] !== '/') {
+ throw $this->matchException($path, "missing leading '/'");
+ }
+ $verbSeparatorPos = $this->verbSeparatorPos($path);
+ if (substr($path, $verbSeparatorPos + 1) !== $this->verb) {
+ throw $this->matchException($path, "trailing verb did not match '{$this->verb}'");
+ }
+ return $this->resourceTemplate->match(substr($path, 1, $verbSeparatorPos - 1));
+ }
+
+ private function matchException(string $path, string $reason)
+ {
+ return new ValidationException("Could not match path '$path' to template '$this': $reason");
+ }
+
+ private function renderVerb()
+ {
+ return $this->verb ? ':' . $this->verb : '';
+ }
+
+ private function verbSeparatorPos(string $path)
+ {
+ $finalSeparatorPos = strrpos($path, '/');
+ $verbSeparatorPos = strrpos($path, ':', $finalSeparatorPos);
+ if ($verbSeparatorPos === false) {
+ $verbSeparatorPos = strlen($path);
+ }
+ return $verbSeparatorPos;
+ }
+}
diff --git a/Gax/src/ResourceTemplate/Parser.php b/Gax/src/ResourceTemplate/Parser.php
new file mode 100644
index 000000000000..2bc70ad330c8
--- /dev/null
+++ b/Gax/src/ResourceTemplate/Parser.php
@@ -0,0 +1,227 @@
+= strlen($path)) {
+ // A trailing '/' has caused the index to exceed the bounds
+ // of the string - provide a helpful error message.
+ throw self::parseError($path, strlen($path) - 1, "invalid trailing '/'");
+ }
+ if ($path[$index] === '{') {
+ // Validate that the { has a matching }
+ $closingBraceIndex = strpos($path, '}', $index);
+ if ($closingBraceIndex === false) {
+ throw self::parseError(
+ $path,
+ strlen($path),
+ "Expected '}' to match '{' at index $index, got end of string"
+ );
+ }
+
+ $segmentStringLengthWithoutBraces = $closingBraceIndex - $index - 1;
+ $segmentStringWithoutBraces = substr($path, $index + 1, $segmentStringLengthWithoutBraces);
+ $index = $closingBraceIndex + 1;
+
+ $nextLiteral = '/';
+ $remainingPath = substr($path, $index);
+ if (!empty($remainingPath)) {
+ // Find the firstnon-slash separator seen, if any.
+ $nextSlashIndex = strpos($remainingPath, '/', 0);
+ $nonSlashSeparators = ['-', '_', '~', '.'];
+ foreach ($nonSlashSeparators as $nonSlashSeparator) {
+ $nonSlashSeparatorIndex = strpos($remainingPath, $nonSlashSeparator, 0);
+ $nextOpenBraceIndex = strpos($remainingPath, '{', 0);
+ if ($nonSlashSeparatorIndex !== false && $nonSlashSeparatorIndex === $nextOpenBraceIndex - 1) {
+ $index += $nonSlashSeparatorIndex;
+ $nextLiteral = $nonSlashSeparator;
+ break;
+ }
+ }
+ }
+
+ return self::parseVariableSegment($segmentStringWithoutBraces, $nextLiteral);
+ } else {
+ $nextSlash = strpos($path, '/', $index);
+ if ($nextSlash === false) {
+ $nextSlash = strlen($path);
+ }
+ $segmentString = substr($path, $index, $nextSlash - $index);
+ $nextLiteral = '/';
+ $index = $nextSlash;
+ return self::parse($segmentString, $path, $index);
+ }
+ }
+
+ /**
+ * @param string $segmentString
+ * @param string $path
+ * @param int $index
+ * @return Segment
+ * @throws ValidationException
+ */
+ private static function parse(string $segmentString, string $path, int $index)
+ {
+ if ($segmentString === '*') {
+ return new Segment(Segment::WILDCARD_SEGMENT);
+ } elseif ($segmentString === '**') {
+ return new Segment(Segment::DOUBLE_WILDCARD_SEGMENT);
+ } else {
+ if (!self::isValidLiteral($segmentString)) {
+ if (empty($segmentString)) {
+ // Create user friendly message in case of empty segment
+ throw self::parseError($path, $index, "Unexpected empty segment (consecutive '/'s are invalid)");
+ } else {
+ throw self::parseError($path, $index, "Unexpected characters in literal segment $segmentString");
+ }
+ }
+ return new Segment(Segment::LITERAL_SEGMENT, $segmentString);
+ }
+ }
+
+ /**
+ * @param string $segmentStringWithoutBraces
+ * @param string $separatorLiteral
+ * @return Segment
+ * @throws ValidationException
+ */
+ private static function parseVariableSegment(string $segmentStringWithoutBraces, string $separatorLiteral)
+ {
+ // Validate there are no nested braces
+ $nestedOpenBracket = strpos($segmentStringWithoutBraces, '{');
+ if ($nestedOpenBracket !== false) {
+ throw new ValidationException(
+ "Unexpected '{' parsing segment $segmentStringWithoutBraces at index $nestedOpenBracket"
+ );
+ }
+
+ $equalsIndex = strpos($segmentStringWithoutBraces, '=');
+ if ($equalsIndex === false) {
+ // If the variable does not contain '=', we assume the pattern is '*' as per google.rpc.Http
+ $variableKey = $segmentStringWithoutBraces;
+ $nestedResource = new RelativeResourceTemplate('*');
+ } else {
+ $variableKey = substr($segmentStringWithoutBraces, 0, $equalsIndex);
+ $nestedResourceString = substr($segmentStringWithoutBraces, $equalsIndex + 1);
+ $nestedResource = new RelativeResourceTemplate($nestedResourceString);
+ }
+
+ if (!self::isValidLiteral($variableKey)) {
+ throw new ValidationException(
+ "Unexpected characters in variable name $variableKey"
+ );
+ }
+ return new Segment(Segment::VARIABLE_SEGMENT, null, $variableKey, $nestedResource, $separatorLiteral);
+ }
+
+ /**
+ * @param string $literal
+ * @param string $path
+ * @param int $index
+ * @return string
+ * @throws ValidationException
+ */
+ private static function parseLiteralFromPath(string $literal, string $path, int &$index)
+ {
+ $literalLength = strlen($literal);
+ if (strlen($path) < ($index + $literalLength)) {
+ throw self::parseError($path, $index, "expected '$literal'");
+ }
+ $consumedLiteral = substr($path, $index, $literalLength);
+ if ($consumedLiteral !== $literal) {
+ throw self::parseError($path, $index, "expected '$literal'");
+ }
+ $index += $literalLength;
+ return $consumedLiteral;
+ }
+
+ private static function parseError(string $path, int $index, string $reason)
+ {
+ return new ValidationException("Error parsing '$path' at index $index: $reason");
+ }
+
+ /**
+ * Check if $literal is a valid segment literal. Segment literals may only contain numbers,
+ * letters, and any of the following: .-~_
+ *
+ * @param string $literal
+ * @return bool
+ */
+ private static function isValidLiteral(string $literal)
+ {
+ return preg_match('/^[0-9a-zA-Z\\.\\-~_]+$/', $literal) === 1;
+ }
+}
diff --git a/Gax/src/ResourceTemplate/RelativeResourceTemplate.php b/Gax/src/ResourceTemplate/RelativeResourceTemplate.php
new file mode 100644
index 000000000000..9415088078d3
--- /dev/null
+++ b/Gax/src/ResourceTemplate/RelativeResourceTemplate.php
@@ -0,0 +1,390 @@
+").
+ *
+ * Examples:
+ * projects
+ * projects/{project}
+ * foo/{bar=**}/fizz/*
+ *
+ * Templates use the syntax of the API platform; see
+ * https://github.com/googleapis/api-common-protos/blob/master/google/api/http.proto
+ * for details. A template consists of a sequence of literals, wildcards, and variable bindings,
+ * where each binding can have a sub-path. A string representation can be parsed into an
+ * instance of AbsoluteResourceTemplate, which can then be used to perform matching and instantiation.
+ *
+ * @internal
+ */
+class RelativeResourceTemplate implements ResourceTemplateInterface
+{
+ /** @var Segment[] */
+ private array $segments;
+
+ /**
+ * RelativeResourceTemplate constructor.
+ *
+ * @param string $path
+ * @throws ValidationException
+ */
+ public function __construct(string $path)
+ {
+ if (empty($path)) {
+ throw new ValidationException('Cannot construct RelativeResourceTemplate from empty string');
+ }
+ $this->segments = Parser::parseSegments($path);
+
+ $doubleWildcardCount = self::countDoubleWildcards($this->segments);
+ if ($doubleWildcardCount > 1) {
+ throw new ValidationException(
+ "Cannot parse '$path': cannot contain more than one path wildcard"
+ );
+ }
+
+ // Check for duplicate keys
+ $keys = [];
+ foreach ($this->segments as $segment) {
+ if ($segment->getSegmentType() === Segment::VARIABLE_SEGMENT) {
+ if (isset($keys[$segment->getKey()])) {
+ throw new ValidationException(
+ "Duplicate key '{$segment->getKey()}' in path $path"
+ );
+ }
+ $keys[$segment->getKey()] = true;
+ }
+ }
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function __toString()
+ {
+ return self::renderSegments($this->segments);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function render(array $bindings)
+ {
+ $literalSegments = [];
+ $keySegmentTuples = self::buildKeySegmentTuples($this->segments);
+ foreach ($keySegmentTuples as list($key, $segment)) {
+ /** @var Segment $segment */
+ if ($segment->getSegmentType() === Segment::LITERAL_SEGMENT) {
+ $literalSegments[] = $segment;
+ continue;
+ }
+ if (!array_key_exists($key, $bindings)) {
+ throw $this->renderingException($bindings, "missing required binding '$key' for segment '$segment'");
+ }
+ $value = $bindings[$key];
+ if (!is_null($value) && $segment->matches($value)) {
+ $literalSegments[] = new Segment(
+ Segment::LITERAL_SEGMENT,
+ $value,
+ $segment->getValue(),
+ $segment->getTemplate(),
+ $segment->getSeparator()
+ );
+ } else {
+ $valueString = is_null($value) ? 'null' : "'$value'";
+ throw $this->renderingException(
+ $bindings,
+ "expected binding '$key' to match segment '$segment', instead got $valueString"
+ );
+ }
+ }
+ return self::renderSegments($literalSegments);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function matches(string $path)
+ {
+ try {
+ $this->match($path);
+ return true;
+ } catch (ValidationException $ex) {
+ return false;
+ }
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function match(string $path)
+ {
+ // High level strategy for matching:
+ // - Build a list of Segments from our template, where any variable segments are
+ // flattened into a single, non-nested list
+ // - Break $path into pieces based on '/'.
+ // - Use the segments to further subdivide the pieces using any applicable non-slash separators.
+ // - Match pieces of the path with Segments in the flattened list
+
+ // In order to build correct bindings after we match the $path against our template, we
+ // need to (a) calculate the correct positional keys for our wildcards, and (b) maintain
+ // information about the variable identifier of any flattened segments. To do this, we
+ // build a list of [string, Segment] tuples, where the string component is the appropriate
+ // key.
+ $keySegmentTuples = self::buildKeySegmentTuples($this->segments);
+
+ $flattenedKeySegmentTuples = self::flattenKeySegmentTuples($keySegmentTuples);
+ $flattenedKeySegmentTuplesCount = count($flattenedKeySegmentTuples);
+ assert($flattenedKeySegmentTuplesCount > 0);
+
+ $slashPathPieces = explode('/', $path);
+ $pathPieces = [];
+ $pathPiecesIndex = 0;
+ $startIndex = 0;
+ $slashPathPiecesCount = count($slashPathPieces);
+ $doubleWildcardPieceCount = $slashPathPiecesCount - $flattenedKeySegmentTuplesCount + 1;
+
+ for ($i = 0; $i < count($flattenedKeySegmentTuples); $i++) {
+ $segmentKey = $flattenedKeySegmentTuples[$i][0];
+ $segment = $flattenedKeySegmentTuples[$i][1];
+ // In our flattened list of segments, we should never encounter a variable segment
+ assert($segment->getSegmentType() !== Segment::VARIABLE_SEGMENT);
+
+ if ($segment->getSegmentType() == Segment::DOUBLE_WILDCARD_SEGMENT) {
+ $pathPiecesForSegment = array_slice($slashPathPieces, $pathPiecesIndex, $doubleWildcardPieceCount);
+ $pathPiece = implode('/', $pathPiecesForSegment);
+ $pathPiecesIndex += $doubleWildcardPieceCount;
+ $pathPieces[] = $pathPiece;
+ continue;
+ }
+
+ if ($segment->getSegmentType() == Segment::WILDCARD_SEGMENT) {
+ if ($pathPiecesIndex >= $slashPathPiecesCount) {
+ break;
+ }
+ }
+ if ($segment->getSeparator() === '/') {
+ if ($pathPiecesIndex >= $slashPathPiecesCount) {
+ throw $this->matchException($path, 'segment and path length mismatch');
+ }
+ $pathPiece = substr($slashPathPieces[$pathPiecesIndex++], $startIndex);
+ $startIndex = 0;
+ } else {
+ $rawPiece = substr($slashPathPieces[$pathPiecesIndex], $startIndex);
+ $pathPieceLength = strpos($rawPiece, $segment->getSeparator());
+ $pathPiece = substr($rawPiece, 0, $pathPieceLength);
+ $startIndex += $pathPieceLength + 1;
+ }
+ $pathPieces[] = $pathPiece;
+ }
+
+ if ($flattenedKeySegmentTuples[$i - 1][1]->getSegmentType() !== Segment::DOUBLE_WILDCARD_SEGMENT) {
+ // Process any remaining pieces. The binding logic will throw exceptions for any invalid paths.
+ for (; $pathPiecesIndex < count($slashPathPieces); $pathPiecesIndex++) {
+ $pathPieces[] = $slashPathPieces[$pathPiecesIndex];
+ }
+ }
+ $pathPiecesCount = count($pathPieces);
+
+ // We would like to match pieces of our path 1:1 with the segments of our template. However,
+ // this is confounded by the presence of double wildcards ('**') in the template, which can
+ // match multiple segments in the path.
+ // Because there can only be one '**' present, we can determine how many segments it must
+ // match by examining the difference in count between the template segments and the
+ // path pieces.
+
+ if ($pathPiecesCount < $flattenedKeySegmentTuplesCount) {
+ // Each segment in $flattenedKeyedSegments must consume at least one
+ // segment in $pathSegments, so matching must fail.
+ throw $this->matchException($path, 'path does not contain enough segments to be matched');
+ }
+
+ $doubleWildcardPieceCount = $pathPiecesCount - $flattenedKeySegmentTuplesCount + 1;
+
+ $bindings = [];
+ $pathPiecesIndex = 0;
+ /** @var Segment $segment */
+ foreach ($flattenedKeySegmentTuples as list($segmentKey, $segment)) {
+ $pathPiece = $pathPieces[$pathPiecesIndex++];
+ if (!$segment->matches($pathPiece)) {
+ throw $this->matchException($path, "expected path element matching '$segment', got '$pathPiece'");
+ }
+
+ // If we have a valid key, add our $pathPiece to the $bindings array. Note that there
+ // may be multiple copies of the same $segmentKey. This is because a flattened variable
+ // segment can match multiple pieces from the path. We can add these to an array and
+ // collapse them all once the bindings are complete.
+ if (isset($segmentKey)) {
+ $bindings += [$segmentKey => []];
+ $bindings[$segmentKey][] = $pathPiece;
+ }
+ }
+
+ // It is possible that we have left over path pieces, which can occur if our template does
+ // not have a double wildcard. In that case, the match should fail.
+ if ($pathPiecesIndex !== $pathPiecesCount) {
+ throw $this->matchException($path, "expected end of path, got '$pathPieces[$pathPiecesIndex]'");
+ }
+
+ // Collapse the bindings from lists into strings
+ $collapsedBindings = [];
+ foreach ($bindings as $key => $boundPieces) {
+ $collapsedBindings[$key] = implode('/', $boundPieces);
+ }
+
+ return $collapsedBindings;
+ }
+
+ private function matchException(string $path, string $reason)
+ {
+ return new ValidationException("Could not match path '$path' to template '$this': $reason");
+ }
+
+ private function renderingException(array $bindings, string $reason)
+ {
+ $bindingsString = print_r($bindings, true);
+ return new ValidationException(
+ "Error rendering '$this': $reason\n" .
+ "Provided bindings: $bindingsString"
+ );
+ }
+
+ /**
+ * @param Segment[] $segments
+ * @param string|null $separator An optional string separator
+ * @return array[] A list of [string, Segment] tuples
+ */
+ private static function buildKeySegmentTuples(array $segments, ?string $separator = null)
+ {
+ $keySegmentTuples = [];
+ $positionalArgumentCounter = 0;
+ foreach ($segments as $segment) {
+ switch ($segment->getSegmentType()) {
+ case Segment::WILDCARD_SEGMENT:
+ case Segment::DOUBLE_WILDCARD_SEGMENT:
+ $positionalKey = "\$$positionalArgumentCounter";
+ $positionalArgumentCounter++;
+ $newSegment = $segment;
+ if ($separator !== null) {
+ $newSegment = new Segment(
+ $segment->getSegmentType(),
+ $segment->getValue(),
+ $segment->getKey(),
+ $segment->getTemplate(),
+ $separator
+ );
+ }
+ $keySegmentTuples[] = [$positionalKey, $newSegment];
+ break;
+ default:
+ $keySegmentTuples[] = [$segment->getKey(), $segment];
+ }
+ }
+ return $keySegmentTuples;
+ }
+
+ /**
+ * @param array[] $keySegmentTuples A list of [string, Segment] tuples
+ * @return array[] A list of [string, Segment] tuples
+ */
+ private static function flattenKeySegmentTuples(array $keySegmentTuples)
+ {
+ $flattenedKeySegmentTuples = [];
+ foreach ($keySegmentTuples as list($key, $segment)) {
+ /** @var Segment $segment */
+ switch ($segment->getSegmentType()) {
+ case Segment::VARIABLE_SEGMENT:
+ // For segment variables, replace the segment with the segments of its children
+ $template = $segment->getTemplate();
+ $nestedKeySegmentTuples = self::buildKeySegmentTuples(
+ $template->segments,
+ $segment->getSeparator()
+ );
+ foreach ($nestedKeySegmentTuples as list($nestedKey, $nestedSegment)) {
+ /** @var Segment $nestedSegment */
+ // Nested variables are not allowed
+ assert($nestedSegment->getSegmentType() !== Segment::VARIABLE_SEGMENT);
+ // Insert the nested segment with key set to the outer key of the
+ // parent variable segment
+ $flattenedKeySegmentTuples[] = [$key, $nestedSegment];
+ }
+ break;
+ default:
+ // For all other segments, don't change the key or segment
+ $flattenedKeySegmentTuples[] = [$key, $segment];
+ }
+ }
+ return $flattenedKeySegmentTuples;
+ }
+
+ /**
+ * @param Segment[] $segments
+ * @return int
+ */
+ private static function countDoubleWildcards(array $segments)
+ {
+ $doubleWildcardCount = 0;
+ foreach ($segments as $segment) {
+ switch ($segment->getSegmentType()) {
+ case Segment::DOUBLE_WILDCARD_SEGMENT:
+ $doubleWildcardCount++;
+ break;
+ case Segment::VARIABLE_SEGMENT:
+ $doubleWildcardCount += self::countDoubleWildcards($segment->getTemplate()->segments);
+ break;
+ }
+ }
+ return $doubleWildcardCount;
+ }
+
+ /**
+ * Joins segments using their separators.
+ * @param array $segmentsToRender
+ * @return string
+ */
+ private static function renderSegments(array $segmentsToRender)
+ {
+ $renderResult = '';
+ for ($i = 0; $i < count($segmentsToRender); $i++) {
+ $segment = $segmentsToRender[$i];
+ $renderResult .= $segment;
+ if ($i < count($segmentsToRender) - 1) {
+ $renderResult .= $segment->getSeparator();
+ }
+ }
+ return $renderResult;
+ }
+}
diff --git a/Gax/src/ResourceTemplate/ResourceTemplateInterface.php b/Gax/src/ResourceTemplate/ResourceTemplateInterface.php
new file mode 100644
index 000000000000..ffa3c8138da5
--- /dev/null
+++ b/Gax/src/ResourceTemplate/ResourceTemplateInterface.php
@@ -0,0 +1,91 @@
+"). (Note that a trailing verb without a
+ * leading slash is not permitted).
+ *
+ * Examples:
+ * projects
+ * /projects
+ * foo/{bar=**}/fizz/*
+ * /foo/{bar=**}/fizz/*:action
+ *
+ * Templates use the syntax of the API platform; see
+ * https://github.com/googleapis/api-common-protos/blob/master/google/api/http.proto
+ * for details. A template consists of a sequence of literals, wildcards, and variable bindings,
+ * where each binding can have a sub-path. A string representation can be parsed into an
+ * instance of AbsoluteResourceTemplate, which can then be used to perform matching and instantiation.
+ *
+ * @internal
+ */
+interface ResourceTemplateInterface
+{
+ /**
+ * @return string A string representation of the resource template
+ */
+ public function __toString();
+
+ /**
+ * Renders a resource template using the provided bindings.
+ *
+ * @param array $bindings An array matching var names to binding strings.
+ * @return string A rendered representation of this resource template.
+ * @throws ValidationException If $bindings does not contain all required keys
+ * or if a sub-template can't be parsed.
+ */
+ public function render(array $bindings);
+
+ /**
+ * Check if $path matches a resource string.
+ *
+ * @param string $path A resource string.
+ * @return bool
+ */
+ public function matches(string $path);
+
+ /**
+ * Matches a given $path to a resource template, and returns an array of bindings between
+ * wildcards / variables in the template and values in the path. If $path does not match the
+ * template, then a ValidationException is thrown.
+ *
+ * @param string $path A resource string.
+ * @throws ValidationException if path can't be matched to the template.
+ * @return array Array matching var names to binding values.
+ */
+ public function match(string $path);
+}
diff --git a/Gax/src/ResourceTemplate/Segment.php b/Gax/src/ResourceTemplate/Segment.php
new file mode 100644
index 000000000000..cee08f015d8c
--- /dev/null
+++ b/Gax/src/ResourceTemplate/Segment.php
@@ -0,0 +1,195 @@
+segmentType = $segmentType;
+ $this->value = $value;
+ $this->key = $key;
+ $this->template = $template;
+ $this->separator = $separator;
+
+ switch ($this->segmentType) {
+ case Segment::LITERAL_SEGMENT:
+ $this->stringRepr = "{$this->value}";
+ break;
+ case Segment::WILDCARD_SEGMENT:
+ $this->stringRepr = '*';
+ break;
+ case Segment::DOUBLE_WILDCARD_SEGMENT:
+ $this->stringRepr = '**';
+ break;
+ case Segment::VARIABLE_SEGMENT:
+ $this->stringRepr = "{{$this->key}={$this->template}}";
+ break;
+ default:
+ throw new ValidationException(
+ "Unexpected Segment type: {$this->segmentType}"
+ );
+ }
+ }
+
+ /**
+ * @return string A string representation of the segment.
+ */
+ public function __toString()
+ {
+ return $this->stringRepr;
+ }
+
+ /**
+ * Checks if $value matches this Segment.
+ *
+ * @param string $value
+ * @return bool
+ * @throws ValidationException
+ */
+ public function matches(string $value)
+ {
+ switch ($this->segmentType) {
+ case Segment::LITERAL_SEGMENT:
+ return $this->value === $value;
+ case Segment::WILDCARD_SEGMENT:
+ return self::isValidBinding($value);
+ case Segment::DOUBLE_WILDCARD_SEGMENT:
+ return self::isValidDoubleWildcardBinding($value);
+ case Segment::VARIABLE_SEGMENT:
+ return $this->template->matches($value);
+ default:
+ throw new ValidationException(
+ "Unexpected Segment type: {$this->segmentType}"
+ );
+ }
+ }
+
+ /**
+ * @return int
+ */
+ public function getSegmentType()
+ {
+ return $this->segmentType;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getKey()
+ {
+ return $this->key;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getValue()
+ {
+ return $this->value;
+ }
+
+ /**
+ * @return RelativeResourceTemplate|null
+ */
+ public function getTemplate()
+ {
+ return $this->template;
+ }
+
+ /**
+ * @return string
+ */
+ public function getSeparator()
+ {
+ return $this->separator;
+ }
+
+ /**
+ * Check if $binding is a valid segment binding. Segment bindings may contain any characters
+ * except a forward slash ('/'), and may not be empty.
+ *
+ * @param string $binding
+ * @return bool
+ */
+ private static function isValidBinding(string $binding)
+ {
+ return preg_match('-^[^/]+$-', $binding) === 1;
+ }
+
+ /**
+ * Check if $binding is a valid double wildcard binding. Segment bindings may contain any
+ * characters, but may not be empty.
+ *
+ * @param string $binding
+ * @return bool
+ */
+ private static function isValidDoubleWildcardBinding(string $binding)
+ {
+ return preg_match('-^.+$-', $binding) === 1;
+ }
+}
diff --git a/Gax/src/RetrySettings.php b/Gax/src/RetrySettings.php
new file mode 100644
index 000000000000..018b5066d1bd
--- /dev/null
+++ b/Gax/src/RetrySettings.php
@@ -0,0 +1,547 @@
+ 100,
+ * 'retryDelayMultiplier' => 1.3,
+ * 'maxRetryDelayMillis' => 60000,
+ * 'initialRpcTimeoutMillis' => 20000,
+ * 'rpcTimeoutMultiplier' => 1.0,
+ * 'maxRpcTimeoutMillis' => 20000,
+ * 'totalTimeoutMillis' => 600000,
+ * 'retryableCodes' => [ApiStatus::DEADLINE_EXCEEDED, ApiStatus::UNAVAILABLE],
+ * ]);
+ * ```
+ *
+ * It is also possible to create a new RetrySettings object from an existing
+ * object using the {@see \Google\ApiCore\RetrySettings::with()} method.
+ *
+ * Example modifying an existing RetrySettings object using `with()`:
+ * ```
+ * $newRetrySettings = $retrySettings->with([
+ * 'totalTimeoutMillis' => 700000,
+ * ]);
+ * ```
+ *
+ * Modifying the retry behavior of an RPC method
+ * ---------------------------------------------
+ *
+ * RetrySettings objects can be used to control retries for many RPC methods in
+ * [google-cloud-php](https://github.com/googleapis/google-cloud-php).
+ * The examples below make use of the
+ * [GroupServiceClient](https://cloud.google.com/php/docs/reference/cloud-monitoring/latest/V3.Client.GroupServiceClient)
+ * from the [Monitoring V3 API](https://github.com/googleapis/google-cloud-php/tree/master/src/Monitoring/V3),
+ * but they can be applied to other APIs in the
+ * [google-cloud-php](https://github.com/googleapis/google-cloud-php) repository.
+ *
+ * It is possible to specify the retry behavior to be used by an RPC via the
+ * `retrySettings` field in the `optionalArgs` parameter. The `retrySettings`
+ * field can contain either a RetrySettings object, or a PHP array containing
+ * the particular retry parameters to be updated.
+ *
+ * Example of disabling retries for a single call to the
+ * [listGroups](https://cloud.google.com/php/docs/reference/cloud-monitoring/latest/V3.Client.GroupServiceClient#_Google_Cloud_Monitoring_V3_Client_GroupServiceClient__listGroups__)
+ * method, and setting a custom timeout:
+ * ```
+ * $result = $client->listGroups($name, [
+ * 'retrySettings' => [
+ * 'retriesEnabled' => false,
+ * 'noRetriesRpcTimeoutMillis' => 5000,
+ * ]
+ * ]);
+ * ```
+ *
+ * Example of creating a new RetrySettings object and using it to override
+ * the retry settings for a call to the
+ * [listGroups](https://cloud.google.com/php/docs/reference/cloud-monitoring/latest/V3.Client.GroupServiceClient#_Google_Cloud_Monitoring_V3_Client_GroupServiceClient__listGroups__)
+ * method:
+ * ```
+ * $customRetrySettings = new RetrySettings([
+ * 'initialRetryDelayMillis' => 100,
+ * 'retryDelayMultiplier' => 1.3,
+ * 'maxRetryDelayMillis' => 60000,
+ * 'initialRpcTimeoutMillis' => 20000,
+ * 'rpcTimeoutMultiplier' => 1.0,
+ * 'maxRpcTimeoutMillis' => 20000,
+ * 'totalTimeoutMillis' => 600000,
+ * 'retryableCodes' => [ApiStatus::DEADLINE_EXCEEDED, ApiStatus::UNAVAILABLE],
+ * ]);
+ *
+ * $result = $client->listGroups($name, [
+ * 'retrySettings' => $customRetrySettings
+ * ]);
+ * ```
+ *
+ * Modifying the default retry behavior for RPC methods on a Client object
+ * -----------------------------------------------------------------------
+ *
+ * It is also possible to specify the retry behavior for RPC methods when
+ * constructing a client object using the 'retrySettingsArray'. The examples
+ * below again make use of the
+ * [GroupServiceClient](https://cloud.google.com/php/docs/reference/cloud-monitoring/latest/V3.Client.GroupServiceClient)
+ * from the [Monitoring V3 API](https://github.com/googleapis/google-cloud-php/tree/main/Monitoring/src/V3),
+ * but they can be applied to other APIs in the
+ * [google-cloud-php](https://github.com/googleapis/google-cloud-php) repository.
+ *
+ * The GroupServiceClient object accepts an optional `retrySettingsArray`
+ * parameter, which can be used to specify retry behavior for RPC methods
+ * on the client. The `retrySettingsArray` accepts a PHP array in which keys
+ * are the names of RPC methods on the client, and values are either a
+ * RetrySettings object or a PHP array containing the particular retry
+ * parameters to be updated.
+ *
+ * Example updating the retry settings for four methods of GroupServiceClient:
+ * ```
+ * use Google\Cloud\Monitoring\V3\GroupServiceClient;
+ *
+ * $customRetrySettings = new RetrySettings([
+ * 'initialRetryDelayMillis' => 100,
+ * 'retryDelayMultiplier' => 1.3,
+ * 'maxRetryDelayMillis' => 60000,
+ * 'initialRpcTimeoutMillis' => 20000,
+ * 'rpcTimeoutMultiplier' => 1.0,
+ * 'maxRpcTimeoutMillis' => 20000,
+ * 'totalTimeoutMillis' => 600000,
+ * 'retryableCodes' => [ApiStatus::DEADLINE_EXCEEDED, ApiStatus::UNAVAILABLE],
+ * ]);
+ *
+ * $updatedCustomRetrySettings = $customRetrySettings->with([
+ * 'totalTimeoutMillis' => 700000
+ * ]);
+ *
+ * $client = new GroupServiceClient([
+ * 'retrySettingsArray' => [
+ * 'listGroups' => ['retriesEnabled' => false],
+ * 'getGroup' => [
+ * 'initialRpcTimeoutMillis' => 10000,
+ * 'maxRpcTimeoutMillis' => 30000,
+ * 'totalTimeoutMillis' => 60000,
+ * ],
+ * 'deleteGroup' => $customRetrySettings,
+ * 'updateGroup' => $updatedCustomRetrySettings
+ * ],
+ * ]);
+ * ```
+ *
+ * Configure the use of logical timeout
+ * ------------------------------------
+ *
+ * To configure the use of a logical timeout, where a logical timeout is the
+ * duration a method is given to complete one or more RPC attempts, with each
+ * attempt using only the time remaining in the logical timeout, use
+ * {@see \Google\ApiCore\RetrySettings::logicalTimeout()} combined with
+ * {@see \Google\ApiCore\RetrySettings::with()}.
+ *
+ * ```
+ * $timeoutSettings = RetrySettings::logicalTimeout(30000);
+ *
+ * $customRetrySettings = $customRetrySettings->with($timeoutSettings);
+ *
+ * $result = $client->listGroups($name, [
+ * 'retrySettings' => $customRetrySettings
+ * ]);
+ * ```
+ *
+ * {@see \Google\ApiCore\RetrySettings::logicalTimeout()} can also be used on a
+ * method call independent of a RetrySettings instance.
+ *
+ * ```
+ * $timeoutSettings = RetrySettings::logicalTimeout(30000);
+ *
+ * $result = $client->listGroups($name, [
+ * 'retrySettings' => $timeoutSettings
+ * ]);
+ * ```
+ */
+class RetrySettings
+{
+ use ValidationTrait;
+
+ const DEFAULT_MAX_RETRIES = 0;
+
+ private $retriesEnabled;
+
+ private $retryableCodes;
+
+ private $initialRetryDelayMillis;
+ private $retryDelayMultiplier;
+ private $maxRetryDelayMillis;
+ private $initialRpcTimeoutMillis;
+ private $rpcTimeoutMultiplier;
+ private $maxRpcTimeoutMillis;
+ private $totalTimeoutMillis;
+
+ private $noRetriesRpcTimeoutMillis;
+
+ /**
+ * The number of maximum retries an operation can do.
+ * This doesn't include the original API call.
+ * Setting this to 0 means no limit.
+ */
+ private int $maxRetries;
+
+ /**
+ * When set, this function will be used to evaluate if the retry should
+ * take place or not. The callable will have the following signature:
+ * function (Exception $e, array $options): bool
+ */
+ private ?Closure $retryFunction;
+
+ /**
+ * Constructs an instance.
+ *
+ * @param array $settings {
+ * Required. Settings for configuring the retry behavior. All parameters are required except
+ * $retriesEnabled and $noRetriesRpcTimeoutMillis, which are optional and have defaults
+ * determined based on the other settings provided.
+ *
+ * @type bool $retriesEnabled Optional. Enables retries. If not specified, the value is
+ * determined using the $retryableCodes setting. If $retryableCodes is empty,
+ * then $retriesEnabled is set to false; otherwise, it is set to true.
+ * @type int $noRetriesRpcTimeoutMillis Optional. The timeout of the rpc call to be used
+ * if $retriesEnabled is false, in milliseconds. It not specified, the value
+ * of $initialRpcTimeoutMillis is used.
+ * @type array $retryableCodes The Status codes that are retryable. Each status should be
+ * either one of the string constants defined on {@see \Google\ApiCore\ApiStatus}
+ * or an integer constant defined on {@see \Google\Rpc\Code}.
+ * @type int $initialRetryDelayMillis The initial delay of retry in milliseconds.
+ * @type int $retryDelayMultiplier The exponential multiplier of retry delay.
+ * @type int $maxRetryDelayMillis The max delay of retry in milliseconds.
+ * @type int $initialRpcTimeoutMillis The initial timeout of rpc call in milliseconds.
+ * @type int $rpcTimeoutMultiplier The exponential multiplier of rpc timeout.
+ * @type int $maxRpcTimeoutMillis The max timeout of rpc call in milliseconds.
+ * @type int $totalTimeoutMillis The max accumulative timeout in total.
+ * @type int $maxRetries The max retries allowed for an operation.
+ * Defaults to the value of the DEFAULT_MAX_RETRIES constant.
+ * This option is experimental.
+ * @type callable $retryFunction This function will be used to decide if we should retry or not.
+ * Callable signature: `function (Exception $e, array $options): bool`
+ * This option is experimental.
+ * }
+ */
+ public function __construct(array $settings)
+ {
+ self::validateNotNull($settings, [
+ 'initialRetryDelayMillis',
+ 'retryDelayMultiplier',
+ 'maxRetryDelayMillis',
+ 'initialRpcTimeoutMillis',
+ 'rpcTimeoutMultiplier',
+ 'maxRpcTimeoutMillis',
+ 'totalTimeoutMillis',
+ 'retryableCodes'
+ ]);
+ $this->initialRetryDelayMillis = $settings['initialRetryDelayMillis'];
+ $this->retryDelayMultiplier = $settings['retryDelayMultiplier'];
+ $this->maxRetryDelayMillis = $settings['maxRetryDelayMillis'];
+ $this->initialRpcTimeoutMillis = $settings['initialRpcTimeoutMillis'];
+ $this->rpcTimeoutMultiplier = $settings['rpcTimeoutMultiplier'];
+ $this->maxRpcTimeoutMillis = $settings['maxRpcTimeoutMillis'];
+ $this->totalTimeoutMillis = $settings['totalTimeoutMillis'];
+ $this->retryableCodes = $settings['retryableCodes'];
+ $this->retriesEnabled = array_key_exists('retriesEnabled', $settings)
+ ? $settings['retriesEnabled']
+ : (count($this->retryableCodes) > 0);
+ $this->noRetriesRpcTimeoutMillis = array_key_exists('noRetriesRpcTimeoutMillis', $settings)
+ ? $settings['noRetriesRpcTimeoutMillis']
+ : $this->initialRpcTimeoutMillis;
+ $this->maxRetries = $settings['maxRetries'] ?? self::DEFAULT_MAX_RETRIES;
+ $this->retryFunction = $settings['retryFunction'] ?? null;
+ }
+
+ /**
+ * Constructs an array mapping method names to CallSettings.
+ *
+ * @param string $serviceName
+ * The fully-qualified name of this service, used as a key into
+ * the client config file.
+ * @param array $clientConfig
+ * An array parsed from the standard API client config file.
+ * @param bool $disableRetries
+ * Disable retries in all loaded RetrySettings objects. Defaults to false.
+ * @throws ValidationException
+ * @return RetrySettings[] $retrySettings
+ */
+ public static function load(
+ string $serviceName,
+ array $clientConfig,
+ bool $disableRetries = false
+ ) {
+ $serviceRetrySettings = [];
+
+ $serviceConfig = $clientConfig['interfaces'][$serviceName];
+ $retryCodes = $serviceConfig['retry_codes'];
+ $retryParams = $serviceConfig['retry_params'];
+ foreach ($serviceConfig['methods'] as $methodName => $methodConfig) {
+ $timeoutMillis = $methodConfig['timeout_millis'];
+
+ if (empty($methodConfig['retry_codes_name']) || empty($methodConfig['retry_params_name'])) {
+ // Construct a RetrySettings object with retries disabled
+ $retrySettings = self::constructDefault()->with([
+ 'noRetriesRpcTimeoutMillis' => $timeoutMillis,
+ ]);
+ } else {
+ $retryCodesName = $methodConfig['retry_codes_name'];
+ $retryParamsName = $methodConfig['retry_params_name'];
+
+ if (!array_key_exists($retryCodesName, $retryCodes)) {
+ throw new ValidationException("Invalid retry_codes_name setting: '$retryCodesName'");
+ }
+ if (!array_key_exists($retryParamsName, $retryParams)) {
+ throw new ValidationException("Invalid retry_params_name setting: '$retryParamsName'");
+ }
+
+ foreach ($retryCodes[$retryCodesName] as $status) {
+ if (!ApiStatus::isValidStatus($status)) {
+ throw new ValidationException("Invalid status code: '$status'");
+ }
+ }
+
+ $retryParameters = self::convertArrayFromSnakeCase($retryParams[$retryParamsName]) + [
+ 'retryableCodes' => $retryCodes[$retryCodesName],
+ 'noRetriesRpcTimeoutMillis' => $timeoutMillis,
+ ];
+ if ($disableRetries) {
+ $retryParameters['retriesEnabled'] = false;
+ }
+
+ $retrySettings = new RetrySettings($retryParameters);
+ }
+
+ $serviceRetrySettings[$methodName] = $retrySettings;
+ }
+
+ return $serviceRetrySettings;
+ }
+
+ public static function constructDefault()
+ {
+ return new RetrySettings([
+ 'retriesEnabled' => false,
+ 'noRetriesRpcTimeoutMillis' => 30000,
+ 'initialRetryDelayMillis' => 100,
+ 'retryDelayMultiplier' => 1.3,
+ 'maxRetryDelayMillis' => 60000,
+ 'initialRpcTimeoutMillis' => 20000,
+ 'rpcTimeoutMultiplier' => 1,
+ 'maxRpcTimeoutMillis' => 20000,
+ 'totalTimeoutMillis' => 600000,
+ 'retryableCodes' => [],
+ 'maxRetries' => self::DEFAULT_MAX_RETRIES,
+ 'retryFunction' => null]);
+ }
+
+ /**
+ * Creates a new instance of RetrySettings that updates the settings in the existing instance
+ * with the settings specified in the $settings parameter.
+ *
+ * @param array $settings {
+ * Settings for configuring the retry behavior. Supports all of the options supported by
+ * the constructor; see {@see \Google\ApiCore\RetrySettings::__construct()}. All parameters
+ * are optional - all unset parameters will default to the value in the existing instance.
+ * }
+ * @return RetrySettings
+ */
+ public function with(array $settings)
+ {
+ $existingSettings = [
+ 'initialRetryDelayMillis' => $this->getInitialRetryDelayMillis(),
+ 'retryDelayMultiplier' => $this->getRetryDelayMultiplier(),
+ 'maxRetryDelayMillis' => $this->getMaxRetryDelayMillis(),
+ 'initialRpcTimeoutMillis' => $this->getInitialRpcTimeoutMillis(),
+ 'rpcTimeoutMultiplier' => $this->getRpcTimeoutMultiplier(),
+ 'maxRpcTimeoutMillis' => $this->getMaxRpcTimeoutMillis(),
+ 'totalTimeoutMillis' => $this->getTotalTimeoutMillis(),
+ 'retryableCodes' => $this->getRetryableCodes(),
+ 'retriesEnabled' => $this->retriesEnabled(),
+ 'noRetriesRpcTimeoutMillis' => $this->getNoRetriesRpcTimeoutMillis(),
+ 'maxRetries' => $this->getMaxRetries(),
+ 'retryFunction' => $this->getRetryFunction(),
+ ];
+ return new RetrySettings($settings + $existingSettings);
+ }
+
+ /**
+ * Creates an associative array of the {@see \Google\ApiCore\RetrySettings} timeout fields configured
+ * with the given timeout specified in the $timeout parameter interpreted as a logical timeout.
+ *
+ * @param int $timeout The timeout in milliseconds to be used as a logical call timeout.
+ * @return array
+ */
+ public static function logicalTimeout(int $timeout)
+ {
+ return [
+ 'initialRpcTimeoutMillis' => $timeout,
+ 'maxRpcTimeoutMillis' => $timeout,
+ 'totalTimeoutMillis' => $timeout,
+ 'noRetriesRpcTimeoutMillis' => $timeout,
+ 'rpcTimeoutMultiplier' => 1.0
+ ];
+ }
+
+ /**
+ * @return bool Returns true if retries are enabled, otherwise returns false.
+ */
+ public function retriesEnabled()
+ {
+ return $this->retriesEnabled;
+ }
+
+ /**
+ * @return int The timeout of the rpc call to be used if $retriesEnabled is false,
+ * in milliseconds.
+ */
+ public function getNoRetriesRpcTimeoutMillis()
+ {
+ return $this->noRetriesRpcTimeoutMillis;
+ }
+
+ /**
+ * @return int[] Status codes to retry
+ */
+ public function getRetryableCodes()
+ {
+ return $this->retryableCodes;
+ }
+
+ /**
+ * @return int The initial retry delay in milliseconds. If $this->retriesEnabled()
+ * is false, this setting is unused.
+ */
+ public function getInitialRetryDelayMillis()
+ {
+ return $this->initialRetryDelayMillis;
+ }
+
+ /**
+ * @return float The retry delay multiplier. If $this->retriesEnabled()
+ * is false, this setting is unused.
+ */
+ public function getRetryDelayMultiplier()
+ {
+ return $this->retryDelayMultiplier;
+ }
+
+ /**
+ * @return int The maximum retry delay in milliseconds. If $this->retriesEnabled()
+ * is false, this setting is unused.
+ */
+ public function getMaxRetryDelayMillis()
+ {
+ return $this->maxRetryDelayMillis;
+ }
+
+ /**
+ * @return int The initial rpc timeout in milliseconds. If $this->retriesEnabled()
+ * is false, this setting is unused - use noRetriesRpcTimeoutMillis to
+ * set the timeout in that case.
+ */
+ public function getInitialRpcTimeoutMillis()
+ {
+ return $this->initialRpcTimeoutMillis;
+ }
+
+ /**
+ * @return float The rpc timeout multiplier. If $this->retriesEnabled()
+ * is false, this setting is unused.
+ */
+ public function getRpcTimeoutMultiplier()
+ {
+ return $this->rpcTimeoutMultiplier;
+ }
+
+ /**
+ * @return int The maximum rpc timeout in milliseconds. If $this->retriesEnabled()
+ * is false, this setting is unused - use noRetriesRpcTimeoutMillis to
+ * set the timeout in that case.
+ */
+ public function getMaxRpcTimeoutMillis()
+ {
+ return $this->maxRpcTimeoutMillis;
+ }
+
+ /**
+ * @return int The total time in milliseconds to spend on the call, including all
+ * retry attempts and delays between attempts. If $this->retriesEnabled()
+ * is false, this setting is unused - use noRetriesRpcTimeoutMillis to
+ * set the timeout in that case.
+ */
+ public function getTotalTimeoutMillis()
+ {
+ return $this->totalTimeoutMillis;
+ }
+
+ /**
+ * @experimental
+ */
+ public function getMaxRetries()
+ {
+ return $this->maxRetries;
+ }
+
+ /**
+ * @experimental
+ */
+ public function getRetryFunction()
+ {
+ return $this->retryFunction;
+ }
+
+ private static function convertArrayFromSnakeCase(array $settings)
+ {
+ $camelCaseSettings = [];
+ foreach ($settings as $key => $value) {
+ $camelCaseKey = str_replace(' ', '', ucwords(str_replace('_', ' ', $key)));
+ $camelCaseSettings[lcfirst($camelCaseKey)] = $value;
+ }
+ return $camelCaseSettings;
+ }
+}
diff --git a/Gax/src/Serializer.php b/Gax/src/Serializer.php
new file mode 100644
index 000000000000..7332781c1736
--- /dev/null
+++ b/Gax/src/Serializer.php
@@ -0,0 +1,582 @@
+fieldTransformers = $fieldTransformers;
+ $this->messageTypeTransformers = $messageTypeTransformers;
+ $this->decodeFieldTransformers = $decodeFieldTransformers;
+ $this->decodeMessageTypeTransformers = $decodeMessageTypeTransformers;
+ $this->customEncoders = $customEncoders;
+ }
+
+ /**
+ * Encode protobuf message as a PHP array
+ *
+ * @param mixed $message
+ * @return array
+ * @throws ValidationException
+ */
+ public function encodeMessage($message)
+ {
+ $cls = get_class($message);
+
+ // If we have supplied a customEncoder for this class type,
+ // then we use that instead of the general encodeMessage definition.
+ if (array_key_exists($cls, $this->customEncoders)) {
+ $func = $this->customEncoders[$cls];
+ return call_user_func($func, $message);
+ }
+ // Get message descriptor
+ $pool = DescriptorPool::getGeneratedPool();
+ $messageType = $pool->getDescriptorByClassName(get_class($message));
+ try {
+ return $this->encodeMessageImpl($message, $messageType);
+ } catch (\Exception $e) {
+ throw new ValidationException(
+ 'Error encoding message: ' . $e->getMessage(),
+ $e->getCode(),
+ $e
+ );
+ }
+ }
+
+ /**
+ * Decode PHP array into the specified protobuf message
+ *
+ * @param mixed $message
+ * @param array $data
+ * @return mixed
+ * @throws ValidationException
+ */
+ public function decodeMessage($message, array $data)
+ {
+ // Get message descriptor
+ $pool = DescriptorPool::getGeneratedPool();
+ $messageType = $pool->getDescriptorByClassName(get_class($message));
+ try {
+ return $this->decodeMessageImpl($message, $messageType, $data);
+ } catch (\Exception $e) {
+ throw new ValidationException(
+ 'Error decoding message: ' . $e->getMessage(),
+ $e->getCode(),
+ $e
+ );
+ }
+ }
+
+ /**
+ * @param Message $message
+ * @return string Json representation of $message
+ * @throws ValidationException
+ */
+ public static function serializeToJson(Message $message)
+ {
+ return json_encode(self::serializeToPhpArray($message), JSON_PRETTY_PRINT);
+ }
+
+ /**
+ * @param Message $message
+ * @return array PHP array representation of $message
+ * @throws ValidationException
+ */
+ public static function serializeToPhpArray(Message $message)
+ {
+ return self::getPhpArraySerializer()->encodeMessage($message);
+ }
+
+ /**
+ * Decode metadata received from gRPC status object
+ *
+ * @param array $metadata
+ * @param null|array $errors
+ * @return array
+ */
+ public static function decodeMetadata(array $metadata, ?array &$errors = null)
+ {
+ if (count($metadata) == 0) {
+ return [];
+ }
+ // ensure known types are available from the descriptor pool
+ KnownTypes::addKnownTypesToDescriptorPool();
+ $result = [];
+ // If metadata contains a "status" bin, use that instead
+ if (isset($metadata['grpc-status-details-bin'])) {
+ $status = new Status();
+ $status->mergeFromString($metadata['grpc-status-details-bin'][0]);
+ foreach ($status->getDetails() as $any) {
+ if (isset(KnownTypes::TYPE_URLS[$any->getTypeUrl()])) {
+ $class = KnownTypes::TYPE_URLS[$any->getTypeUrl()];
+ new $class(); // add known types to descriptor pool
+ }
+ try {
+ $error = $any->unpack();
+ } catch (Exception $ex) {
+ // failed to unpack the $any object - keep the object as-is instead
+ $error = $any;
+ }
+ if (!is_null($errors)) {
+ $errors[] = $error;
+ }
+ $result[] = [
+ '@type' => $any->getTypeUrl(),
+ ] + self::serializeToPhpArray($error);
+ }
+ return $result;
+ }
+
+ // look for individual error detail bins and decode those
+ // NOTE: This method SHOULD be superceeded by 'grpc-status-details-bin' in every case, but
+ // we are keeping it for now to be safe
+ foreach ($metadata as $key => $values) {
+ foreach ($values as $value) {
+ $decodedValue = ['@type' => $key];
+ if (self::hasBinaryHeaderSuffix($key)) {
+ if (isset(KnownTypes::BIN_TYPES[$key])) {
+ $class = KnownTypes::BIN_TYPES[$key];
+ /**
+ * @var BadRequest
+ * | DebugInfo
+ * | ErrorInfo
+ * | Help
+ * | LocalizedMessage
+ * | PreconditionFailure
+ * | QuotaFailure
+ * | RequestInfo
+ * | ResourceInfo
+ * | RetryInfo $message
+ */
+ $message = new $class();
+ try {
+ $message->mergeFromString($value);
+ $decodedValue += self::serializeToPhpArray($message);
+ if (!is_null($errors)) {
+ $errors[] = $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 iterable $anyArray
+ * @return array
+ */
+ public static function decodeAnyMessages($anyArray)
+ {
+ $results = [];
+ foreach ($anyArray as $any) {
+ try {
+ /** @var Any $any */
+ /** @var Message $unpacked */
+ $unpacked = $any->unpack();
+ $results[] = self::serializeToPhpArray($unpacked);
+ } catch (\Exception $ex) {
+ // failed to unpack the $any object - show as unknown binary data
+ $results[] = [
+ 'typeUrl' => $any->getTypeUrl(),
+ 'value' => '',
+ ];
+ }
+ }
+ return $results;
+ }
+
+ /**
+ * @param FieldDescriptor $field
+ * @param Message|array|string $data
+ * @return mixed
+ * @throws \Exception
+ */
+ private function encodeElement(FieldDescriptor $field, $data)
+ {
+ switch ($field->getType()) {
+ case GPBType::MESSAGE:
+ if (is_array($data)) {
+ $result = $data;
+ } else {
+ $result = $this->encodeMessageImpl($data, $field->getMessageType());
+ }
+ $messageType = $field->getMessageType()->getFullName();
+ if (isset($this->messageTypeTransformers[$messageType])) {
+ $result = $this->messageTypeTransformers[$messageType]($result);
+ }
+ break;
+ default:
+ $result = $data;
+ break;
+ }
+
+ if (isset($this->fieldTransformers[$field->getName()])) {
+ $result = $this->fieldTransformers[$field->getName()]($result);
+ }
+ return $result;
+ }
+
+ private function getDescriptorMaps(Descriptor $descriptor)
+ {
+ if (!isset($this->descriptorMaps[$descriptor->getFullName()])) {
+ $fieldsByName = [];
+ $fieldCount = $descriptor->getFieldCount();
+ for ($i = 0; $i < $fieldCount; $i++) {
+ $field = $descriptor->getField($i);
+ $fieldsByName[$field->getName()] = $field;
+ }
+ $fieldToOneof = [];
+ $oneofCount = $descriptor->getOneofDeclCount();
+ for ($i = 0; $i < $oneofCount; $i++) {
+ $oneof = $descriptor->getOneofDecl($i);
+ $oneofFieldCount = $oneof->getFieldCount();
+ for ($j = 0; $j < $oneofFieldCount; $j++) {
+ $field = $oneof->getField($j);
+ $fieldToOneof[$field->getName()] = $oneof->getName();
+ }
+ }
+ $this->descriptorMaps[$descriptor->getFullName()] = [$fieldsByName, $fieldToOneof];
+ }
+ return $this->descriptorMaps[$descriptor->getFullName()];
+ }
+
+ /**
+ * @param Message $message
+ * @param Descriptor $messageType
+ * @return array
+ * @throws \Exception
+ */
+ private function encodeMessageImpl(Message $message, Descriptor $messageType)
+ {
+ $data = [];
+
+ // Call the getDescriptorMaps outside of the loop to save processing.
+ // Use the same set of fields to loop over, instead of using field count.
+ list($fields, $fieldsToOneof) = $this->getDescriptorMaps($messageType);
+ foreach ($fields as $field) {
+ $key = $field->getName();
+ $getter = self::getGetter($key);
+ $v = $message->$getter();
+
+ if (is_null($v)) {
+ continue;
+ }
+
+ // Check and skip unset fields inside oneofs
+ if (isset($fieldsToOneof[$key])) {
+ $oneofName = $fieldsToOneof[$key];
+ $oneofGetter = self::getGetter($oneofName);
+ if ($message->$oneofGetter() !== $key) {
+ continue;
+ }
+ }
+
+ if ($field->isMap()) {
+ list($mapFieldsByName, $_) = $this->getDescriptorMaps($field->getMessageType());
+ $keyField = $mapFieldsByName[self::MAP_KEY_FIELD_NAME];
+ $valueField = $mapFieldsByName[self::MAP_VALUE_FIELD_NAME];
+ $arr = [];
+ foreach ($v as $k => $vv) {
+ $arr[$this->encodeElement($keyField, $k)] = $this->encodeElement($valueField, $vv);
+ }
+ $v = $arr;
+ } elseif ($this->checkFieldRepeated($field)) {
+ $arr = [];
+ foreach ($v as $k => $vv) {
+ $arr[$k] = $this->encodeElement($field, $vv);
+ }
+ $v = $arr;
+ } else {
+ $v = $this->encodeElement($field, $v);
+ }
+
+ $key = self::toCamelCase($key);
+ $data[$key] = $v;
+ }
+
+ return $data;
+ }
+
+ /**
+ * @param FieldDescriptor $field
+ * @param mixed $data
+ * @return mixed
+ * @throws \Exception
+ */
+ private function decodeElement(FieldDescriptor $field, $data)
+ {
+ if (isset($this->decodeFieldTransformers[$field->getName()])) {
+ $data = $this->decodeFieldTransformers[$field->getName()]($data);
+ }
+
+ switch ($field->getType()) {
+ case GPBType::MESSAGE:
+ if ($data instanceof Message) {
+ return $data;
+ }
+ $messageType = $field->getMessageType();
+ $messageTypeName = $messageType->getFullName();
+ $klass = $messageType->getClass();
+ $msg = new $klass();
+ if (isset($this->decodeMessageTypeTransformers[$messageTypeName])) {
+ $data = $this->decodeMessageTypeTransformers[$messageTypeName]($data);
+ }
+
+ return $this->decodeMessageImpl($msg, $messageType, $data);
+ default:
+ return $data;
+ }
+ }
+
+ /**
+ * @param Message $message
+ * @param Descriptor $messageType
+ * @param array $data
+ * @return mixed
+ * @throws \Exception
+ */
+ private function decodeMessageImpl(Message $message, Descriptor $messageType, array $data)
+ {
+ list($fieldsByName, $_) = $this->getDescriptorMaps($messageType);
+ foreach ($data as $key => $v) {
+ // Get the field by tag number or name
+ $fieldName = self::toSnakeCase($key);
+
+ // Unknown field found
+ if (!isset($fieldsByName[$fieldName])) {
+ throw new RuntimeException(sprintf(
+ 'cannot handle unknown field %s on message %s',
+ $fieldName,
+ $messageType->getFullName()
+ ));
+ }
+
+ /** @var FieldDescriptor $field */
+ $field = $fieldsByName[$fieldName];
+
+ if ($field->isMap()) {
+ list($mapFieldsByName, $_) = $this->getDescriptorMaps($field->getMessageType());
+ $keyField = $mapFieldsByName[self::MAP_KEY_FIELD_NAME];
+ $valueField = $mapFieldsByName[self::MAP_VALUE_FIELD_NAME];
+ $arr = [];
+ foreach ($v as $k => $vv) {
+ $arr[$this->decodeElement($keyField, $k)] = $this->decodeElement($valueField, $vv);
+ }
+ $value = $arr;
+ } elseif ($this->checkFieldRepeated($field)) {
+ $arr = [];
+ foreach ($v as $k => $vv) {
+ $arr[$k] = $this->decodeElement($field, $vv);
+ }
+ $value = $arr;
+ } else {
+ $value = $this->decodeElement($field, $v);
+ }
+
+ $setter = self::getSetter($field->getName());
+ $message->$setter($value);
+
+ // We must unset $value here, otherwise the protobuf c extension will mix up the references
+ // and setting one value will change all others
+ unset($value);
+ }
+ return $message;
+ }
+
+ /**
+ * @param FieldDescriptor $field
+ * @return bool
+ */
+ private function checkFieldRepeated(FieldDescriptor $field): bool
+ {
+ // @phpstan-ignore function.alreadyNarrowedType
+ if (method_exists($field, 'isRepeated')) {
+ return $field->isRepeated();
+ }
+ if (method_exists($field, 'getLabel')) {
+ return $field->getLabel() === GPBLabel::REPEATED;
+ }
+ throw new \LogicException('unable to check field repeated');
+ }
+
+ /**
+ * @param string $name
+ * @return string Getter function
+ */
+ public static function getGetter(string $name)
+ {
+ if (!isset(self::$getterMap[$name])) {
+ self::$getterMap[$name] = 'get' . ucfirst(self::toCamelCase($name));
+ }
+ return self::$getterMap[$name];
+ }
+
+ /**
+ * @param string $name
+ * @return string Setter function
+ */
+ public static function getSetter(string $name)
+ {
+ if (!isset(self::$setterMap[$name])) {
+ self::$setterMap[$name] = 'set' . ucfirst(self::toCamelCase($name));
+ }
+ return self::$setterMap[$name];
+ }
+
+ /**
+ * Convert string from camelCase to snake_case
+ *
+ * @param string $key
+ * @return string
+ */
+ public static function toSnakeCase(string $key)
+ {
+ if (!isset(self::$snakeCaseMap[$key])) {
+ self::$snakeCaseMap[$key] = strtolower(
+ preg_replace(['/([a-z\d])([A-Z])/', '/([^_])([A-Z][a-z])/'], '$1_$2', $key)
+ );
+ }
+ return self::$snakeCaseMap[$key];
+ }
+
+ /**
+ * Convert string from snake_case to camelCase
+ *
+ * @param string $key
+ * @return string
+ */
+ public static function toCamelCase(string $key)
+ {
+ if (!isset(self::$camelCaseMap[$key])) {
+ self::$camelCaseMap[$key] = lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', $key))));
+ }
+ return self::$camelCaseMap[$key];
+ }
+
+ private static function hasBinaryHeaderSuffix(string $key)
+ {
+ return substr_compare($key, '-bin', strlen($key) - 4) === 0;
+ }
+
+ private static function getPhpArraySerializer()
+ {
+ if (is_null(self::$phpArraySerializer)) {
+ self::$phpArraySerializer = new Serializer();
+ }
+ return self::$phpArraySerializer;
+ }
+
+ public static function loadKnownMetadataTypes()
+ {
+ foreach (KnownTypes::allKnownTypes() 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
diff --git a/Gax/src/ServerStream.php b/Gax/src/ServerStream.php
new file mode 100644
index 000000000000..3257e65504fa
--- /dev/null
+++ b/Gax/src/ServerStream.php
@@ -0,0 +1,128 @@
+call = $serverStreamingCall;
+ if (array_key_exists('resourcesGetMethod', $streamingDescriptor)) {
+ $this->resourcesGetMethod = $streamingDescriptor['resourcesGetMethod'];
+ }
+ $this->logger = $logger;
+ }
+
+ /**
+ * A generator which yields results from the server until the streaming call
+ * completes. Throws an ApiException if the streaming call failed.
+ *
+ * @throws ApiException
+ * @return \Generator|mixed
+ */
+ public function readAll()
+ {
+ $resourcesGetMethod = $this->resourcesGetMethod;
+ foreach ($this->call->responses() as $response) {
+ if ($this->logger && $response instanceof Message) {
+ $responseEvent = new RpcLogEvent();
+ $responseEvent->payload = $response->serializeToJsonString();
+ $responseEvent->processId = (int) getmypid();
+ $responseEvent->requestId = crc32((string) spl_object_id($this) . getmypid());
+
+ $this->logResponse($responseEvent);
+ }
+
+ if (!is_null($resourcesGetMethod)) {
+ foreach ($response->$resourcesGetMethod() as $resource) {
+ yield $resource;
+ }
+ } else {
+ yield $response;
+ }
+ }
+
+ // Errors in the REST transport will be thrown from there and not reach
+ // this handling. Successful REST server-streams will have an OK status.
+ $status = $this->call->getStatus();
+
+ if ($this->logger) {
+ $statusEvent = new RpcLogEvent();
+ $statusEvent->status = $status->code;
+ $statusEvent->processId = (int) getmypid();
+ $statusEvent->requestId = crc32((string) spl_object_id($this) . getmypid());
+
+ $this->logResponse($statusEvent);
+ }
+
+ if ($status->code !== Code::OK) {
+ throw ApiException::createFromStdClass($status);
+ }
+ }
+
+ /**
+ * Return the underlying call object.
+ *
+ * @return ServerStreamingCallInterface
+ */
+ public function getServerStreamingCall()
+ {
+ return $this->call;
+ }
+}
diff --git a/Gax/src/ServerStreamingCallInterface.php b/Gax/src/ServerStreamingCallInterface.php
new file mode 100644
index 000000000000..a78ff275edb9
--- /dev/null
+++ b/Gax/src/ServerStreamingCallInterface.php
@@ -0,0 +1,95 @@
+ $metadata Metadata to send with the call, if applicable
+ * (optional)
+ * @param array $options An array of options, possible keys:
+ * 'flags' => a number (optional)
+ * @return void
+ */
+ public function start($data, array $metadata = [], array $options = []);
+
+ /**
+ * @return mixed An iterator of response values.
+ */
+ public function responses();
+
+ /**
+ * Return the status of the server stream.
+ *
+ * @return \stdClass The API status.
+ */
+ public function getStatus();
+
+ /**
+ * @return mixed The metadata sent by the server.
+ */
+ public function getMetadata();
+
+ /**
+ * @return mixed The trailing metadata sent by the server.
+ */
+ public function getTrailingMetadata();
+
+ /**
+ * @return string The URI of the endpoint.
+ */
+ public function getPeer();
+
+ /**
+ * Cancels the call.
+ *
+ * @return void
+ */
+ public function cancel();
+
+ /**
+ * Set the CallCredentials for the underlying Call.
+ *
+ * @param mixed $call_credentials The CallCredentials object
+ *
+ * @return void
+ */
+ public function setCallCredentials($call_credentials);
+}
diff --git a/Gax/src/ServiceAddressTrait.php b/Gax/src/ServiceAddressTrait.php
new file mode 100644
index 000000000000..1343a233caed
--- /dev/null
+++ b/Gax/src/ServiceAddressTrait.php
@@ -0,0 +1,64 @@
+assertSame($expected, $actual);
+
+ return;
+ }
+
+ if (is_array($expected) || $expected instanceof RepeatedField) {
+ if (is_array($expected) === is_array($actual)) {
+ $this->assertEquals($expected, $actual);
+ }
+
+ $this->assertCount(count($expected), $actual);
+
+ $expectedValues = $this->getValues($expected);
+ $actualValues = $this->getValues($actual);
+
+ for ($i = 0; $i < count($expectedValues); $i++) {
+ $expectedElement = $expectedValues[$i];
+ $actualElement = $actualValues[$i];
+ $this->assertProtobufEquals($expectedElement, $actualElement);
+ }
+ } else {
+ $this->assertEquals($expected, $actual);
+ if ($expected instanceof Message) {
+ $pool = DescriptorPool::getGeneratedPool();
+ $descriptor = $pool->getDescriptorByClassName(get_class($expected));
+
+ $fieldCount = $descriptor->getFieldCount();
+ for ($i = 0; $i < $fieldCount; $i++) {
+ $field = $descriptor->getField($i);
+ $getter = Serializer::getGetter($field->getName());
+ $expectedFieldValue = $expected->$getter();
+ $actualFieldValue = $actual->$getter();
+ $this->assertProtobufEquals($expectedFieldValue, $actualFieldValue);
+ }
+ }
+ }
+ }
+
+ /**
+ * @param iterable $field
+ */
+ private function getValues($field)
+ {
+ return array_values(
+ is_array($field)
+ ? $field
+ : iterator_to_array($field)
+ );
+ }
+}
diff --git a/Gax/src/Testing/MessageAwareArrayComparator.php b/Gax/src/Testing/MessageAwareArrayComparator.php
new file mode 100644
index 000000000000..9feedb0ef324
--- /dev/null
+++ b/Gax/src/Testing/MessageAwareArrayComparator.php
@@ -0,0 +1,32 @@
+exporter = new MessageAwareExporter();
+ }
+}
diff --git a/Gax/src/Testing/MessageAwareExporter.php b/Gax/src/Testing/MessageAwareExporter.php
new file mode 100644
index 000000000000..2aeb29cb0122
--- /dev/null
+++ b/Gax/src/Testing/MessageAwareExporter.php
@@ -0,0 +1,47 @@
+responses = $responses;
+ $this->deserialize = $deserialize;
+ if (is_null($status)) {
+ $status = new MockStatus(Code::OK);
+ }
+ $this->status = $status;
+ }
+
+ /**
+ * @return mixed|null
+ * @throws ApiException
+ */
+ public function read()
+ {
+ if (count($this->responses) > 0) {
+ $resp = array_shift($this->responses);
+ if (is_null($resp)) {
+ // Null was added to the responses list to simulate a failed stream
+ // To ensure that getStatus can now be called, we clear the remaining
+ // responses and set writesDone to true
+ $this->responses = [];
+ $this->writesDone();
+ return null;
+ }
+ $obj = $this->deserializeMessage($resp, $this->deserialize);
+ return $obj;
+ } elseif ($this->writesDone) {
+ return null;
+ } else {
+ throw new ApiException(
+ 'No more responses to read, but closeWrite() not called - '
+ . 'this would be blocking',
+ Grpc\STATUS_INTERNAL,
+ null
+ );
+ }
+ }
+
+ /**
+ * @return stdClass|null
+ * @throws ApiException
+ */
+ public function getStatus()
+ {
+ if (count($this->responses) > 0) {
+ throw new ApiException(
+ 'Calls to getStatus() will block if all responses are not read',
+ Grpc\STATUS_INTERNAL,
+ null
+ );
+ }
+ if (!$this->writesDone) {
+ throw new ApiException(
+ 'Calls to getStatus() will block if closeWrite() not called',
+ Grpc\STATUS_INTERNAL,
+ null
+ );
+ }
+ return $this->status;
+ }
+
+ /**
+ * Save the request object, to be retrieved via getReceivedCalls()
+ * @param Message|mixed $request The request object
+ * @param array $options An array of options.
+ * @throws ApiException
+ */
+ public function write($request, array $options = [])
+ {
+ if ($this->writesDone) {
+ throw new ApiException(
+ 'Cannot call write() after writesDone()',
+ Grpc\STATUS_INTERNAL,
+ null
+ );
+ }
+ if (is_a($request, '\Google\Protobuf\Internal\Message')) {
+ /** @var Message $newRequest */
+ $newRequest = new $request();
+ $newRequest->mergeFromString($request->serializeToString());
+ $request = $newRequest;
+ }
+ $this->receivedWrites[] = $request;
+ }
+
+ /**
+ * Set writesDone to true
+ */
+ public function writesDone()
+ {
+ $this->writesDone = true;
+ }
+
+ /**
+ * Return a list of calls made to write(), and clear $receivedFuncCalls.
+ *
+ * @return mixed[] An array of received requests
+ */
+ public function popReceivedCalls()
+ {
+ $receivedFuncCallsTemp = $this->receivedWrites;
+ $this->receivedWrites = [];
+ return $receivedFuncCallsTemp;
+ }
+}
diff --git a/Gax/src/Testing/MockClientStreamingCall.php b/Gax/src/Testing/MockClientStreamingCall.php
new file mode 100644
index 000000000000..ca28e3dbf24c
--- /dev/null
+++ b/Gax/src/Testing/MockClientStreamingCall.php
@@ -0,0 +1,111 @@
+mockUnaryCall = new MockUnaryCall($response, $deserialize, $status);
+ }
+
+ /**
+ * Immediately return the preset response object and status.
+ * @return array The response object and status.
+ */
+ public function wait()
+ {
+ $this->waitCalled = true;
+ return $this->mockUnaryCall->wait();
+ }
+
+ /**
+ * Save the request object, to be retrieved via getReceivedCalls()
+ * @param Message|mixed $request The request object
+ * @param array $options An array of options
+ * @throws ApiException
+ */
+ public function write($request, array $options = [])
+ {
+ if ($this->waitCalled) {
+ throw new ApiException('Cannot call write() after wait()', Code::INTERNAL, ApiStatus::INTERNAL);
+ }
+ if (is_a($request, '\Google\Protobuf\Internal\Message')) {
+ /** @var Message $newRequest */
+ $newRequest = new $request();
+ $newRequest->mergeFromString($request->serializeToString());
+ $request = $newRequest;
+ }
+ $this->receivedWrites[] = $request;
+ }
+
+ /**
+ * Return a list of calls made to write(), and clear $receivedFuncCalls.
+ *
+ * @return mixed[] An array of received requests
+ */
+ public function popReceivedCalls()
+ {
+ $receivedFuncCallsTemp = $this->receivedWrites;
+ $this->receivedWrites = [];
+ return $receivedFuncCallsTemp;
+ }
+}
diff --git a/Gax/src/Testing/MockGrpcTransport.php b/Gax/src/Testing/MockGrpcTransport.php
new file mode 100644
index 000000000000..d6af2fe4f4b7
--- /dev/null
+++ b/Gax/src/Testing/MockGrpcTransport.php
@@ -0,0 +1,142 @@
+mockCall = $mockCall;
+ $opts = ['credentials' => ChannelCredentials::createSsl()];
+ parent::__construct('', $opts, logger: $logger);
+ }
+
+ /**
+ * @param string $method
+ * @param array $arguments
+ * @param callable $deserialize
+ */
+ protected function _simpleRequest(
+ $method,
+ $arguments,
+ $deserialize,
+ array $metadata = [],
+ array $options = []
+ ) {
+ $this->logCall($method, $deserialize, $metadata, $options, $arguments);
+ return $this->mockCall;
+ }
+
+ /**
+ * @param string $method
+ * @param callable $deserialize
+ */
+ protected function _clientStreamRequest(
+ $method,
+ $deserialize,
+ array $metadata = [],
+ array $options = []
+ ) {
+ $this->logCall($method, $deserialize, $metadata, $options);
+ return $this->mockCall;
+ }
+
+ /**
+ * @param string $method
+ * @param array $arguments
+ * @param callable $deserialize
+ */
+ protected function _serverStreamRequest(
+ $method,
+ $arguments,
+ $deserialize,
+ array $metadata = [],
+ array $options = []
+ ) {
+ $this->logCall($method, $deserialize, $metadata, $options, $arguments);
+ return $this->mockCall;
+ }
+
+ /**
+ * @param string $method
+ * @param callable $deserialize
+ */
+ protected function _bidiRequest(
+ $method,
+ $deserialize,
+ array $metadata = [],
+ array $options = []
+ ) {
+ $this->logCall($method, $deserialize, $metadata, $options);
+ return $this->mockCall;
+ }
+
+ /**
+ * @param string $method
+ * @param callable $deserialize
+ * @param array $arguments
+ */
+ private function logCall(
+ $method,
+ $deserialize,
+ array $metadata = [],
+ array $options = [],
+ $arguments = null
+ ) {
+ $this->requestArguments = [
+ 'method' => $method,
+ 'arguments' => $arguments,
+ 'deserialize' => $deserialize,
+ 'metadata' => $metadata,
+ 'options' => $options,
+ ];
+ }
+
+ public function getRequestArguments()
+ {
+ return $this->requestArguments;
+ }
+}
diff --git a/Gax/src/Testing/MockRequest.php b/Gax/src/Testing/MockRequest.php
new file mode 100644
index 000000000000..be0099103a52
--- /dev/null
+++ b/Gax/src/Testing/MockRequest.php
@@ -0,0 +1,85 @@
+google.apicore.testing.MockRequest
+ *
+ * @internal
+ */
+class MockRequest extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * Generated from protobuf field string page_token = 1;
+ */
+ protected $page_token = '';
+ /**
+ * Generated from protobuf field uint64 page_size = 2;
+ */
+ protected $page_size = 0;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $page_token
+ * @type int|string $page_size
+ * }
+ */
+ public function __construct($data = null)
+ {
+ Mocks::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * Generated from protobuf field 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;
+ }
+}
diff --git a/Gax/src/Testing/MockRequestBody.php b/Gax/src/Testing/MockRequestBody.php
new file mode 100644
index 000000000000..6039f856965b
--- /dev/null
+++ b/Gax/src/Testing/MockRequestBody.php
@@ -0,0 +1,646 @@
+google.apicore.testing.MockRequestBody
+ *
+ * @internal
+ */
+class MockRequestBody extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * Generated from protobuf field string name = 1;
+ */
+ protected $name = '';
+ /**
+ * Generated from protobuf field uint64 number = 2;
+ */
+ protected $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;
+ */
+ protected $nested_message = null;
+ /**
+ * Generated from protobuf field .google.protobuf.BytesValue bytes_value = 5;
+ */
+ protected $bytes_value = null;
+ /**
+ * Generated from protobuf field .google.protobuf.Duration duration_value = 6;
+ */
+ protected $duration_value = null;
+ /**
+ * Generated from protobuf field .google.protobuf.FieldMask field_mask = 7;
+ */
+ protected $field_mask = null;
+ /**
+ * Generated from protobuf field .google.protobuf.Int64Value int64_value = 8;
+ */
+ protected $int64_value = null;
+ /**
+ * Generated from protobuf field .google.protobuf.ListValue list_value = 9;
+ */
+ protected $list_value = null;
+ /**
+ * Generated from protobuf field .google.protobuf.StringValue string_value = 10;
+ */
+ protected $string_value = null;
+ /**
+ * Generated from protobuf field .google.protobuf.Struct struct_value = 11;
+ */
+ protected $struct_value = null;
+ /**
+ * Generated from protobuf field .google.protobuf.Timestamp timestamp_value = 12;
+ */
+ protected $timestamp_value = null;
+ /**
+ * Generated from protobuf field .google.protobuf.Value value_value = 13;
+ */
+ protected $value_value = null;
+ protected $oneof_field;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $name
+ * @type int|string $number
+ * @type string[]|\Google\Protobuf\RepeatedField $repeated_field
+ * @type \Google\ApiCore\Testing\MockRequestBody $nested_message
+ * @type \Google\Protobuf\BytesValue $bytes_value
+ * @type \Google\Protobuf\Duration $duration_value
+ * @type \Google\Protobuf\FieldMask $field_mask
+ * @type \Google\Protobuf\Int64Value $int64_value
+ * @type \Google\Protobuf\ListValue $list_value
+ * @type \Google\Protobuf\StringValue $string_value
+ * @type \Google\Protobuf\Struct $struct_value
+ * @type \Google\Protobuf\Timestamp $timestamp_value
+ * @type \Google\Protobuf\Value $value_value
+ * @type string $field_1
+ * @type string $field_2
+ * @type string $field_3
+ * }
+ */
+ public function __construct($data = null)
+ {
+ \GPBMetadata\ApiCore\Testing\Mocks::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * Generated from protobuf field 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\RepeatedField
+ */
+ public function getRepeatedField()
+ {
+ return $this->repeated_field;
+ }
+
+ /**
+ * Generated from protobuf field repeated string repeated_field = 3;
+ * @param string[]|\Google\Protobuf\RepeatedField $var
+ * @return $this
+ */
+ public function setRepeatedField($var)
+ {
+ $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::STRING);
+ $this->repeated_field = $arr;
+
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field .google.apicore.testing.MockRequestBody nested_message = 4;
+ * @return \Google\ApiCore\Testing\MockRequestBody
+ */
+ public function getNestedMessage()
+ {
+ return isset($this->nested_message) ? $this->nested_message : null;
+ }
+
+ public function hasNestedMessage()
+ {
+ return isset($this->nested_message);
+ }
+
+ public function clearNestedMessage()
+ {
+ unset($this->nested_message);
+ }
+
+ /**
+ * Generated from protobuf field .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 isset($this->bytes_value) ? $this->bytes_value : null;
+ }
+
+ public function hasBytesValue()
+ {
+ return isset($this->bytes_value);
+ }
+
+ public function clearBytesValue()
+ {
+ unset($this->bytes_value);
+ }
+
+ /**
+ * Returns the unboxed value from getBytesValue()
+
+ * Generated from protobuf field .google.protobuf.BytesValue bytes_value = 5;
+ * @return string|null
+ */
+ public function getBytesValueUnwrapped()
+ {
+ return $this->readWrapperValue('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;
+ }
+
+ /**
+ * Sets the field by wrapping a primitive type in a Google\Protobuf\BytesValue object.
+
+ * Generated from protobuf field .google.protobuf.BytesValue bytes_value = 5;
+ * @param string|null $var
+ * @return $this
+ */
+ public function setBytesValueUnwrapped($var)
+ {
+ $this->writeWrapperValue('bytes_value', $var);
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field .google.protobuf.Duration duration_value = 6;
+ * @return \Google\Protobuf\Duration
+ */
+ public function getDurationValue()
+ {
+ return isset($this->duration_value) ? $this->duration_value : null;
+ }
+
+ public function hasDurationValue()
+ {
+ return isset($this->duration_value);
+ }
+
+ public function clearDurationValue()
+ {
+ unset($this->duration_value);
+ }
+
+ /**
+ * Generated from protobuf field .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 isset($this->field_mask) ? $this->field_mask : null;
+ }
+
+ public function hasFieldMask()
+ {
+ return isset($this->field_mask);
+ }
+
+ public function clearFieldMask()
+ {
+ unset($this->field_mask);
+ }
+
+ /**
+ * Generated from protobuf field .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 isset($this->int64_value) ? $this->int64_value : null;
+ }
+
+ public function hasInt64Value()
+ {
+ return isset($this->int64_value);
+ }
+
+ public function clearInt64Value()
+ {
+ unset($this->int64_value);
+ }
+
+ /**
+ * Returns the unboxed value from getInt64Value()
+
+ * Generated from protobuf field .google.protobuf.Int64Value int64_value = 8;
+ * @return int|string|null
+ */
+ public function getInt64ValueUnwrapped()
+ {
+ return $this->readWrapperValue('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;
+ }
+
+ /**
+ * Sets the field by wrapping a primitive type in a Google\Protobuf\Int64Value object.
+
+ * Generated from protobuf field .google.protobuf.Int64Value int64_value = 8;
+ * @param int|string|null $var
+ * @return $this
+ */
+ public function setInt64ValueUnwrapped($var)
+ {
+ $this->writeWrapperValue('int64_value', $var);
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field .google.protobuf.ListValue list_value = 9;
+ * @return \Google\Protobuf\ListValue
+ */
+ public function getListValue()
+ {
+ return isset($this->list_value) ? $this->list_value : null;
+ }
+
+ public function hasListValue()
+ {
+ return isset($this->list_value);
+ }
+
+ public function clearListValue()
+ {
+ unset($this->list_value);
+ }
+
+ /**
+ * Generated from protobuf field .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 isset($this->string_value) ? $this->string_value : null;
+ }
+
+ public function hasStringValue()
+ {
+ return isset($this->string_value);
+ }
+
+ public function clearStringValue()
+ {
+ unset($this->string_value);
+ }
+
+ /**
+ * Returns the unboxed value from getStringValue()
+
+ * Generated from protobuf field .google.protobuf.StringValue string_value = 10;
+ * @return string|null
+ */
+ public function getStringValueUnwrapped()
+ {
+ return $this->readWrapperValue('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;
+ }
+
+ /**
+ * Sets the field by wrapping a primitive type in a Google\Protobuf\StringValue object.
+
+ * Generated from protobuf field .google.protobuf.StringValue string_value = 10;
+ * @param string|null $var
+ * @return $this
+ */
+ public function setStringValueUnwrapped($var)
+ {
+ $this->writeWrapperValue('string_value', $var);
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field .google.protobuf.Struct struct_value = 11;
+ * @return \Google\Protobuf\Struct
+ */
+ public function getStructValue()
+ {
+ return isset($this->struct_value) ? $this->struct_value : null;
+ }
+
+ public function hasStructValue()
+ {
+ return isset($this->struct_value);
+ }
+
+ public function clearStructValue()
+ {
+ unset($this->struct_value);
+ }
+
+ /**
+ * Generated from protobuf field .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 isset($this->timestamp_value) ? $this->timestamp_value : null;
+ }
+
+ public function hasTimestampValue()
+ {
+ return isset($this->timestamp_value);
+ }
+
+ public function clearTimestampValue()
+ {
+ unset($this->timestamp_value);
+ }
+
+ /**
+ * Generated from protobuf field .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 isset($this->value_value) ? $this->value_value : null;
+ }
+
+ public function hasValueValue()
+ {
+ return isset($this->value_value);
+ }
+
+ public function clearValueValue()
+ {
+ unset($this->value_value);
+ }
+
+ /**
+ * Generated from protobuf field .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);
+ }
+
+ public function hasField1()
+ {
+ return $this->hasOneof(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);
+ }
+
+ public function hasField2()
+ {
+ return $this->hasOneof(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);
+ }
+
+ public function hasField3()
+ {
+ return $this->hasOneof(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');
+ }
+}
diff --git a/Gax/src/Testing/MockResponse.php b/Gax/src/Testing/MockResponse.php
new file mode 100644
index 000000000000..c9135ce4ee13
--- /dev/null
+++ b/Gax/src/Testing/MockResponse.php
@@ -0,0 +1,165 @@
+google.apicore.testing.MockResponse
+ *
+ * @internal
+ */
+class MockResponse extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * Generated from protobuf field string name = 1;
+ */
+ protected $name = '';
+ /**
+ * Generated from protobuf field uint64 number = 2;
+ */
+ protected $number = 0;
+ /**
+ * Generated from protobuf field repeated string resources_list = 3;
+ */
+ private $resources_list;
+ /**
+ * Generated from protobuf field string next_page_token = 4;
+ */
+ protected $next_page_token = '';
+ /**
+ * Generated from protobuf field map resources_map = 5;
+ */
+ private $resources_map;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $name
+ * @type int|string $number
+ * @type string[]|\Google\Protobuf\RepeatedField $resources_list
+ * @type string $next_page_token
+ * @type array|\Google\Protobuf\Internal\MapField $resources_map
+ * }
+ */
+ public function __construct($data = null)
+ {
+ \GPBMetadata\ApiCore\Testing\Mocks::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * Generated from protobuf field 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\RepeatedField
+ */
+ public function getResourcesList()
+ {
+ return $this->resources_list;
+ }
+
+ /**
+ * Generated from protobuf field repeated string resources_list = 3;
+ * @param string[]|\Google\Protobuf\RepeatedField $var
+ * @return $this
+ */
+ public function setResourcesList($var)
+ {
+ $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::STRING);
+ $this->resources_list = $arr;
+
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field 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;
+ }
+
+ /**
+ * Generated from protobuf field map resources_map = 5;
+ * @return \Google\Protobuf\Internal\MapField
+ */
+ public function getResourcesMap()
+ {
+ return $this->resources_map;
+ }
+
+ /**
+ * Generated from protobuf field map resources_map = 5;
+ * @param array|\Google\Protobuf\Internal\MapField $var
+ * @return $this
+ */
+ public function setResourcesMap($var)
+ {
+ $arr = GPBUtil::checkMapField($var, \Google\Protobuf\Internal\GPBType::STRING, \Google\Protobuf\Internal\GPBType::STRING);
+ $this->resources_map = $arr;
+
+ return $this;
+ }
+}
diff --git a/Gax/src/Testing/MockServerStreamingCall.php b/Gax/src/Testing/MockServerStreamingCall.php
new file mode 100644
index 000000000000..d0dcead5d2bf
--- /dev/null
+++ b/Gax/src/Testing/MockServerStreamingCall.php
@@ -0,0 +1,98 @@
+responses = $responses;
+ $this->deserialize = $deserialize;
+ if (is_null($status)) {
+ $status = new MockStatus(Code::OK, 'OK', []);
+ } elseif ($status instanceof stdClass) {
+ if (!property_exists($status, 'metadata')) {
+ $status->metadata = [];
+ }
+ }
+ $this->status = $status;
+ }
+
+ public function responses()
+ {
+ while (count($this->responses) > 0) {
+ $resp = array_shift($this->responses);
+ $obj = $this->deserializeMessage($resp, $this->deserialize);
+ yield $obj;
+ }
+ }
+
+ /**
+ * @return stdClass|null
+ * @throws ApiException
+ */
+ public function getStatus()
+ {
+ if (count($this->responses) > 0) {
+ throw new ApiException(
+ 'Calls to getStatus() will block if all responses are not read',
+ Code::INTERNAL,
+ ApiStatus::INTERNAL
+ );
+ }
+ return $this->status;
+ }
+}
diff --git a/Gax/src/Testing/MockStatus.php b/Gax/src/Testing/MockStatus.php
new file mode 100644
index 000000000000..6f290635631b
--- /dev/null
+++ b/Gax/src/Testing/MockStatus.php
@@ -0,0 +1,53 @@
+code = $code;
+ $this->details = $details;
+ $this->metadata = $metadata;
+ }
+}
diff --git a/Gax/src/Testing/MockStubTrait.php b/Gax/src/Testing/MockStubTrait.php
new file mode 100644
index 000000000000..db69910aafbe
--- /dev/null
+++ b/Gax/src/Testing/MockStubTrait.php
@@ -0,0 +1,296 @@
+deserialize = $deserialize;
+ }
+
+ /**
+ * Overrides the _simpleRequest method in \Grpc\BaseStub
+ * (https://github.com/grpc/grpc/blob/master/src/php/lib/Grpc/BaseStub.php)
+ * Returns a MockUnaryCall object that will return the first item from $responses
+ * @param string $method The API method name to be called
+ * @param \Google\Protobuf\Internal\Message $argument The request object to the API method
+ * @param callable $deserialize A function to deserialize the response object
+ * @param array $metadata
+ * @param array $options
+ * @return MockUnaryCall
+ */
+ public function _simpleRequest(
+ $method,
+ $argument,
+ $deserialize,
+ array $metadata = [],
+ array $options = []
+ ) {
+ $this->receivedFuncCalls[] = new ReceivedRequest($method, $argument, $deserialize, $metadata, $options);
+ if (count($this->responses) < 1) {
+ throw new UnderflowException('ran out of responses');
+ }
+ list($response, $status) = array_shift($this->responses);
+ $call = new MockUnaryCall($response, $deserialize, $status);
+ $this->callObjects[] = $call;
+ return $call;
+ }
+
+ /**
+ * Overrides the _clientStreamRequest method in \Grpc\BaseStub
+ * (https://github.com/grpc/grpc/blob/master/src/php/lib/Grpc/BaseStub.php)
+ * Returns a MockClientStreamingCall object that will return the first item from $responses
+ *
+ * @param string $method The name of the method to call
+ * @param callable $deserialize A function that deserializes the responses
+ * @param array $metadata A metadata map to send to the server
+ * (optional)
+ * @param array $options An array of options (optional)
+ *
+ * @return MockClientStreamingCall The active call object
+ */
+ public function _clientStreamRequest(
+ $method,
+ $deserialize,
+ array $metadata = [],
+ array $options = []
+ ) {
+ $this->receivedFuncCalls[] = new ReceivedRequest($method, null, $deserialize, $metadata, $options);
+ if (count($this->responses) < 1) {
+ throw new UnderflowException('ran out of responses');
+ }
+ list($response, $status) = array_shift($this->responses);
+ $call = new MockClientStreamingCall($response, $deserialize, $status);
+ $this->callObjects[] = $call;
+ return $call;
+ }
+
+ /**
+ * Overrides the _serverStreamRequest method in \Grpc\BaseStub
+ * (https://github.com/grpc/grpc/blob/master/src/php/lib/Grpc/BaseStub.php)
+ * Returns a MockServerStreamingCall object that will stream items from $responses, and return
+ * a final status of $serverStreamingStatus.
+ *
+ * @param string $method The name of the method to call
+ * @param \Google\Protobuf\Internal\Message $argument The argument to the method
+ * @param callable $deserialize A function that deserializes the responses
+ * @param array $metadata A metadata map to send to the server
+ * (optional)
+ * @param array $options An array of options (optional)
+ *
+ * @return MockServerStreamingCall The active call object
+ */
+ public function _serverStreamRequest(
+ $method,
+ $argument,
+ $deserialize,
+ array $metadata = [],
+ array $options = []
+ ) {
+
+ if (is_a($argument, '\Google\Protobuf\Internal\Message')) {
+ /** @var Message $newArgument */
+ $newArgument = new $argument();
+ $newArgument->mergeFromString($argument->serializeToString());
+ $argument = $newArgument;
+ }
+ $this->receivedFuncCalls[] = new ReceivedRequest($method, $argument, $deserialize, $metadata, $options);
+ $responses = self::stripStatusFromResponses($this->responses);
+ $this->responses = [];
+ $call = new MockServerStreamingCall($responses, $deserialize, $this->serverStreamingStatus);
+ $this->callObjects[] = $call;
+ return $call;
+ }
+
+ /**
+ * Overrides the _bidiRequest method in \Grpc\BaseStub
+ * (https://github.com/grpc/grpc/blob/master/src/php/lib/Grpc/BaseStub.php)
+ * Returns a MockBidiStreamingCall object that will stream items from $responses, and return
+ * a final status of $serverStreamingStatus.
+ *
+ * @param string $method The name of the method to call
+ * @param callable $deserialize A function that deserializes the responses
+ * @param array $metadata A metadata map to send to the server
+ * (optional)
+ * @param array $options An array of options (optional)
+ *
+ * @return MockBidiStreamingCall The active call object
+ */
+ public function _bidiRequest(
+ $method,
+ $deserialize,
+ array $metadata = [],
+ array $options = []
+ ) {
+
+ $this->receivedFuncCalls[] = new ReceivedRequest($method, null, $deserialize, $metadata, $options);
+ $responses = self::stripStatusFromResponses($this->responses);
+ $this->responses = [];
+ $call = new MockBidiStreamingCall($responses, $deserialize, $this->serverStreamingStatus);
+ $this->callObjects[] = $call;
+ return $call;
+ }
+
+ public static function stripStatusFromResponses($responses)
+ {
+ $strippedResponses = [];
+ foreach ($responses as $response) {
+ list($resp, $_) = $response;
+ $strippedResponses[] = $resp;
+ }
+ return $strippedResponses;
+ }
+
+ /**
+ * Add a response object, and an optional status, to the list of responses to be returned via
+ * _simpleRequest.
+ * @param \Google\Protobuf\Internal\Message $response
+ * @param stdClass $status
+ */
+ public function addResponse($response, ?stdClass $status = null)
+ {
+ if (!$this->deserialize && $response) {
+ $this->deserialize = [get_class($response), 'decode'];
+ }
+
+ if (is_a($response, '\Google\Protobuf\Internal\Message')) {
+ $response = $response->serializeToString();
+ }
+ $this->responses[] = [$response, $status];
+ }
+
+ /**
+ * Set the status object to be used when creating streaming calls.
+ *
+ * @param stdClass $status
+ */
+ public function setStreamingStatus(stdClass $status)
+ {
+ $this->serverStreamingStatus = $status;
+ }
+
+ /**
+ * Return a list of calls made to _simpleRequest, and clear $receivedFuncCalls.
+ *
+ * @return ReceivedRequest[] An array of received requests
+ */
+ public function popReceivedCalls()
+ {
+ $receivedFuncCallsTemp = $this->receivedFuncCalls;
+ $this->receivedFuncCalls = [];
+ return $receivedFuncCallsTemp;
+ }
+
+ /**
+ * @return int The number of calls received.
+ */
+ public function getReceivedCallCount()
+ {
+ return count($this->receivedFuncCalls);
+ }
+
+ /**
+ * @return mixed[] The call objects created by calls to the stub
+ */
+ public function popCallObjects()
+ {
+ $callObjectsTemp = $this->callObjects;
+ $this->callObjects = [];
+ return $callObjectsTemp;
+ }
+
+ /**
+ * @return bool True if $receivedFuncCalls and $response are empty.
+ */
+ public function isExhausted()
+ {
+ return count($this->receivedFuncCalls) === 0
+ && count($this->responses) === 0;
+ }
+
+ /**
+ * @param mixed $responseObject
+ * @param stdClass|null $status
+ * @param callable $deserialize
+ * @return static An instance of the current class type.
+ */
+ public static function create($responseObject, ?stdClass $status = null, ?callable $deserialize = null)
+ {
+ $stub = new static($deserialize); // @phpstan-ignore-line
+ $stub->addResponse($responseObject, $status);
+ return $stub;
+ }
+
+ /**
+ * Creates a sequence such that the responses are returned in order.
+ * @param mixed[] $sequence
+ * @param callable $deserialize
+ * @param stdClass $finalStatus
+ * @return static An instance of the current class type.
+ */
+ public static function createWithResponseSequence(array $sequence, ?callable $deserialize = null, ?stdClass $finalStatus = null)
+ {
+ $stub = new static($deserialize); // @phpstan-ignore-line
+ foreach ($sequence as $elem) {
+ if (count($elem) == 1) {
+ list($resp, $status) = [$elem, null];
+ } else {
+ list($resp, $status) = $elem;
+ }
+ $stub->addResponse($resp, $status);
+ }
+ if ($finalStatus) {
+ $stub->setStreamingStatus($finalStatus);
+ }
+ return $stub;
+ }
+}
diff --git a/Gax/src/Testing/MockTransport.php b/Gax/src/Testing/MockTransport.php
new file mode 100644
index 000000000000..333cb0b519d8
--- /dev/null
+++ b/Gax/src/Testing/MockTransport.php
@@ -0,0 +1,114 @@
+agentHeaderDescriptor = $agentHeaderDescriptor;
+ }
+
+ public function startUnaryCall(Call $call, array $options)
+ {
+ $call = call_user_func([$this, $call->getMethod()], $call, $options);
+ return $promise = new Promise(
+ function () use ($call, &$promise) {
+ list($response, $status) = $call->wait();
+
+ if ($status->code == Code::OK) {
+ $promise->resolve($response);
+ } else {
+ throw ApiException::createFromStdClass($status);
+ }
+ },
+ [$call, 'cancel']
+ );
+ }
+
+ public function startBidiStreamingCall(Call $call, array $options)
+ {
+ $newArgs = ['/' . $call->getMethod(), $this->deserialize, $options, $options];
+ $response = $this->_bidiRequest(...$newArgs);
+ return new BidiStream($response, $call->getDescriptor());
+ }
+
+ public function startClientStreamingCall(Call $call, array $options)
+ {
+ $newArgs = ['/' . $call->getMethod(), $this->deserialize, $options, $options];
+ $response = $this->_clientStreamRequest(...$newArgs);
+ return new ClientStream($response, $call->getDescriptor());
+ }
+
+ public function startServerStreamingCall(Call $call, array $options)
+ {
+ $newArgs = ['/' . $call->getMethod(), $call->getMessage(), $this->deserialize, $options, $options];
+ $response = $this->_serverStreamRequest(...$newArgs);
+ return new ServerStream($response, $call->getDescriptor());
+ }
+
+ public function __call(string $name, array $arguments)
+ {
+ $call = $arguments[0];
+ $options = $arguments[1];
+ $decode = $call->getDecodeType() ? [$call->getDecodeType(), 'decode'] : null;
+ return $this->_simpleRequest(
+ '/' . $call->getMethod(),
+ $call->getMessage(),
+ $decode,
+ isset($options['headers']) ? $options['headers'] : [],
+ $options
+ );
+ }
+
+ public function close()
+ {
+ // does nothing
+ }
+}
diff --git a/Gax/src/Testing/MockUnaryCall.php b/Gax/src/Testing/MockUnaryCall.php
new file mode 100644
index 000000000000..431b041bfa47
--- /dev/null
+++ b/Gax/src/Testing/MockUnaryCall.php
@@ -0,0 +1,83 @@
+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,
+ ];
+ }
+}
diff --git a/Gax/src/Testing/ProtobufGPBEmptyComparator.php b/Gax/src/Testing/ProtobufGPBEmptyComparator.php
new file mode 100644
index 000000000000..b6871a8e4884
--- /dev/null
+++ b/Gax/src/Testing/ProtobufGPBEmptyComparator.php
@@ -0,0 +1,61 @@
+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'
+ );
+ }
+ }
+}
diff --git a/Gax/src/Testing/ReceivedRequest.php b/Gax/src/Testing/ReceivedRequest.php
new file mode 100644
index 000000000000..97cc1d2714d6
--- /dev/null
+++ b/Gax/src/Testing/ReceivedRequest.php
@@ -0,0 +1,79 @@
+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'];
+ }
+}
diff --git a/Gax/src/Testing/SerializationTrait.php b/Gax/src/Testing/SerializationTrait.php
new file mode 100644
index 000000000000..c703f0dea310
--- /dev/null
+++ b/Gax/src/Testing/SerializationTrait.php
@@ -0,0 +1,72 @@
+$deserializeFunc($message);
+ } elseif (is_string($message)) {
+ $obj->mergeFromString($message);
+ }
+
+ return $obj;
+ }
+
+ // Protobuf-PHP implementation
+ return call_user_func($deserialize, $message);
+ }
+}
diff --git a/Gax/src/Testing/mocks.proto b/Gax/src/Testing/mocks.proto
new file mode 100644
index 000000000000..314f131397a2
--- /dev/null
+++ b/Gax/src/Testing/mocks.proto
@@ -0,0 +1,46 @@
+syntax = "proto3";
+
+package google.apicore.testing;
+
+import "google/protobuf/field_mask.proto";
+import "google/protobuf/timestamp.proto";
+import "google/protobuf/duration.proto";
+import "google/protobuf/struct.proto";
+import "google/protobuf/wrappers.proto";
+
+option php_namespace = "Google\\ApiCore\\Testing";
+option php_metadata_namespace = "GPBMetadata\\ApiCore\\Testing";
+
+message MockRequest {
+ string page_token = 1;
+ uint64 page_size = 2;
+}
+
+message MockResponse {
+ string name = 1;
+ uint64 number = 2;
+ repeated string resources_list = 3;
+ string next_page_token = 4;
+ map resources_map = 5;
+}
+
+message MockRequestBody {
+ string name = 1;
+ uint64 number = 2;
+ repeated string repeated_field = 3;
+ MockRequestBody nested_message = 4;
+ google.protobuf.BytesValue bytes_value = 5;
+ google.protobuf.Duration duration_value = 6;
+ google.protobuf.FieldMask field_mask = 7;
+ google.protobuf.Int64Value int64_value = 8;
+ google.protobuf.ListValue list_value = 9;
+ google.protobuf.StringValue string_value = 10;
+ google.protobuf.Struct struct_value = 11;
+ google.protobuf.Timestamp timestamp_value = 12;
+ google.protobuf.Value value_value = 13;
+ oneof oneof_field {
+ string field_1 = 14;
+ string field_2 = 15;
+ string field_3 = 16;
+ }
+}
diff --git a/Gax/src/Transport/Grpc/ForwardingCall.php b/Gax/src/Transport/Grpc/ForwardingCall.php
new file mode 100644
index 000000000000..2ec4f73fc4aa
--- /dev/null
+++ b/Gax/src/Transport/Grpc/ForwardingCall.php
@@ -0,0 +1,90 @@
+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();
+ }
+}
diff --git a/Gax/src/Transport/Grpc/ForwardingServerStreamingCall.php b/Gax/src/Transport/Grpc/ForwardingServerStreamingCall.php
new file mode 100644
index 000000000000..10e4ee706f37
--- /dev/null
+++ b/Gax/src/Transport/Grpc/ForwardingServerStreamingCall.php
@@ -0,0 +1,65 @@
+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();
+ }
+}
diff --git a/Gax/src/Transport/Grpc/ForwardingUnaryCall.php b/Gax/src/Transport/Grpc/ForwardingUnaryCall.php
new file mode 100644
index 000000000000..81b00646dafd
--- /dev/null
+++ b/Gax/src/Transport/Grpc/ForwardingUnaryCall.php
@@ -0,0 +1,56 @@
+innerCall->wait();
+ }
+}
diff --git a/Gax/src/Transport/Grpc/ServerStreamingCallWrapper.php b/Gax/src/Transport/Grpc/ServerStreamingCallWrapper.php
new file mode 100644
index 000000000000..bdf49d624f1d
--- /dev/null
+++ b/Gax/src/Transport/Grpc/ServerStreamingCallWrapper.php
@@ -0,0 +1,123 @@
+stream = $stream;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function start($data, array $metadata = [], array $callOptions = [])
+ {
+ $this->stream->start($data, $metadata, $callOptions);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function responses()
+ {
+ foreach ($this->stream->responses() as $response) {
+ yield $response;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getStatus()
+ {
+ return $this->stream->getStatus();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getMetadata()
+ {
+ return $this->stream->getMetadata();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getTrailingMetadata()
+ {
+ return $this->stream->getTrailingMetadata();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getPeer()
+ {
+ return $this->stream->getPeer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function cancel()
+ {
+ $this->stream->cancel();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setCallCredentials($call_credentials)
+ {
+ $this->stream->setCallCredentials($call_credentials);
+ }
+}
diff --git a/Gax/src/Transport/Grpc/UnaryInterceptorInterface.php b/Gax/src/Transport/Grpc/UnaryInterceptorInterface.php
new file mode 100644
index 000000000000..fa510ea39a6f
--- /dev/null
+++ b/Gax/src/Transport/Grpc/UnaryInterceptorInterface.php
@@ -0,0 +1,61 @@
+baseUri = $baseUri;
+ $this->httpHandler = $httpHandler;
+ $this->transportName = 'grpc-fallback';
+ }
+
+ /**
+ * Builds a GrpcFallbackTransport.
+ *
+ * @param string $apiEndpoint
+ * The address of the API remote host, for example "example.googleapis.com".
+ * @param array $config {
+ * Config options used to construct the grpc-fallback transport.
+ *
+ * @type callable $httpHandler A handler used to deliver PSR-7 requests.
+ * }
+ * @return GrpcFallbackTransport
+ * @throws ValidationException
+ */
+ public static function build(string $apiEndpoint, array $config = [])
+ {
+ $config += [
+ 'httpHandler' => null,
+ 'clientCertSource' => null,
+ 'logger' => null,
+ ];
+ list($baseUri, $port) = self::normalizeServiceAddress($apiEndpoint);
+ $httpHandler = $config['httpHandler'] ?: self::buildHttpHandlerAsync(logger: $config['logger']);
+ $transport = new GrpcFallbackTransport("$baseUri:$port", $httpHandler);
+ if ($config['clientCertSource']) {
+ $transport->configureMtlsChannel($config['clientCertSource']);
+ }
+ return $transport;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function startUnaryCall(Call $call, array $options)
+ {
+ $httpHandler = $this->httpHandler;
+
+ $options['requestId'] = crc32((string) spl_object_id($call) . getmypid());
+
+ return $httpHandler(
+ $this->buildRequest($call, $options),
+ $this->getCallOptions($options)
+ )->then(
+ function (ResponseInterface $response) use ($options) {
+ if (isset($options['metadataCallback'])) {
+ $metadataCallback = $options['metadataCallback'];
+ $metadataCallback($response->getHeaders());
+ }
+ return $response;
+ }
+ )->then(
+ function (ResponseInterface $response) use ($call) {
+ return $this->unpackResponse($call, $response);
+ },
+ function (\Exception $ex) {
+ throw $this->transformException($ex);
+ }
+ );
+ }
+
+ /**
+ * @param Call $call
+ * @param array $options
+ * @return RequestInterface
+ */
+ private function buildRequest(Call $call, array $options)
+ {
+ // Build common headers and set the content type to 'application/x-protobuf'
+ $headers = ['Content-Type' => 'application/x-protobuf'] + self::buildCommonHeaders($options);
+
+ // It is necessary to supply 'grpc-web' in the 'x-goog-api-client' header
+ // when using the grpc-fallback protocol.
+ $headers += ['x-goog-api-client' => []];
+ $headers['x-goog-api-client'][] = 'grpc-web';
+
+ // Uri format: https:///$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 = $options['transportOptions']['grpcFallbackOptions'] ?? [];
+
+ if (isset($options['timeoutMillis'])) {
+ $callOptions['timeout'] = $options['timeoutMillis'] / 1000;
+ }
+
+ if (isset($options['retryAttempt'])) {
+ $callOptions['retryAttempt'] = $options['retryAttempt'];
+ }
+
+ if (isset($options['requestId'])) {
+ $callOptions['requestId'] = $options['requestId'];
+ }
+
+ if ($this->clientCertSource) {
+ list($cert, $key) = self::loadClientCertSource($this->clientCertSource);
+ $callOptions['cert'] = $cert;
+ $callOptions['key'] = $key;
+ }
+
+ return $callOptions;
+ }
+
+ /**
+ * @param \Exception $ex
+ * @return \Exception
+ */
+ private function transformException(\Exception $ex)
+ {
+ if ($ex instanceof RequestException && $ex->hasResponse()) {
+ $res = $ex->getResponse();
+ $body = (string) $res->getBody();
+ $status = new Status();
+ try {
+ $status->mergeFromString($body);
+ return ApiException::createFromRpcStatus($status);
+ } catch (\Exception $parseException) {
+ // We were unable to parse the response body into a $status object. Instead,
+ // create an ApiException using the unparsed $body as message.
+ $code = ApiStatus::rpcCodeFromHttpStatusCode($res->getStatusCode());
+ return ApiException::createFromApiResponse($body, $code, null, $parseException);
+ }
+ } else {
+ return $ex;
+ }
+ }
+}
diff --git a/Gax/src/Transport/GrpcTransport.php b/Gax/src/Transport/GrpcTransport.php
new file mode 100644
index 000000000000..5fd03a6fe52d
--- /dev/null
+++ b/Gax/src/Transport/GrpcTransport.php
@@ -0,0 +1,366 @@
+logger = $logger;
+ }
+
+ /**
+ * Builds a GrpcTransport.
+ *
+ * @param string $apiEndpoint
+ * The address of the API remote host, for example "example.googleapis.com. May also
+ * include the port, for example "example.googleapis.com:443"
+ * @param array $config {
+ * Config options used to construct the gRPC transport.
+ *
+ * @type array $stubOpts Options used to construct the gRPC stub (see
+ * {@link https://grpc.github.io/grpc/core/group__grpc__arg__keys.html}).
+ * @type Channel $channel Grpc channel to be used.
+ * @type Interceptor[]|UnaryInterceptorInterface[] $interceptors *EXPERIMENTAL*
+ * Interceptors used to intercept RPC invocations before a call starts.
+ * Please note that implementations of
+ * {@see \Google\ApiCore\Transport\Grpc\UnaryInterceptorInterface} are
+ * considered deprecated and support will be removed in a future
+ * release. To prepare for this, please take the time to convert
+ * `UnaryInterceptorInterface` implementations over to a class which
+ * extends {@see Grpc\Interceptor}.
+ * @type callable $clientCertSource A callable which returns the client cert as a string.
+ * }
+ * @return GrpcTransport
+ * @throws ValidationException
+ */
+ public static function build(string $apiEndpoint, array $config = [])
+ {
+ self::validateGrpcSupport();
+ $config += [
+ 'stubOpts' => [],
+ 'channel' => null,
+ 'interceptors' => [],
+ 'clientCertSource' => null,
+ 'logger' => null,
+ ];
+ list($addr, $port) = self::normalizeServiceAddress($apiEndpoint);
+ $host = "$addr:$port";
+ $stubOpts = $config['stubOpts'];
+ // Set the required 'credentials' key in stubOpts if it is not already set. Use
+ // array_key_exists because null is a valid value.
+ if (!array_key_exists('credentials', $stubOpts)) {
+ if (isset($config['clientCertSource'])) {
+ list($cert, $key) = self::loadClientCertSource($config['clientCertSource']);
+ $stubOpts['credentials'] = ChannelCredentials::createSsl(null, $key, $cert);
+ } else {
+ $stubOpts['credentials'] = ChannelCredentials::createSsl();
+ }
+ }
+ $channel = $config['channel'];
+ if (!is_null($channel) && !($channel instanceof Channel)) {
+ throw new ValidationException(
+ "Channel argument to GrpcTransport must be of type \Grpc\Channel, " .
+ 'instead got: ' . print_r($channel, true)
+ );
+ }
+ try {
+ if ($config['logger'] === false) {
+ $config['logger'] = null;
+ }
+ return new GrpcTransport($host, $stubOpts, $channel, $config['interceptors'], $config['logger']);
+ } catch (Exception $ex) {
+ throw new ValidationException(
+ 'Failed to build GrpcTransport: ' . $ex->getMessage(),
+ $ex->getCode(),
+ $ex
+ );
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function startBidiStreamingCall(Call $call, array $options)
+ {
+ $this->verifyUniverseDomain($options);
+
+ $bidiStream = new BidiStream(
+ $this->_bidiRequest(
+ '/' . $call->getMethod(),
+ [$call->getDecodeType(), 'decode'],
+ isset($options['headers']) ? $options['headers'] : [],
+ $this->getCallOptions($options)
+ ),
+ $call->getDescriptor(),
+ $this->logger
+ );
+
+ if ($this->logger) {
+ $requestEvent = new RpcLogEvent();
+
+ $requestEvent->headers = $options['headers'] ?? [];
+ $requestEvent->retryAttempt = $options['retryAttempt'] ?? null;
+ $requestEvent->serviceName = $options['serviceName'] ?? null;
+ $requestEvent->rpcName = $call->getMethod();
+ $requestEvent->processId = (int) getmypid();
+ $requestEvent->requestId = crc32((string) spl_object_id($bidiStream) . getmypid());
+
+ $this->logRequest($requestEvent);
+ }
+
+ return $bidiStream;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function startClientStreamingCall(Call $call, array $options)
+ {
+
+ $this->verifyUniverseDomain($options);
+
+ return new ClientStream(
+ $this->_clientStreamRequest(
+ '/' . $call->getMethod(),
+ [$call->getDecodeType(), 'decode'],
+ isset($options['headers']) ? $options['headers'] : [],
+ $this->getCallOptions($options)
+ ),
+ $call->getDescriptor(),
+ $this->logger
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function startServerStreamingCall(Call $call, array $options)
+ {
+ $this->verifyUniverseDomain($options);
+
+ $message = $call->getMessage();
+
+ if (!$message) {
+ throw new \InvalidArgumentException('A message is required for ServerStreaming calls.');
+ }
+
+ // This simultaenously creates and starts a \Grpc\ServerStreamingCall.
+ $stream = $this->_serverStreamRequest(
+ '/' . $call->getMethod(),
+ $message,
+ [$call->getDecodeType(), 'decode'],
+ isset($options['headers']) ? $options['headers'] : [],
+ $this->getCallOptions($options)
+ );
+
+ $serverStream = new ServerStream(
+ new ServerStreamingCallWrapper($stream),
+ $call->getDescriptor(),
+ $this->logger
+ );
+
+ if ($this->logger) {
+ $requestEvent = new RpcLogEvent();
+
+ $requestEvent->headers = $options['headers'];
+ $requestEvent->payload = $call->getMessage()->serializeToJsonString();
+ $requestEvent->retryAttempt = $options['retryAttempt'] ?? null;
+ $requestEvent->serviceName = $options['serviceName'] ?? null;
+ $requestEvent->rpcName = $call->getMethod();
+ $requestEvent->processId = (int) getmypid();
+ $requestEvent->requestId = crc32((string) spl_object_id($serverStream) . getmypid());
+
+ $this->logRequest($requestEvent);
+ }
+
+ return $serverStream;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function startUnaryCall(Call $call, array $options)
+ {
+ $this->verifyUniverseDomain($options);
+ $headers = $options['headers'] ?? [];
+ $requestEvent = null;
+
+ $unaryCall = $this->_simpleRequest(
+ '/' . $call->getMethod(),
+ $call->getMessage(),
+ [$call->getDecodeType(), 'decode'],
+ isset($options['headers']) ? $options['headers'] : [],
+ $this->getCallOptions($options)
+ );
+
+ if ($this->logger) {
+ $requestEvent = new RpcLogEvent();
+
+ $requestEvent->headers = $headers;
+ $requestEvent->payload = $call->getMessage()->serializeToJsonString();
+ $requestEvent->retryAttempt = $options['retryAttempt'] ?? null;
+ $requestEvent->serviceName = $options['serviceName'] ?? null;
+ $requestEvent->rpcName = $call->getMethod();
+ $requestEvent->processId = (int) getmypid();
+ $requestEvent->requestId = crc32((string) spl_object_id($call) . getmypid());
+
+ $this->logRequest($requestEvent);
+ }
+
+ /** @var Promise $promise */
+ $promise = new Promise(
+ function () use ($unaryCall, $options, &$promise, $requestEvent) {
+ list($response, $status) = $unaryCall->wait();
+
+ if ($this->logger) {
+ $responseEvent = new RpcLogEvent($requestEvent->milliseconds);
+
+ $responseEvent->headers = $status->metadata;
+ $responseEvent->payload = ($response) ? $response->serializeToJsonString() : null;
+ $responseEvent->status = $status->code;
+ $responseEvent->processId = $requestEvent->processId;
+ $responseEvent->requestId = $requestEvent->requestId;
+
+ $this->logResponse($responseEvent);
+ }
+
+ if ($status->code == Code::OK) {
+ if (isset($options['metadataCallback'])) {
+ $metadataCallback = $options['metadataCallback'];
+ $metadataCallback($unaryCall->getMetadata());
+ }
+ $promise->resolve($response);
+ } else {
+ throw ApiException::createFromStdClass($status);
+ }
+ },
+ [$unaryCall, 'cancel']
+ );
+
+ return $promise;
+ }
+
+ private function verifyUniverseDomain(array $options)
+ {
+ if (isset($options['credentialsWrapper'])) {
+ $options['credentialsWrapper']->checkUniverseDomain();
+ }
+ }
+
+ private function getCallOptions(array $options)
+ {
+ $callOptions = $options['transportOptions']['grpcOptions'] ?? [];
+
+ if (isset($options['credentialsWrapper'])) {
+ $audience = $options['audience'] ?? null;
+ $credentialsWrapper = $options['credentialsWrapper'];
+ $callOptions['call_credentials_callback'] = $credentialsWrapper
+ ->getAuthorizationHeaderCallback($audience);
+ }
+
+ if (isset($options['timeoutMillis'])) {
+ $callOptions['timeout'] = $options['timeoutMillis'] * 1000;
+ }
+
+ return $callOptions;
+ }
+
+ private static function loadClientCertSource(callable $clientCertSource)
+ {
+ return call_user_func($clientCertSource);
+ }
+}
diff --git a/Gax/src/Transport/HttpUnaryTransportTrait.php b/Gax/src/Transport/HttpUnaryTransportTrait.php
new file mode 100644
index 000000000000..38c4ca1f5522
--- /dev/null
+++ b/Gax/src/Transport/HttpUnaryTransportTrait.php
@@ -0,0 +1,171 @@
+throwUnsupportedException();
+ }
+
+ /**
+ * {@inheritdoc}
+ * @return never
+ * @throws \BadMethodCallException
+ */
+ public function startServerStreamingCall(Call $call, array $options)
+ {
+ $this->throwUnsupportedException();
+ }
+
+ /**
+ * {@inheritdoc}
+ * @return never
+ * @throws \BadMethodCallException
+ */
+ public function startBidiStreamingCall(Call $call, array $options)
+ {
+ $this->throwUnsupportedException();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ // Nothing to do.
+ }
+
+ /**
+ * @param array $options
+ * @return array
+ */
+ private static function buildCommonHeaders(array $options)
+ {
+ $headers = $options['headers'] ?? [];
+
+ if (!is_array($headers)) {
+ throw new \InvalidArgumentException(
+ 'The "headers" option must be an array'
+ );
+ }
+
+ // If not already set, add an auth header to the request
+ if (!isset($headers['Authorization']) && isset($options['credentialsWrapper'])) {
+ $credentialsWrapper = $options['credentialsWrapper'];
+ $audience = $options['audience'] ?? null;
+ $callback = $credentialsWrapper
+ ->getAuthorizationHeaderCallback($audience);
+ // Prevent unexpected behavior, as the authorization header callback
+ // uses lowercase "authorization"
+ unset($headers['authorization']);
+ // Mitigate scenario where InsecureCredentialsWrapper returns null.
+ $authHeaders = empty($callback) ? [] : $callback();
+ if (!is_array($authHeaders)) {
+ throw new \UnexpectedValueException(
+ 'Expected array response from authorization header callback'
+ );
+ }
+ $headers += $authHeaders;
+ }
+
+ return $headers;
+ }
+
+ /**
+ * @return callable
+ * @throws ValidationException
+ */
+ private static function buildHttpHandlerAsync(null|false|LoggerInterface $logger = null)
+ {
+ try {
+ return [HttpHandlerFactory::build(logger: $logger), 'async'];
+ } catch (Exception $ex) {
+ throw new ValidationException('Failed to build HttpHandler', $ex->getCode(), $ex);
+ }
+ }
+
+ /**
+ * Set the path to a client certificate.
+ *
+ * @param callable $clientCertSource
+ */
+ private function configureMtlsChannel(callable $clientCertSource)
+ {
+ $this->clientCertSource = $clientCertSource;
+ }
+
+ /**
+ * @return never
+ * @throws \BadMethodCallException
+ */
+ private function throwUnsupportedException()
+ {
+ throw new \BadMethodCallException(
+ "Streaming calls are not supported while using the {$this->transportName} transport."
+ );
+ }
+
+ private static function loadClientCertSource(callable $clientCertSource)
+ {
+ $certFile = tempnam(sys_get_temp_dir(), 'cert');
+ $keyFile = tempnam(sys_get_temp_dir(), 'key');
+ list($cert, $key) = call_user_func($clientCertSource);
+ file_put_contents($certFile, $cert);
+ file_put_contents($keyFile, $key);
+
+ // the key and the cert are returned in one temporary file
+ return [$certFile, $keyFile];
+ }
+}
diff --git a/Gax/src/Transport/Rest/JsonStreamDecoder.php b/Gax/src/Transport/Rest/JsonStreamDecoder.php
new file mode 100644
index 000000000000..90aba068034b
--- /dev/null
+++ b/Gax/src/Transport/Rest/JsonStreamDecoder.php
@@ -0,0 +1,238 @@
+ $options {
+ * An array of optional arguments.
+ *
+ * @type bool $ignoreUnknown
+ * Toggles whether or not to throw an exception when an unknown field
+ * is encountered in a response message. The default is true.
+ * @type int $readChunkSizeBytes
+ * The upper size limit in bytes that can be read at a time from the
+ * response stream. The default is 1 KB.
+ * }
+ *
+ * @experimental
+ */
+ public function __construct(StreamInterface $stream, string $decodeType, array $options = [])
+ {
+ $this->stream = $stream;
+ $this->decodeType = $decodeType;
+
+ if (isset($options['ignoreUnknown'])) {
+ $this->ignoreUnknown = $options['ignoreUnknown'];
+ }
+ if (isset($options['readChunkSize'])) {
+ $this->readChunkSize = $options['readChunkSizeBytes'];
+ }
+ }
+
+ /**
+ * Begins decoding the configured response stream. It is a generator which
+ * yields messages of the given decode type from the stream until the stream
+ * completes. Throws an Exception if the stream is closed before the closing
+ * byte is read or if it encounters an error while decoding a message.
+ *
+ * @throws RuntimeException
+ * @return \Generator
+ */
+ public function decode()
+ {
+ try {
+ foreach ($this->doDecode() as $response) {
+ yield $response;
+ }
+ } catch (RuntimeException $re) {
+ $msg = $re->getMessage();
+ $streamClosedException =
+ strpos($msg, 'Stream is detached') !== false ||
+ strpos($msg, 'Unexpected stream close') !== false;
+
+ // Only throw the exception if close() was not called and it was not
+ // a closing-related exception.
+ if (!$this->closeCalled || !$streamClosedException) {
+ throw $re;
+ }
+ }
+ }
+
+ /**
+ * @return \Generator
+ */
+ private function doDecode()
+ {
+ $decodeType = $this->decodeType;
+ $str = false;
+ $prev = $chunk = '';
+ $start = $end = $cursor = $level = 0;
+ while ($chunk !== '' || !$this->stream->eof()) {
+ // Read up to $readChunkSize bytes from the stream.
+ $chunk .= $this->stream->read($this->readChunkSize);
+
+ // If the response stream has been closed and the only byte
+ // remaining is the closing array bracket, we are done.
+ if ($this->stream->eof() && $chunk === ']') {
+ $level--;
+ break;
+ }
+
+ // Parse the freshly read data available in $chunk.
+ $chunkLength = strlen($chunk);
+ while ($cursor < $chunkLength) {
+ // Access the next byte for processing.
+ $b = $chunk[$cursor];
+
+ // Track open/close double quotes of a key or value. Do not
+ // toggle flag with the pervious byte was an escape character.
+ if ($b === '"' && $prev !== self::ESCAPE_CHAR) {
+ $str = !$str;
+ }
+
+ // Skip over new lines that break up items.
+ if ($b === "\n" && $level === 1) {
+ $start++;
+ }
+
+ // Ignore commas separating messages in the stream array.
+ if ($b === ',' && $level === 1) {
+ $start++;
+ }
+ // Track the opening of a new array or object if not in a string
+ // value.
+ if (($b === '{' || $b === '[') && !$str) {
+ $level++;
+ // Opening of the array/root object.
+ // Do not include it in the messageBuffer.
+ if ($level === 1) {
+ $start++;
+ }
+ }
+ // Track the closing of an object if not in a string value.
+ if ($b === '}' && !$str) {
+ $level--;
+ if ($level === 1) {
+ $end = $cursor + 1;
+ }
+ }
+ // Track the closing of an array if not in a string value.
+ if ($b === ']' && !$str) {
+ $level--;
+ // If we are seeing a closing square bracket at the
+ // response message level, e.g. {"foo], there is a problem.
+ if ($level === 1) {
+ throw new \RuntimeException('Received closing byte mid-message');
+ }
+ }
+
+ // A message-closing byte was just buffered. Decode the
+ // message with the decode type, clearing the messageBuffer,
+ // and yield it.
+ //
+ // TODO(noahdietz): Support google.protobuf.*Value messages that
+ // are encoded as primitives and separated by commas.
+ if ($end !== 0) {
+ $length = $end - $start;
+ /** @var \Google\Protobuf\Internal\Message $return */
+ $return = new $decodeType();
+ $return->mergeFromJsonString(
+ substr($chunk, $start, $length),
+ $this->ignoreUnknown
+ );
+ yield $return;
+
+ // Dump the part of the chunk used for parsing the message
+ // and use the remaining for the next message.
+ $remaining = $chunkLength - $length;
+ $chunk = substr($chunk, $end, $remaining);
+
+ // Reset all indices and exit chunk processing.
+ $start = 0;
+ $end = 0;
+ $cursor = 0;
+ break;
+ }
+
+ $cursor++;
+
+ // An escaped back slash should not escape the following character.
+ if ($b === self::ESCAPE_CHAR && $prev === self::ESCAPE_CHAR) {
+ $b = '';
+ }
+ $prev = $b;
+ }
+ // If after attempting to process the chunk, no progress was made and the
+ // stream is closed, we must break as the stream has closed prematurely.
+ if ($cursor === $chunkLength && $this->stream->eof()) {
+ break;
+ }
+ }
+ if ($level > 0) {
+ throw new \RuntimeException('Unexpected stream close before receiving the closing byte');
+ }
+ }
+
+ /**
+ * Closes the underlying stream. If the stream is actively being decoded, an
+ * exception will not be thrown due to the interruption.
+ *
+ * @return void
+ */
+ public function close()
+ {
+ $this->closeCalled = true;
+ $this->stream->close();
+ }
+}
diff --git a/Gax/src/Transport/Rest/RestServerStreamingCall.php b/Gax/src/Transport/Rest/RestServerStreamingCall.php
new file mode 100644
index 000000000000..80d5e3e3c14f
--- /dev/null
+++ b/Gax/src/Transport/Rest/RestServerStreamingCall.php
@@ -0,0 +1,197 @@
+ */
+ private array $decoderOptions;
+
+ private RequestInterface $originalRequest;
+ private JsonStreamDecoder $decoder;
+ private string $decodeType;
+ private ?ResponseInterface $response;
+ private stdClass $status;
+
+ /**
+ * @param callable $httpHandler
+ * @param string $decodeType
+ * @param array $decoderOptions
+ */
+ public function __construct(callable $httpHandler, string $decodeType, array $decoderOptions)
+ {
+ $this->httpHandler = $httpHandler;
+ $this->decodeType = $decodeType;
+ $this->decoderOptions = $decoderOptions;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function start($request, array $headers = [], array $callOptions = [])
+ {
+ $this->originalRequest = $this->appendHeaders($request, $headers);
+
+ try {
+ $handler = $this->httpHandler;
+ $response = $handler(
+ $this->originalRequest,
+ $callOptions
+ )->wait();
+ } catch (\Exception $ex) {
+ if ($ex instanceof RequestException && $ex->hasResponse()) {
+ $ex = ApiException::createFromRequestException($ex, /* isStream */ true);
+ }
+ throw $ex;
+ }
+
+ // Create an OK Status for a successful request just so that it
+ // has a return value.
+ $this->status = new stdClass();
+ $this->status->code = Code::OK;
+ $this->status->message = ApiStatus::OK;
+ $this->status->details = [];
+
+ $this->response = $response;
+ }
+
+ /**
+ * @param RequestInterface $request
+ * @param array $headers
+ * @return RequestInterface
+ */
+ private function appendHeaders(RequestInterface $request, array $headers)
+ {
+ foreach ($headers as $key => $value) {
+ $request = $request->hasHeader($key) ?
+ $request->withAddedHeader($key, $value) :
+ $request->withHeader($key, $value);
+ }
+
+ return $request;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function responses()
+ {
+ if (is_null($this->response)) {
+ throw new \Exception('Stream has not been started.');
+ }
+
+ // Decode the stream and yield responses as they are read.
+ $this->decoder = new JsonStreamDecoder(
+ $this->response->getBody(),
+ $this->decodeType,
+ $this->decoderOptions
+ );
+
+ foreach ($this->decoder->decode() as $message) {
+ yield $message;
+ }
+ }
+
+ /**
+ * Return the status of the server stream. If the call has not been started
+ * this will be null.
+ *
+ * @return stdClass The status, with integer $code, string
+ * $details, and array $metadata members
+ */
+ public function getStatus()
+ {
+ return $this->status;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getMetadata()
+ {
+ return is_null($this->response) ? null : $this->response->getHeaders();
+ }
+
+ /**
+ * The Rest transport does not support trailing metadata. This is a
+ * passthrough to getMetadata().
+ */
+ public function getTrailingMetadata()
+ {
+ return $this->getMetadata();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getPeer()
+ {
+ return $this->originalRequest->getUri();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function cancel()
+ {
+ if (isset($this->decoder)) {
+ $this->decoder->close();
+ }
+ }
+
+ /**
+ * For the REST transport this is a no-op.
+ * {@inheritdoc}
+ */
+ public function setCallCredentials($call_credentials)
+ {
+ // Do nothing.
+ }
+}
diff --git a/Gax/src/Transport/RestTransport.php b/Gax/src/Transport/RestTransport.php
new file mode 100644
index 000000000000..aab687182aa8
--- /dev/null
+++ b/Gax/src/Transport/RestTransport.php
@@ -0,0 +1,283 @@
+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.
+ * @type callable $clientCertSource A callable which returns the client cert as a string.
+ * @type bool $hasEmulator True if the emulator is enabled.
+ * }
+ * @return RestTransport
+ * @throws ValidationException
+ */
+ public static function build(string $apiEndpoint, string $restConfigPath, array $config = [])
+ {
+ $config += [
+ 'httpHandler' => null,
+ 'clientCertSource' => null,
+ 'hasEmulator' => false,
+ 'logger' => null,
+ ];
+ list($baseUri, $port) = self::normalizeServiceAddress($apiEndpoint);
+ $requestBuilder = $config['hasEmulator']
+ ? new InsecureRequestBuilder("$baseUri:$port", $restConfigPath)
+ : new RequestBuilder("$baseUri:$port", $restConfigPath);
+ $httpHandler = $config['httpHandler'] ?: self::buildHttpHandlerAsync($config['logger']);
+ $transport = new RestTransport($requestBuilder, $httpHandler);
+ if ($config['clientCertSource']) {
+ $transport->configureMtlsChannel($config['clientCertSource']);
+ }
+ return $transport;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function startUnaryCall(Call $call, array $options)
+ {
+ $headers = self::buildCommonHeaders($options);
+
+ // Add the $call object ID for logging
+ $options['requestId'] = crc32((string) spl_object_id($call) . getmypid());
+
+ // call the HTTP handler
+ $httpHandler = $this->httpHandler;
+ return $httpHandler(
+ $this->requestBuilder->build(
+ $call->getMethod(),
+ $call->getMessage(),
+ $headers
+ ),
+ $this->getCallOptions($options)
+ )->then(
+ function (ResponseInterface $response) use ($call, $options) {
+ $decodeType = $call->getDecodeType();
+ /** @var Message $return */
+ $return = new $decodeType();
+ $body = (string) $response->getBody();
+
+ // In some rare cases LRO response metadata may not be loaded
+ // in the descriptor pool, triggering an exception. The catch
+ // statement handles this case and attempts to add the LRO
+ // metadata type to the pool by directly instantiating the
+ // metadata class.
+ try {
+ $return->mergeFromJsonString(
+ $body,
+ true
+ );
+ } catch (\Exception $ex) {
+ if (!isset($options['metadataReturnType'])) {
+ throw $ex;
+ }
+
+ if (strpos($ex->getMessage(), 'Error occurred during parsing:') !== 0) {
+ throw $ex;
+ }
+
+ new $options['metadataReturnType']();
+ $return->mergeFromJsonString(
+ $body,
+ true
+ );
+ }
+
+ if (isset($options['metadataCallback'])) {
+ $metadataCallback = $options['metadataCallback'];
+ $metadataCallback($response->getHeaders());
+ }
+
+ return $return;
+ },
+ function (\Throwable $ex) {
+ if ($ex instanceof RequestException && $ex->hasResponse()) {
+ throw ApiException::createFromRequestException($ex);
+ }
+
+ throw $ex;
+ }
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ * @throws \BadMethodCallException for forwards compatibility with older GAPIC clients
+ */
+ public function startServerStreamingCall(Call $call, array $options)
+ {
+ $message = $call->getMessage();
+ if (!$message) {
+ throw new \InvalidArgumentException('A message is required for ServerStreaming calls.');
+ }
+
+ // Maintain forwards compatibility with older GAPIC clients not configured for REST server streaming
+ // @see https://github.com/googleapis/gax-php/issues/370
+ if (!$this->requestBuilder->pathExists($call->getMethod())) {
+ $this->unsupportedServerStreamingCall($call, $options);
+ }
+
+ $headers = self::buildCommonHeaders($options);
+ $callOptions = $this->getCallOptions($options);
+ $request = $this->requestBuilder->build(
+ $call->getMethod(),
+ $call->getMessage()
+ // Exclude headers here because they will be added in doServerStreamRequest().
+ );
+
+ $decoderOptions = [];
+ if (isset($options['decoderOptions'])) {
+ $decoderOptions = $options['decoderOptions'];
+ }
+
+ return new ServerStream(
+ $this->doServerStreamRequest(
+ $this->httpHandler,
+ $request,
+ $headers,
+ $call->getDecodeType(),
+ $callOptions,
+ $decoderOptions
+ ),
+ $call->getDescriptor()
+ );
+ }
+
+ /**
+ * Creates and starts a RestServerStreamingCall.
+ *
+ * @param callable $httpHandler The HTTP Handler to invoke the request with.
+ * @param RequestInterface $request The request to invoke.
+ * @param array $headers The headers to include in the request.
+ * @param string $decodeType The response stream message type to decode.
+ * @param array $callOptions The call options to use when making the call.
+ * @param array $decoderOptions The options to use for the JsonStreamDecoder.
+ *
+ * @return RestServerStreamingCall
+ */
+ private function doServerStreamRequest(
+ $httpHandler,
+ $request,
+ $headers,
+ $decodeType,
+ $callOptions,
+ $decoderOptions = []
+ ) {
+ $call = new RestServerStreamingCall(
+ $httpHandler,
+ $decodeType,
+ $decoderOptions
+ );
+ $call->start($request, $headers, $callOptions);
+
+ return $call;
+ }
+
+ /**
+ * @param array $options
+ *
+ * @return array
+ */
+ private function getCallOptions(array $options)
+ {
+ $callOptions = $options['transportOptions']['restOptions'] ?? [];
+
+ if (isset($options['timeoutMillis'])) {
+ $callOptions['timeout'] = $options['timeoutMillis'] / 1000;
+ }
+
+ if ($this->clientCertSource) {
+ list($cert, $key) = self::loadClientCertSource($this->clientCertSource);
+ $callOptions['cert'] = $cert;
+ $callOptions['key'] = $key;
+ }
+
+ if (isset($options['retryAttempt'])) {
+ $callOptions['retryAttempt'] = $options['retryAttempt'];
+ }
+
+ if (isset($options['requestId'])) {
+ $callOptions['requestId'] = $options['requestId'];
+ }
+
+ return $callOptions;
+ }
+}
diff --git a/Gax/src/Transport/TransportInterface.php b/Gax/src/Transport/TransportInterface.php
new file mode 100644
index 000000000000..682a4301989c
--- /dev/null
+++ b/Gax/src/Transport/TransportInterface.php
@@ -0,0 +1,91 @@
+ $options
+ *
+ * @return BidiStream
+ */
+ public function startBidiStreamingCall(Call $call, array $options);
+
+ /**
+ * Starts a client streaming call.
+ *
+ * @param Call $call
+ * @param array $options
+ *
+ * @return ClientStream
+ */
+ public function startClientStreamingCall(Call $call, array $options);
+
+ /**
+ * Starts a server streaming call.
+ *
+ * @param Call $call
+ * @param array $options
+ *
+ * @return ServerStream
+ */
+ public function startServerStreamingCall(Call $call, array $options);
+
+ /**
+ * Returns a promise used to execute network requests.
+ *
+ * @param Call $call
+ * @param array $options
+ *
+ * @return PromiseInterface
+ * @throws ValidationException
+ */
+ public function startUnaryCall(Call $call, array $options);
+
+ /**
+ * Closes the connection, if one exists.
+ *
+ * @return void
+ */
+ public function close();
+}
diff --git a/Gax/src/UriTrait.php b/Gax/src/UriTrait.php
new file mode 100644
index 000000000000..d3fc6aa3701d
--- /dev/null
+++ b/Gax/src/UriTrait.php
@@ -0,0 +1,69 @@
+ &$v) {
+ if (is_bool($v)) {
+ $v = $v ? 'true' : 'false';
+ }
+ }
+
+ return Utils::uriFor($uri)
+ ->withQuery(
+ Query::build($query)
+ );
+ }
+}
diff --git a/Gax/src/ValidationException.php b/Gax/src/ValidationException.php
new file mode 100644
index 000000000000..27a66a82c4e6
--- /dev/null
+++ b/Gax/src/ValidationException.php
@@ -0,0 +1,41 @@
+ ['credentials' => ChannelCredentials::createInsecure()]]
+ );
+
+ // build REST transport
+ $restConfigPath = __DIR__ . '/src/V1beta1/resources/echo_rest_client_config.php';
+ $requestBuilder = new InsecureRequestBuilder('localhost:7469', $restConfigPath);
+ $httpHandler = HttpHandlerFactory::build();
+ $rest = new RestTransport($requestBuilder, [$httpHandler, 'async']);
+
+ return [[$grpc], [$rest]];
+ }
+
+ /** @dataProvider provideTransport **/
+ public function testFailWithDetails(TransportInterface $transport): void
+ {
+ $echoClient = new EchoClient([
+ 'credentials' => new InsecureCredentialsWrapper(),
+ 'transport' => $transport,
+ ]);
+ try {
+ $echoClient->failEchoWithDetails(new FailEchoWithDetailsRequest());
+
+ // this should not be reached
+ $this->fail('errors should have been thrown');
+ } catch (ApiException $e) {
+ }
+
+ $this->assertGreaterThan(0, count($e->getErrorDetails()));
+ foreach ($e->getErrorDetails() as $detail) {
+ $this->assertInstanceOf(Message::class, $detail);
+ }
+ }
+}
diff --git a/Gax/tests/Conformance/metadata/V1Beta1/Compliance.php b/Gax/tests/Conformance/metadata/V1Beta1/Compliance.php
new file mode 100644
index 000000000000..9dfe4416cf50
Binary files /dev/null and b/Gax/tests/Conformance/metadata/V1Beta1/Compliance.php differ
diff --git a/Gax/tests/Conformance/metadata/V1Beta1/Identity.php b/Gax/tests/Conformance/metadata/V1Beta1/Identity.php
new file mode 100644
index 000000000000..8637df482fcc
Binary files /dev/null and b/Gax/tests/Conformance/metadata/V1Beta1/Identity.php differ
diff --git a/Gax/tests/Conformance/metadata/V1Beta1/Messaging.php b/Gax/tests/Conformance/metadata/V1Beta1/Messaging.php
new file mode 100644
index 000000000000..3b8b801a1b3f
Binary files /dev/null and b/Gax/tests/Conformance/metadata/V1Beta1/Messaging.php differ
diff --git a/Gax/tests/Conformance/metadata/V1Beta1/PBEcho.php b/Gax/tests/Conformance/metadata/V1Beta1/PBEcho.php
new file mode 100644
index 000000000000..a3ace5c606f4
Binary files /dev/null and b/Gax/tests/Conformance/metadata/V1Beta1/PBEcho.php differ
diff --git a/Gax/tests/Conformance/metadata/V1Beta1/RestError.php b/Gax/tests/Conformance/metadata/V1Beta1/RestError.php
new file mode 100644
index 000000000000..e5b5923dd3c5
--- /dev/null
+++ b/Gax/tests/Conformance/metadata/V1Beta1/RestError.php
@@ -0,0 +1,36 @@
+internalAddGeneratedFile(
+ '
+Ź
+(google/showcase/v1beta1/rest_error.protogoogle.showcase.v1beta1google/rpc/code.proto"·
+ RestError8
+error (2).google.showcase.v1beta1.RestError.Statusp
+Status
+code (
+message (
+status (2.google.rpc.Code%
+details (2.google.protobuf.AnyBq
+com.google.showcase.v1beta1PZ4github.com/googleapis/gapic-showcase/server/genprotoęGoogle::Showcase::V1beta1bproto3'
+ , true);
+
+ static::$is_initialized = true;
+ }
+}
+
diff --git a/Gax/tests/Conformance/metadata/V1Beta1/Sequence.php b/Gax/tests/Conformance/metadata/V1Beta1/Sequence.php
new file mode 100644
index 000000000000..d2f1e57bc591
--- /dev/null
+++ b/Gax/tests/Conformance/metadata/V1Beta1/Sequence.php
@@ -0,0 +1,97 @@
+internalAddGeneratedFile(
+ '
+ą
+&google/showcase/v1beta1/sequence.protogoogle.showcase.v1beta1google/api/client.protogoogle/api/field_behavior.protogoogle/api/resource.protogoogle/protobuf/duration.protogoogle/protobuf/empty.protogoogle/protobuf/timestamp.protogoogle/rpc/status.proto"ó
+Sequence
+name ( BŕA=
+ responses (2*.google.showcase.v1beta1.Sequence.ResponseX
+Response"
+status (2.google.rpc.Status(
+delay (2.google.protobuf.Duration:;ęA8
+ showcase.googleapis.com/Sequencesequences/{sequence}"Ę
+StreamingSequence
+name ( BŕA
+content ( F
+ responses (23.google.showcase.v1beta1.StreamingSequence.Responsep
+Response"
+status (2.google.rpc.Status(
+delay (2.google.protobuf.Duration
+response_index (:WęAT
+)showcase.googleapis.com/StreamingSequence\'streamingSequences/{streaming_sequence}"Ň
+StreamingSequenceReport
+name ( BŕAJ
+attempts (28.google.showcase.v1beta1.StreamingSequenceReport.Attemptŕ
+Attempt
+attempt_number (4
+attempt_deadline (2.google.protobuf.Timestamp1
+
response_time (2.google.protobuf.Timestamp0
+
attempt_delay (2.google.protobuf.Duration"
+status (2.google.rpc.Status:uęAr
+/showcase.googleapis.com/StreamingSequenceReport?streamingSequences/{streaming_sequence}/streamingSequenceReport"›
+SequenceReport
+name ( BŕAA
+attempts (2/.google.showcase.v1beta1.SequenceReport.Attemptŕ
+Attempt
+attempt_number (4
+attempt_deadline (2.google.protobuf.Timestamp1
+
response_time (2.google.protobuf.Timestamp0
+
attempt_delay (2.google.protobuf.Duration"
+status (2.google.rpc.Status:PęAM
+&showcase.googleapis.com/SequenceReport#sequences/{sequence}/sequenceReport"L
+CreateSequenceRequest3
+sequence (2!.google.showcase.v1beta1.Sequence"h
+CreateStreamingSequenceRequestF
+streaming_sequence (2*.google.showcase.v1beta1.StreamingSequence"P
+AttemptSequenceRequest6
+name ( B(ŕAúA"
+ showcase.googleapis.com/Sequence"€
+AttemptStreamingSequenceRequest?
+name ( B1ŕAúA+
+)showcase.googleapis.com/StreamingSequence
+last_fail_index (BŕA"3
+ AttemptStreamingSequenceResponse
+content ( "X
+GetSequenceReportRequest<
+name ( B.ŕAúA(
+&showcase.googleapis.com/SequenceReport"j
+!GetStreamingSequenceReportRequestE
+name ( B7ŕAúA1
+/showcase.googleapis.com/StreamingSequenceReport2đ
+SequenceService”
+CreateSequence..google.showcase.v1beta1.CreateSequenceRequest!.google.showcase.v1beta1.Sequence"/ÚAsequence‚Óä“"/v1beta1/sequences:sequenceĚ
+CreateStreamingSequence7.google.showcase.v1beta1.CreateStreamingSequenceRequest*.google.showcase.v1beta1.StreamingSequence"LÚAstreaming_sequence‚Óä“1"/v1beta1/streamingSequences:streaming_sequenceŞ
+GetSequenceReport1.google.showcase.v1beta1.GetSequenceReportRequest\'.google.showcase.v1beta1.SequenceReport"9ÚAname‚Óä“,*/v1beta1/{name=sequences/*/sequenceReport}×
+GetStreamingSequenceReport:.google.showcase.v1beta1.GetStreamingSequenceReportRequest0.google.showcase.v1beta1.StreamingSequenceReport"KÚAname‚Óä“>google.showcase.v1beta1.AttemptSequenceRequest
+ */
+class AttemptSequenceRequest extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = REQUIRED, (.google.api.resource_reference) = {
+ */
+ protected $name = '';
+
+ /**
+ * @param string $name Please see {@see SequenceServiceClient::sequenceName()} for help formatting this field.
+ *
+ * @return \Google\Showcase\V1beta1\AttemptSequenceRequest
+ *
+ * @experimental
+ */
+ public static function build(string $name): self
+ {
+ return (new self())
+ ->setName($name);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $name
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Sequence::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = REQUIRED, (.google.api.resource_reference) = {
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = REQUIRED, (.google.api.resource_reference) = {
+ * @param string $var
+ * @return $this
+ */
+ public function setName($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->name = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/AttemptStreamingSequenceRequest.php b/Gax/tests/Conformance/src/V1beta1/AttemptStreamingSequenceRequest.php
new file mode 100644
index 000000000000..9d9c65e5bb47
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/AttemptStreamingSequenceRequest.php
@@ -0,0 +1,113 @@
+google.showcase.v1beta1.AttemptStreamingSequenceRequest
+ */
+class AttemptStreamingSequenceRequest extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = REQUIRED, (.google.api.resource_reference) = {
+ */
+ protected $name = '';
+ /**
+ * used to send the index of the last failed message
+ * in the string "content" of an AttemptStreamingSequenceResponse
+ * needed for stream resumption logic testing
+ *
+ * Generated from protobuf field int32 last_fail_index = 2 [(.google.api.field_behavior) = OPTIONAL];
+ */
+ protected $last_fail_index = 0;
+
+ /**
+ * @param string $name Please see {@see SequenceServiceClient::streamingSequenceName()} for help formatting this field.
+ *
+ * @return \Google\Showcase\V1beta1\AttemptStreamingSequenceRequest
+ *
+ * @experimental
+ */
+ public static function build(string $name): self
+ {
+ return (new self())
+ ->setName($name);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $name
+ * @type int $last_fail_index
+ * used to send the index of the last failed message
+ * in the string "content" of an AttemptStreamingSequenceResponse
+ * needed for stream resumption logic testing
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Sequence::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = REQUIRED, (.google.api.resource_reference) = {
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = REQUIRED, (.google.api.resource_reference) = {
+ * @param string $var
+ * @return $this
+ */
+ public function setName($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->name = $var;
+
+ return $this;
+ }
+
+ /**
+ * used to send the index of the last failed message
+ * in the string "content" of an AttemptStreamingSequenceResponse
+ * needed for stream resumption logic testing
+ *
+ * Generated from protobuf field int32 last_fail_index = 2 [(.google.api.field_behavior) = OPTIONAL];
+ * @return int
+ */
+ public function getLastFailIndex()
+ {
+ return $this->last_fail_index;
+ }
+
+ /**
+ * used to send the index of the last failed message
+ * in the string "content" of an AttemptStreamingSequenceResponse
+ * needed for stream resumption logic testing
+ *
+ * Generated from protobuf field int32 last_fail_index = 2 [(.google.api.field_behavior) = OPTIONAL];
+ * @param int $var
+ * @return $this
+ */
+ public function setLastFailIndex($var)
+ {
+ GPBUtil::checkInt32($var);
+ $this->last_fail_index = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/AttemptStreamingSequenceResponse.php b/Gax/tests/Conformance/src/V1beta1/AttemptStreamingSequenceResponse.php
new file mode 100644
index 000000000000..87caa59a51f1
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/AttemptStreamingSequenceResponse.php
@@ -0,0 +1,67 @@
+google.showcase.v1beta1.AttemptStreamingSequenceResponse
+ */
+class AttemptStreamingSequenceResponse extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The content specified in the request.
+ *
+ * Generated from protobuf field string content = 1;
+ */
+ protected $content = '';
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $content
+ * The content specified in the request.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Sequence::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The content specified in the request.
+ *
+ * Generated from protobuf field string content = 1;
+ * @return string
+ */
+ public function getContent()
+ {
+ return $this->content;
+ }
+
+ /**
+ * The content specified in the request.
+ *
+ * Generated from protobuf field string content = 1;
+ * @param string $var
+ * @return $this
+ */
+ public function setContent($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->content = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/BlockRequest.php b/Gax/tests/Conformance/src/V1beta1/BlockRequest.php
new file mode 100644
index 000000000000..15a41c87f523
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/BlockRequest.php
@@ -0,0 +1,155 @@
+google.showcase.v1beta1.BlockRequest
+ */
+class BlockRequest extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The amount of time to block before returning a response.
+ *
+ * Generated from protobuf field .google.protobuf.Duration response_delay = 1;
+ */
+ protected $response_delay = null;
+ protected $response;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type \Google\Protobuf\Duration $response_delay
+ * The amount of time to block before returning a response.
+ * @type \Google\Rpc\Status $error
+ * The error that will be returned by the server. If this code is specified
+ * to be the OK rpc code, an empty response will be returned.
+ * @type \Google\Showcase\V1beta1\BlockResponse $success
+ * The response to be returned that will signify successful method call.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\PBEcho::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The amount of time to block before returning a response.
+ *
+ * Generated from protobuf field .google.protobuf.Duration response_delay = 1;
+ * @return \Google\Protobuf\Duration|null
+ */
+ public function getResponseDelay()
+ {
+ return $this->response_delay;
+ }
+
+ public function hasResponseDelay()
+ {
+ return isset($this->response_delay);
+ }
+
+ public function clearResponseDelay()
+ {
+ unset($this->response_delay);
+ }
+
+ /**
+ * The amount of time to block before returning a response.
+ *
+ * Generated from protobuf field .google.protobuf.Duration response_delay = 1;
+ * @param \Google\Protobuf\Duration $var
+ * @return $this
+ */
+ public function setResponseDelay($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Protobuf\Duration::class);
+ $this->response_delay = $var;
+
+ return $this;
+ }
+
+ /**
+ * The error that will be returned by the server. If this code is specified
+ * to be the OK rpc code, an empty response will be returned.
+ *
+ * Generated from protobuf field .google.rpc.Status error = 2;
+ * @return \Google\Rpc\Status|null
+ */
+ public function getError()
+ {
+ return $this->readOneof(2);
+ }
+
+ public function hasError()
+ {
+ return $this->hasOneof(2);
+ }
+
+ /**
+ * The error that will be returned by the server. If this code is specified
+ * to be the OK rpc code, an empty response will be returned.
+ *
+ * Generated from protobuf field .google.rpc.Status error = 2;
+ * @param \Google\Rpc\Status $var
+ * @return $this
+ */
+ public function setError($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Rpc\Status::class);
+ $this->writeOneof(2, $var);
+
+ return $this;
+ }
+
+ /**
+ * The response to be returned that will signify successful method call.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.BlockResponse success = 3;
+ * @return \Google\Showcase\V1beta1\BlockResponse|null
+ */
+ public function getSuccess()
+ {
+ return $this->readOneof(3);
+ }
+
+ public function hasSuccess()
+ {
+ return $this->hasOneof(3);
+ }
+
+ /**
+ * The response to be returned that will signify successful method call.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.BlockResponse success = 3;
+ * @param \Google\Showcase\V1beta1\BlockResponse $var
+ * @return $this
+ */
+ public function setSuccess($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Showcase\V1beta1\BlockResponse::class);
+ $this->writeOneof(3, $var);
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getResponse()
+ {
+ return $this->whichOneof("response");
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/BlockResponse.php b/Gax/tests/Conformance/src/V1beta1/BlockResponse.php
new file mode 100644
index 000000000000..4e0d4285db3b
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/BlockResponse.php
@@ -0,0 +1,71 @@
+google.showcase.v1beta1.BlockResponse
+ */
+class BlockResponse extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * This content can contain anything, the server will not depend on a value
+ * here.
+ *
+ * Generated from protobuf field string content = 1;
+ */
+ protected $content = '';
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $content
+ * This content can contain anything, the server will not depend on a value
+ * here.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\PBEcho::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * This content can contain anything, the server will not depend on a value
+ * here.
+ *
+ * Generated from protobuf field string content = 1;
+ * @return string
+ */
+ public function getContent()
+ {
+ return $this->content;
+ }
+
+ /**
+ * This content can contain anything, the server will not depend on a value
+ * here.
+ *
+ * Generated from protobuf field string content = 1;
+ * @param string $var
+ * @return $this
+ */
+ public function setContent($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->content = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/Blurb.php b/Gax/tests/Conformance/src/V1beta1/Blurb.php
new file mode 100644
index 000000000000..b901092c60d2
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/Blurb.php
@@ -0,0 +1,352 @@
+google.showcase.v1beta1.Blurb
+ */
+class Blurb extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The resource name of the chat room.
+ *
+ * Generated from protobuf field string name = 1;
+ */
+ protected $name = '';
+ /**
+ * The resource name of the blurb's author.
+ *
+ * Generated from protobuf field string user = 2 [(.google.api.field_behavior) = REQUIRED, (.google.api.resource_reference) = {
+ */
+ protected $user = '';
+ /**
+ * The timestamp at which the blurb was created.
+ *
+ * Generated from protobuf field .google.protobuf.Timestamp create_time = 5 [(.google.api.field_behavior) = OUTPUT_ONLY];
+ */
+ protected $create_time = null;
+ /**
+ * The latest timestamp at which the blurb was updated.
+ *
+ * Generated from protobuf field .google.protobuf.Timestamp update_time = 6 [(.google.api.field_behavior) = OUTPUT_ONLY];
+ */
+ protected $update_time = null;
+ protected $content;
+ protected $legacy_id;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $name
+ * The resource name of the chat room.
+ * @type string $user
+ * The resource name of the blurb's author.
+ * @type string $text
+ * The textual content of this blurb.
+ * @type string $image
+ * The image content of this blurb.
+ * @type \Google\Protobuf\Timestamp $create_time
+ * The timestamp at which the blurb was created.
+ * @type \Google\Protobuf\Timestamp $update_time
+ * The latest timestamp at which the blurb was updated.
+ * @type string $legacy_room_id
+ * The legacy id of the room. This field is used to signal
+ * the use of the compound resource pattern
+ * `rooms/{room}/blurbs/legacy/{legacy_room}.{blurb}`
+ * @type string $legacy_user_id
+ * The legacy id of the user. This field is used to signal
+ * the use of the compound resource pattern
+ * `users/{user}/profile/blurbs/legacy/{legacy_user}~{blurb}`
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Messaging::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The resource name of the chat room.
+ *
+ * Generated from protobuf field string name = 1;
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * The resource name of the chat room.
+ *
+ * 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;
+ }
+
+ /**
+ * The resource name of the blurb's author.
+ *
+ * Generated from protobuf field string user = 2 [(.google.api.field_behavior) = REQUIRED, (.google.api.resource_reference) = {
+ * @return string
+ */
+ public function getUser()
+ {
+ return $this->user;
+ }
+
+ /**
+ * The resource name of the blurb's author.
+ *
+ * Generated from protobuf field string user = 2 [(.google.api.field_behavior) = REQUIRED, (.google.api.resource_reference) = {
+ * @param string $var
+ * @return $this
+ */
+ public function setUser($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->user = $var;
+
+ return $this;
+ }
+
+ /**
+ * The textual content of this blurb.
+ *
+ * Generated from protobuf field string text = 3;
+ * @return string
+ */
+ public function getText()
+ {
+ return $this->readOneof(3);
+ }
+
+ public function hasText()
+ {
+ return $this->hasOneof(3);
+ }
+
+ /**
+ * The textual content of this blurb.
+ *
+ * Generated from protobuf field string text = 3;
+ * @param string $var
+ * @return $this
+ */
+ public function setText($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->writeOneof(3, $var);
+
+ return $this;
+ }
+
+ /**
+ * The image content of this blurb.
+ *
+ * Generated from protobuf field bytes image = 4;
+ * @return string
+ */
+ public function getImage()
+ {
+ return $this->readOneof(4);
+ }
+
+ public function hasImage()
+ {
+ return $this->hasOneof(4);
+ }
+
+ /**
+ * The image content of this blurb.
+ *
+ * Generated from protobuf field bytes image = 4;
+ * @param string $var
+ * @return $this
+ */
+ public function setImage($var)
+ {
+ GPBUtil::checkString($var, False);
+ $this->writeOneof(4, $var);
+
+ return $this;
+ }
+
+ /**
+ * The timestamp at which the blurb was created.
+ *
+ * Generated from protobuf field .google.protobuf.Timestamp create_time = 5 [(.google.api.field_behavior) = OUTPUT_ONLY];
+ * @return \Google\Protobuf\Timestamp|null
+ */
+ public function getCreateTime()
+ {
+ return $this->create_time;
+ }
+
+ public function hasCreateTime()
+ {
+ return isset($this->create_time);
+ }
+
+ public function clearCreateTime()
+ {
+ unset($this->create_time);
+ }
+
+ /**
+ * The timestamp at which the blurb was created.
+ *
+ * Generated from protobuf field .google.protobuf.Timestamp create_time = 5 [(.google.api.field_behavior) = OUTPUT_ONLY];
+ * @param \Google\Protobuf\Timestamp $var
+ * @return $this
+ */
+ public function setCreateTime($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Protobuf\Timestamp::class);
+ $this->create_time = $var;
+
+ return $this;
+ }
+
+ /**
+ * The latest timestamp at which the blurb was updated.
+ *
+ * Generated from protobuf field .google.protobuf.Timestamp update_time = 6 [(.google.api.field_behavior) = OUTPUT_ONLY];
+ * @return \Google\Protobuf\Timestamp|null
+ */
+ public function getUpdateTime()
+ {
+ return $this->update_time;
+ }
+
+ public function hasUpdateTime()
+ {
+ return isset($this->update_time);
+ }
+
+ public function clearUpdateTime()
+ {
+ unset($this->update_time);
+ }
+
+ /**
+ * The latest timestamp at which the blurb was updated.
+ *
+ * Generated from protobuf field .google.protobuf.Timestamp update_time = 6 [(.google.api.field_behavior) = OUTPUT_ONLY];
+ * @param \Google\Protobuf\Timestamp $var
+ * @return $this
+ */
+ public function setUpdateTime($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Protobuf\Timestamp::class);
+ $this->update_time = $var;
+
+ return $this;
+ }
+
+ /**
+ * The legacy id of the room. This field is used to signal
+ * the use of the compound resource pattern
+ * `rooms/{room}/blurbs/legacy/{legacy_room}.{blurb}`
+ *
+ * Generated from protobuf field string legacy_room_id = 7;
+ * @return string
+ */
+ public function getLegacyRoomId()
+ {
+ return $this->readOneof(7);
+ }
+
+ public function hasLegacyRoomId()
+ {
+ return $this->hasOneof(7);
+ }
+
+ /**
+ * The legacy id of the room. This field is used to signal
+ * the use of the compound resource pattern
+ * `rooms/{room}/blurbs/legacy/{legacy_room}.{blurb}`
+ *
+ * Generated from protobuf field string legacy_room_id = 7;
+ * @param string $var
+ * @return $this
+ */
+ public function setLegacyRoomId($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->writeOneof(7, $var);
+
+ return $this;
+ }
+
+ /**
+ * The legacy id of the user. This field is used to signal
+ * the use of the compound resource pattern
+ * `users/{user}/profile/blurbs/legacy/{legacy_user}~{blurb}`
+ *
+ * Generated from protobuf field string legacy_user_id = 8;
+ * @return string
+ */
+ public function getLegacyUserId()
+ {
+ return $this->readOneof(8);
+ }
+
+ public function hasLegacyUserId()
+ {
+ return $this->hasOneof(8);
+ }
+
+ /**
+ * The legacy id of the user. This field is used to signal
+ * the use of the compound resource pattern
+ * `users/{user}/profile/blurbs/legacy/{legacy_user}~{blurb}`
+ *
+ * Generated from protobuf field string legacy_user_id = 8;
+ * @param string $var
+ * @return $this
+ */
+ public function setLegacyUserId($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->writeOneof(8, $var);
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getContent()
+ {
+ return $this->whichOneof("content");
+ }
+
+ /**
+ * @return string
+ */
+ public function getLegacyId()
+ {
+ return $this->whichOneof("legacy_id");
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/Client/ComplianceClient.php b/Gax/tests/Conformance/src/V1beta1/Client/ComplianceClient.php
new file mode 100644
index 000000000000..913c3456efec
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/Client/ComplianceClient.php
@@ -0,0 +1,480 @@
+ getEnumAsync(EnumRequest $request, array $optionalArgs = [])
+ * @method PromiseInterface repeatDataBodyAsync(RepeatRequest $request, array $optionalArgs = [])
+ * @method PromiseInterface repeatDataBodyInfoAsync(RepeatRequest $request, array $optionalArgs = [])
+ * @method PromiseInterface repeatDataBodyPatchAsync(RepeatRequest $request, array $optionalArgs = [])
+ * @method PromiseInterface repeatDataBodyPutAsync(RepeatRequest $request, array $optionalArgs = [])
+ * @method PromiseInterface repeatDataPathResourceAsync(RepeatRequest $request, array $optionalArgs = [])
+ * @method PromiseInterface repeatDataPathTrailingResourceAsync(RepeatRequest $request, array $optionalArgs = [])
+ * @method PromiseInterface repeatDataQueryAsync(RepeatRequest $request, array $optionalArgs = [])
+ * @method PromiseInterface repeatDataSimplePathAsync(RepeatRequest $request, array $optionalArgs = [])
+ * @method PromiseInterface verifyEnumAsync(EnumResponse $request, array $optionalArgs = [])
+ */
+final class ComplianceClient
+{
+ use GapicClientTrait;
+
+ /** The name of the service. */
+ private const SERVICE_NAME = 'google.showcase.v1beta1.Compliance';
+
+ /** The default address of the service. */
+ private const SERVICE_ADDRESS = 'localhost:7469';
+
+ /** The default port of the service. */
+ private const DEFAULT_SERVICE_PORT = 443;
+
+ /** The name of the code generator, to be included in the agent header. */
+ private const CODEGEN_NAME = 'gapic';
+
+ /** The default scopes required by the service. */
+ public static $serviceScopes = [];
+
+ private static function getClientDefaults()
+ {
+ return [
+ 'serviceName' => self::SERVICE_NAME,
+ 'apiEndpoint' => self::SERVICE_ADDRESS . ':' . self::DEFAULT_SERVICE_PORT,
+ 'clientConfig' => __DIR__ . '/../resources/compliance_client_config.json',
+ 'descriptorsConfigPath' => __DIR__ . '/../resources/compliance_descriptor_config.php',
+ 'gcpApiConfigPath' => __DIR__ . '/../resources/compliance_grpc_config.json',
+ 'credentialsConfig' => [
+ 'defaultScopes' => self::$serviceScopes,
+ ],
+ 'transportConfig' => [
+ 'rest' => [
+ 'restClientConfigPath' => __DIR__ . '/../resources/compliance_rest_client_config.php',
+ ],
+ ],
+ ];
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param array $options {
+ * Optional. Options for configuring the service API wrapper.
+ *
+ * @type string $apiEndpoint
+ * The address of the API remote host. May optionally include the port, formatted
+ * as ":". Default 'localhost:7469:443'.
+ * @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
+ * {@see \Google\Auth\FetchAuthTokenInterface} object or
+ * {@see \Google\ApiCore\CredentialsWrapper} object. Note that when one of these
+ * objects are provided, any settings in $credentialsConfig will be ignored.
+ * *Important*: If you accept a credential configuration (credential
+ * JSON/File/Stream) from an external source for authentication to Google Cloud
+ * Platform, you must validate it before providing it to any Google API or library.
+ * Providing an unvalidated credential configuration to Google APIs can compromise
+ * the security of your systems and data. For more information {@see
+ * https://cloud.google.com/docs/authentication/external/externally-sourced-credentials}
+ * @type array $credentialsConfig
+ * Options used to configure credentials, including auth token caching, for the
+ * client. For a full list of supporting configuration options, see
+ * {@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|TransportInterface $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 {@see \Google\ApiCore\Transport\TransportInterface} object. Note
+ * that when this object 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 {@see \Google\ApiCore\Transport\GrpcTransport::build()} and
+ * {@see \Google\ApiCore\Transport\RestTransport::build()} methods for the
+ * supported options.
+ * @type callable $clientCertSource
+ * A callable which returns the client cert as a string. This can be used to
+ * provide a certificate and private key to the transport layer for mTLS.
+ * @type false|LoggerInterface $logger
+ * A PSR-3 compliant logger. If set to false, logging is disabled, ignoring the
+ * 'GOOGLE_SDK_PHP_LOGGING' environment flag
+ * }
+ *
+ * @throws ValidationException
+ *
+ * @experimental
+ */
+ public function __construct(array $options = [])
+ {
+ $clientOptions = $this->buildClientOptions($options);
+ $this->setClientOptions($clientOptions);
+ }
+
+ /** Handles execution of the async variants for each documented method. */
+ public function __call($method, $args)
+ {
+ if (substr($method, -5) !== 'Async') {
+ trigger_error('Call to undefined method ' . __CLASS__ . "::$method()", E_USER_ERROR);
+ }
+
+ array_unshift($args, substr($method, 0, -5));
+ return call_user_func_array([$this, 'startAsyncCall'], $args);
+ }
+
+ /**
+ * This method requests an enum value from the server. Depending on the contents of EnumRequest, the enum value returned will be a known enum declared in the
+ * .proto file, or a made-up enum value the is unknown to the client. To verify that clients can round-trip unknown enum values they receive, use the
+ * response from this RPC as the request to VerifyEnum()
+ *
+ * The values of enums sent by the server when a known or unknown value is requested will be the same within a single Showcase server run (this is needed for
+ * VerifyEnum() to work) but are not guaranteed to be the same across separate Showcase server runs.
+ *
+ * The async variant is {@see ComplianceClient::getEnumAsync()} .
+ *
+ * @example samples/V1beta1/ComplianceClient/get_enum.php
+ *
+ * @param EnumRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @return EnumResponse
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function getEnum(EnumRequest $request, array $callOptions = []): EnumResponse
+ {
+ return $this->startApiCall('GetEnum', $request, $callOptions)->wait();
+ }
+
+ /**
+ * This method echoes the ComplianceData request. This method exercises
+ * sending the entire request object in the REST body.
+ *
+ * The async variant is {@see ComplianceClient::repeatDataBodyAsync()} .
+ *
+ * @example samples/V1beta1/ComplianceClient/repeat_data_body.php
+ *
+ * @param RepeatRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @return RepeatResponse
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function repeatDataBody(RepeatRequest $request, array $callOptions = []): RepeatResponse
+ {
+ return $this->startApiCall('RepeatDataBody', $request, $callOptions)->wait();
+ }
+
+ /**
+ * This method echoes the ComplianceData request. This method exercises
+ * sending the a message-type field in the REST body. Per AIP-127, only
+ * top-level, non-repeated fields can be sent this way.
+ *
+ * The async variant is {@see ComplianceClient::repeatDataBodyInfoAsync()} .
+ *
+ * @example samples/V1beta1/ComplianceClient/repeat_data_body_info.php
+ *
+ * @param RepeatRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @return RepeatResponse
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function repeatDataBodyInfo(RepeatRequest $request, array $callOptions = []): RepeatResponse
+ {
+ return $this->startApiCall('RepeatDataBodyInfo', $request, $callOptions)->wait();
+ }
+
+ /**
+ * This method echoes the ComplianceData request, using the HTTP PATCH method.
+ *
+ * The async variant is {@see ComplianceClient::repeatDataBodyPatchAsync()} .
+ *
+ * @example samples/V1beta1/ComplianceClient/repeat_data_body_patch.php
+ *
+ * @param RepeatRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @return RepeatResponse
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function repeatDataBodyPatch(RepeatRequest $request, array $callOptions = []): RepeatResponse
+ {
+ return $this->startApiCall('RepeatDataBodyPatch', $request, $callOptions)->wait();
+ }
+
+ /**
+ * This method echoes the ComplianceData request, using the HTTP PUT method.
+ *
+ * The async variant is {@see ComplianceClient::repeatDataBodyPutAsync()} .
+ *
+ * @example samples/V1beta1/ComplianceClient/repeat_data_body_put.php
+ *
+ * @param RepeatRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @return RepeatResponse
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function repeatDataBodyPut(RepeatRequest $request, array $callOptions = []): RepeatResponse
+ {
+ return $this->startApiCall('RepeatDataBodyPut', $request, $callOptions)->wait();
+ }
+
+ /**
+ * Same as RepeatDataSimplePath, but with a path resource.
+ *
+ * The async variant is {@see ComplianceClient::repeatDataPathResourceAsync()} .
+ *
+ * @example samples/V1beta1/ComplianceClient/repeat_data_path_resource.php
+ *
+ * @param RepeatRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @return RepeatResponse
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function repeatDataPathResource(RepeatRequest $request, array $callOptions = []): RepeatResponse
+ {
+ return $this->startApiCall('RepeatDataPathResource', $request, $callOptions)->wait();
+ }
+
+ /**
+ * Same as RepeatDataSimplePath, but with a trailing resource.
+ *
+ * The async variant is
+ * {@see ComplianceClient::repeatDataPathTrailingResourceAsync()} .
+ *
+ * @example samples/V1beta1/ComplianceClient/repeat_data_path_trailing_resource.php
+ *
+ * @param RepeatRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @return RepeatResponse
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function repeatDataPathTrailingResource(RepeatRequest $request, array $callOptions = []): RepeatResponse
+ {
+ return $this->startApiCall('RepeatDataPathTrailingResource', $request, $callOptions)->wait();
+ }
+
+ /**
+ * This method echoes the ComplianceData request. This method exercises
+ * sending all request fields as query parameters.
+ *
+ * The async variant is {@see ComplianceClient::repeatDataQueryAsync()} .
+ *
+ * @example samples/V1beta1/ComplianceClient/repeat_data_query.php
+ *
+ * @param RepeatRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @return RepeatResponse
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function repeatDataQuery(RepeatRequest $request, array $callOptions = []): RepeatResponse
+ {
+ return $this->startApiCall('RepeatDataQuery', $request, $callOptions)->wait();
+ }
+
+ /**
+ * This method echoes the ComplianceData request. This method exercises
+ * sending some parameters as "simple" path variables (i.e., of the form
+ * "/bar/{foo}" rather than "/{foo=bar/*}"), and the rest as query parameters.
+ *
+ * The async variant is {@see ComplianceClient::repeatDataSimplePathAsync()} .
+ *
+ * @example samples/V1beta1/ComplianceClient/repeat_data_simple_path.php
+ *
+ * @param RepeatRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @return RepeatResponse
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function repeatDataSimplePath(RepeatRequest $request, array $callOptions = []): RepeatResponse
+ {
+ return $this->startApiCall('RepeatDataSimplePath', $request, $callOptions)->wait();
+ }
+
+ /**
+ * This method is used to verify that clients can round-trip enum values, which is particularly important for unknown enum values over REST. VerifyEnum()
+ * verifies that its request, which is presumably the response that the client previously got to a GetEnum(), contains the correct data. If so, it responds
+ * with the same EnumResponse; otherwise, the RPC errors.
+ *
+ * This works because the values of enums sent by the server when a known or unknown value is requested will be the same within a single Showcase server run,
+ * although they are not guaranteed to be the same across separate Showcase server runs.
+ *
+ * The async variant is {@see ComplianceClient::verifyEnumAsync()} .
+ *
+ * @example samples/V1beta1/ComplianceClient/verify_enum.php
+ *
+ * @param EnumResponse $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @return EnumResponse
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function verifyEnum(EnumResponse $request, array $callOptions = []): EnumResponse
+ {
+ return $this->startApiCall('VerifyEnum', $request, $callOptions)->wait();
+ }
+}
diff --git a/Gax/tests/Conformance/src/V1beta1/Client/EchoClient.php b/Gax/tests/Conformance/src/V1beta1/Client/EchoClient.php
new file mode 100644
index 000000000000..eab8a8bc2fd5
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/Client/EchoClient.php
@@ -0,0 +1,579 @@
+ blockAsync(BlockRequest $request, array $optionalArgs = [])
+ * @method PromiseInterface echoAsync(EchoRequest $request, array $optionalArgs = [])
+ * @method PromiseInterface echoErrorDetailsAsync(EchoErrorDetailsRequest $request, array $optionalArgs = [])
+ * @method PromiseInterface failEchoWithDetailsAsync(FailEchoWithDetailsRequest $request, array $optionalArgs = [])
+ * @method PromiseInterface pagedExpandAsync(PagedExpandRequest $request, array $optionalArgs = [])
+ * @method PromiseInterface pagedExpandLegacyAsync(PagedExpandLegacyRequest $request, array $optionalArgs = [])
+ * @method PromiseInterface pagedExpandLegacyMappedAsync(PagedExpandRequest $request, array $optionalArgs = [])
+ * @method PromiseInterface waitAsync(WaitRequest $request, array $optionalArgs = [])
+ */
+final class EchoClient
+{
+ use GapicClientTrait;
+
+ /** The name of the service. */
+ private const SERVICE_NAME = 'google.showcase.v1beta1.Echo';
+
+ /** The default address of the service. */
+ private const SERVICE_ADDRESS = 'localhost:7469';
+
+ /** The default port of the service. */
+ private const DEFAULT_SERVICE_PORT = 443;
+
+ /** The name of the code generator, to be included in the agent header. */
+ private const CODEGEN_NAME = 'gapic';
+
+ /** The api version of the service */
+ private string $apiVersion = 'v1_20240408';
+
+ /** The default scopes required by the service. */
+ public static $serviceScopes = [];
+
+ private $operationsClient;
+
+ private static function getClientDefaults()
+ {
+ return [
+ 'serviceName' => self::SERVICE_NAME,
+ 'apiEndpoint' => self::SERVICE_ADDRESS . ':' . self::DEFAULT_SERVICE_PORT,
+ 'clientConfig' => __DIR__ . '/../resources/echo_client_config.json',
+ 'descriptorsConfigPath' => __DIR__ . '/../resources/echo_descriptor_config.php',
+ 'gcpApiConfigPath' => __DIR__ . '/../resources/echo_grpc_config.json',
+ 'credentialsConfig' => [
+ 'defaultScopes' => self::$serviceScopes,
+ ],
+ 'transportConfig' => [
+ 'rest' => [
+ 'restClientConfigPath' => __DIR__ . '/../resources/echo_rest_client_config.php',
+ ],
+ ],
+ ];
+ }
+
+ /**
+ * Return an OperationsClient object with the same endpoint as $this.
+ *
+ * @return OperationsClient
+ *
+ * @experimental
+ */
+ public function getOperationsClient()
+ {
+ return $this->operationsClient;
+ }
+
+ /**
+ * Resume an existing long running operation that was previously started by a long
+ * running API method. If $methodName is not provided, or does not match a long
+ * running API method, then the operation can still be resumed, but the
+ * OperationResponse object will not deserialize the final response.
+ *
+ * @param string $operationName The name of the long running operation
+ * @param string $methodName The name of the method used to start the operation
+ *
+ * @return OperationResponse
+ *
+ * @experimental
+ */
+ public function resumeOperation($operationName, $methodName = null)
+ {
+ $options = isset($this->descriptors[$methodName]['longRunning']) ? $this->descriptors[$methodName]['longRunning'] : [];
+ $operation = new OperationResponse($operationName, $this->getOperationsClient(), $options);
+ $operation->reload();
+ return $operation;
+ }
+
+ /**
+ * Create the default operation client for the service.
+ *
+ * @param array $options ClientOptions for the client.
+ *
+ * @return OperationsClient
+ */
+ private function createOperationsClient(array $options)
+ {
+ // Unset client-specific configuration options
+ unset($options['serviceName'], $options['clientConfig'], $options['descriptorsConfigPath']);
+
+ if (isset($options['operationsClient'])) {
+ return $options['operationsClient'];
+ }
+
+ return new OperationsClient($options);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param array $options {
+ * Optional. Options for configuring the service API wrapper.
+ *
+ * @type string $apiEndpoint
+ * The address of the API remote host. May optionally include the port, formatted
+ * as ":". Default 'localhost:7469:443'.
+ * @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
+ * {@see \Google\Auth\FetchAuthTokenInterface} object or
+ * {@see \Google\ApiCore\CredentialsWrapper} object. Note that when one of these
+ * objects are provided, any settings in $credentialsConfig will be ignored.
+ * *Important*: If you accept a credential configuration (credential
+ * JSON/File/Stream) from an external source for authentication to Google Cloud
+ * Platform, you must validate it before providing it to any Google API or library.
+ * Providing an unvalidated credential configuration to Google APIs can compromise
+ * the security of your systems and data. For more information {@see
+ * https://cloud.google.com/docs/authentication/external/externally-sourced-credentials}
+ * @type array $credentialsConfig
+ * Options used to configure credentials, including auth token caching, for the
+ * client. For a full list of supporting configuration options, see
+ * {@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|TransportInterface $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 {@see \Google\ApiCore\Transport\TransportInterface} object. Note
+ * that when this object 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 {@see \Google\ApiCore\Transport\GrpcTransport::build()} and
+ * {@see \Google\ApiCore\Transport\RestTransport::build()} methods for the
+ * supported options.
+ * @type callable $clientCertSource
+ * A callable which returns the client cert as a string. This can be used to
+ * provide a certificate and private key to the transport layer for mTLS.
+ * @type false|LoggerInterface $logger
+ * A PSR-3 compliant logger. If set to false, logging is disabled, ignoring the
+ * 'GOOGLE_SDK_PHP_LOGGING' environment flag
+ * }
+ *
+ * @throws ValidationException
+ *
+ * @experimental
+ */
+ public function __construct(array $options = [])
+ {
+ $clientOptions = $this->buildClientOptions($options);
+ $this->setClientOptions($clientOptions);
+ $this->operationsClient = $this->createOperationsClient($clientOptions);
+ }
+
+ /** Handles execution of the async variants for each documented method. */
+ public function __call($method, $args)
+ {
+ if (substr($method, -5) !== 'Async') {
+ trigger_error('Call to undefined method ' . __CLASS__ . "::$method()", E_USER_ERROR);
+ }
+
+ array_unshift($args, substr($method, 0, -5));
+ return call_user_func_array([$this, 'startAsyncCall'], $args);
+ }
+
+ /**
+ * This method will block (wait) for the requested amount of time
+ * and then return the response or error.
+ * This method showcases how a client handles delays or retries.
+ *
+ * The async variant is {@see EchoClient::blockAsync()} .
+ *
+ * @example samples/V1beta1/EchoClient/block.php
+ *
+ * @param BlockRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @return BlockResponse
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function block(BlockRequest $request, array $callOptions = []): BlockResponse
+ {
+ return $this->startApiCall('Block', $request, $callOptions)->wait();
+ }
+
+ /**
+ * This method, upon receiving a request on the stream, will pass the same
+ * content back on the stream. This method showcases bidirectional
+ * streaming RPCs.
+ *
+ * @example samples/V1beta1/EchoClient/chat.php
+ *
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type int $timeoutMillis
+ * Timeout to use for this call.
+ * }
+ *
+ * @return BidiStream
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function chat(array $callOptions = []): BidiStream
+ {
+ return $this->startApiCall('Chat', null, $callOptions);
+ }
+
+ /**
+ * This method will collect the words given to it. When the stream is closed
+ * by the client, this method will return the a concatenation of the strings
+ * passed to it. This method showcases client-side streaming RPCs.
+ *
+ * @example samples/V1beta1/EchoClient/collect.php
+ *
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type int $timeoutMillis
+ * Timeout to use for this call.
+ * }
+ *
+ * @return ClientStream
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function collect(array $callOptions = []): ClientStream
+ {
+ return $this->startApiCall('Collect', null, $callOptions);
+ }
+
+ /**
+ * This method simply echoes the request. This method showcases unary RPCs.
+ *
+ * The async variant is {@see EchoClient::echoAsync()} .
+ *
+ * @example samples/V1beta1/EchoClient/echo.php
+ *
+ * @param EchoRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @return EchoResponse
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function echo(EchoRequest $request, array $callOptions = []): EchoResponse
+ {
+ return $this->startApiCall('Echo', $request, $callOptions)->wait();
+ }
+
+ /**
+ * This method returns error details in a repeated "google.protobuf.Any"
+ * field. This method showcases handling errors thus encoded, particularly
+ * over REST transport. Note that GAPICs only allow the type
+ * "google.protobuf.Any" for field paths ending in "error.details", and, at
+ * run-time, the actual types for these fields must be one of the types in
+ * google/rpc/error_details.proto.
+ *
+ * The async variant is {@see EchoClient::echoErrorDetailsAsync()} .
+ *
+ * @example samples/V1beta1/EchoClient/echo_error_details.php
+ *
+ * @param EchoErrorDetailsRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @return EchoErrorDetailsResponse
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function echoErrorDetails(EchoErrorDetailsRequest $request, array $callOptions = []): EchoErrorDetailsResponse
+ {
+ return $this->startApiCall('EchoErrorDetails', $request, $callOptions)->wait();
+ }
+
+ /**
+ * This method splits the given content into words and will pass each word back
+ * through the stream. This method showcases server-side streaming RPCs.
+ *
+ * @example samples/V1beta1/EchoClient/expand.php
+ *
+ * @param ExpandRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type int $timeoutMillis
+ * Timeout to use for this call.
+ * }
+ *
+ * @return ServerStream
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function expand(ExpandRequest $request, array $callOptions = []): ServerStream
+ {
+ return $this->startApiCall('Expand', $request, $callOptions);
+ }
+
+ /**
+ * This method always fails with a gRPC "Aborted" error status that contains
+ * multiple error details. These include one instance of each of the standard
+ * ones in error_details.proto
+ * (https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto)
+ * plus a custom, Showcase-defined PoetryError. The intent of this RPC is to
+ * verify that GAPICs can process these various error details and surface them
+ * to the user in an idiomatic form.
+ *
+ * The async variant is {@see EchoClient::failEchoWithDetailsAsync()} .
+ *
+ * @example samples/V1beta1/EchoClient/fail_echo_with_details.php
+ *
+ * @param FailEchoWithDetailsRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @return FailEchoWithDetailsResponse
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function failEchoWithDetails(FailEchoWithDetailsRequest $request, array $callOptions = []): FailEchoWithDetailsResponse
+ {
+ return $this->startApiCall('FailEchoWithDetails', $request, $callOptions)->wait();
+ }
+
+ /**
+ * This is similar to the Expand method but instead of returning a stream of
+ * expanded words, this method returns a paged list of expanded words.
+ *
+ * The async variant is {@see EchoClient::pagedExpandAsync()} .
+ *
+ * @example samples/V1beta1/EchoClient/paged_expand.php
+ *
+ * @param PagedExpandRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @return PagedListResponse
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function pagedExpand(PagedExpandRequest $request, array $callOptions = []): PagedListResponse
+ {
+ return $this->startApiCall('PagedExpand', $request, $callOptions);
+ }
+
+ /**
+ * This is similar to the PagedExpand except that it uses
+ * max_results instead of page_size, as some legacy APIs still
+ * do. New APIs should NOT use this pattern.
+ *
+ * The async variant is {@see EchoClient::pagedExpandLegacyAsync()} .
+ *
+ * @example samples/V1beta1/EchoClient/paged_expand_legacy.php
+ *
+ * @param PagedExpandLegacyRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @return PagedExpandResponse
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function pagedExpandLegacy(PagedExpandLegacyRequest $request, array $callOptions = []): PagedExpandResponse
+ {
+ return $this->startApiCall('PagedExpandLegacy', $request, $callOptions)->wait();
+ }
+
+ /**
+ * This method returns a map containing lists of words that appear in the input, keyed by their
+ * initial character. The only words returned are the ones included in the current page,
+ * as determined by page_token and page_size, which both refer to the word indices in the
+ * input. This paging result consisting of a map of lists is a pattern used by some legacy
+ * APIs. New APIs should NOT use this pattern.
+ *
+ * The async variant is {@see EchoClient::pagedExpandLegacyMappedAsync()} .
+ *
+ * @example samples/V1beta1/EchoClient/paged_expand_legacy_mapped.php
+ *
+ * @param PagedExpandRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @return PagedListResponse
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function pagedExpandLegacyMapped(PagedExpandRequest $request, array $callOptions = []): PagedListResponse
+ {
+ return $this->startApiCall('PagedExpandLegacyMapped', $request, $callOptions);
+ }
+
+ /**
+ * This method will wait for the requested amount of time and then return.
+ * This method showcases how a client handles a request timeout.
+ *
+ * The async variant is {@see EchoClient::waitAsync()} .
+ *
+ * @example samples/V1beta1/EchoClient/wait.php
+ *
+ * @param WaitRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @return OperationResponse
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function wait(WaitRequest $request, array $callOptions = []): OperationResponse
+ {
+ return $this->startApiCall('Wait', $request, $callOptions)->wait();
+ }
+}
diff --git a/Gax/tests/Conformance/src/V1beta1/Client/IdentityClient.php b/Gax/tests/Conformance/src/V1beta1/Client/IdentityClient.php
new file mode 100644
index 000000000000..01c047823a4c
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/Client/IdentityClient.php
@@ -0,0 +1,367 @@
+ createUserAsync(CreateUserRequest $request, array $optionalArgs = [])
+ * @method PromiseInterface deleteUserAsync(DeleteUserRequest $request, array $optionalArgs = [])
+ * @method PromiseInterface getUserAsync(GetUserRequest $request, array $optionalArgs = [])
+ * @method PromiseInterface listUsersAsync(ListUsersRequest $request, array $optionalArgs = [])
+ * @method PromiseInterface updateUserAsync(UpdateUserRequest $request, array $optionalArgs = [])
+ */
+final class IdentityClient
+{
+ use GapicClientTrait;
+ use ResourceHelperTrait;
+
+ /** The name of the service. */
+ private const SERVICE_NAME = 'google.showcase.v1beta1.Identity';
+
+ /** The default address of the service. */
+ private const SERVICE_ADDRESS = 'localhost:7469';
+
+ /** The default port of the service. */
+ private const DEFAULT_SERVICE_PORT = 443;
+
+ /** The name of the code generator, to be included in the agent header. */
+ private const CODEGEN_NAME = 'gapic';
+
+ /** The default scopes required by the service. */
+ public static $serviceScopes = [];
+
+ private static function getClientDefaults()
+ {
+ return [
+ 'serviceName' => self::SERVICE_NAME,
+ 'apiEndpoint' => self::SERVICE_ADDRESS . ':' . self::DEFAULT_SERVICE_PORT,
+ 'clientConfig' => __DIR__ . '/../resources/identity_client_config.json',
+ 'descriptorsConfigPath' => __DIR__ . '/../resources/identity_descriptor_config.php',
+ 'gcpApiConfigPath' => __DIR__ . '/../resources/identity_grpc_config.json',
+ 'credentialsConfig' => [
+ 'defaultScopes' => self::$serviceScopes,
+ ],
+ 'transportConfig' => [
+ 'rest' => [
+ 'restClientConfigPath' => __DIR__ . '/../resources/identity_rest_client_config.php',
+ ],
+ ],
+ ];
+ }
+
+ /**
+ * Formats a string containing the fully-qualified path to represent a user
+ * resource.
+ *
+ * @param string $user
+ *
+ * @return string The formatted user resource.
+ *
+ * @experimental
+ */
+ public static function userName(string $user): string
+ {
+ return self::getPathTemplate('user')->render([
+ 'user' => $user,
+ ]);
+ }
+
+ /**
+ * Parses a formatted name string and returns an associative array of the components in the name.
+ * The following name formats are supported:
+ * Template: Pattern
+ * - user: users/{user}
+ *
+ * The optional $template argument can be supplied to specify a particular pattern,
+ * and must match one of the templates listed above. If no $template argument is
+ * provided, or if the $template argument does not match one of the templates
+ * listed, then parseName will check each of the supported templates, and return
+ * the first match.
+ *
+ * @param string $formattedName The formatted name string
+ * @param ?string $template Optional name of template to match
+ *
+ * @return array An associative array from name component IDs to component values.
+ *
+ * @throws ValidationException If $formattedName could not be matched.
+ *
+ * @experimental
+ */
+ public static function parseName(string $formattedName, ?string $template = null): array
+ {
+ return self::parseFormattedName($formattedName, $template);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param array $options {
+ * Optional. Options for configuring the service API wrapper.
+ *
+ * @type string $apiEndpoint
+ * The address of the API remote host. May optionally include the port, formatted
+ * as ":". Default 'localhost:7469:443'.
+ * @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
+ * {@see \Google\Auth\FetchAuthTokenInterface} object or
+ * {@see \Google\ApiCore\CredentialsWrapper} object. Note that when one of these
+ * objects are provided, any settings in $credentialsConfig will be ignored.
+ * *Important*: If you accept a credential configuration (credential
+ * JSON/File/Stream) from an external source for authentication to Google Cloud
+ * Platform, you must validate it before providing it to any Google API or library.
+ * Providing an unvalidated credential configuration to Google APIs can compromise
+ * the security of your systems and data. For more information {@see
+ * https://cloud.google.com/docs/authentication/external/externally-sourced-credentials}
+ * @type array $credentialsConfig
+ * Options used to configure credentials, including auth token caching, for the
+ * client. For a full list of supporting configuration options, see
+ * {@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|TransportInterface $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 {@see \Google\ApiCore\Transport\TransportInterface} object. Note
+ * that when this object 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 {@see \Google\ApiCore\Transport\GrpcTransport::build()} and
+ * {@see \Google\ApiCore\Transport\RestTransport::build()} methods for the
+ * supported options.
+ * @type callable $clientCertSource
+ * A callable which returns the client cert as a string. This can be used to
+ * provide a certificate and private key to the transport layer for mTLS.
+ * @type false|LoggerInterface $logger
+ * A PSR-3 compliant logger. If set to false, logging is disabled, ignoring the
+ * 'GOOGLE_SDK_PHP_LOGGING' environment flag
+ * }
+ *
+ * @throws ValidationException
+ *
+ * @experimental
+ */
+ public function __construct(array $options = [])
+ {
+ $clientOptions = $this->buildClientOptions($options);
+ $this->setClientOptions($clientOptions);
+ }
+
+ /** Handles execution of the async variants for each documented method. */
+ public function __call($method, $args)
+ {
+ if (substr($method, -5) !== 'Async') {
+ trigger_error('Call to undefined method ' . __CLASS__ . "::$method()", E_USER_ERROR);
+ }
+
+ array_unshift($args, substr($method, 0, -5));
+ return call_user_func_array([$this, 'startAsyncCall'], $args);
+ }
+
+ /**
+ * Creates a user.
+ *
+ * The async variant is {@see IdentityClient::createUserAsync()} .
+ *
+ * @example samples/V1beta1/IdentityClient/create_user.php
+ *
+ * @param CreateUserRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @return User
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function createUser(CreateUserRequest $request, array $callOptions = []): User
+ {
+ return $this->startApiCall('CreateUser', $request, $callOptions)->wait();
+ }
+
+ /**
+ * Deletes a user, their profile, and all of their authored messages.
+ *
+ * The async variant is {@see IdentityClient::deleteUserAsync()} .
+ *
+ * @example samples/V1beta1/IdentityClient/delete_user.php
+ *
+ * @param DeleteUserRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function deleteUser(DeleteUserRequest $request, array $callOptions = []): void
+ {
+ $this->startApiCall('DeleteUser', $request, $callOptions)->wait();
+ }
+
+ /**
+ * Retrieves the User with the given uri.
+ *
+ * The async variant is {@see IdentityClient::getUserAsync()} .
+ *
+ * @example samples/V1beta1/IdentityClient/get_user.php
+ *
+ * @param GetUserRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @return User
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function getUser(GetUserRequest $request, array $callOptions = []): User
+ {
+ return $this->startApiCall('GetUser', $request, $callOptions)->wait();
+ }
+
+ /**
+ * Lists all users.
+ *
+ * The async variant is {@see IdentityClient::listUsersAsync()} .
+ *
+ * @example samples/V1beta1/IdentityClient/list_users.php
+ *
+ * @param ListUsersRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @return PagedListResponse
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function listUsers(ListUsersRequest $request, array $callOptions = []): PagedListResponse
+ {
+ return $this->startApiCall('ListUsers', $request, $callOptions);
+ }
+
+ /**
+ * Updates a user.
+ *
+ * The async variant is {@see IdentityClient::updateUserAsync()} .
+ *
+ * @example samples/V1beta1/IdentityClient/update_user.php
+ *
+ * @param UpdateUserRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @return User
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function updateUser(UpdateUserRequest $request, array $callOptions = []): User
+ {
+ return $this->startApiCall('UpdateUser', $request, $callOptions)->wait();
+ }
+}
diff --git a/Gax/tests/Conformance/src/V1beta1/Client/MessagingClient.php b/Gax/tests/Conformance/src/V1beta1/Client/MessagingClient.php
new file mode 100644
index 000000000000..6acea6553826
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/Client/MessagingClient.php
@@ -0,0 +1,833 @@
+ createBlurbAsync(CreateBlurbRequest $request, array $optionalArgs = [])
+ * @method PromiseInterface createRoomAsync(CreateRoomRequest $request, array $optionalArgs = [])
+ * @method PromiseInterface deleteBlurbAsync(DeleteBlurbRequest $request, array $optionalArgs = [])
+ * @method PromiseInterface deleteRoomAsync(DeleteRoomRequest $request, array $optionalArgs = [])
+ * @method PromiseInterface getBlurbAsync(GetBlurbRequest $request, array $optionalArgs = [])
+ * @method PromiseInterface getRoomAsync(GetRoomRequest $request, array $optionalArgs = [])
+ * @method PromiseInterface listBlurbsAsync(ListBlurbsRequest $request, array $optionalArgs = [])
+ * @method PromiseInterface listRoomsAsync(ListRoomsRequest $request, array $optionalArgs = [])
+ * @method PromiseInterface searchBlurbsAsync(SearchBlurbsRequest $request, array $optionalArgs = [])
+ * @method PromiseInterface updateBlurbAsync(UpdateBlurbRequest $request, array $optionalArgs = [])
+ * @method PromiseInterface updateRoomAsync(UpdateRoomRequest $request, array $optionalArgs = [])
+ */
+final class MessagingClient
+{
+ use GapicClientTrait;
+ use ResourceHelperTrait;
+
+ /** The name of the service. */
+ private const SERVICE_NAME = 'google.showcase.v1beta1.Messaging';
+
+ /** The default address of the service. */
+ private const SERVICE_ADDRESS = 'localhost:7469';
+
+ /** The default port of the service. */
+ private const DEFAULT_SERVICE_PORT = 443;
+
+ /** The name of the code generator, to be included in the agent header. */
+ private const CODEGEN_NAME = 'gapic';
+
+ /** The default scopes required by the service. */
+ public static $serviceScopes = [];
+
+ private $operationsClient;
+
+ private static function getClientDefaults()
+ {
+ return [
+ 'serviceName' => self::SERVICE_NAME,
+ 'apiEndpoint' => self::SERVICE_ADDRESS . ':' . self::DEFAULT_SERVICE_PORT,
+ 'clientConfig' => __DIR__ . '/../resources/messaging_client_config.json',
+ 'descriptorsConfigPath' => __DIR__ . '/../resources/messaging_descriptor_config.php',
+ 'gcpApiConfigPath' => __DIR__ . '/../resources/messaging_grpc_config.json',
+ 'credentialsConfig' => [
+ 'defaultScopes' => self::$serviceScopes,
+ ],
+ 'transportConfig' => [
+ 'rest' => [
+ 'restClientConfigPath' => __DIR__ . '/../resources/messaging_rest_client_config.php',
+ ],
+ ],
+ ];
+ }
+
+ /**
+ * Return an OperationsClient object with the same endpoint as $this.
+ *
+ * @return OperationsClient
+ *
+ * @experimental
+ */
+ public function getOperationsClient()
+ {
+ return $this->operationsClient;
+ }
+
+ /**
+ * Resume an existing long running operation that was previously started by a long
+ * running API method. If $methodName is not provided, or does not match a long
+ * running API method, then the operation can still be resumed, but the
+ * OperationResponse object will not deserialize the final response.
+ *
+ * @param string $operationName The name of the long running operation
+ * @param string $methodName The name of the method used to start the operation
+ *
+ * @return OperationResponse
+ *
+ * @experimental
+ */
+ public function resumeOperation($operationName, $methodName = null)
+ {
+ $options = isset($this->descriptors[$methodName]['longRunning']) ? $this->descriptors[$methodName]['longRunning'] : [];
+ $operation = new OperationResponse($operationName, $this->getOperationsClient(), $options);
+ $operation->reload();
+ return $operation;
+ }
+
+ /**
+ * Create the default operation client for the service.
+ *
+ * @param array $options ClientOptions for the client.
+ *
+ * @return OperationsClient
+ */
+ private function createOperationsClient(array $options)
+ {
+ // Unset client-specific configuration options
+ unset($options['serviceName'], $options['clientConfig'], $options['descriptorsConfigPath']);
+
+ if (isset($options['operationsClient'])) {
+ return $options['operationsClient'];
+ }
+
+ return new OperationsClient($options);
+ }
+
+ /**
+ * Formats a string containing the fully-qualified path to represent a blurb
+ * resource.
+ *
+ * @param string $user
+ * @param string $blurb
+ *
+ * @return string The formatted blurb resource.
+ *
+ * @experimental
+ */
+ public static function blurbName(string $user, string $blurb): string
+ {
+ return self::getPathTemplate('blurb')->render([
+ 'user' => $user,
+ 'blurb' => $blurb,
+ ]);
+ }
+
+ /**
+ * Formats a string containing the fully-qualified path to represent a room
+ * resource.
+ *
+ * @param string $room
+ *
+ * @return string The formatted room resource.
+ *
+ * @experimental
+ */
+ public static function roomName(string $room): string
+ {
+ return self::getPathTemplate('room')->render([
+ 'room' => $room,
+ ]);
+ }
+
+ /**
+ * Formats a string containing the fully-qualified path to represent a room_blurb
+ * resource.
+ *
+ * @param string $room
+ * @param string $blurb
+ *
+ * @return string The formatted room_blurb resource.
+ *
+ * @experimental
+ */
+ public static function roomBlurbName(string $room, string $blurb): string
+ {
+ return self::getPathTemplate('roomBlurb')->render([
+ 'room' => $room,
+ 'blurb' => $blurb,
+ ]);
+ }
+
+ /**
+ * Formats a string containing the fully-qualified path to represent a
+ * room_legacy_room resource.
+ *
+ * @param string $room
+ * @param string $legacyRoom
+ *
+ * @return string The formatted room_legacy_room resource.
+ *
+ * @experimental
+ */
+ public static function roomLegacyRoomName(string $room, string $legacyRoom): string
+ {
+ return self::getPathTemplate('roomLegacyRoom')->render([
+ 'room' => $room,
+ 'legacy_room' => $legacyRoom,
+ ]);
+ }
+
+ /**
+ * Formats a string containing the fully-qualified path to represent a
+ * room_legacy_room_blurb resource.
+ *
+ * @param string $room
+ * @param string $legacyRoom
+ * @param string $blurb
+ *
+ * @return string The formatted room_legacy_room_blurb resource.
+ *
+ * @experimental
+ */
+ public static function roomLegacyRoomBlurbName(string $room, string $legacyRoom, string $blurb): string
+ {
+ return self::getPathTemplate('roomLegacyRoomBlurb')->render([
+ 'room' => $room,
+ 'legacy_room' => $legacyRoom,
+ 'blurb' => $blurb,
+ ]);
+ }
+
+ /**
+ * Formats a string containing the fully-qualified path to represent a user
+ * resource.
+ *
+ * @param string $user
+ *
+ * @return string The formatted user resource.
+ *
+ * @experimental
+ */
+ public static function userName(string $user): string
+ {
+ return self::getPathTemplate('user')->render([
+ 'user' => $user,
+ ]);
+ }
+
+ /**
+ * Formats a string containing the fully-qualified path to represent a user_blurb
+ * resource.
+ *
+ * @param string $user
+ * @param string $blurb
+ *
+ * @return string The formatted user_blurb resource.
+ *
+ * @experimental
+ */
+ public static function userBlurbName(string $user, string $blurb): string
+ {
+ return self::getPathTemplate('userBlurb')->render([
+ 'user' => $user,
+ 'blurb' => $blurb,
+ ]);
+ }
+
+ /**
+ * Formats a string containing the fully-qualified path to represent a
+ * user_blurb_legacy_user resource.
+ *
+ * @param string $user
+ * @param string $blurb
+ * @param string $legacyUser
+ *
+ * @return string The formatted user_blurb_legacy_user resource.
+ *
+ * @experimental
+ */
+ public static function userBlurbLegacyUserName(string $user, string $blurb, string $legacyUser): string
+ {
+ return self::getPathTemplate('userBlurbLegacyUser')->render([
+ 'user' => $user,
+ 'blurb' => $blurb,
+ 'legacy_user' => $legacyUser,
+ ]);
+ }
+
+ /**
+ * Parses a formatted name string and returns an associative array of the components in the name.
+ * The following name formats are supported:
+ * Template: Pattern
+ * - blurb: users/{user}/blurbs/{blurb}
+ * - room: rooms/{room}
+ * - roomBlurb: rooms/{room}/blurbs/{blurb}
+ * - roomLegacyRoom: rooms/{room}/legacy_room/{legacy_room}
+ * - roomLegacyRoomBlurb: rooms/{room}/legacy_room/{legacy_room}/blurbs/{blurb}
+ * - user: users/{user}
+ * - userBlurb: users/{user}/blurbs/{blurb}
+ * - userBlurbLegacyUser: users/{user}/blurbs/{blurb}/legacy/{legacy_user}
+ *
+ * The optional $template argument can be supplied to specify a particular pattern,
+ * and must match one of the templates listed above. If no $template argument is
+ * provided, or if the $template argument does not match one of the templates
+ * listed, then parseName will check each of the supported templates, and return
+ * the first match.
+ *
+ * @param string $formattedName The formatted name string
+ * @param ?string $template Optional name of template to match
+ *
+ * @return array An associative array from name component IDs to component values.
+ *
+ * @throws ValidationException If $formattedName could not be matched.
+ *
+ * @experimental
+ */
+ public static function parseName(string $formattedName, ?string $template = null): array
+ {
+ return self::parseFormattedName($formattedName, $template);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param array $options {
+ * Optional. Options for configuring the service API wrapper.
+ *
+ * @type string $apiEndpoint
+ * The address of the API remote host. May optionally include the port, formatted
+ * as ":". Default 'localhost:7469:443'.
+ * @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
+ * {@see \Google\Auth\FetchAuthTokenInterface} object or
+ * {@see \Google\ApiCore\CredentialsWrapper} object. Note that when one of these
+ * objects are provided, any settings in $credentialsConfig will be ignored.
+ * *Important*: If you accept a credential configuration (credential
+ * JSON/File/Stream) from an external source for authentication to Google Cloud
+ * Platform, you must validate it before providing it to any Google API or library.
+ * Providing an unvalidated credential configuration to Google APIs can compromise
+ * the security of your systems and data. For more information {@see
+ * https://cloud.google.com/docs/authentication/external/externally-sourced-credentials}
+ * @type array $credentialsConfig
+ * Options used to configure credentials, including auth token caching, for the
+ * client. For a full list of supporting configuration options, see
+ * {@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|TransportInterface $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 {@see \Google\ApiCore\Transport\TransportInterface} object. Note
+ * that when this object 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 {@see \Google\ApiCore\Transport\GrpcTransport::build()} and
+ * {@see \Google\ApiCore\Transport\RestTransport::build()} methods for the
+ * supported options.
+ * @type callable $clientCertSource
+ * A callable which returns the client cert as a string. This can be used to
+ * provide a certificate and private key to the transport layer for mTLS.
+ * @type false|LoggerInterface $logger
+ * A PSR-3 compliant logger. If set to false, logging is disabled, ignoring the
+ * 'GOOGLE_SDK_PHP_LOGGING' environment flag
+ * }
+ *
+ * @throws ValidationException
+ *
+ * @experimental
+ */
+ public function __construct(array $options = [])
+ {
+ $clientOptions = $this->buildClientOptions($options);
+ $this->setClientOptions($clientOptions);
+ $this->operationsClient = $this->createOperationsClient($clientOptions);
+ }
+
+ /** Handles execution of the async variants for each documented method. */
+ public function __call($method, $args)
+ {
+ if (substr($method, -5) !== 'Async') {
+ trigger_error('Call to undefined method ' . __CLASS__ . "::$method()", E_USER_ERROR);
+ }
+
+ array_unshift($args, substr($method, 0, -5));
+ return call_user_func_array([$this, 'startAsyncCall'], $args);
+ }
+
+ /**
+ * This method starts a bidirectional stream that receives all blurbs that
+ * are being created after the stream has started and sends requests to create
+ * blurbs. If an invalid blurb is requested to be created, the stream will
+ * close with an error.
+ *
+ * @example samples/V1beta1/MessagingClient/connect.php
+ *
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type int $timeoutMillis
+ * Timeout to use for this call.
+ * }
+ *
+ * @return BidiStream
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function connect(array $callOptions = []): BidiStream
+ {
+ return $this->startApiCall('Connect', null, $callOptions);
+ }
+
+ /**
+ * Creates a blurb. If the parent is a room, the blurb is understood to be a
+ * message in that room. If the parent is a profile, the blurb is understood
+ * to be a post on the profile.
+ *
+ * The async variant is {@see MessagingClient::createBlurbAsync()} .
+ *
+ * @example samples/V1beta1/MessagingClient/create_blurb.php
+ *
+ * @param CreateBlurbRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @return Blurb
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function createBlurb(CreateBlurbRequest $request, array $callOptions = []): Blurb
+ {
+ return $this->startApiCall('CreateBlurb', $request, $callOptions)->wait();
+ }
+
+ /**
+ * Creates a room.
+ *
+ * The async variant is {@see MessagingClient::createRoomAsync()} .
+ *
+ * @example samples/V1beta1/MessagingClient/create_room.php
+ *
+ * @param CreateRoomRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @return Room
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function createRoom(CreateRoomRequest $request, array $callOptions = []): Room
+ {
+ return $this->startApiCall('CreateRoom', $request, $callOptions)->wait();
+ }
+
+ /**
+ * Deletes a blurb.
+ *
+ * The async variant is {@see MessagingClient::deleteBlurbAsync()} .
+ *
+ * @example samples/V1beta1/MessagingClient/delete_blurb.php
+ *
+ * @param DeleteBlurbRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function deleteBlurb(DeleteBlurbRequest $request, array $callOptions = []): void
+ {
+ $this->startApiCall('DeleteBlurb', $request, $callOptions)->wait();
+ }
+
+ /**
+ * Deletes a room and all of its blurbs.
+ *
+ * The async variant is {@see MessagingClient::deleteRoomAsync()} .
+ *
+ * @example samples/V1beta1/MessagingClient/delete_room.php
+ *
+ * @param DeleteRoomRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function deleteRoom(DeleteRoomRequest $request, array $callOptions = []): void
+ {
+ $this->startApiCall('DeleteRoom', $request, $callOptions)->wait();
+ }
+
+ /**
+ * Retrieves the Blurb with the given resource name.
+ *
+ * The async variant is {@see MessagingClient::getBlurbAsync()} .
+ *
+ * @example samples/V1beta1/MessagingClient/get_blurb.php
+ *
+ * @param GetBlurbRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @return Blurb
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function getBlurb(GetBlurbRequest $request, array $callOptions = []): Blurb
+ {
+ return $this->startApiCall('GetBlurb', $request, $callOptions)->wait();
+ }
+
+ /**
+ * Retrieves the Room with the given resource name.
+ *
+ * The async variant is {@see MessagingClient::getRoomAsync()} .
+ *
+ * @example samples/V1beta1/MessagingClient/get_room.php
+ *
+ * @param GetRoomRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @return Room
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function getRoom(GetRoomRequest $request, array $callOptions = []): Room
+ {
+ return $this->startApiCall('GetRoom', $request, $callOptions)->wait();
+ }
+
+ /**
+ * Lists blurbs for a specific chat room or user profile depending on the
+ * parent resource name.
+ *
+ * The async variant is {@see MessagingClient::listBlurbsAsync()} .
+ *
+ * @example samples/V1beta1/MessagingClient/list_blurbs.php
+ *
+ * @param ListBlurbsRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @return PagedListResponse
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function listBlurbs(ListBlurbsRequest $request, array $callOptions = []): PagedListResponse
+ {
+ return $this->startApiCall('ListBlurbs', $request, $callOptions);
+ }
+
+ /**
+ * Lists all chat rooms.
+ *
+ * The async variant is {@see MessagingClient::listRoomsAsync()} .
+ *
+ * @example samples/V1beta1/MessagingClient/list_rooms.php
+ *
+ * @param ListRoomsRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @return PagedListResponse
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function listRooms(ListRoomsRequest $request, array $callOptions = []): PagedListResponse
+ {
+ return $this->startApiCall('ListRooms', $request, $callOptions);
+ }
+
+ /**
+ * This method searches through all blurbs across all rooms and profiles
+ * for blurbs containing to words found in the query. Only posts that
+ * contain an exact match of a queried word will be returned.
+ *
+ * The async variant is {@see MessagingClient::searchBlurbsAsync()} .
+ *
+ * @example samples/V1beta1/MessagingClient/search_blurbs.php
+ *
+ * @param SearchBlurbsRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @return OperationResponse
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function searchBlurbs(SearchBlurbsRequest $request, array $callOptions = []): OperationResponse
+ {
+ return $this->startApiCall('SearchBlurbs', $request, $callOptions)->wait();
+ }
+
+ /**
+ * This is a stream to create multiple blurbs. If an invalid blurb is
+ * requested to be created, the stream will close with an error.
+ *
+ * @example samples/V1beta1/MessagingClient/send_blurbs.php
+ *
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type int $timeoutMillis
+ * Timeout to use for this call.
+ * }
+ *
+ * @return ClientStream
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function sendBlurbs(array $callOptions = []): ClientStream
+ {
+ return $this->startApiCall('SendBlurbs', null, $callOptions);
+ }
+
+ /**
+ * This returns a stream that emits the blurbs that are created for a
+ * particular chat room or user profile.
+ *
+ * @example samples/V1beta1/MessagingClient/stream_blurbs.php
+ *
+ * @param StreamBlurbsRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type int $timeoutMillis
+ * Timeout to use for this call.
+ * }
+ *
+ * @return ServerStream
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function streamBlurbs(StreamBlurbsRequest $request, array $callOptions = []): ServerStream
+ {
+ return $this->startApiCall('StreamBlurbs', $request, $callOptions);
+ }
+
+ /**
+ * Updates a blurb.
+ *
+ * The async variant is {@see MessagingClient::updateBlurbAsync()} .
+ *
+ * @example samples/V1beta1/MessagingClient/update_blurb.php
+ *
+ * @param UpdateBlurbRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @return Blurb
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function updateBlurb(UpdateBlurbRequest $request, array $callOptions = []): Blurb
+ {
+ return $this->startApiCall('UpdateBlurb', $request, $callOptions)->wait();
+ }
+
+ /**
+ * Updates a room.
+ *
+ * The async variant is {@see MessagingClient::updateRoomAsync()} .
+ *
+ * @example samples/V1beta1/MessagingClient/update_room.php
+ *
+ * @param UpdateRoomRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @return Room
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function updateRoom(UpdateRoomRequest $request, array $callOptions = []): Room
+ {
+ return $this->startApiCall('UpdateRoom', $request, $callOptions)->wait();
+ }
+}
diff --git a/Gax/tests/Conformance/src/V1beta1/Client/SequenceServiceClient.php b/Gax/tests/Conformance/src/V1beta1/Client/SequenceServiceClient.php
new file mode 100644
index 000000000000..f5f8681d1c76
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/Client/SequenceServiceClient.php
@@ -0,0 +1,453 @@
+ attemptSequenceAsync(AttemptSequenceRequest $request, array $optionalArgs = [])
+ * @method PromiseInterface createSequenceAsync(CreateSequenceRequest $request, array $optionalArgs = [])
+ * @method PromiseInterface createStreamingSequenceAsync(CreateStreamingSequenceRequest $request, array $optionalArgs = [])
+ * @method PromiseInterface getSequenceReportAsync(GetSequenceReportRequest $request, array $optionalArgs = [])
+ * @method PromiseInterface getStreamingSequenceReportAsync(GetStreamingSequenceReportRequest $request, array $optionalArgs = [])
+ */
+final class SequenceServiceClient
+{
+ use GapicClientTrait;
+ use ResourceHelperTrait;
+
+ /** The name of the service. */
+ private const SERVICE_NAME = 'google.showcase.v1beta1.SequenceService';
+
+ /** The default address of the service. */
+ private const SERVICE_ADDRESS = 'localhost:7469';
+
+ /** The default port of the service. */
+ private const DEFAULT_SERVICE_PORT = 443;
+
+ /** The name of the code generator, to be included in the agent header. */
+ private const CODEGEN_NAME = 'gapic';
+
+ /** The default scopes required by the service. */
+ public static $serviceScopes = [];
+
+ private static function getClientDefaults()
+ {
+ return [
+ 'serviceName' => self::SERVICE_NAME,
+ 'apiEndpoint' => self::SERVICE_ADDRESS . ':' . self::DEFAULT_SERVICE_PORT,
+ 'clientConfig' => __DIR__ . '/../resources/sequence_service_client_config.json',
+ 'descriptorsConfigPath' => __DIR__ . '/../resources/sequence_service_descriptor_config.php',
+ 'gcpApiConfigPath' => __DIR__ . '/../resources/sequence_service_grpc_config.json',
+ 'credentialsConfig' => [
+ 'defaultScopes' => self::$serviceScopes,
+ ],
+ 'transportConfig' => [
+ 'rest' => [
+ 'restClientConfigPath' => __DIR__ . '/../resources/sequence_service_rest_client_config.php',
+ ],
+ ],
+ ];
+ }
+
+ /**
+ * Formats a string containing the fully-qualified path to represent a sequence
+ * resource.
+ *
+ * @param string $sequence
+ *
+ * @return string The formatted sequence resource.
+ *
+ * @experimental
+ */
+ public static function sequenceName(string $sequence): string
+ {
+ return self::getPathTemplate('sequence')->render([
+ 'sequence' => $sequence,
+ ]);
+ }
+
+ /**
+ * Formats a string containing the fully-qualified path to represent a
+ * sequence_report resource.
+ *
+ * @param string $sequence
+ *
+ * @return string The formatted sequence_report resource.
+ *
+ * @experimental
+ */
+ public static function sequenceReportName(string $sequence): string
+ {
+ return self::getPathTemplate('sequenceReport')->render([
+ 'sequence' => $sequence,
+ ]);
+ }
+
+ /**
+ * Formats a string containing the fully-qualified path to represent a
+ * streaming_sequence resource.
+ *
+ * @param string $streamingSequence
+ *
+ * @return string The formatted streaming_sequence resource.
+ *
+ * @experimental
+ */
+ public static function streamingSequenceName(string $streamingSequence): string
+ {
+ return self::getPathTemplate('streamingSequence')->render([
+ 'streaming_sequence' => $streamingSequence,
+ ]);
+ }
+
+ /**
+ * Formats a string containing the fully-qualified path to represent a
+ * streaming_sequence_report resource.
+ *
+ * @param string $streamingSequence
+ *
+ * @return string The formatted streaming_sequence_report resource.
+ *
+ * @experimental
+ */
+ public static function streamingSequenceReportName(string $streamingSequence): string
+ {
+ return self::getPathTemplate('streamingSequenceReport')->render([
+ 'streaming_sequence' => $streamingSequence,
+ ]);
+ }
+
+ /**
+ * Parses a formatted name string and returns an associative array of the components in the name.
+ * The following name formats are supported:
+ * Template: Pattern
+ * - sequence: sequences/{sequence}
+ * - sequenceReport: sequences/{sequence}/sequenceReport
+ * - streamingSequence: streamingSequences/{streaming_sequence}
+ * - streamingSequenceReport: streamingSequences/{streaming_sequence}/streamingSequenceReport
+ *
+ * The optional $template argument can be supplied to specify a particular pattern,
+ * and must match one of the templates listed above. If no $template argument is
+ * provided, or if the $template argument does not match one of the templates
+ * listed, then parseName will check each of the supported templates, and return
+ * the first match.
+ *
+ * @param string $formattedName The formatted name string
+ * @param ?string $template Optional name of template to match
+ *
+ * @return array An associative array from name component IDs to component values.
+ *
+ * @throws ValidationException If $formattedName could not be matched.
+ *
+ * @experimental
+ */
+ public static function parseName(string $formattedName, ?string $template = null): array
+ {
+ return self::parseFormattedName($formattedName, $template);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param array $options {
+ * Optional. Options for configuring the service API wrapper.
+ *
+ * @type string $apiEndpoint
+ * The address of the API remote host. May optionally include the port, formatted
+ * as ":". Default 'localhost:7469:443'.
+ * @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
+ * {@see \Google\Auth\FetchAuthTokenInterface} object or
+ * {@see \Google\ApiCore\CredentialsWrapper} object. Note that when one of these
+ * objects are provided, any settings in $credentialsConfig will be ignored.
+ * *Important*: If you accept a credential configuration (credential
+ * JSON/File/Stream) from an external source for authentication to Google Cloud
+ * Platform, you must validate it before providing it to any Google API or library.
+ * Providing an unvalidated credential configuration to Google APIs can compromise
+ * the security of your systems and data. For more information {@see
+ * https://cloud.google.com/docs/authentication/external/externally-sourced-credentials}
+ * @type array $credentialsConfig
+ * Options used to configure credentials, including auth token caching, for the
+ * client. For a full list of supporting configuration options, see
+ * {@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|TransportInterface $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 {@see \Google\ApiCore\Transport\TransportInterface} object. Note
+ * that when this object 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 {@see \Google\ApiCore\Transport\GrpcTransport::build()} and
+ * {@see \Google\ApiCore\Transport\RestTransport::build()} methods for the
+ * supported options.
+ * @type callable $clientCertSource
+ * A callable which returns the client cert as a string. This can be used to
+ * provide a certificate and private key to the transport layer for mTLS.
+ * @type false|LoggerInterface $logger
+ * A PSR-3 compliant logger. If set to false, logging is disabled, ignoring the
+ * 'GOOGLE_SDK_PHP_LOGGING' environment flag
+ * }
+ *
+ * @throws ValidationException
+ *
+ * @experimental
+ */
+ public function __construct(array $options = [])
+ {
+ $clientOptions = $this->buildClientOptions($options);
+ $this->setClientOptions($clientOptions);
+ }
+
+ /** Handles execution of the async variants for each documented method. */
+ public function __call($method, $args)
+ {
+ if (substr($method, -5) !== 'Async') {
+ trigger_error('Call to undefined method ' . __CLASS__ . "::$method()", E_USER_ERROR);
+ }
+
+ array_unshift($args, substr($method, 0, -5));
+ return call_user_func_array([$this, 'startAsyncCall'], $args);
+ }
+
+ /**
+ * Attempts a sequence.
+ *
+ * The async variant is {@see SequenceServiceClient::attemptSequenceAsync()} .
+ *
+ * @example samples/V1beta1/SequenceServiceClient/attempt_sequence.php
+ *
+ * @param AttemptSequenceRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function attemptSequence(AttemptSequenceRequest $request, array $callOptions = []): void
+ {
+ $this->startApiCall('AttemptSequence', $request, $callOptions)->wait();
+ }
+
+ /**
+ * Attempts a streaming sequence.
+ * May not function as expected in HTTP mode due to when http statuses are sent
+ * See https://github.com/googleapis/gapic-showcase/issues/1377 for more details
+ *
+ * @example samples/V1beta1/SequenceServiceClient/attempt_streaming_sequence.php
+ *
+ * @param AttemptStreamingSequenceRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type int $timeoutMillis
+ * Timeout to use for this call.
+ * }
+ *
+ * @return ServerStream
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function attemptStreamingSequence(AttemptStreamingSequenceRequest $request, array $callOptions = []): ServerStream
+ {
+ return $this->startApiCall('AttemptStreamingSequence', $request, $callOptions);
+ }
+
+ /**
+ * Creates a sequence.
+ *
+ * The async variant is {@see SequenceServiceClient::createSequenceAsync()} .
+ *
+ * @example samples/V1beta1/SequenceServiceClient/create_sequence.php
+ *
+ * @param CreateSequenceRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @return Sequence
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function createSequence(CreateSequenceRequest $request, array $callOptions = []): Sequence
+ {
+ return $this->startApiCall('CreateSequence', $request, $callOptions)->wait();
+ }
+
+ /**
+ * Creates a sequence.
+ *
+ * The async variant is
+ * {@see SequenceServiceClient::createStreamingSequenceAsync()} .
+ *
+ * @example samples/V1beta1/SequenceServiceClient/create_streaming_sequence.php
+ *
+ * @param CreateStreamingSequenceRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @return StreamingSequence
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function createStreamingSequence(CreateStreamingSequenceRequest $request, array $callOptions = []): StreamingSequence
+ {
+ return $this->startApiCall('CreateStreamingSequence', $request, $callOptions)->wait();
+ }
+
+ /**
+ * Retrieves a sequence.
+ *
+ * The async variant is {@see SequenceServiceClient::getSequenceReportAsync()} .
+ *
+ * @example samples/V1beta1/SequenceServiceClient/get_sequence_report.php
+ *
+ * @param GetSequenceReportRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @return SequenceReport
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function getSequenceReport(GetSequenceReportRequest $request, array $callOptions = []): SequenceReport
+ {
+ return $this->startApiCall('GetSequenceReport', $request, $callOptions)->wait();
+ }
+
+ /**
+ * Retrieves a sequence.
+ *
+ * The async variant is
+ * {@see SequenceServiceClient::getStreamingSequenceReportAsync()} .
+ *
+ * @example samples/V1beta1/SequenceServiceClient/get_streaming_sequence_report.php
+ *
+ * @param GetStreamingSequenceReportRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @return StreamingSequenceReport
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function getStreamingSequenceReport(GetStreamingSequenceReportRequest $request, array $callOptions = []): StreamingSequenceReport
+ {
+ return $this->startApiCall('GetStreamingSequenceReport', $request, $callOptions)->wait();
+ }
+}
diff --git a/Gax/tests/Conformance/src/V1beta1/Client/TestingClient.php b/Gax/tests/Conformance/src/V1beta1/Client/TestingClient.php
new file mode 100644
index 000000000000..27f0778416a8
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/Client/TestingClient.php
@@ -0,0 +1,494 @@
+kadabra->alakazam)
+ * 2) [Nonsense][]: `pokemon/*/psychic/*`
+ *
+ * This class provides the ability to make remote calls to the backing service through method
+ * calls that map to API methods.
+ *
+ * Many parameters require resource names to be formatted in a particular way. To
+ * assist with these names, this class includes a format method for each type of
+ * name, and additionally a parseName method to extract the individual identifiers
+ * contained within formatted names that are returned by the API.
+ *
+ * @experimental
+ *
+ * @method PromiseInterface createSessionAsync(CreateSessionRequest $request, array $optionalArgs = [])
+ * @method PromiseInterface deleteSessionAsync(DeleteSessionRequest $request, array $optionalArgs = [])
+ * @method PromiseInterface deleteTestAsync(DeleteTestRequest $request, array $optionalArgs = [])
+ * @method PromiseInterface getSessionAsync(GetSessionRequest $request, array $optionalArgs = [])
+ * @method PromiseInterface listSessionsAsync(ListSessionsRequest $request, array $optionalArgs = [])
+ * @method PromiseInterface listTestsAsync(ListTestsRequest $request, array $optionalArgs = [])
+ * @method PromiseInterface reportSessionAsync(ReportSessionRequest $request, array $optionalArgs = [])
+ * @method PromiseInterface verifyTestAsync(VerifyTestRequest $request, array $optionalArgs = [])
+ */
+final class TestingClient
+{
+ use GapicClientTrait;
+ use ResourceHelperTrait;
+
+ /** The name of the service. */
+ private const SERVICE_NAME = 'google.showcase.v1beta1.Testing';
+
+ /** The default address of the service. */
+ private const SERVICE_ADDRESS = 'localhost:7469';
+
+ /** The default port of the service. */
+ private const DEFAULT_SERVICE_PORT = 443;
+
+ /** The name of the code generator, to be included in the agent header. */
+ private const CODEGEN_NAME = 'gapic';
+
+ /** The default scopes required by the service. */
+ public static $serviceScopes = [];
+
+ private static function getClientDefaults()
+ {
+ return [
+ 'serviceName' => self::SERVICE_NAME,
+ 'apiEndpoint' => self::SERVICE_ADDRESS . ':' . self::DEFAULT_SERVICE_PORT,
+ 'clientConfig' => __DIR__ . '/../resources/testing_client_config.json',
+ 'descriptorsConfigPath' => __DIR__ . '/../resources/testing_descriptor_config.php',
+ 'gcpApiConfigPath' => __DIR__ . '/../resources/testing_grpc_config.json',
+ 'credentialsConfig' => [
+ 'defaultScopes' => self::$serviceScopes,
+ ],
+ 'transportConfig' => [
+ 'rest' => [
+ 'restClientConfigPath' => __DIR__ . '/../resources/testing_rest_client_config.php',
+ ],
+ ],
+ ];
+ }
+
+ /**
+ * Formats a string containing the fully-qualified path to represent a session
+ * resource.
+ *
+ * @param string $session
+ *
+ * @return string The formatted session resource.
+ *
+ * @experimental
+ */
+ public static function sessionName(string $session): string
+ {
+ return self::getPathTemplate('session')->render([
+ 'session' => $session,
+ ]);
+ }
+
+ /**
+ * Formats a string containing the fully-qualified path to represent a test
+ * resource.
+ *
+ * @param string $session
+ * @param string $test
+ *
+ * @return string The formatted test resource.
+ *
+ * @experimental
+ */
+ public static function testName(string $session, string $test): string
+ {
+ return self::getPathTemplate('test')->render([
+ 'session' => $session,
+ 'test' => $test,
+ ]);
+ }
+
+ /**
+ * Parses a formatted name string and returns an associative array of the components in the name.
+ * The following name formats are supported:
+ * Template: Pattern
+ * - session: sessions/{session}
+ * - test: sessions/{session}/tests/{test}
+ *
+ * The optional $template argument can be supplied to specify a particular pattern,
+ * and must match one of the templates listed above. If no $template argument is
+ * provided, or if the $template argument does not match one of the templates
+ * listed, then parseName will check each of the supported templates, and return
+ * the first match.
+ *
+ * @param string $formattedName The formatted name string
+ * @param ?string $template Optional name of template to match
+ *
+ * @return array An associative array from name component IDs to component values.
+ *
+ * @throws ValidationException If $formattedName could not be matched.
+ *
+ * @experimental
+ */
+ public static function parseName(string $formattedName, ?string $template = null): array
+ {
+ return self::parseFormattedName($formattedName, $template);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param array $options {
+ * Optional. Options for configuring the service API wrapper.
+ *
+ * @type string $apiEndpoint
+ * The address of the API remote host. May optionally include the port, formatted
+ * as ":". Default 'localhost:7469:443'.
+ * @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
+ * {@see \Google\Auth\FetchAuthTokenInterface} object or
+ * {@see \Google\ApiCore\CredentialsWrapper} object. Note that when one of these
+ * objects are provided, any settings in $credentialsConfig will be ignored.
+ * *Important*: If you accept a credential configuration (credential
+ * JSON/File/Stream) from an external source for authentication to Google Cloud
+ * Platform, you must validate it before providing it to any Google API or library.
+ * Providing an unvalidated credential configuration to Google APIs can compromise
+ * the security of your systems and data. For more information {@see
+ * https://cloud.google.com/docs/authentication/external/externally-sourced-credentials}
+ * @type array $credentialsConfig
+ * Options used to configure credentials, including auth token caching, for the
+ * client. For a full list of supporting configuration options, see
+ * {@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|TransportInterface $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 {@see \Google\ApiCore\Transport\TransportInterface} object. Note
+ * that when this object 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 {@see \Google\ApiCore\Transport\GrpcTransport::build()} and
+ * {@see \Google\ApiCore\Transport\RestTransport::build()} methods for the
+ * supported options.
+ * @type callable $clientCertSource
+ * A callable which returns the client cert as a string. This can be used to
+ * provide a certificate and private key to the transport layer for mTLS.
+ * @type false|LoggerInterface $logger
+ * A PSR-3 compliant logger. If set to false, logging is disabled, ignoring the
+ * 'GOOGLE_SDK_PHP_LOGGING' environment flag
+ * }
+ *
+ * @throws ValidationException
+ *
+ * @experimental
+ */
+ public function __construct(array $options = [])
+ {
+ $clientOptions = $this->buildClientOptions($options);
+ $this->setClientOptions($clientOptions);
+ }
+
+ /** Handles execution of the async variants for each documented method. */
+ public function __call($method, $args)
+ {
+ if (substr($method, -5) !== 'Async') {
+ trigger_error('Call to undefined method ' . __CLASS__ . "::$method()", E_USER_ERROR);
+ }
+
+ array_unshift($args, substr($method, 0, -5));
+ return call_user_func_array([$this, 'startAsyncCall'], $args);
+ }
+
+ /**
+ * Creates a new testing session.
+ * Adding this comment with special characters for comment formatting tests:
+ * 1. (abra->kadabra->alakazam)
+ * 2) [Nonsense][]: `pokemon/*/psychic/*`
+ *
+ * The async variant is {@see TestingClient::createSessionAsync()} .
+ *
+ * @example samples/V1beta1/TestingClient/create_session.php
+ *
+ * @param CreateSessionRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @return Session
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function createSession(CreateSessionRequest $request, array $callOptions = []): Session
+ {
+ return $this->startApiCall('CreateSession', $request, $callOptions)->wait();
+ }
+
+ /**
+ * Delete a test session.
+ *
+ * The async variant is {@see TestingClient::deleteSessionAsync()} .
+ *
+ * @example samples/V1beta1/TestingClient/delete_session.php
+ *
+ * @param DeleteSessionRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function deleteSession(DeleteSessionRequest $request, array $callOptions = []): void
+ {
+ $this->startApiCall('DeleteSession', $request, $callOptions)->wait();
+ }
+
+ /**
+ * Explicitly decline to implement a test.
+ *
+ * This removes the test from subsequent `ListTests` calls, and
+ * attempting to do the test will error.
+ *
+ * This method will error if attempting to delete a required test.
+ *
+ * The async variant is {@see TestingClient::deleteTestAsync()} .
+ *
+ * @example samples/V1beta1/TestingClient/delete_test.php
+ *
+ * @param DeleteTestRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function deleteTest(DeleteTestRequest $request, array $callOptions = []): void
+ {
+ $this->startApiCall('DeleteTest', $request, $callOptions)->wait();
+ }
+
+ /**
+ * Gets a testing session.
+ *
+ * The async variant is {@see TestingClient::getSessionAsync()} .
+ *
+ * @example samples/V1beta1/TestingClient/get_session.php
+ *
+ * @param GetSessionRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @return Session
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function getSession(GetSessionRequest $request, array $callOptions = []): Session
+ {
+ return $this->startApiCall('GetSession', $request, $callOptions)->wait();
+ }
+
+ /**
+ * Lists the current test sessions.
+ *
+ * The async variant is {@see TestingClient::listSessionsAsync()} .
+ *
+ * @example samples/V1beta1/TestingClient/list_sessions.php
+ *
+ * @param ListSessionsRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @return PagedListResponse
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function listSessions(ListSessionsRequest $request, array $callOptions = []): PagedListResponse
+ {
+ return $this->startApiCall('ListSessions', $request, $callOptions);
+ }
+
+ /**
+ * List the tests of a sessesion.
+ *
+ * The async variant is {@see TestingClient::listTestsAsync()} .
+ *
+ * @example samples/V1beta1/TestingClient/list_tests.php
+ *
+ * @param ListTestsRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @return PagedListResponse
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function listTests(ListTestsRequest $request, array $callOptions = []): PagedListResponse
+ {
+ return $this->startApiCall('ListTests', $request, $callOptions);
+ }
+
+ /**
+ * Report on the status of a session.
+ * This generates a report detailing which tests have been completed,
+ * and an overall rollup.
+ *
+ * The async variant is {@see TestingClient::reportSessionAsync()} .
+ *
+ * @example samples/V1beta1/TestingClient/report_session.php
+ *
+ * @param ReportSessionRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @return ReportSessionResponse
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function reportSession(ReportSessionRequest $request, array $callOptions = []): ReportSessionResponse
+ {
+ return $this->startApiCall('ReportSession', $request, $callOptions)->wait();
+ }
+
+ /**
+ * Register a response to a test.
+ *
+ * In cases where a test involves registering a final answer at the
+ * end of the test, this method provides the means to do so.
+ *
+ * The async variant is {@see TestingClient::verifyTestAsync()} .
+ *
+ * @example samples/V1beta1/TestingClient/verify_test.php
+ *
+ * @param VerifyTestRequest $request A request to house fields associated with the call.
+ * @param array $callOptions {
+ * Optional.
+ *
+ * @type RetrySettings|array $retrySettings
+ * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an
+ * associative array of retry settings parameters. See the documentation on
+ * {@see RetrySettings} for example usage.
+ * }
+ *
+ * @return VerifyTestResponse
+ *
+ * @throws ApiException Thrown if the API call fails.
+ *
+ * @experimental
+ */
+ public function verifyTest(VerifyTestRequest $request, array $callOptions = []): VerifyTestResponse
+ {
+ return $this->startApiCall('VerifyTest', $request, $callOptions)->wait();
+ }
+}
diff --git a/Gax/tests/Conformance/src/V1beta1/ComplianceData.php b/Gax/tests/Conformance/src/V1beta1/ComplianceData.php
new file mode 100644
index 000000000000..68891e78a876
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/ComplianceData.php
@@ -0,0 +1,725 @@
+google.showcase.v1beta1.ComplianceData
+ */
+class ComplianceData extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * Generated from protobuf field string f_string = 1;
+ */
+ protected $f_string = '';
+ /**
+ * Generated from protobuf field int32 f_int32 = 2;
+ */
+ protected $f_int32 = 0;
+ /**
+ * Generated from protobuf field sint32 f_sint32 = 3;
+ */
+ protected $f_sint32 = 0;
+ /**
+ * Generated from protobuf field sfixed32 f_sfixed32 = 4;
+ */
+ protected $f_sfixed32 = 0;
+ /**
+ * Generated from protobuf field uint32 f_uint32 = 5;
+ */
+ protected $f_uint32 = 0;
+ /**
+ * Generated from protobuf field fixed32 f_fixed32 = 6;
+ */
+ protected $f_fixed32 = 0;
+ /**
+ * Generated from protobuf field int64 f_int64 = 7;
+ */
+ protected $f_int64 = 0;
+ /**
+ * Generated from protobuf field sint64 f_sint64 = 8;
+ */
+ protected $f_sint64 = 0;
+ /**
+ * Generated from protobuf field sfixed64 f_sfixed64 = 9;
+ */
+ protected $f_sfixed64 = 0;
+ /**
+ * Generated from protobuf field uint64 f_uint64 = 10;
+ */
+ protected $f_uint64 = 0;
+ /**
+ * Generated from protobuf field fixed64 f_fixed64 = 11;
+ */
+ protected $f_fixed64 = 0;
+ /**
+ * Generated from protobuf field double f_double = 12;
+ */
+ protected $f_double = 0.0;
+ /**
+ * Generated from protobuf field float f_float = 13;
+ */
+ protected $f_float = 0.0;
+ /**
+ * Generated from protobuf field bool f_bool = 14;
+ */
+ protected $f_bool = false;
+ /**
+ * Generated from protobuf field bytes f_bytes = 15;
+ */
+ protected $f_bytes = '';
+ /**
+ * Generated from protobuf field .google.showcase.v1beta1.ComplianceData.LifeKingdom f_kingdom = 22;
+ */
+ protected $f_kingdom = 0;
+ /**
+ * Generated from protobuf field .google.showcase.v1beta1.ComplianceDataChild f_child = 16;
+ */
+ protected $f_child = null;
+ /**
+ * Generated from protobuf field optional string p_string = 17;
+ */
+ protected $p_string = null;
+ /**
+ * Generated from protobuf field optional int32 p_int32 = 18;
+ */
+ protected $p_int32 = null;
+ /**
+ * Generated from protobuf field optional double p_double = 19;
+ */
+ protected $p_double = null;
+ /**
+ * Generated from protobuf field optional bool p_bool = 20;
+ */
+ protected $p_bool = null;
+ /**
+ * Generated from protobuf field optional .google.showcase.v1beta1.ComplianceData.LifeKingdom p_kingdom = 23;
+ */
+ protected $p_kingdom = null;
+ /**
+ * Generated from protobuf field optional .google.showcase.v1beta1.ComplianceDataChild p_child = 21;
+ */
+ protected $p_child = null;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $f_string
+ * @type int $f_int32
+ * @type int $f_sint32
+ * @type int $f_sfixed32
+ * @type int $f_uint32
+ * @type int $f_fixed32
+ * @type int|string $f_int64
+ * @type int|string $f_sint64
+ * @type int|string $f_sfixed64
+ * @type int|string $f_uint64
+ * @type int|string $f_fixed64
+ * @type float $f_double
+ * @type float $f_float
+ * @type bool $f_bool
+ * @type string $f_bytes
+ * @type int $f_kingdom
+ * @type \Google\Showcase\V1beta1\ComplianceDataChild $f_child
+ * @type string $p_string
+ * @type int $p_int32
+ * @type float $p_double
+ * @type bool $p_bool
+ * @type int $p_kingdom
+ * @type \Google\Showcase\V1beta1\ComplianceDataChild $p_child
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Compliance::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * Generated from protobuf field string f_string = 1;
+ * @return string
+ */
+ public function getFString()
+ {
+ return $this->f_string;
+ }
+
+ /**
+ * Generated from protobuf field string f_string = 1;
+ * @param string $var
+ * @return $this
+ */
+ public function setFString($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->f_string = $var;
+
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field int32 f_int32 = 2;
+ * @return int
+ */
+ public function getFInt32()
+ {
+ return $this->f_int32;
+ }
+
+ /**
+ * Generated from protobuf field int32 f_int32 = 2;
+ * @param int $var
+ * @return $this
+ */
+ public function setFInt32($var)
+ {
+ GPBUtil::checkInt32($var);
+ $this->f_int32 = $var;
+
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field sint32 f_sint32 = 3;
+ * @return int
+ */
+ public function getFSint32()
+ {
+ return $this->f_sint32;
+ }
+
+ /**
+ * Generated from protobuf field sint32 f_sint32 = 3;
+ * @param int $var
+ * @return $this
+ */
+ public function setFSint32($var)
+ {
+ GPBUtil::checkInt32($var);
+ $this->f_sint32 = $var;
+
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field sfixed32 f_sfixed32 = 4;
+ * @return int
+ */
+ public function getFSfixed32()
+ {
+ return $this->f_sfixed32;
+ }
+
+ /**
+ * Generated from protobuf field sfixed32 f_sfixed32 = 4;
+ * @param int $var
+ * @return $this
+ */
+ public function setFSfixed32($var)
+ {
+ GPBUtil::checkInt32($var);
+ $this->f_sfixed32 = $var;
+
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field uint32 f_uint32 = 5;
+ * @return int
+ */
+ public function getFUint32()
+ {
+ return $this->f_uint32;
+ }
+
+ /**
+ * Generated from protobuf field uint32 f_uint32 = 5;
+ * @param int $var
+ * @return $this
+ */
+ public function setFUint32($var)
+ {
+ GPBUtil::checkUint32($var);
+ $this->f_uint32 = $var;
+
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field fixed32 f_fixed32 = 6;
+ * @return int
+ */
+ public function getFFixed32()
+ {
+ return $this->f_fixed32;
+ }
+
+ /**
+ * Generated from protobuf field fixed32 f_fixed32 = 6;
+ * @param int $var
+ * @return $this
+ */
+ public function setFFixed32($var)
+ {
+ GPBUtil::checkUint32($var);
+ $this->f_fixed32 = $var;
+
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field int64 f_int64 = 7;
+ * @return int|string
+ */
+ public function getFInt64()
+ {
+ return $this->f_int64;
+ }
+
+ /**
+ * Generated from protobuf field int64 f_int64 = 7;
+ * @param int|string $var
+ * @return $this
+ */
+ public function setFInt64($var)
+ {
+ GPBUtil::checkInt64($var);
+ $this->f_int64 = $var;
+
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field sint64 f_sint64 = 8;
+ * @return int|string
+ */
+ public function getFSint64()
+ {
+ return $this->f_sint64;
+ }
+
+ /**
+ * Generated from protobuf field sint64 f_sint64 = 8;
+ * @param int|string $var
+ * @return $this
+ */
+ public function setFSint64($var)
+ {
+ GPBUtil::checkInt64($var);
+ $this->f_sint64 = $var;
+
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field sfixed64 f_sfixed64 = 9;
+ * @return int|string
+ */
+ public function getFSfixed64()
+ {
+ return $this->f_sfixed64;
+ }
+
+ /**
+ * Generated from protobuf field sfixed64 f_sfixed64 = 9;
+ * @param int|string $var
+ * @return $this
+ */
+ public function setFSfixed64($var)
+ {
+ GPBUtil::checkInt64($var);
+ $this->f_sfixed64 = $var;
+
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field uint64 f_uint64 = 10;
+ * @return int|string
+ */
+ public function getFUint64()
+ {
+ return $this->f_uint64;
+ }
+
+ /**
+ * Generated from protobuf field uint64 f_uint64 = 10;
+ * @param int|string $var
+ * @return $this
+ */
+ public function setFUint64($var)
+ {
+ GPBUtil::checkUint64($var);
+ $this->f_uint64 = $var;
+
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field fixed64 f_fixed64 = 11;
+ * @return int|string
+ */
+ public function getFFixed64()
+ {
+ return $this->f_fixed64;
+ }
+
+ /**
+ * Generated from protobuf field fixed64 f_fixed64 = 11;
+ * @param int|string $var
+ * @return $this
+ */
+ public function setFFixed64($var)
+ {
+ GPBUtil::checkUint64($var);
+ $this->f_fixed64 = $var;
+
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field double f_double = 12;
+ * @return float
+ */
+ public function getFDouble()
+ {
+ return $this->f_double;
+ }
+
+ /**
+ * Generated from protobuf field double f_double = 12;
+ * @param float $var
+ * @return $this
+ */
+ public function setFDouble($var)
+ {
+ GPBUtil::checkDouble($var);
+ $this->f_double = $var;
+
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field float f_float = 13;
+ * @return float
+ */
+ public function getFFloat()
+ {
+ return $this->f_float;
+ }
+
+ /**
+ * Generated from protobuf field float f_float = 13;
+ * @param float $var
+ * @return $this
+ */
+ public function setFFloat($var)
+ {
+ GPBUtil::checkFloat($var);
+ $this->f_float = $var;
+
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field bool f_bool = 14;
+ * @return bool
+ */
+ public function getFBool()
+ {
+ return $this->f_bool;
+ }
+
+ /**
+ * Generated from protobuf field bool f_bool = 14;
+ * @param bool $var
+ * @return $this
+ */
+ public function setFBool($var)
+ {
+ GPBUtil::checkBool($var);
+ $this->f_bool = $var;
+
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field bytes f_bytes = 15;
+ * @return string
+ */
+ public function getFBytes()
+ {
+ return $this->f_bytes;
+ }
+
+ /**
+ * Generated from protobuf field bytes f_bytes = 15;
+ * @param string $var
+ * @return $this
+ */
+ public function setFBytes($var)
+ {
+ GPBUtil::checkString($var, False);
+ $this->f_bytes = $var;
+
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field .google.showcase.v1beta1.ComplianceData.LifeKingdom f_kingdom = 22;
+ * @return int
+ */
+ public function getFKingdom()
+ {
+ return $this->f_kingdom;
+ }
+
+ /**
+ * Generated from protobuf field .google.showcase.v1beta1.ComplianceData.LifeKingdom f_kingdom = 22;
+ * @param int $var
+ * @return $this
+ */
+ public function setFKingdom($var)
+ {
+ GPBUtil::checkEnum($var, \Google\Showcase\V1beta1\ComplianceData\LifeKingdom::class);
+ $this->f_kingdom = $var;
+
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field .google.showcase.v1beta1.ComplianceDataChild f_child = 16;
+ * @return \Google\Showcase\V1beta1\ComplianceDataChild|null
+ */
+ public function getFChild()
+ {
+ return $this->f_child;
+ }
+
+ public function hasFChild()
+ {
+ return isset($this->f_child);
+ }
+
+ public function clearFChild()
+ {
+ unset($this->f_child);
+ }
+
+ /**
+ * Generated from protobuf field .google.showcase.v1beta1.ComplianceDataChild f_child = 16;
+ * @param \Google\Showcase\V1beta1\ComplianceDataChild $var
+ * @return $this
+ */
+ public function setFChild($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Showcase\V1beta1\ComplianceDataChild::class);
+ $this->f_child = $var;
+
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field optional string p_string = 17;
+ * @return string
+ */
+ public function getPString()
+ {
+ return isset($this->p_string) ? $this->p_string : '';
+ }
+
+ public function hasPString()
+ {
+ return isset($this->p_string);
+ }
+
+ public function clearPString()
+ {
+ unset($this->p_string);
+ }
+
+ /**
+ * Generated from protobuf field optional string p_string = 17;
+ * @param string $var
+ * @return $this
+ */
+ public function setPString($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->p_string = $var;
+
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field optional int32 p_int32 = 18;
+ * @return int
+ */
+ public function getPInt32()
+ {
+ return isset($this->p_int32) ? $this->p_int32 : 0;
+ }
+
+ public function hasPInt32()
+ {
+ return isset($this->p_int32);
+ }
+
+ public function clearPInt32()
+ {
+ unset($this->p_int32);
+ }
+
+ /**
+ * Generated from protobuf field optional int32 p_int32 = 18;
+ * @param int $var
+ * @return $this
+ */
+ public function setPInt32($var)
+ {
+ GPBUtil::checkInt32($var);
+ $this->p_int32 = $var;
+
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field optional double p_double = 19;
+ * @return float
+ */
+ public function getPDouble()
+ {
+ return isset($this->p_double) ? $this->p_double : 0.0;
+ }
+
+ public function hasPDouble()
+ {
+ return isset($this->p_double);
+ }
+
+ public function clearPDouble()
+ {
+ unset($this->p_double);
+ }
+
+ /**
+ * Generated from protobuf field optional double p_double = 19;
+ * @param float $var
+ * @return $this
+ */
+ public function setPDouble($var)
+ {
+ GPBUtil::checkDouble($var);
+ $this->p_double = $var;
+
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field optional bool p_bool = 20;
+ * @return bool
+ */
+ public function getPBool()
+ {
+ return isset($this->p_bool) ? $this->p_bool : false;
+ }
+
+ public function hasPBool()
+ {
+ return isset($this->p_bool);
+ }
+
+ public function clearPBool()
+ {
+ unset($this->p_bool);
+ }
+
+ /**
+ * Generated from protobuf field optional bool p_bool = 20;
+ * @param bool $var
+ * @return $this
+ */
+ public function setPBool($var)
+ {
+ GPBUtil::checkBool($var);
+ $this->p_bool = $var;
+
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field optional .google.showcase.v1beta1.ComplianceData.LifeKingdom p_kingdom = 23;
+ * @return int
+ */
+ public function getPKingdom()
+ {
+ return isset($this->p_kingdom) ? $this->p_kingdom : 0;
+ }
+
+ public function hasPKingdom()
+ {
+ return isset($this->p_kingdom);
+ }
+
+ public function clearPKingdom()
+ {
+ unset($this->p_kingdom);
+ }
+
+ /**
+ * Generated from protobuf field optional .google.showcase.v1beta1.ComplianceData.LifeKingdom p_kingdom = 23;
+ * @param int $var
+ * @return $this
+ */
+ public function setPKingdom($var)
+ {
+ GPBUtil::checkEnum($var, \Google\Showcase\V1beta1\ComplianceData\LifeKingdom::class);
+ $this->p_kingdom = $var;
+
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field optional .google.showcase.v1beta1.ComplianceDataChild p_child = 21;
+ * @return \Google\Showcase\V1beta1\ComplianceDataChild|null
+ */
+ public function getPChild()
+ {
+ return $this->p_child;
+ }
+
+ public function hasPChild()
+ {
+ return isset($this->p_child);
+ }
+
+ public function clearPChild()
+ {
+ unset($this->p_child);
+ }
+
+ /**
+ * Generated from protobuf field optional .google.showcase.v1beta1.ComplianceDataChild p_child = 21;
+ * @param \Google\Showcase\V1beta1\ComplianceDataChild $var
+ * @return $this
+ */
+ public function setPChild($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Showcase\V1beta1\ComplianceDataChild::class);
+ $this->p_child = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/ComplianceData/LifeKingdom.php b/Gax/tests/Conformance/src/V1beta1/ComplianceData/LifeKingdom.php
new file mode 100644
index 000000000000..3a9ad08eebaf
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/ComplianceData/LifeKingdom.php
@@ -0,0 +1,76 @@
+google.showcase.v1beta1.ComplianceData.LifeKingdom
+ */
+class LifeKingdom
+{
+ /**
+ * Generated from protobuf enum LIFE_KINGDOM_UNSPECIFIED = 0;
+ */
+ const LIFE_KINGDOM_UNSPECIFIED = 0;
+ /**
+ * Generated from protobuf enum ARCHAEBACTERIA = 1;
+ */
+ const ARCHAEBACTERIA = 1;
+ /**
+ * Generated from protobuf enum EUBACTERIA = 2;
+ */
+ const EUBACTERIA = 2;
+ /**
+ * Generated from protobuf enum PROTISTA = 3;
+ */
+ const PROTISTA = 3;
+ /**
+ * Generated from protobuf enum FUNGI = 4;
+ */
+ const FUNGI = 4;
+ /**
+ * Generated from protobuf enum PLANTAE = 5;
+ */
+ const PLANTAE = 5;
+ /**
+ * Generated from protobuf enum ANIMALIA = 6;
+ */
+ const ANIMALIA = 6;
+
+ private static $valueToName = [
+ self::LIFE_KINGDOM_UNSPECIFIED => 'LIFE_KINGDOM_UNSPECIFIED',
+ self::ARCHAEBACTERIA => 'ARCHAEBACTERIA',
+ self::EUBACTERIA => 'EUBACTERIA',
+ self::PROTISTA => 'PROTISTA',
+ self::FUNGI => 'FUNGI',
+ self::PLANTAE => 'PLANTAE',
+ self::ANIMALIA => 'ANIMALIA',
+ ];
+
+ public static function name($value)
+ {
+ if (!isset(self::$valueToName[$value])) {
+ throw new UnexpectedValueException(sprintf(
+ 'Enum %s has no name defined for value %s', __CLASS__, $value));
+ }
+ return self::$valueToName[$value];
+ }
+
+
+ public static function value($name)
+ {
+ $const = __CLASS__ . '::' . strtoupper($name);
+ if (!defined($const)) {
+ throw new UnexpectedValueException(sprintf(
+ 'Enum %s has no value defined for name %s', __CLASS__, $name));
+ }
+ return constant($const);
+ }
+}
+
+// Adding a class alias for backwards compatibility with the previous class name.
+class_alias(LifeKingdom::class, \Google\Showcase\V1beta1\ComplianceData_LifeKingdom::class);
+
diff --git a/Gax/tests/Conformance/src/V1beta1/ComplianceDataChild.php b/Gax/tests/Conformance/src/V1beta1/ComplianceDataChild.php
new file mode 100644
index 000000000000..eed572707028
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/ComplianceDataChild.php
@@ -0,0 +1,415 @@
+google.showcase.v1beta1.ComplianceDataChild
+ */
+class ComplianceDataChild extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * Generated from protobuf field string f_string = 1;
+ */
+ protected $f_string = '';
+ /**
+ * Generated from protobuf field float f_float = 2;
+ */
+ protected $f_float = 0.0;
+ /**
+ * Generated from protobuf field double f_double = 3;
+ */
+ protected $f_double = 0.0;
+ /**
+ * Generated from protobuf field bool f_bool = 4;
+ */
+ protected $f_bool = false;
+ /**
+ * Generated from protobuf field .google.showcase.v1beta1.Continent f_continent = 11;
+ */
+ protected $f_continent = 0;
+ /**
+ * Generated from protobuf field .google.showcase.v1beta1.ComplianceDataGrandchild f_child = 5;
+ */
+ protected $f_child = null;
+ /**
+ * Generated from protobuf field optional string p_string = 6;
+ */
+ protected $p_string = null;
+ /**
+ * Generated from protobuf field optional float p_float = 7;
+ */
+ protected $p_float = null;
+ /**
+ * Generated from protobuf field optional double p_double = 8;
+ */
+ protected $p_double = null;
+ /**
+ * Generated from protobuf field optional bool p_bool = 9;
+ */
+ protected $p_bool = null;
+ /**
+ * Generated from protobuf field .google.showcase.v1beta1.Continent p_continent = 12;
+ */
+ protected $p_continent = 0;
+ /**
+ * Generated from protobuf field optional .google.showcase.v1beta1.ComplianceDataGrandchild p_child = 10;
+ */
+ protected $p_child = null;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $f_string
+ * @type float $f_float
+ * @type float $f_double
+ * @type bool $f_bool
+ * @type int $f_continent
+ * @type \Google\Showcase\V1beta1\ComplianceDataGrandchild $f_child
+ * @type string $p_string
+ * @type float $p_float
+ * @type float $p_double
+ * @type bool $p_bool
+ * @type int $p_continent
+ * @type \Google\Showcase\V1beta1\ComplianceDataGrandchild $p_child
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Compliance::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * Generated from protobuf field string f_string = 1;
+ * @return string
+ */
+ public function getFString()
+ {
+ return $this->f_string;
+ }
+
+ /**
+ * Generated from protobuf field string f_string = 1;
+ * @param string $var
+ * @return $this
+ */
+ public function setFString($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->f_string = $var;
+
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field float f_float = 2;
+ * @return float
+ */
+ public function getFFloat()
+ {
+ return $this->f_float;
+ }
+
+ /**
+ * Generated from protobuf field float f_float = 2;
+ * @param float $var
+ * @return $this
+ */
+ public function setFFloat($var)
+ {
+ GPBUtil::checkFloat($var);
+ $this->f_float = $var;
+
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field double f_double = 3;
+ * @return float
+ */
+ public function getFDouble()
+ {
+ return $this->f_double;
+ }
+
+ /**
+ * Generated from protobuf field double f_double = 3;
+ * @param float $var
+ * @return $this
+ */
+ public function setFDouble($var)
+ {
+ GPBUtil::checkDouble($var);
+ $this->f_double = $var;
+
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field bool f_bool = 4;
+ * @return bool
+ */
+ public function getFBool()
+ {
+ return $this->f_bool;
+ }
+
+ /**
+ * Generated from protobuf field bool f_bool = 4;
+ * @param bool $var
+ * @return $this
+ */
+ public function setFBool($var)
+ {
+ GPBUtil::checkBool($var);
+ $this->f_bool = $var;
+
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field .google.showcase.v1beta1.Continent f_continent = 11;
+ * @return int
+ */
+ public function getFContinent()
+ {
+ return $this->f_continent;
+ }
+
+ /**
+ * Generated from protobuf field .google.showcase.v1beta1.Continent f_continent = 11;
+ * @param int $var
+ * @return $this
+ */
+ public function setFContinent($var)
+ {
+ GPBUtil::checkEnum($var, \Google\Showcase\V1beta1\Continent::class);
+ $this->f_continent = $var;
+
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field .google.showcase.v1beta1.ComplianceDataGrandchild f_child = 5;
+ * @return \Google\Showcase\V1beta1\ComplianceDataGrandchild|null
+ */
+ public function getFChild()
+ {
+ return $this->f_child;
+ }
+
+ public function hasFChild()
+ {
+ return isset($this->f_child);
+ }
+
+ public function clearFChild()
+ {
+ unset($this->f_child);
+ }
+
+ /**
+ * Generated from protobuf field .google.showcase.v1beta1.ComplianceDataGrandchild f_child = 5;
+ * @param \Google\Showcase\V1beta1\ComplianceDataGrandchild $var
+ * @return $this
+ */
+ public function setFChild($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Showcase\V1beta1\ComplianceDataGrandchild::class);
+ $this->f_child = $var;
+
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field optional string p_string = 6;
+ * @return string
+ */
+ public function getPString()
+ {
+ return isset($this->p_string) ? $this->p_string : '';
+ }
+
+ public function hasPString()
+ {
+ return isset($this->p_string);
+ }
+
+ public function clearPString()
+ {
+ unset($this->p_string);
+ }
+
+ /**
+ * Generated from protobuf field optional string p_string = 6;
+ * @param string $var
+ * @return $this
+ */
+ public function setPString($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->p_string = $var;
+
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field optional float p_float = 7;
+ * @return float
+ */
+ public function getPFloat()
+ {
+ return isset($this->p_float) ? $this->p_float : 0.0;
+ }
+
+ public function hasPFloat()
+ {
+ return isset($this->p_float);
+ }
+
+ public function clearPFloat()
+ {
+ unset($this->p_float);
+ }
+
+ /**
+ * Generated from protobuf field optional float p_float = 7;
+ * @param float $var
+ * @return $this
+ */
+ public function setPFloat($var)
+ {
+ GPBUtil::checkFloat($var);
+ $this->p_float = $var;
+
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field optional double p_double = 8;
+ * @return float
+ */
+ public function getPDouble()
+ {
+ return isset($this->p_double) ? $this->p_double : 0.0;
+ }
+
+ public function hasPDouble()
+ {
+ return isset($this->p_double);
+ }
+
+ public function clearPDouble()
+ {
+ unset($this->p_double);
+ }
+
+ /**
+ * Generated from protobuf field optional double p_double = 8;
+ * @param float $var
+ * @return $this
+ */
+ public function setPDouble($var)
+ {
+ GPBUtil::checkDouble($var);
+ $this->p_double = $var;
+
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field optional bool p_bool = 9;
+ * @return bool
+ */
+ public function getPBool()
+ {
+ return isset($this->p_bool) ? $this->p_bool : false;
+ }
+
+ public function hasPBool()
+ {
+ return isset($this->p_bool);
+ }
+
+ public function clearPBool()
+ {
+ unset($this->p_bool);
+ }
+
+ /**
+ * Generated from protobuf field optional bool p_bool = 9;
+ * @param bool $var
+ * @return $this
+ */
+ public function setPBool($var)
+ {
+ GPBUtil::checkBool($var);
+ $this->p_bool = $var;
+
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field .google.showcase.v1beta1.Continent p_continent = 12;
+ * @return int
+ */
+ public function getPContinent()
+ {
+ return $this->p_continent;
+ }
+
+ /**
+ * Generated from protobuf field .google.showcase.v1beta1.Continent p_continent = 12;
+ * @param int $var
+ * @return $this
+ */
+ public function setPContinent($var)
+ {
+ GPBUtil::checkEnum($var, \Google\Showcase\V1beta1\Continent::class);
+ $this->p_continent = $var;
+
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field optional .google.showcase.v1beta1.ComplianceDataGrandchild p_child = 10;
+ * @return \Google\Showcase\V1beta1\ComplianceDataGrandchild|null
+ */
+ public function getPChild()
+ {
+ return $this->p_child;
+ }
+
+ public function hasPChild()
+ {
+ return isset($this->p_child);
+ }
+
+ public function clearPChild()
+ {
+ unset($this->p_child);
+ }
+
+ /**
+ * Generated from protobuf field optional .google.showcase.v1beta1.ComplianceDataGrandchild p_child = 10;
+ * @param \Google\Showcase\V1beta1\ComplianceDataGrandchild $var
+ * @return $this
+ */
+ public function setPChild($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Showcase\V1beta1\ComplianceDataGrandchild::class);
+ $this->p_child = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/ComplianceDataGrandchild.php b/Gax/tests/Conformance/src/V1beta1/ComplianceDataGrandchild.php
new file mode 100644
index 000000000000..d0a12353e381
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/ComplianceDataGrandchild.php
@@ -0,0 +1,112 @@
+google.showcase.v1beta1.ComplianceDataGrandchild
+ */
+class ComplianceDataGrandchild extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * Generated from protobuf field string f_string = 1;
+ */
+ protected $f_string = '';
+ /**
+ * Generated from protobuf field double f_double = 2;
+ */
+ protected $f_double = 0.0;
+ /**
+ * Generated from protobuf field bool f_bool = 3;
+ */
+ protected $f_bool = false;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $f_string
+ * @type float $f_double
+ * @type bool $f_bool
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Compliance::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * Generated from protobuf field string f_string = 1;
+ * @return string
+ */
+ public function getFString()
+ {
+ return $this->f_string;
+ }
+
+ /**
+ * Generated from protobuf field string f_string = 1;
+ * @param string $var
+ * @return $this
+ */
+ public function setFString($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->f_string = $var;
+
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field double f_double = 2;
+ * @return float
+ */
+ public function getFDouble()
+ {
+ return $this->f_double;
+ }
+
+ /**
+ * Generated from protobuf field double f_double = 2;
+ * @param float $var
+ * @return $this
+ */
+ public function setFDouble($var)
+ {
+ GPBUtil::checkDouble($var);
+ $this->f_double = $var;
+
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field bool f_bool = 3;
+ * @return bool
+ */
+ public function getFBool()
+ {
+ return $this->f_bool;
+ }
+
+ /**
+ * Generated from protobuf field bool f_bool = 3;
+ * @param bool $var
+ * @return $this
+ */
+ public function setFBool($var)
+ {
+ GPBUtil::checkBool($var);
+ $this->f_bool = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/ComplianceGroup.php b/Gax/tests/Conformance/src/V1beta1/ComplianceGroup.php
new file mode 100644
index 000000000000..90c88454c74c
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/ComplianceGroup.php
@@ -0,0 +1,116 @@
+google.showcase.v1beta1.ComplianceGroup
+ */
+class ComplianceGroup extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * Generated from protobuf field string name = 1;
+ */
+ protected $name = '';
+ /**
+ * Generated from protobuf field repeated string rpcs = 2;
+ */
+ private $rpcs;
+ /**
+ * Generated from protobuf field repeated .google.showcase.v1beta1.RepeatRequest requests = 3;
+ */
+ private $requests;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $name
+ * @type array|\Google\Protobuf\Internal\RepeatedField $rpcs
+ * @type array<\Google\Showcase\V1beta1\RepeatRequest>|\Google\Protobuf\Internal\RepeatedField $requests
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Compliance::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 repeated string rpcs = 2;
+ * @return \Google\Protobuf\Internal\RepeatedField
+ */
+ public function getRpcs()
+ {
+ return $this->rpcs;
+ }
+
+ /**
+ * Generated from protobuf field repeated string rpcs = 2;
+ * @param array|\Google\Protobuf\Internal\RepeatedField $var
+ * @return $this
+ */
+ public function setRpcs($var)
+ {
+ $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::STRING);
+ $this->rpcs = $arr;
+
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field repeated .google.showcase.v1beta1.RepeatRequest requests = 3;
+ * @return \Google\Protobuf\Internal\RepeatedField
+ */
+ public function getRequests()
+ {
+ return $this->requests;
+ }
+
+ /**
+ * Generated from protobuf field repeated .google.showcase.v1beta1.RepeatRequest requests = 3;
+ * @param array<\Google\Showcase\V1beta1\RepeatRequest>|\Google\Protobuf\Internal\RepeatedField $var
+ * @return $this
+ */
+ public function setRequests($var)
+ {
+ $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Google\Showcase\V1beta1\RepeatRequest::class);
+ $this->requests = $arr;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/ComplianceSuite.php b/Gax/tests/Conformance/src/V1beta1/ComplianceSuite.php
new file mode 100644
index 000000000000..eb7f3841151d
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/ComplianceSuite.php
@@ -0,0 +1,62 @@
+google.showcase.v1beta1.ComplianceSuite
+ */
+class ComplianceSuite extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * Generated from protobuf field repeated .google.showcase.v1beta1.ComplianceGroup group = 1;
+ */
+ private $group;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type array<\Google\Showcase\V1beta1\ComplianceGroup>|\Google\Protobuf\Internal\RepeatedField $group
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Compliance::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * Generated from protobuf field repeated .google.showcase.v1beta1.ComplianceGroup group = 1;
+ * @return \Google\Protobuf\Internal\RepeatedField
+ */
+ public function getGroup()
+ {
+ return $this->group;
+ }
+
+ /**
+ * Generated from protobuf field repeated .google.showcase.v1beta1.ComplianceGroup group = 1;
+ * @param array<\Google\Showcase\V1beta1\ComplianceGroup>|\Google\Protobuf\Internal\RepeatedField $var
+ * @return $this
+ */
+ public function setGroup($var)
+ {
+ $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Google\Showcase\V1beta1\ComplianceGroup::class);
+ $this->group = $arr;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/ConnectRequest.php b/Gax/tests/Conformance/src/V1beta1/ConnectRequest.php
new file mode 100644
index 000000000000..1b5275e023c1
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/ConnectRequest.php
@@ -0,0 +1,112 @@
+google.showcase.v1beta1.ConnectRequest
+ */
+class ConnectRequest extends \Google\Protobuf\Internal\Message
+{
+ protected $request;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type \Google\Showcase\V1beta1\ConnectRequest\ConnectConfig $config
+ * Provides information that specifies how to process subsequent requests.
+ * The first `ConnectRequest` message must contain a `config` message.
+ * @type \Google\Showcase\V1beta1\Blurb $blurb
+ * The blurb to be created.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Messaging::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * Provides information that specifies how to process subsequent requests.
+ * The first `ConnectRequest` message must contain a `config` message.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.ConnectRequest.ConnectConfig config = 1;
+ * @return \Google\Showcase\V1beta1\ConnectRequest\ConnectConfig|null
+ */
+ public function getConfig()
+ {
+ return $this->readOneof(1);
+ }
+
+ public function hasConfig()
+ {
+ return $this->hasOneof(1);
+ }
+
+ /**
+ * Provides information that specifies how to process subsequent requests.
+ * The first `ConnectRequest` message must contain a `config` message.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.ConnectRequest.ConnectConfig config = 1;
+ * @param \Google\Showcase\V1beta1\ConnectRequest\ConnectConfig $var
+ * @return $this
+ */
+ public function setConfig($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Showcase\V1beta1\ConnectRequest\ConnectConfig::class);
+ $this->writeOneof(1, $var);
+
+ return $this;
+ }
+
+ /**
+ * The blurb to be created.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Blurb blurb = 2;
+ * @return \Google\Showcase\V1beta1\Blurb|null
+ */
+ public function getBlurb()
+ {
+ return $this->readOneof(2);
+ }
+
+ public function hasBlurb()
+ {
+ return $this->hasOneof(2);
+ }
+
+ /**
+ * The blurb to be created.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Blurb blurb = 2;
+ * @param \Google\Showcase\V1beta1\Blurb $var
+ * @return $this
+ */
+ public function setBlurb($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Showcase\V1beta1\Blurb::class);
+ $this->writeOneof(2, $var);
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getRequest()
+ {
+ return $this->whichOneof("request");
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/ConnectRequest/ConnectConfig.php b/Gax/tests/Conformance/src/V1beta1/ConnectRequest/ConnectConfig.php
new file mode 100644
index 000000000000..db6dd4cd8de3
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/ConnectRequest/ConnectConfig.php
@@ -0,0 +1,68 @@
+google.showcase.v1beta1.ConnectRequest.ConnectConfig
+ */
+class ConnectConfig extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The room or profile to follow and create messages for.
+ *
+ * Generated from protobuf field string parent = 1 [(.google.api.resource_reference) = {
+ */
+ protected $parent = '';
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $parent
+ * The room or profile to follow and create messages for.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Messaging::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The room or profile to follow and create messages for.
+ *
+ * Generated from protobuf field string parent = 1 [(.google.api.resource_reference) = {
+ * @return string
+ */
+ public function getParent()
+ {
+ return $this->parent;
+ }
+
+ /**
+ * The room or profile to follow and create messages for.
+ *
+ * Generated from protobuf field string parent = 1 [(.google.api.resource_reference) = {
+ * @param string $var
+ * @return $this
+ */
+ public function setParent($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->parent = $var;
+
+ return $this;
+ }
+
+}
+
+// Adding a class alias for backwards compatibility with the previous class name.
+class_alias(ConnectConfig::class, \Google\Showcase\V1beta1\ConnectRequest_ConnectConfig::class);
+
diff --git a/Gax/tests/Conformance/src/V1beta1/Continent.php b/Gax/tests/Conformance/src/V1beta1/Continent.php
new file mode 100644
index 000000000000..ca1acaf104f5
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/Continent.php
@@ -0,0 +1,68 @@
+google.showcase.v1beta1.Continent
+ */
+class Continent
+{
+ /**
+ * Generated from protobuf enum CONTINENT_UNSPECIFIED = 0;
+ */
+ const CONTINENT_UNSPECIFIED = 0;
+ /**
+ * Generated from protobuf enum AFRICA = 1;
+ */
+ const AFRICA = 1;
+ /**
+ * Generated from protobuf enum AMERICA = 2;
+ */
+ const AMERICA = 2;
+ /**
+ * Generated from protobuf enum ANTARTICA = 3;
+ */
+ const ANTARTICA = 3;
+ /**
+ * Generated from protobuf enum AUSTRALIA = 4;
+ */
+ const AUSTRALIA = 4;
+ /**
+ * Generated from protobuf enum EUROPE = 5;
+ */
+ const EUROPE = 5;
+
+ private static $valueToName = [
+ self::CONTINENT_UNSPECIFIED => 'CONTINENT_UNSPECIFIED',
+ self::AFRICA => 'AFRICA',
+ self::AMERICA => 'AMERICA',
+ self::ANTARTICA => 'ANTARTICA',
+ self::AUSTRALIA => 'AUSTRALIA',
+ self::EUROPE => 'EUROPE',
+ ];
+
+ public static function name($value)
+ {
+ if (!isset(self::$valueToName[$value])) {
+ throw new UnexpectedValueException(sprintf(
+ 'Enum %s has no name defined for value %s', __CLASS__, $value));
+ }
+ return self::$valueToName[$value];
+ }
+
+
+ public static function value($name)
+ {
+ $const = __CLASS__ . '::' . strtoupper($name);
+ if (!defined($const)) {
+ throw new UnexpectedValueException(sprintf(
+ 'Enum %s has no value defined for name %s', __CLASS__, $name));
+ }
+ return constant($const);
+ }
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/CreateBlurbRequest.php b/Gax/tests/Conformance/src/V1beta1/CreateBlurbRequest.php
new file mode 100644
index 000000000000..2e591f68cbdc
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/CreateBlurbRequest.php
@@ -0,0 +1,133 @@
+google.showcase.v1beta1.CreateBlurbRequest
+ */
+class CreateBlurbRequest extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The resource name of the chat room or user profile that this blurb will
+ * be tied to.
+ *
+ * Generated from protobuf field string parent = 1 [(.google.api.field_behavior) = REQUIRED, (.google.api.resource_reference) = {
+ */
+ protected $parent = '';
+ /**
+ * The blurb to create.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Blurb blurb = 2;
+ */
+ protected $blurb = null;
+
+ /**
+ * @param string $parent The resource name of the chat room or user profile that this blurb will
+ * be tied to. Please see
+ * {@see MessagingClient::userName()} for help formatting this field.
+ * @param \Google\Showcase\V1beta1\Blurb $blurb The blurb to create.
+ *
+ * @return \Google\Showcase\V1beta1\CreateBlurbRequest
+ *
+ * @experimental
+ */
+ public static function build(string $parent, \Google\Showcase\V1beta1\Blurb $blurb): self
+ {
+ return (new self())
+ ->setParent($parent)
+ ->setBlurb($blurb);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $parent
+ * The resource name of the chat room or user profile that this blurb will
+ * be tied to.
+ * @type \Google\Showcase\V1beta1\Blurb $blurb
+ * The blurb to create.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Messaging::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The resource name of the chat room or user profile that this blurb will
+ * be tied to.
+ *
+ * Generated from protobuf field string parent = 1 [(.google.api.field_behavior) = REQUIRED, (.google.api.resource_reference) = {
+ * @return string
+ */
+ public function getParent()
+ {
+ return $this->parent;
+ }
+
+ /**
+ * The resource name of the chat room or user profile that this blurb will
+ * be tied to.
+ *
+ * Generated from protobuf field string parent = 1 [(.google.api.field_behavior) = REQUIRED, (.google.api.resource_reference) = {
+ * @param string $var
+ * @return $this
+ */
+ public function setParent($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->parent = $var;
+
+ return $this;
+ }
+
+ /**
+ * The blurb to create.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Blurb blurb = 2;
+ * @return \Google\Showcase\V1beta1\Blurb|null
+ */
+ public function getBlurb()
+ {
+ return $this->blurb;
+ }
+
+ public function hasBlurb()
+ {
+ return isset($this->blurb);
+ }
+
+ public function clearBlurb()
+ {
+ unset($this->blurb);
+ }
+
+ /**
+ * The blurb to create.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Blurb blurb = 2;
+ * @param \Google\Showcase\V1beta1\Blurb $var
+ * @return $this
+ */
+ public function setBlurb($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Showcase\V1beta1\Blurb::class);
+ $this->blurb = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/CreateRoomRequest.php b/Gax/tests/Conformance/src/V1beta1/CreateRoomRequest.php
new file mode 100644
index 000000000000..e06b41262b2f
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/CreateRoomRequest.php
@@ -0,0 +1,91 @@
+google.showcase.v1beta1.CreateRoomRequest
+ */
+class CreateRoomRequest extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The room to create.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Room room = 1;
+ */
+ protected $room = null;
+
+ /**
+ * @param \Google\Showcase\V1beta1\Room $room The room to create.
+ *
+ * @return \Google\Showcase\V1beta1\CreateRoomRequest
+ *
+ * @experimental
+ */
+ public static function build(\Google\Showcase\V1beta1\Room $room): self
+ {
+ return (new self())
+ ->setRoom($room);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type \Google\Showcase\V1beta1\Room $room
+ * The room to create.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Messaging::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The room to create.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Room room = 1;
+ * @return \Google\Showcase\V1beta1\Room|null
+ */
+ public function getRoom()
+ {
+ return $this->room;
+ }
+
+ public function hasRoom()
+ {
+ return isset($this->room);
+ }
+
+ public function clearRoom()
+ {
+ unset($this->room);
+ }
+
+ /**
+ * The room to create.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Room room = 1;
+ * @param \Google\Showcase\V1beta1\Room $var
+ * @return $this
+ */
+ public function setRoom($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Showcase\V1beta1\Room::class);
+ $this->room = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/CreateSequenceRequest.php b/Gax/tests/Conformance/src/V1beta1/CreateSequenceRequest.php
new file mode 100644
index 000000000000..824bb223a633
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/CreateSequenceRequest.php
@@ -0,0 +1,81 @@
+google.showcase.v1beta1.CreateSequenceRequest
+ */
+class CreateSequenceRequest extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * Generated from protobuf field .google.showcase.v1beta1.Sequence sequence = 1;
+ */
+ protected $sequence = null;
+
+ /**
+ * @param \Google\Showcase\V1beta1\Sequence $sequence
+ *
+ * @return \Google\Showcase\V1beta1\CreateSequenceRequest
+ *
+ * @experimental
+ */
+ public static function build(\Google\Showcase\V1beta1\Sequence $sequence): self
+ {
+ return (new self())
+ ->setSequence($sequence);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type \Google\Showcase\V1beta1\Sequence $sequence
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Sequence::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * Generated from protobuf field .google.showcase.v1beta1.Sequence sequence = 1;
+ * @return \Google\Showcase\V1beta1\Sequence|null
+ */
+ public function getSequence()
+ {
+ return $this->sequence;
+ }
+
+ public function hasSequence()
+ {
+ return isset($this->sequence);
+ }
+
+ public function clearSequence()
+ {
+ unset($this->sequence);
+ }
+
+ /**
+ * Generated from protobuf field .google.showcase.v1beta1.Sequence sequence = 1;
+ * @param \Google\Showcase\V1beta1\Sequence $var
+ * @return $this
+ */
+ public function setSequence($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Showcase\V1beta1\Sequence::class);
+ $this->sequence = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/CreateSessionRequest.php b/Gax/tests/Conformance/src/V1beta1/CreateSessionRequest.php
new file mode 100644
index 000000000000..e61ac834e170
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/CreateSessionRequest.php
@@ -0,0 +1,85 @@
+google.showcase.v1beta1.CreateSessionRequest
+ */
+class CreateSessionRequest extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The session to be created.
+ * Sessions are immutable once they are created (although they can
+ * be deleted).
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Session session = 1;
+ */
+ protected $session = null;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type \Google\Showcase\V1beta1\Session $session
+ * The session to be created.
+ * Sessions are immutable once they are created (although they can
+ * be deleted).
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Testing::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The session to be created.
+ * Sessions are immutable once they are created (although they can
+ * be deleted).
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Session session = 1;
+ * @return \Google\Showcase\V1beta1\Session|null
+ */
+ public function getSession()
+ {
+ return $this->session;
+ }
+
+ public function hasSession()
+ {
+ return isset($this->session);
+ }
+
+ public function clearSession()
+ {
+ unset($this->session);
+ }
+
+ /**
+ * The session to be created.
+ * Sessions are immutable once they are created (although they can
+ * be deleted).
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Session session = 1;
+ * @param \Google\Showcase\V1beta1\Session $var
+ * @return $this
+ */
+ public function setSession($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Showcase\V1beta1\Session::class);
+ $this->session = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/CreateStreamingSequenceRequest.php b/Gax/tests/Conformance/src/V1beta1/CreateStreamingSequenceRequest.php
new file mode 100644
index 000000000000..60eaae273f4e
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/CreateStreamingSequenceRequest.php
@@ -0,0 +1,81 @@
+google.showcase.v1beta1.CreateStreamingSequenceRequest
+ */
+class CreateStreamingSequenceRequest extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * Generated from protobuf field .google.showcase.v1beta1.StreamingSequence streaming_sequence = 1;
+ */
+ protected $streaming_sequence = null;
+
+ /**
+ * @param \Google\Showcase\V1beta1\StreamingSequence $streamingSequence
+ *
+ * @return \Google\Showcase\V1beta1\CreateStreamingSequenceRequest
+ *
+ * @experimental
+ */
+ public static function build(\Google\Showcase\V1beta1\StreamingSequence $streamingSequence): self
+ {
+ return (new self())
+ ->setStreamingSequence($streamingSequence);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type \Google\Showcase\V1beta1\StreamingSequence $streaming_sequence
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Sequence::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * Generated from protobuf field .google.showcase.v1beta1.StreamingSequence streaming_sequence = 1;
+ * @return \Google\Showcase\V1beta1\StreamingSequence|null
+ */
+ public function getStreamingSequence()
+ {
+ return $this->streaming_sequence;
+ }
+
+ public function hasStreamingSequence()
+ {
+ return isset($this->streaming_sequence);
+ }
+
+ public function clearStreamingSequence()
+ {
+ unset($this->streaming_sequence);
+ }
+
+ /**
+ * Generated from protobuf field .google.showcase.v1beta1.StreamingSequence streaming_sequence = 1;
+ * @param \Google\Showcase\V1beta1\StreamingSequence $var
+ * @return $this
+ */
+ public function setStreamingSequence($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Showcase\V1beta1\StreamingSequence::class);
+ $this->streaming_sequence = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/CreateUserRequest.php b/Gax/tests/Conformance/src/V1beta1/CreateUserRequest.php
new file mode 100644
index 000000000000..72af5e185bbc
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/CreateUserRequest.php
@@ -0,0 +1,91 @@
+google.showcase.v1beta1.CreateUserRequest
+ */
+class CreateUserRequest extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The user to create.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.User user = 1;
+ */
+ protected $user = null;
+
+ /**
+ * @param \Google\Showcase\V1beta1\User $user The user to create.
+ *
+ * @return \Google\Showcase\V1beta1\CreateUserRequest
+ *
+ * @experimental
+ */
+ public static function build(\Google\Showcase\V1beta1\User $user): self
+ {
+ return (new self())
+ ->setUser($user);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type \Google\Showcase\V1beta1\User $user
+ * The user to create.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Identity::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The user to create.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.User user = 1;
+ * @return \Google\Showcase\V1beta1\User|null
+ */
+ public function getUser()
+ {
+ return $this->user;
+ }
+
+ public function hasUser()
+ {
+ return isset($this->user);
+ }
+
+ public function clearUser()
+ {
+ unset($this->user);
+ }
+
+ /**
+ * The user to create.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.User user = 1;
+ * @param \Google\Showcase\V1beta1\User $var
+ * @return $this
+ */
+ public function setUser($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Showcase\V1beta1\User::class);
+ $this->user = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/DeleteBlurbRequest.php b/Gax/tests/Conformance/src/V1beta1/DeleteBlurbRequest.php
new file mode 100644
index 000000000000..33d021c69a43
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/DeleteBlurbRequest.php
@@ -0,0 +1,82 @@
+google.showcase.v1beta1.DeleteBlurbRequest
+ */
+class DeleteBlurbRequest extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The resource name of the requested blurb.
+ *
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = REQUIRED, (.google.api.resource_reference) = {
+ */
+ protected $name = '';
+
+ /**
+ * @param string $name The resource name of the requested blurb. Please see
+ * {@see MessagingClient::blurbName()} for help formatting this field.
+ *
+ * @return \Google\Showcase\V1beta1\DeleteBlurbRequest
+ *
+ * @experimental
+ */
+ public static function build(string $name): self
+ {
+ return (new self())
+ ->setName($name);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $name
+ * The resource name of the requested blurb.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Messaging::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The resource name of the requested blurb.
+ *
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = REQUIRED, (.google.api.resource_reference) = {
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * The resource name of the requested blurb.
+ *
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = REQUIRED, (.google.api.resource_reference) = {
+ * @param string $var
+ * @return $this
+ */
+ public function setName($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->name = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/DeleteRoomRequest.php b/Gax/tests/Conformance/src/V1beta1/DeleteRoomRequest.php
new file mode 100644
index 000000000000..7c5f4b83f76e
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/DeleteRoomRequest.php
@@ -0,0 +1,82 @@
+google.showcase.v1beta1.DeleteRoomRequest
+ */
+class DeleteRoomRequest extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The resource name of the requested room.
+ *
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = REQUIRED, (.google.api.resource_reference) = {
+ */
+ protected $name = '';
+
+ /**
+ * @param string $name The resource name of the requested room. Please see
+ * {@see MessagingClient::roomName()} for help formatting this field.
+ *
+ * @return \Google\Showcase\V1beta1\DeleteRoomRequest
+ *
+ * @experimental
+ */
+ public static function build(string $name): self
+ {
+ return (new self())
+ ->setName($name);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $name
+ * The resource name of the requested room.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Messaging::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The resource name of the requested room.
+ *
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = REQUIRED, (.google.api.resource_reference) = {
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * The resource name of the requested room.
+ *
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = REQUIRED, (.google.api.resource_reference) = {
+ * @param string $var
+ * @return $this
+ */
+ public function setName($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->name = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/DeleteSessionRequest.php b/Gax/tests/Conformance/src/V1beta1/DeleteSessionRequest.php
new file mode 100644
index 000000000000..0a2da3f92578
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/DeleteSessionRequest.php
@@ -0,0 +1,67 @@
+google.showcase.v1beta1.DeleteSessionRequest
+ */
+class DeleteSessionRequest extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The session to be deleted.
+ *
+ * Generated from protobuf field string name = 1 [(.google.api.resource_reference) = {
+ */
+ protected $name = '';
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $name
+ * The session to be deleted.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Testing::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The session to be deleted.
+ *
+ * Generated from protobuf field string name = 1 [(.google.api.resource_reference) = {
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * The session to be deleted.
+ *
+ * Generated from protobuf field string name = 1 [(.google.api.resource_reference) = {
+ * @param string $var
+ * @return $this
+ */
+ public function setName($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->name = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/DeleteTestRequest.php b/Gax/tests/Conformance/src/V1beta1/DeleteTestRequest.php
new file mode 100644
index 000000000000..9be9c9fee37d
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/DeleteTestRequest.php
@@ -0,0 +1,67 @@
+google.showcase.v1beta1.DeleteTestRequest
+ */
+class DeleteTestRequest extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The test to be deleted.
+ *
+ * Generated from protobuf field string name = 1 [(.google.api.resource_reference) = {
+ */
+ protected $name = '';
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $name
+ * The test to be deleted.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Testing::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The test to be deleted.
+ *
+ * Generated from protobuf field string name = 1 [(.google.api.resource_reference) = {
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * The test to be deleted.
+ *
+ * Generated from protobuf field string name = 1 [(.google.api.resource_reference) = {
+ * @param string $var
+ * @return $this
+ */
+ public function setName($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->name = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/DeleteUserRequest.php b/Gax/tests/Conformance/src/V1beta1/DeleteUserRequest.php
new file mode 100644
index 000000000000..b0f77bf415f7
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/DeleteUserRequest.php
@@ -0,0 +1,82 @@
+google.showcase.v1beta1.DeleteUserRequest
+ */
+class DeleteUserRequest extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The resource name of the user to delete.
+ *
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = REQUIRED, (.google.api.resource_reference) = {
+ */
+ protected $name = '';
+
+ /**
+ * @param string $name The resource name of the user to delete. Please see
+ * {@see IdentityClient::userName()} for help formatting this field.
+ *
+ * @return \Google\Showcase\V1beta1\DeleteUserRequest
+ *
+ * @experimental
+ */
+ public static function build(string $name): self
+ {
+ return (new self())
+ ->setName($name);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $name
+ * The resource name of the user to delete.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Identity::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The resource name of the user to delete.
+ *
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = REQUIRED, (.google.api.resource_reference) = {
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * The resource name of the user to delete.
+ *
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = REQUIRED, (.google.api.resource_reference) = {
+ * @param string $var
+ * @return $this
+ */
+ public function setName($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->name = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/EchoErrorDetailsRequest.php b/Gax/tests/Conformance/src/V1beta1/EchoErrorDetailsRequest.php
new file mode 100644
index 000000000000..682a580b49d9
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/EchoErrorDetailsRequest.php
@@ -0,0 +1,109 @@
+google.showcase.v1beta1.EchoErrorDetailsRequest
+ */
+class EchoErrorDetailsRequest extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * Content to return in a singular `*.error.details` field of type
+ * `google.protobuf.Any`
+ *
+ * Generated from protobuf field string single_detail_text = 1;
+ */
+ protected $single_detail_text = '';
+ /**
+ * Content to return in a repeated `*.error.details` field of type
+ * `google.protobuf.Any`
+ *
+ * Generated from protobuf field repeated string multi_detail_text = 2;
+ */
+ private $multi_detail_text;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $single_detail_text
+ * Content to return in a singular `*.error.details` field of type
+ * `google.protobuf.Any`
+ * @type array|\Google\Protobuf\Internal\RepeatedField $multi_detail_text
+ * Content to return in a repeated `*.error.details` field of type
+ * `google.protobuf.Any`
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\PBEcho::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * Content to return in a singular `*.error.details` field of type
+ * `google.protobuf.Any`
+ *
+ * Generated from protobuf field string single_detail_text = 1;
+ * @return string
+ */
+ public function getSingleDetailText()
+ {
+ return $this->single_detail_text;
+ }
+
+ /**
+ * Content to return in a singular `*.error.details` field of type
+ * `google.protobuf.Any`
+ *
+ * Generated from protobuf field string single_detail_text = 1;
+ * @param string $var
+ * @return $this
+ */
+ public function setSingleDetailText($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->single_detail_text = $var;
+
+ return $this;
+ }
+
+ /**
+ * Content to return in a repeated `*.error.details` field of type
+ * `google.protobuf.Any`
+ *
+ * Generated from protobuf field repeated string multi_detail_text = 2;
+ * @return \Google\Protobuf\Internal\RepeatedField
+ */
+ public function getMultiDetailText()
+ {
+ return $this->multi_detail_text;
+ }
+
+ /**
+ * Content to return in a repeated `*.error.details` field of type
+ * `google.protobuf.Any`
+ *
+ * Generated from protobuf field repeated string multi_detail_text = 2;
+ * @param array|\Google\Protobuf\Internal\RepeatedField $var
+ * @return $this
+ */
+ public function setMultiDetailText($var)
+ {
+ $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::STRING);
+ $this->multi_detail_text = $arr;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/EchoErrorDetailsResponse.php b/Gax/tests/Conformance/src/V1beta1/EchoErrorDetailsResponse.php
new file mode 100644
index 000000000000..602daec90811
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/EchoErrorDetailsResponse.php
@@ -0,0 +1,107 @@
+google.showcase.v1beta1.EchoErrorDetailsResponse
+ */
+class EchoErrorDetailsResponse extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * Generated from protobuf field .google.showcase.v1beta1.EchoErrorDetailsResponse.SingleDetail single_detail = 1;
+ */
+ protected $single_detail = null;
+ /**
+ * Generated from protobuf field .google.showcase.v1beta1.EchoErrorDetailsResponse.MultipleDetails multiple_details = 2;
+ */
+ protected $multiple_details = null;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type \Google\Showcase\V1beta1\EchoErrorDetailsResponse\SingleDetail $single_detail
+ * @type \Google\Showcase\V1beta1\EchoErrorDetailsResponse\MultipleDetails $multiple_details
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\PBEcho::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * Generated from protobuf field .google.showcase.v1beta1.EchoErrorDetailsResponse.SingleDetail single_detail = 1;
+ * @return \Google\Showcase\V1beta1\EchoErrorDetailsResponse\SingleDetail|null
+ */
+ public function getSingleDetail()
+ {
+ return $this->single_detail;
+ }
+
+ public function hasSingleDetail()
+ {
+ return isset($this->single_detail);
+ }
+
+ public function clearSingleDetail()
+ {
+ unset($this->single_detail);
+ }
+
+ /**
+ * Generated from protobuf field .google.showcase.v1beta1.EchoErrorDetailsResponse.SingleDetail single_detail = 1;
+ * @param \Google\Showcase\V1beta1\EchoErrorDetailsResponse\SingleDetail $var
+ * @return $this
+ */
+ public function setSingleDetail($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Showcase\V1beta1\EchoErrorDetailsResponse\SingleDetail::class);
+ $this->single_detail = $var;
+
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field .google.showcase.v1beta1.EchoErrorDetailsResponse.MultipleDetails multiple_details = 2;
+ * @return \Google\Showcase\V1beta1\EchoErrorDetailsResponse\MultipleDetails|null
+ */
+ public function getMultipleDetails()
+ {
+ return $this->multiple_details;
+ }
+
+ public function hasMultipleDetails()
+ {
+ return isset($this->multiple_details);
+ }
+
+ public function clearMultipleDetails()
+ {
+ unset($this->multiple_details);
+ }
+
+ /**
+ * Generated from protobuf field .google.showcase.v1beta1.EchoErrorDetailsResponse.MultipleDetails multiple_details = 2;
+ * @param \Google\Showcase\V1beta1\EchoErrorDetailsResponse\MultipleDetails $var
+ * @return $this
+ */
+ public function setMultipleDetails($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Showcase\V1beta1\EchoErrorDetailsResponse\MultipleDetails::class);
+ $this->multiple_details = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/EchoErrorDetailsResponse/MultipleDetails.php b/Gax/tests/Conformance/src/V1beta1/EchoErrorDetailsResponse/MultipleDetails.php
new file mode 100644
index 000000000000..354dc83201aa
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/EchoErrorDetailsResponse/MultipleDetails.php
@@ -0,0 +1,71 @@
+google.showcase.v1beta1.EchoErrorDetailsResponse.MultipleDetails
+ */
+class MultipleDetails extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * Generated from protobuf field .google.showcase.v1beta1.ErrorWithMultipleDetails error = 1;
+ */
+ protected $error = null;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type \Google\Showcase\V1beta1\ErrorWithMultipleDetails $error
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\PBEcho::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * Generated from protobuf field .google.showcase.v1beta1.ErrorWithMultipleDetails error = 1;
+ * @return \Google\Showcase\V1beta1\ErrorWithMultipleDetails|null
+ */
+ public function getError()
+ {
+ return $this->error;
+ }
+
+ public function hasError()
+ {
+ return isset($this->error);
+ }
+
+ public function clearError()
+ {
+ unset($this->error);
+ }
+
+ /**
+ * Generated from protobuf field .google.showcase.v1beta1.ErrorWithMultipleDetails error = 1;
+ * @param \Google\Showcase\V1beta1\ErrorWithMultipleDetails $var
+ * @return $this
+ */
+ public function setError($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Showcase\V1beta1\ErrorWithMultipleDetails::class);
+ $this->error = $var;
+
+ return $this;
+ }
+
+}
+
+// Adding a class alias for backwards compatibility with the previous class name.
+class_alias(MultipleDetails::class, \Google\Showcase\V1beta1\EchoErrorDetailsResponse_MultipleDetails::class);
+
diff --git a/Gax/tests/Conformance/src/V1beta1/EchoErrorDetailsResponse/SingleDetail.php b/Gax/tests/Conformance/src/V1beta1/EchoErrorDetailsResponse/SingleDetail.php
new file mode 100644
index 000000000000..5ed6b6660faf
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/EchoErrorDetailsResponse/SingleDetail.php
@@ -0,0 +1,71 @@
+google.showcase.v1beta1.EchoErrorDetailsResponse.SingleDetail
+ */
+class SingleDetail extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * Generated from protobuf field .google.showcase.v1beta1.ErrorWithSingleDetail error = 1;
+ */
+ protected $error = null;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type \Google\Showcase\V1beta1\ErrorWithSingleDetail $error
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\PBEcho::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * Generated from protobuf field .google.showcase.v1beta1.ErrorWithSingleDetail error = 1;
+ * @return \Google\Showcase\V1beta1\ErrorWithSingleDetail|null
+ */
+ public function getError()
+ {
+ return $this->error;
+ }
+
+ public function hasError()
+ {
+ return isset($this->error);
+ }
+
+ public function clearError()
+ {
+ unset($this->error);
+ }
+
+ /**
+ * Generated from protobuf field .google.showcase.v1beta1.ErrorWithSingleDetail error = 1;
+ * @param \Google\Showcase\V1beta1\ErrorWithSingleDetail $var
+ * @return $this
+ */
+ public function setError($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Showcase\V1beta1\ErrorWithSingleDetail::class);
+ $this->error = $var;
+
+ return $this;
+ }
+
+}
+
+// Adding a class alias for backwards compatibility with the previous class name.
+class_alias(SingleDetail::class, \Google\Showcase\V1beta1\EchoErrorDetailsResponse_SingleDetail::class);
+
diff --git a/Gax/tests/Conformance/src/V1beta1/EchoRequest.php b/Gax/tests/Conformance/src/V1beta1/EchoRequest.php
new file mode 100644
index 000000000000..036cd7b23a9d
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/EchoRequest.php
@@ -0,0 +1,291 @@
+google.showcase.v1beta1.EchoRequest
+ */
+class EchoRequest extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The severity to be echoed by the server.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Severity severity = 3;
+ */
+ protected $severity = 0;
+ /**
+ * Optional. This field can be set to test the routing annotation on the Echo method.
+ *
+ * Generated from protobuf field string header = 4;
+ */
+ protected $header = '';
+ /**
+ * Optional. This field can be set to test the routing annotation on the Echo method.
+ *
+ * Generated from protobuf field string other_header = 5;
+ */
+ protected $other_header = '';
+ /**
+ * To facilitate testing of https://google.aip.dev/client-libraries/4235
+ *
+ * Generated from protobuf field string request_id = 7 [(.google.api.field_info) = {
+ */
+ protected $request_id = '';
+ /**
+ * To facilitate testing of https://google.aip.dev/client-libraries/4235
+ *
+ * Generated from protobuf field optional string other_request_id = 8 [(.google.api.field_info) = {
+ */
+ protected $other_request_id = null;
+ protected $response;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $content
+ * The content to be echoed by the server.
+ * @type \Google\Rpc\Status $error
+ * The error to be thrown by the server.
+ * @type int $severity
+ * The severity to be echoed by the server.
+ * @type string $header
+ * Optional. This field can be set to test the routing annotation on the Echo method.
+ * @type string $other_header
+ * Optional. This field can be set to test the routing annotation on the Echo method.
+ * @type string $request_id
+ * To facilitate testing of https://google.aip.dev/client-libraries/4235
+ * @type string $other_request_id
+ * To facilitate testing of https://google.aip.dev/client-libraries/4235
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\PBEcho::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The content to be echoed by the server.
+ *
+ * Generated from protobuf field string content = 1;
+ * @return string
+ */
+ public function getContent()
+ {
+ return $this->readOneof(1);
+ }
+
+ public function hasContent()
+ {
+ return $this->hasOneof(1);
+ }
+
+ /**
+ * The content to be echoed by the server.
+ *
+ * Generated from protobuf field string content = 1;
+ * @param string $var
+ * @return $this
+ */
+ public function setContent($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->writeOneof(1, $var);
+
+ return $this;
+ }
+
+ /**
+ * The error to be thrown by the server.
+ *
+ * Generated from protobuf field .google.rpc.Status error = 2;
+ * @return \Google\Rpc\Status|null
+ */
+ public function getError()
+ {
+ return $this->readOneof(2);
+ }
+
+ public function hasError()
+ {
+ return $this->hasOneof(2);
+ }
+
+ /**
+ * The error to be thrown by the server.
+ *
+ * Generated from protobuf field .google.rpc.Status error = 2;
+ * @param \Google\Rpc\Status $var
+ * @return $this
+ */
+ public function setError($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Rpc\Status::class);
+ $this->writeOneof(2, $var);
+
+ return $this;
+ }
+
+ /**
+ * The severity to be echoed by the server.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Severity severity = 3;
+ * @return int
+ */
+ public function getSeverity()
+ {
+ return $this->severity;
+ }
+
+ /**
+ * The severity to be echoed by the server.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Severity severity = 3;
+ * @param int $var
+ * @return $this
+ */
+ public function setSeverity($var)
+ {
+ GPBUtil::checkEnum($var, \Google\Showcase\V1beta1\Severity::class);
+ $this->severity = $var;
+
+ return $this;
+ }
+
+ /**
+ * Optional. This field can be set to test the routing annotation on the Echo method.
+ *
+ * Generated from protobuf field string header = 4;
+ * @return string
+ */
+ public function getHeader()
+ {
+ return $this->header;
+ }
+
+ /**
+ * Optional. This field can be set to test the routing annotation on the Echo method.
+ *
+ * Generated from protobuf field string header = 4;
+ * @param string $var
+ * @return $this
+ */
+ public function setHeader($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->header = $var;
+
+ return $this;
+ }
+
+ /**
+ * Optional. This field can be set to test the routing annotation on the Echo method.
+ *
+ * Generated from protobuf field string other_header = 5;
+ * @return string
+ */
+ public function getOtherHeader()
+ {
+ return $this->other_header;
+ }
+
+ /**
+ * Optional. This field can be set to test the routing annotation on the Echo method.
+ *
+ * Generated from protobuf field string other_header = 5;
+ * @param string $var
+ * @return $this
+ */
+ public function setOtherHeader($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->other_header = $var;
+
+ return $this;
+ }
+
+ /**
+ * To facilitate testing of https://google.aip.dev/client-libraries/4235
+ *
+ * Generated from protobuf field string request_id = 7 [(.google.api.field_info) = {
+ * @return string
+ */
+ public function getRequestId()
+ {
+ return $this->request_id;
+ }
+
+ /**
+ * To facilitate testing of https://google.aip.dev/client-libraries/4235
+ *
+ * Generated from protobuf field string request_id = 7 [(.google.api.field_info) = {
+ * @param string $var
+ * @return $this
+ */
+ public function setRequestId($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->request_id = $var;
+
+ return $this;
+ }
+
+ /**
+ * To facilitate testing of https://google.aip.dev/client-libraries/4235
+ *
+ * Generated from protobuf field optional string other_request_id = 8 [(.google.api.field_info) = {
+ * @return string
+ */
+ public function getOtherRequestId()
+ {
+ return isset($this->other_request_id) ? $this->other_request_id : '';
+ }
+
+ public function hasOtherRequestId()
+ {
+ return isset($this->other_request_id);
+ }
+
+ public function clearOtherRequestId()
+ {
+ unset($this->other_request_id);
+ }
+
+ /**
+ * To facilitate testing of https://google.aip.dev/client-libraries/4235
+ *
+ * Generated from protobuf field optional string other_request_id = 8 [(.google.api.field_info) = {
+ * @param string $var
+ * @return $this
+ */
+ public function setOtherRequestId($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->other_request_id = $var;
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getResponse()
+ {
+ return $this->whichOneof("response");
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/EchoResponse.php b/Gax/tests/Conformance/src/V1beta1/EchoResponse.php
new file mode 100644
index 000000000000..6376a550329e
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/EchoResponse.php
@@ -0,0 +1,169 @@
+google.showcase.v1beta1.EchoResponse
+ */
+class EchoResponse extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The content specified in the request.
+ *
+ * Generated from protobuf field string content = 1;
+ */
+ protected $content = '';
+ /**
+ * The severity specified in the request.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Severity severity = 2;
+ */
+ protected $severity = 0;
+ /**
+ * The request ID specified or autopopulated in the request.
+ *
+ * Generated from protobuf field string request_id = 3;
+ */
+ protected $request_id = '';
+ /**
+ * The other request ID specified or autopopulated in the request.
+ *
+ * Generated from protobuf field string other_request_id = 4;
+ */
+ protected $other_request_id = '';
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $content
+ * The content specified in the request.
+ * @type int $severity
+ * The severity specified in the request.
+ * @type string $request_id
+ * The request ID specified or autopopulated in the request.
+ * @type string $other_request_id
+ * The other request ID specified or autopopulated in the request.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\PBEcho::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The content specified in the request.
+ *
+ * Generated from protobuf field string content = 1;
+ * @return string
+ */
+ public function getContent()
+ {
+ return $this->content;
+ }
+
+ /**
+ * The content specified in the request.
+ *
+ * Generated from protobuf field string content = 1;
+ * @param string $var
+ * @return $this
+ */
+ public function setContent($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->content = $var;
+
+ return $this;
+ }
+
+ /**
+ * The severity specified in the request.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Severity severity = 2;
+ * @return int
+ */
+ public function getSeverity()
+ {
+ return $this->severity;
+ }
+
+ /**
+ * The severity specified in the request.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Severity severity = 2;
+ * @param int $var
+ * @return $this
+ */
+ public function setSeverity($var)
+ {
+ GPBUtil::checkEnum($var, \Google\Showcase\V1beta1\Severity::class);
+ $this->severity = $var;
+
+ return $this;
+ }
+
+ /**
+ * The request ID specified or autopopulated in the request.
+ *
+ * Generated from protobuf field string request_id = 3;
+ * @return string
+ */
+ public function getRequestId()
+ {
+ return $this->request_id;
+ }
+
+ /**
+ * The request ID specified or autopopulated in the request.
+ *
+ * Generated from protobuf field string request_id = 3;
+ * @param string $var
+ * @return $this
+ */
+ public function setRequestId($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->request_id = $var;
+
+ return $this;
+ }
+
+ /**
+ * The other request ID specified or autopopulated in the request.
+ *
+ * Generated from protobuf field string other_request_id = 4;
+ * @return string
+ */
+ public function getOtherRequestId()
+ {
+ return $this->other_request_id;
+ }
+
+ /**
+ * The other request ID specified or autopopulated in the request.
+ *
+ * Generated from protobuf field string other_request_id = 4;
+ * @param string $var
+ * @return $this
+ */
+ public function setOtherRequestId($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->other_request_id = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/EnumRequest.php b/Gax/tests/Conformance/src/V1beta1/EnumRequest.php
new file mode 100644
index 000000000000..9bcf0f24c6b5
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/EnumRequest.php
@@ -0,0 +1,65 @@
+google.showcase.v1beta1.EnumRequest
+ */
+class EnumRequest extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * Whether the client is requesting a new, unknown enum value or a known enum value already declared in this proto file.
+ *
+ * Generated from protobuf field bool unknown_enum = 1;
+ */
+ protected $unknown_enum = false;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type bool $unknown_enum
+ * Whether the client is requesting a new, unknown enum value or a known enum value already declared in this proto file.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Compliance::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * Whether the client is requesting a new, unknown enum value or a known enum value already declared in this proto file.
+ *
+ * Generated from protobuf field bool unknown_enum = 1;
+ * @return bool
+ */
+ public function getUnknownEnum()
+ {
+ return $this->unknown_enum;
+ }
+
+ /**
+ * Whether the client is requesting a new, unknown enum value or a known enum value already declared in this proto file.
+ *
+ * Generated from protobuf field bool unknown_enum = 1;
+ * @param bool $var
+ * @return $this
+ */
+ public function setUnknownEnum($var)
+ {
+ GPBUtil::checkBool($var);
+ $this->unknown_enum = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/EnumResponse.php b/Gax/tests/Conformance/src/V1beta1/EnumResponse.php
new file mode 100644
index 000000000000..2891e8686ef6
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/EnumResponse.php
@@ -0,0 +1,109 @@
+google.showcase.v1beta1.EnumResponse
+ */
+class EnumResponse extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The original request for a known or unknown enum from the server.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.EnumRequest request = 1;
+ */
+ protected $request = null;
+ /**
+ * The actual enum the server provided.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Continent continent = 2;
+ */
+ protected $continent = 0;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type \Google\Showcase\V1beta1\EnumRequest $request
+ * The original request for a known or unknown enum from the server.
+ * @type int $continent
+ * The actual enum the server provided.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Compliance::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The original request for a known or unknown enum from the server.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.EnumRequest request = 1;
+ * @return \Google\Showcase\V1beta1\EnumRequest|null
+ */
+ public function getRequest()
+ {
+ return $this->request;
+ }
+
+ public function hasRequest()
+ {
+ return isset($this->request);
+ }
+
+ public function clearRequest()
+ {
+ unset($this->request);
+ }
+
+ /**
+ * The original request for a known or unknown enum from the server.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.EnumRequest request = 1;
+ * @param \Google\Showcase\V1beta1\EnumRequest $var
+ * @return $this
+ */
+ public function setRequest($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Showcase\V1beta1\EnumRequest::class);
+ $this->request = $var;
+
+ return $this;
+ }
+
+ /**
+ * The actual enum the server provided.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Continent continent = 2;
+ * @return int
+ */
+ public function getContinent()
+ {
+ return $this->continent;
+ }
+
+ /**
+ * The actual enum the server provided.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Continent continent = 2;
+ * @param int $var
+ * @return $this
+ */
+ public function setContinent($var)
+ {
+ GPBUtil::checkEnum($var, \Google\Showcase\V1beta1\Continent::class);
+ $this->continent = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/ErrorWithMultipleDetails.php b/Gax/tests/Conformance/src/V1beta1/ErrorWithMultipleDetails.php
new file mode 100644
index 000000000000..6a8103073138
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/ErrorWithMultipleDetails.php
@@ -0,0 +1,58 @@
+google.showcase.v1beta1.ErrorWithMultipleDetails
+ */
+class ErrorWithMultipleDetails extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * Generated from protobuf field repeated .google.protobuf.Any details = 1;
+ */
+ private $details;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type array<\Google\Protobuf\Any>|\Google\Protobuf\Internal\RepeatedField $details
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\PBEcho::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * Generated from protobuf field repeated .google.protobuf.Any details = 1;
+ * @return \Google\Protobuf\Internal\RepeatedField
+ */
+ public function getDetails()
+ {
+ return $this->details;
+ }
+
+ /**
+ * Generated from protobuf field repeated .google.protobuf.Any details = 1;
+ * @param array<\Google\Protobuf\Any>|\Google\Protobuf\Internal\RepeatedField $var
+ * @return $this
+ */
+ public function setDetails($var)
+ {
+ $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Google\Protobuf\Any::class);
+ $this->details = $arr;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/ErrorWithSingleDetail.php b/Gax/tests/Conformance/src/V1beta1/ErrorWithSingleDetail.php
new file mode 100644
index 000000000000..2160690b7ace
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/ErrorWithSingleDetail.php
@@ -0,0 +1,68 @@
+google.showcase.v1beta1.ErrorWithSingleDetail
+ */
+class ErrorWithSingleDetail extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * Generated from protobuf field .google.protobuf.Any details = 1;
+ */
+ protected $details = null;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type \Google\Protobuf\Any $details
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\PBEcho::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * Generated from protobuf field .google.protobuf.Any details = 1;
+ * @return \Google\Protobuf\Any|null
+ */
+ public function getDetails()
+ {
+ return $this->details;
+ }
+
+ public function hasDetails()
+ {
+ return isset($this->details);
+ }
+
+ public function clearDetails()
+ {
+ unset($this->details);
+ }
+
+ /**
+ * Generated from protobuf field .google.protobuf.Any details = 1;
+ * @param \Google\Protobuf\Any $var
+ * @return $this
+ */
+ public function setDetails($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Protobuf\Any::class);
+ $this->details = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/ExpandRequest.php b/Gax/tests/Conformance/src/V1beta1/ExpandRequest.php
new file mode 100644
index 000000000000..ff777df17271
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/ExpandRequest.php
@@ -0,0 +1,170 @@
+google.showcase.v1beta1.ExpandRequest
+ */
+class ExpandRequest extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The content that will be split into words and returned on the stream.
+ *
+ * Generated from protobuf field string content = 1;
+ */
+ protected $content = '';
+ /**
+ * The error that is thrown after all words are sent on the stream.
+ *
+ * Generated from protobuf field .google.rpc.Status error = 2;
+ */
+ protected $error = null;
+ /**
+ *The wait time between each server streaming messages
+ *
+ * Generated from protobuf field .google.protobuf.Duration stream_wait_time = 3;
+ */
+ protected $stream_wait_time = null;
+
+ /**
+ * @param string $content The content that will be split into words and returned on the stream.
+ * @param \Google\Rpc\Status $error The error that is thrown after all words are sent on the stream.
+ *
+ * @return \Google\Showcase\V1beta1\ExpandRequest
+ *
+ * @experimental
+ */
+ public static function build(string $content, \Google\Rpc\Status $error): self
+ {
+ return (new self())
+ ->setContent($content)
+ ->setError($error);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $content
+ * The content that will be split into words and returned on the stream.
+ * @type \Google\Rpc\Status $error
+ * The error that is thrown after all words are sent on the stream.
+ * @type \Google\Protobuf\Duration $stream_wait_time
+ * The wait time between each server streaming messages
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\PBEcho::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The content that will be split into words and returned on the stream.
+ *
+ * Generated from protobuf field string content = 1;
+ * @return string
+ */
+ public function getContent()
+ {
+ return $this->content;
+ }
+
+ /**
+ * The content that will be split into words and returned on the stream.
+ *
+ * Generated from protobuf field string content = 1;
+ * @param string $var
+ * @return $this
+ */
+ public function setContent($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->content = $var;
+
+ return $this;
+ }
+
+ /**
+ * The error that is thrown after all words are sent on the stream.
+ *
+ * Generated from protobuf field .google.rpc.Status error = 2;
+ * @return \Google\Rpc\Status|null
+ */
+ public function getError()
+ {
+ return $this->error;
+ }
+
+ public function hasError()
+ {
+ return isset($this->error);
+ }
+
+ public function clearError()
+ {
+ unset($this->error);
+ }
+
+ /**
+ * The error that is thrown after all words are sent on the stream.
+ *
+ * Generated from protobuf field .google.rpc.Status error = 2;
+ * @param \Google\Rpc\Status $var
+ * @return $this
+ */
+ public function setError($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Rpc\Status::class);
+ $this->error = $var;
+
+ return $this;
+ }
+
+ /**
+ *The wait time between each server streaming messages
+ *
+ * Generated from protobuf field .google.protobuf.Duration stream_wait_time = 3;
+ * @return \Google\Protobuf\Duration|null
+ */
+ public function getStreamWaitTime()
+ {
+ return $this->stream_wait_time;
+ }
+
+ public function hasStreamWaitTime()
+ {
+ return isset($this->stream_wait_time);
+ }
+
+ public function clearStreamWaitTime()
+ {
+ unset($this->stream_wait_time);
+ }
+
+ /**
+ *The wait time between each server streaming messages
+ *
+ * Generated from protobuf field .google.protobuf.Duration stream_wait_time = 3;
+ * @param \Google\Protobuf\Duration $var
+ * @return $this
+ */
+ public function setStreamWaitTime($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Protobuf\Duration::class);
+ $this->stream_wait_time = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/FailEchoWithDetailsRequest.php b/Gax/tests/Conformance/src/V1beta1/FailEchoWithDetailsRequest.php
new file mode 100644
index 000000000000..bd16d204766a
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/FailEchoWithDetailsRequest.php
@@ -0,0 +1,71 @@
+google.showcase.v1beta1.FailEchoWithDetailsRequest
+ */
+class FailEchoWithDetailsRequest extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * Optional message to echo back in the PoetryError. If empty, a value will be
+ * provided.
+ *
+ * Generated from protobuf field string message = 1;
+ */
+ protected $message = '';
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $message
+ * Optional message to echo back in the PoetryError. If empty, a value will be
+ * provided.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\PBEcho::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * Optional message to echo back in the PoetryError. If empty, a value will be
+ * provided.
+ *
+ * Generated from protobuf field string message = 1;
+ * @return string
+ */
+ public function getMessage()
+ {
+ return $this->message;
+ }
+
+ /**
+ * Optional message to echo back in the PoetryError. If empty, a value will be
+ * provided.
+ *
+ * Generated from protobuf field string message = 1;
+ * @param string $var
+ * @return $this
+ */
+ public function setMessage($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->message = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/FailEchoWithDetailsResponse.php b/Gax/tests/Conformance/src/V1beta1/FailEchoWithDetailsResponse.php
new file mode 100644
index 000000000000..f7ab8fc97a23
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/FailEchoWithDetailsResponse.php
@@ -0,0 +1,34 @@
+google.showcase.v1beta1.FailEchoWithDetailsResponse
+ */
+class FailEchoWithDetailsResponse extends \Google\Protobuf\Internal\Message
+{
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\PBEcho::initOnce();
+ parent::__construct($data);
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/GetBlurbRequest.php b/Gax/tests/Conformance/src/V1beta1/GetBlurbRequest.php
new file mode 100644
index 000000000000..5746c7116432
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/GetBlurbRequest.php
@@ -0,0 +1,82 @@
+google.showcase.v1beta1.GetBlurbRequest
+ */
+class GetBlurbRequest extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The resource name of the requested blurb.
+ *
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = REQUIRED, (.google.api.resource_reference) = {
+ */
+ protected $name = '';
+
+ /**
+ * @param string $name The resource name of the requested blurb. Please see
+ * {@see MessagingClient::blurbName()} for help formatting this field.
+ *
+ * @return \Google\Showcase\V1beta1\GetBlurbRequest
+ *
+ * @experimental
+ */
+ public static function build(string $name): self
+ {
+ return (new self())
+ ->setName($name);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $name
+ * The resource name of the requested blurb.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Messaging::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The resource name of the requested blurb.
+ *
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = REQUIRED, (.google.api.resource_reference) = {
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * The resource name of the requested blurb.
+ *
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = REQUIRED, (.google.api.resource_reference) = {
+ * @param string $var
+ * @return $this
+ */
+ public function setName($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->name = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/GetRoomRequest.php b/Gax/tests/Conformance/src/V1beta1/GetRoomRequest.php
new file mode 100644
index 000000000000..f80ff9f40ea4
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/GetRoomRequest.php
@@ -0,0 +1,82 @@
+google.showcase.v1beta1.GetRoomRequest
+ */
+class GetRoomRequest extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The resource name of the requested room.
+ *
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = REQUIRED, (.google.api.resource_reference) = {
+ */
+ protected $name = '';
+
+ /**
+ * @param string $name The resource name of the requested room. Please see
+ * {@see MessagingClient::roomName()} for help formatting this field.
+ *
+ * @return \Google\Showcase\V1beta1\GetRoomRequest
+ *
+ * @experimental
+ */
+ public static function build(string $name): self
+ {
+ return (new self())
+ ->setName($name);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $name
+ * The resource name of the requested room.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Messaging::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The resource name of the requested room.
+ *
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = REQUIRED, (.google.api.resource_reference) = {
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * The resource name of the requested room.
+ *
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = REQUIRED, (.google.api.resource_reference) = {
+ * @param string $var
+ * @return $this
+ */
+ public function setName($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->name = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/GetSequenceReportRequest.php b/Gax/tests/Conformance/src/V1beta1/GetSequenceReportRequest.php
new file mode 100644
index 000000000000..9cff8d1d607a
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/GetSequenceReportRequest.php
@@ -0,0 +1,71 @@
+google.showcase.v1beta1.GetSequenceReportRequest
+ */
+class GetSequenceReportRequest extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = REQUIRED, (.google.api.resource_reference) = {
+ */
+ protected $name = '';
+
+ /**
+ * @param string $name Please see {@see SequenceServiceClient::sequenceReportName()} for help formatting this field.
+ *
+ * @return \Google\Showcase\V1beta1\GetSequenceReportRequest
+ *
+ * @experimental
+ */
+ public static function build(string $name): self
+ {
+ return (new self())
+ ->setName($name);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $name
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Sequence::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = REQUIRED, (.google.api.resource_reference) = {
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = REQUIRED, (.google.api.resource_reference) = {
+ * @param string $var
+ * @return $this
+ */
+ public function setName($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->name = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/GetSessionRequest.php b/Gax/tests/Conformance/src/V1beta1/GetSessionRequest.php
new file mode 100644
index 000000000000..5ef01cb9114e
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/GetSessionRequest.php
@@ -0,0 +1,67 @@
+google.showcase.v1beta1.GetSessionRequest
+ */
+class GetSessionRequest extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The session to be retrieved.
+ *
+ * Generated from protobuf field string name = 1 [(.google.api.resource_reference) = {
+ */
+ protected $name = '';
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $name
+ * The session to be retrieved.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Testing::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The session to be retrieved.
+ *
+ * Generated from protobuf field string name = 1 [(.google.api.resource_reference) = {
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * The session to be retrieved.
+ *
+ * Generated from protobuf field string name = 1 [(.google.api.resource_reference) = {
+ * @param string $var
+ * @return $this
+ */
+ public function setName($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->name = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/GetStreamingSequenceReportRequest.php b/Gax/tests/Conformance/src/V1beta1/GetStreamingSequenceReportRequest.php
new file mode 100644
index 000000000000..8817ae7b93ff
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/GetStreamingSequenceReportRequest.php
@@ -0,0 +1,71 @@
+google.showcase.v1beta1.GetStreamingSequenceReportRequest
+ */
+class GetStreamingSequenceReportRequest extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = REQUIRED, (.google.api.resource_reference) = {
+ */
+ protected $name = '';
+
+ /**
+ * @param string $name Please see {@see SequenceServiceClient::streamingSequenceReportName()} for help formatting this field.
+ *
+ * @return \Google\Showcase\V1beta1\GetStreamingSequenceReportRequest
+ *
+ * @experimental
+ */
+ public static function build(string $name): self
+ {
+ return (new self())
+ ->setName($name);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $name
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Sequence::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = REQUIRED, (.google.api.resource_reference) = {
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = REQUIRED, (.google.api.resource_reference) = {
+ * @param string $var
+ * @return $this
+ */
+ public function setName($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->name = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/GetUserRequest.php b/Gax/tests/Conformance/src/V1beta1/GetUserRequest.php
new file mode 100644
index 000000000000..a10d124a1f61
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/GetUserRequest.php
@@ -0,0 +1,82 @@
+google.showcase.v1beta1.GetUserRequest
+ */
+class GetUserRequest extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The resource name of the requested user.
+ *
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = REQUIRED, (.google.api.resource_reference) = {
+ */
+ protected $name = '';
+
+ /**
+ * @param string $name The resource name of the requested user. Please see
+ * {@see IdentityClient::userName()} for help formatting this field.
+ *
+ * @return \Google\Showcase\V1beta1\GetUserRequest
+ *
+ * @experimental
+ */
+ public static function build(string $name): self
+ {
+ return (new self())
+ ->setName($name);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $name
+ * The resource name of the requested user.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Identity::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The resource name of the requested user.
+ *
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = REQUIRED, (.google.api.resource_reference) = {
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * The resource name of the requested user.
+ *
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = REQUIRED, (.google.api.resource_reference) = {
+ * @param string $var
+ * @return $this
+ */
+ public function setName($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->name = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/Issue.php b/Gax/tests/Conformance/src/V1beta1/Issue.php
new file mode 100644
index 000000000000..85a9ba84d14c
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/Issue.php
@@ -0,0 +1,135 @@
+google.showcase.v1beta1.Issue
+ */
+class Issue extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The type of the issue.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Issue.Type type = 1;
+ */
+ protected $type = 0;
+ /**
+ * The severity of the issue.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Issue.Severity severity = 2;
+ */
+ protected $severity = 0;
+ /**
+ * A description of the issue.
+ *
+ * Generated from protobuf field string description = 3;
+ */
+ protected $description = '';
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type int $type
+ * The type of the issue.
+ * @type int $severity
+ * The severity of the issue.
+ * @type string $description
+ * A description of the issue.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Testing::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The type of the issue.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Issue.Type type = 1;
+ * @return int
+ */
+ public function getType()
+ {
+ return $this->type;
+ }
+
+ /**
+ * The type of the issue.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Issue.Type type = 1;
+ * @param int $var
+ * @return $this
+ */
+ public function setType($var)
+ {
+ GPBUtil::checkEnum($var, \Google\Showcase\V1beta1\Issue\Type::class);
+ $this->type = $var;
+
+ return $this;
+ }
+
+ /**
+ * The severity of the issue.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Issue.Severity severity = 2;
+ * @return int
+ */
+ public function getSeverity()
+ {
+ return $this->severity;
+ }
+
+ /**
+ * The severity of the issue.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Issue.Severity severity = 2;
+ * @param int $var
+ * @return $this
+ */
+ public function setSeverity($var)
+ {
+ GPBUtil::checkEnum($var, \Google\Showcase\V1beta1\Issue\Severity::class);
+ $this->severity = $var;
+
+ return $this;
+ }
+
+ /**
+ * A description of the issue.
+ *
+ * Generated from protobuf field string description = 3;
+ * @return string
+ */
+ public function getDescription()
+ {
+ return $this->description;
+ }
+
+ /**
+ * A description of the issue.
+ *
+ * Generated from protobuf field string description = 3;
+ * @param string $var
+ * @return $this
+ */
+ public function setDescription($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->description = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/Issue/Severity.php b/Gax/tests/Conformance/src/V1beta1/Issue/Severity.php
new file mode 100644
index 000000000000..2bd33a3a413b
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/Issue/Severity.php
@@ -0,0 +1,62 @@
+google.showcase.v1beta1.Issue.Severity
+ */
+class Severity
+{
+ /**
+ * Generated from protobuf enum SEVERITY_UNSPECIFIED = 0;
+ */
+ const SEVERITY_UNSPECIFIED = 0;
+ /**
+ * Errors.
+ *
+ * Generated from protobuf enum ERROR = 1;
+ */
+ const ERROR = 1;
+ /**
+ * Warnings.
+ *
+ * Generated from protobuf enum WARNING = 2;
+ */
+ const WARNING = 2;
+
+ private static $valueToName = [
+ self::SEVERITY_UNSPECIFIED => 'SEVERITY_UNSPECIFIED',
+ self::ERROR => 'ERROR',
+ self::WARNING => 'WARNING',
+ ];
+
+ public static function name($value)
+ {
+ if (!isset(self::$valueToName[$value])) {
+ throw new UnexpectedValueException(sprintf(
+ 'Enum %s has no name defined for value %s', __CLASS__, $value));
+ }
+ return self::$valueToName[$value];
+ }
+
+
+ public static function value($name)
+ {
+ $const = __CLASS__ . '::' . strtoupper($name);
+ if (!defined($const)) {
+ throw new UnexpectedValueException(sprintf(
+ 'Enum %s has no value defined for name %s', __CLASS__, $name));
+ }
+ return constant($const);
+ }
+}
+
+// Adding a class alias for backwards compatibility with the previous class name.
+class_alias(Severity::class, \Google\Showcase\V1beta1\Issue_Severity::class);
+
diff --git a/Gax/tests/Conformance/src/V1beta1/Issue/Type.php b/Gax/tests/Conformance/src/V1beta1/Issue/Type.php
new file mode 100644
index 000000000000..b7d0104db46c
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/Issue/Type.php
@@ -0,0 +1,70 @@
+google.showcase.v1beta1.Issue.Type
+ */
+class Type
+{
+ /**
+ * Generated from protobuf enum TYPE_UNSPECIFIED = 0;
+ */
+ const TYPE_UNSPECIFIED = 0;
+ /**
+ * The test was never instrumented.
+ *
+ * Generated from protobuf enum SKIPPED = 1;
+ */
+ const SKIPPED = 1;
+ /**
+ * The test was started but never confirmed.
+ *
+ * Generated from protobuf enum PENDING = 2;
+ */
+ const PENDING = 2;
+ /**
+ * The test was instrumented, but Showcase got an unexpected
+ * value when the generator tried to confirm success.
+ *
+ * Generated from protobuf enum INCORRECT_CONFIRMATION = 3;
+ */
+ const INCORRECT_CONFIRMATION = 3;
+
+ private static $valueToName = [
+ self::TYPE_UNSPECIFIED => 'TYPE_UNSPECIFIED',
+ self::SKIPPED => 'SKIPPED',
+ self::PENDING => 'PENDING',
+ self::INCORRECT_CONFIRMATION => 'INCORRECT_CONFIRMATION',
+ ];
+
+ public static function name($value)
+ {
+ if (!isset(self::$valueToName[$value])) {
+ throw new UnexpectedValueException(sprintf(
+ 'Enum %s has no name defined for value %s', __CLASS__, $value));
+ }
+ return self::$valueToName[$value];
+ }
+
+
+ public static function value($name)
+ {
+ $const = __CLASS__ . '::' . strtoupper($name);
+ if (!defined($const)) {
+ throw new UnexpectedValueException(sprintf(
+ 'Enum %s has no value defined for name %s', __CLASS__, $name));
+ }
+ return constant($const);
+ }
+}
+
+// Adding a class alias for backwards compatibility with the previous class name.
+class_alias(Type::class, \Google\Showcase\V1beta1\Issue_Type::class);
+
diff --git a/Gax/tests/Conformance/src/V1beta1/ListBlurbsRequest.php b/Gax/tests/Conformance/src/V1beta1/ListBlurbsRequest.php
new file mode 100644
index 000000000000..bd659054d7dc
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/ListBlurbsRequest.php
@@ -0,0 +1,166 @@
+google.showcase.v1beta1.ListBlurbsRequest
+ */
+class ListBlurbsRequest extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The resource name of the requested room or profile who blurbs to list.
+ *
+ * Generated from protobuf field string parent = 1 [(.google.api.field_behavior) = REQUIRED, (.google.api.resource_reference) = {
+ */
+ protected $parent = '';
+ /**
+ * The maximum number of blurbs to return. Server may return fewer
+ * blurbs than requested. If unspecified, server will pick an appropriate
+ * default.
+ *
+ * Generated from protobuf field int32 page_size = 2;
+ */
+ protected $page_size = 0;
+ /**
+ * The value of google.showcase.v1beta1.ListBlurbsResponse.next_page_token
+ * returned from the previous call to
+ * `google.showcase.v1beta1.Messaging\ListBlurbs` method.
+ *
+ * Generated from protobuf field string page_token = 3;
+ */
+ protected $page_token = '';
+
+ /**
+ * @param string $parent The resource name of the requested room or profile who blurbs to list. Please see
+ * {@see MessagingClient::userName()} for help formatting this field.
+ *
+ * @return \Google\Showcase\V1beta1\ListBlurbsRequest
+ *
+ * @experimental
+ */
+ public static function build(string $parent): self
+ {
+ return (new self())
+ ->setParent($parent);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $parent
+ * The resource name of the requested room or profile who blurbs to list.
+ * @type int $page_size
+ * The maximum number of blurbs to return. Server may return fewer
+ * blurbs than requested. If unspecified, server will pick an appropriate
+ * default.
+ * @type string $page_token
+ * The value of google.showcase.v1beta1.ListBlurbsResponse.next_page_token
+ * returned from the previous call to
+ * `google.showcase.v1beta1.Messaging\ListBlurbs` method.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Messaging::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The resource name of the requested room or profile who blurbs to list.
+ *
+ * Generated from protobuf field string parent = 1 [(.google.api.field_behavior) = REQUIRED, (.google.api.resource_reference) = {
+ * @return string
+ */
+ public function getParent()
+ {
+ return $this->parent;
+ }
+
+ /**
+ * The resource name of the requested room or profile who blurbs to list.
+ *
+ * Generated from protobuf field string parent = 1 [(.google.api.field_behavior) = REQUIRED, (.google.api.resource_reference) = {
+ * @param string $var
+ * @return $this
+ */
+ public function setParent($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->parent = $var;
+
+ return $this;
+ }
+
+ /**
+ * The maximum number of blurbs to return. Server may return fewer
+ * blurbs than requested. If unspecified, server will pick an appropriate
+ * default.
+ *
+ * Generated from protobuf field int32 page_size = 2;
+ * @return int
+ */
+ public function getPageSize()
+ {
+ return $this->page_size;
+ }
+
+ /**
+ * The maximum number of blurbs to return. Server may return fewer
+ * blurbs than requested. If unspecified, server will pick an appropriate
+ * default.
+ *
+ * Generated from protobuf field int32 page_size = 2;
+ * @param int $var
+ * @return $this
+ */
+ public function setPageSize($var)
+ {
+ GPBUtil::checkInt32($var);
+ $this->page_size = $var;
+
+ return $this;
+ }
+
+ /**
+ * The value of google.showcase.v1beta1.ListBlurbsResponse.next_page_token
+ * returned from the previous call to
+ * `google.showcase.v1beta1.Messaging\ListBlurbs` method.
+ *
+ * Generated from protobuf field string page_token = 3;
+ * @return string
+ */
+ public function getPageToken()
+ {
+ return $this->page_token;
+ }
+
+ /**
+ * The value of google.showcase.v1beta1.ListBlurbsResponse.next_page_token
+ * returned from the previous call to
+ * `google.showcase.v1beta1.Messaging\ListBlurbs` method.
+ *
+ * Generated from protobuf field string page_token = 3;
+ * @param string $var
+ * @return $this
+ */
+ public function setPageToken($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->page_token = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/ListBlurbsResponse.php b/Gax/tests/Conformance/src/V1beta1/ListBlurbsResponse.php
new file mode 100644
index 000000000000..d55589d67ad9
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/ListBlurbsResponse.php
@@ -0,0 +1,114 @@
+google.showcase.v1beta1.ListBlurbsResponse
+ */
+class ListBlurbsResponse extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The list of blurbs.
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.Blurb blurbs = 1;
+ */
+ private $blurbs;
+ /**
+ * A token to retrieve next page of results.
+ * Pass this value in ListBlurbsRequest.page_token field in the subsequent
+ * call to `google.showcase.v1beta1.Blurb\ListBlurbs` method to retrieve
+ * the next page of results.
+ *
+ * Generated from protobuf field string next_page_token = 2;
+ */
+ protected $next_page_token = '';
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type array<\Google\Showcase\V1beta1\Blurb>|\Google\Protobuf\Internal\RepeatedField $blurbs
+ * The list of blurbs.
+ * @type string $next_page_token
+ * A token to retrieve next page of results.
+ * Pass this value in ListBlurbsRequest.page_token field in the subsequent
+ * call to `google.showcase.v1beta1.Blurb\ListBlurbs` method to retrieve
+ * the next page of results.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Messaging::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The list of blurbs.
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.Blurb blurbs = 1;
+ * @return \Google\Protobuf\Internal\RepeatedField
+ */
+ public function getBlurbs()
+ {
+ return $this->blurbs;
+ }
+
+ /**
+ * The list of blurbs.
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.Blurb blurbs = 1;
+ * @param array<\Google\Showcase\V1beta1\Blurb>|\Google\Protobuf\Internal\RepeatedField $var
+ * @return $this
+ */
+ public function setBlurbs($var)
+ {
+ $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Google\Showcase\V1beta1\Blurb::class);
+ $this->blurbs = $arr;
+
+ return $this;
+ }
+
+ /**
+ * A token to retrieve next page of results.
+ * Pass this value in ListBlurbsRequest.page_token field in the subsequent
+ * call to `google.showcase.v1beta1.Blurb\ListBlurbs` method to retrieve
+ * the next page of results.
+ *
+ * Generated from protobuf field string next_page_token = 2;
+ * @return string
+ */
+ public function getNextPageToken()
+ {
+ return $this->next_page_token;
+ }
+
+ /**
+ * A token to retrieve next page of results.
+ * Pass this value in ListBlurbsRequest.page_token field in the subsequent
+ * call to `google.showcase.v1beta1.Blurb\ListBlurbs` method to retrieve
+ * the next page of results.
+ *
+ * Generated from protobuf field string next_page_token = 2;
+ * @param string $var
+ * @return $this
+ */
+ public function setNextPageToken($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->next_page_token = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/ListRoomsRequest.php b/Gax/tests/Conformance/src/V1beta1/ListRoomsRequest.php
new file mode 100644
index 000000000000..1146bc5a2b12
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/ListRoomsRequest.php
@@ -0,0 +1,114 @@
+google.showcase.v1beta1.ListRoomsRequest
+ */
+class ListRoomsRequest extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The maximum number of rooms return. Server may return fewer rooms
+ * than requested. If unspecified, server will pick an appropriate default.
+ *
+ * Generated from protobuf field int32 page_size = 1;
+ */
+ protected $page_size = 0;
+ /**
+ * The value of google.showcase.v1beta1.ListRoomsResponse.next_page_token
+ * returned from the previous call to
+ * `google.showcase.v1beta1.Messaging\ListRooms` method.
+ *
+ * Generated from protobuf field string page_token = 2;
+ */
+ protected $page_token = '';
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type int $page_size
+ * The maximum number of rooms return. Server may return fewer rooms
+ * than requested. If unspecified, server will pick an appropriate default.
+ * @type string $page_token
+ * The value of google.showcase.v1beta1.ListRoomsResponse.next_page_token
+ * returned from the previous call to
+ * `google.showcase.v1beta1.Messaging\ListRooms` method.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Messaging::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The maximum number of rooms return. Server may return fewer rooms
+ * than requested. If unspecified, server will pick an appropriate default.
+ *
+ * Generated from protobuf field int32 page_size = 1;
+ * @return int
+ */
+ public function getPageSize()
+ {
+ return $this->page_size;
+ }
+
+ /**
+ * The maximum number of rooms return. Server may return fewer rooms
+ * than requested. If unspecified, server will pick an appropriate default.
+ *
+ * Generated from protobuf field int32 page_size = 1;
+ * @param int $var
+ * @return $this
+ */
+ public function setPageSize($var)
+ {
+ GPBUtil::checkInt32($var);
+ $this->page_size = $var;
+
+ return $this;
+ }
+
+ /**
+ * The value of google.showcase.v1beta1.ListRoomsResponse.next_page_token
+ * returned from the previous call to
+ * `google.showcase.v1beta1.Messaging\ListRooms` method.
+ *
+ * Generated from protobuf field string page_token = 2;
+ * @return string
+ */
+ public function getPageToken()
+ {
+ return $this->page_token;
+ }
+
+ /**
+ * The value of google.showcase.v1beta1.ListRoomsResponse.next_page_token
+ * returned from the previous call to
+ * `google.showcase.v1beta1.Messaging\ListRooms` method.
+ *
+ * Generated from protobuf field string page_token = 2;
+ * @param string $var
+ * @return $this
+ */
+ public function setPageToken($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->page_token = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/ListRoomsResponse.php b/Gax/tests/Conformance/src/V1beta1/ListRoomsResponse.php
new file mode 100644
index 000000000000..c626913defbb
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/ListRoomsResponse.php
@@ -0,0 +1,114 @@
+google.showcase.v1beta1.ListRoomsResponse
+ */
+class ListRoomsResponse extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The list of rooms.
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.Room rooms = 1;
+ */
+ private $rooms;
+ /**
+ * A token to retrieve next page of results.
+ * Pass this value in ListRoomsRequest.page_token field in the subsequent
+ * call to `google.showcase.v1beta1.Messaging\ListRooms` method to retrieve
+ * the next page of results.
+ *
+ * Generated from protobuf field string next_page_token = 2;
+ */
+ protected $next_page_token = '';
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type array<\Google\Showcase\V1beta1\Room>|\Google\Protobuf\Internal\RepeatedField $rooms
+ * The list of rooms.
+ * @type string $next_page_token
+ * A token to retrieve next page of results.
+ * Pass this value in ListRoomsRequest.page_token field in the subsequent
+ * call to `google.showcase.v1beta1.Messaging\ListRooms` method to retrieve
+ * the next page of results.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Messaging::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The list of rooms.
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.Room rooms = 1;
+ * @return \Google\Protobuf\Internal\RepeatedField
+ */
+ public function getRooms()
+ {
+ return $this->rooms;
+ }
+
+ /**
+ * The list of rooms.
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.Room rooms = 1;
+ * @param array<\Google\Showcase\V1beta1\Room>|\Google\Protobuf\Internal\RepeatedField $var
+ * @return $this
+ */
+ public function setRooms($var)
+ {
+ $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Google\Showcase\V1beta1\Room::class);
+ $this->rooms = $arr;
+
+ return $this;
+ }
+
+ /**
+ * A token to retrieve next page of results.
+ * Pass this value in ListRoomsRequest.page_token field in the subsequent
+ * call to `google.showcase.v1beta1.Messaging\ListRooms` method to retrieve
+ * the next page of results.
+ *
+ * Generated from protobuf field string next_page_token = 2;
+ * @return string
+ */
+ public function getNextPageToken()
+ {
+ return $this->next_page_token;
+ }
+
+ /**
+ * A token to retrieve next page of results.
+ * Pass this value in ListRoomsRequest.page_token field in the subsequent
+ * call to `google.showcase.v1beta1.Messaging\ListRooms` method to retrieve
+ * the next page of results.
+ *
+ * Generated from protobuf field string next_page_token = 2;
+ * @param string $var
+ * @return $this
+ */
+ public function setNextPageToken($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->next_page_token = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/ListSessionsRequest.php b/Gax/tests/Conformance/src/V1beta1/ListSessionsRequest.php
new file mode 100644
index 000000000000..83892186d25d
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/ListSessionsRequest.php
@@ -0,0 +1,101 @@
+google.showcase.v1beta1.ListSessionsRequest
+ */
+class ListSessionsRequest extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The maximum number of sessions to return per page.
+ *
+ * Generated from protobuf field int32 page_size = 1;
+ */
+ protected $page_size = 0;
+ /**
+ * The page token, for retrieving subsequent pages.
+ *
+ * Generated from protobuf field string page_token = 2;
+ */
+ protected $page_token = '';
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type int $page_size
+ * The maximum number of sessions to return per page.
+ * @type string $page_token
+ * The page token, for retrieving subsequent pages.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Testing::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The maximum number of sessions to return per page.
+ *
+ * Generated from protobuf field int32 page_size = 1;
+ * @return int
+ */
+ public function getPageSize()
+ {
+ return $this->page_size;
+ }
+
+ /**
+ * The maximum number of sessions to return per page.
+ *
+ * Generated from protobuf field int32 page_size = 1;
+ * @param int $var
+ * @return $this
+ */
+ public function setPageSize($var)
+ {
+ GPBUtil::checkInt32($var);
+ $this->page_size = $var;
+
+ return $this;
+ }
+
+ /**
+ * The page token, for retrieving subsequent pages.
+ *
+ * Generated from protobuf field string page_token = 2;
+ * @return string
+ */
+ public function getPageToken()
+ {
+ return $this->page_token;
+ }
+
+ /**
+ * The page token, for retrieving subsequent pages.
+ *
+ * Generated from protobuf field string page_token = 2;
+ * @param string $var
+ * @return $this
+ */
+ public function setPageToken($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->page_token = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/ListSessionsResponse.php b/Gax/tests/Conformance/src/V1beta1/ListSessionsResponse.php
new file mode 100644
index 000000000000..94d7705eeefd
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/ListSessionsResponse.php
@@ -0,0 +1,105 @@
+google.showcase.v1beta1.ListSessionsResponse
+ */
+class ListSessionsResponse extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The sessions being returned.
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.Session sessions = 1;
+ */
+ private $sessions;
+ /**
+ * The next page token, if any.
+ * An empty value here means the last page has been reached.
+ *
+ * Generated from protobuf field string next_page_token = 2;
+ */
+ protected $next_page_token = '';
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type array<\Google\Showcase\V1beta1\Session>|\Google\Protobuf\Internal\RepeatedField $sessions
+ * The sessions being returned.
+ * @type string $next_page_token
+ * The next page token, if any.
+ * An empty value here means the last page has been reached.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Testing::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The sessions being returned.
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.Session sessions = 1;
+ * @return \Google\Protobuf\Internal\RepeatedField
+ */
+ public function getSessions()
+ {
+ return $this->sessions;
+ }
+
+ /**
+ * The sessions being returned.
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.Session sessions = 1;
+ * @param array<\Google\Showcase\V1beta1\Session>|\Google\Protobuf\Internal\RepeatedField $var
+ * @return $this
+ */
+ public function setSessions($var)
+ {
+ $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Google\Showcase\V1beta1\Session::class);
+ $this->sessions = $arr;
+
+ return $this;
+ }
+
+ /**
+ * The next page token, if any.
+ * An empty value here means the last page has been reached.
+ *
+ * Generated from protobuf field string next_page_token = 2;
+ * @return string
+ */
+ public function getNextPageToken()
+ {
+ return $this->next_page_token;
+ }
+
+ /**
+ * The next page token, if any.
+ * An empty value here means the last page has been reached.
+ *
+ * Generated from protobuf field string next_page_token = 2;
+ * @param string $var
+ * @return $this
+ */
+ public function setNextPageToken($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->next_page_token = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/ListTestsRequest.php b/Gax/tests/Conformance/src/V1beta1/ListTestsRequest.php
new file mode 100644
index 000000000000..317cb391a742
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/ListTestsRequest.php
@@ -0,0 +1,135 @@
+google.showcase.v1beta1.ListTestsRequest
+ */
+class ListTestsRequest extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The session.
+ *
+ * Generated from protobuf field string parent = 1 [(.google.api.resource_reference) = {
+ */
+ protected $parent = '';
+ /**
+ * The maximum number of tests to return per page.
+ *
+ * Generated from protobuf field int32 page_size = 2;
+ */
+ protected $page_size = 0;
+ /**
+ * The page token, for retrieving subsequent pages.
+ *
+ * Generated from protobuf field string page_token = 3;
+ */
+ protected $page_token = '';
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $parent
+ * The session.
+ * @type int $page_size
+ * The maximum number of tests to return per page.
+ * @type string $page_token
+ * The page token, for retrieving subsequent pages.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Testing::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The session.
+ *
+ * Generated from protobuf field string parent = 1 [(.google.api.resource_reference) = {
+ * @return string
+ */
+ public function getParent()
+ {
+ return $this->parent;
+ }
+
+ /**
+ * The session.
+ *
+ * Generated from protobuf field string parent = 1 [(.google.api.resource_reference) = {
+ * @param string $var
+ * @return $this
+ */
+ public function setParent($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->parent = $var;
+
+ return $this;
+ }
+
+ /**
+ * The maximum number of tests to return per page.
+ *
+ * Generated from protobuf field int32 page_size = 2;
+ * @return int
+ */
+ public function getPageSize()
+ {
+ return $this->page_size;
+ }
+
+ /**
+ * The maximum number of tests to return per page.
+ *
+ * Generated from protobuf field int32 page_size = 2;
+ * @param int $var
+ * @return $this
+ */
+ public function setPageSize($var)
+ {
+ GPBUtil::checkInt32($var);
+ $this->page_size = $var;
+
+ return $this;
+ }
+
+ /**
+ * The page token, for retrieving subsequent pages.
+ *
+ * Generated from protobuf field string page_token = 3;
+ * @return string
+ */
+ public function getPageToken()
+ {
+ return $this->page_token;
+ }
+
+ /**
+ * The page token, for retrieving subsequent pages.
+ *
+ * Generated from protobuf field string page_token = 3;
+ * @param string $var
+ * @return $this
+ */
+ public function setPageToken($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->page_token = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/ListTestsResponse.php b/Gax/tests/Conformance/src/V1beta1/ListTestsResponse.php
new file mode 100644
index 000000000000..b4371f404e04
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/ListTestsResponse.php
@@ -0,0 +1,105 @@
+google.showcase.v1beta1.ListTestsResponse
+ */
+class ListTestsResponse extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The tests being returned.
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.Test tests = 1;
+ */
+ private $tests;
+ /**
+ * The next page token, if any.
+ * An empty value here means the last page has been reached.
+ *
+ * Generated from protobuf field string next_page_token = 2;
+ */
+ protected $next_page_token = '';
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type array<\Google\Showcase\V1beta1\Test>|\Google\Protobuf\Internal\RepeatedField $tests
+ * The tests being returned.
+ * @type string $next_page_token
+ * The next page token, if any.
+ * An empty value here means the last page has been reached.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Testing::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The tests being returned.
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.Test tests = 1;
+ * @return \Google\Protobuf\Internal\RepeatedField
+ */
+ public function getTests()
+ {
+ return $this->tests;
+ }
+
+ /**
+ * The tests being returned.
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.Test tests = 1;
+ * @param array<\Google\Showcase\V1beta1\Test>|\Google\Protobuf\Internal\RepeatedField $var
+ * @return $this
+ */
+ public function setTests($var)
+ {
+ $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Google\Showcase\V1beta1\Test::class);
+ $this->tests = $arr;
+
+ return $this;
+ }
+
+ /**
+ * The next page token, if any.
+ * An empty value here means the last page has been reached.
+ *
+ * Generated from protobuf field string next_page_token = 2;
+ * @return string
+ */
+ public function getNextPageToken()
+ {
+ return $this->next_page_token;
+ }
+
+ /**
+ * The next page token, if any.
+ * An empty value here means the last page has been reached.
+ *
+ * Generated from protobuf field string next_page_token = 2;
+ * @param string $var
+ * @return $this
+ */
+ public function setNextPageToken($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->next_page_token = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/ListUsersRequest.php b/Gax/tests/Conformance/src/V1beta1/ListUsersRequest.php
new file mode 100644
index 000000000000..74e8ac32b7f3
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/ListUsersRequest.php
@@ -0,0 +1,114 @@
+google.showcase.v1beta1.ListUsersRequest
+ */
+class ListUsersRequest extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The maximum number of users to return. Server may return fewer users
+ * than requested. If unspecified, server will pick an appropriate default.
+ *
+ * Generated from protobuf field int32 page_size = 1;
+ */
+ protected $page_size = 0;
+ /**
+ * The value of google.showcase.v1beta1.ListUsersResponse.next_page_token
+ * returned from the previous call to
+ * `google.showcase.v1beta1.Identity\ListUsers` method.
+ *
+ * Generated from protobuf field string page_token = 2;
+ */
+ protected $page_token = '';
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type int $page_size
+ * The maximum number of users to return. Server may return fewer users
+ * than requested. If unspecified, server will pick an appropriate default.
+ * @type string $page_token
+ * The value of google.showcase.v1beta1.ListUsersResponse.next_page_token
+ * returned from the previous call to
+ * `google.showcase.v1beta1.Identity\ListUsers` method.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Identity::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The maximum number of users to return. Server may return fewer users
+ * than requested. If unspecified, server will pick an appropriate default.
+ *
+ * Generated from protobuf field int32 page_size = 1;
+ * @return int
+ */
+ public function getPageSize()
+ {
+ return $this->page_size;
+ }
+
+ /**
+ * The maximum number of users to return. Server may return fewer users
+ * than requested. If unspecified, server will pick an appropriate default.
+ *
+ * Generated from protobuf field int32 page_size = 1;
+ * @param int $var
+ * @return $this
+ */
+ public function setPageSize($var)
+ {
+ GPBUtil::checkInt32($var);
+ $this->page_size = $var;
+
+ return $this;
+ }
+
+ /**
+ * The value of google.showcase.v1beta1.ListUsersResponse.next_page_token
+ * returned from the previous call to
+ * `google.showcase.v1beta1.Identity\ListUsers` method.
+ *
+ * Generated from protobuf field string page_token = 2;
+ * @return string
+ */
+ public function getPageToken()
+ {
+ return $this->page_token;
+ }
+
+ /**
+ * The value of google.showcase.v1beta1.ListUsersResponse.next_page_token
+ * returned from the previous call to
+ * `google.showcase.v1beta1.Identity\ListUsers` method.
+ *
+ * Generated from protobuf field string page_token = 2;
+ * @param string $var
+ * @return $this
+ */
+ public function setPageToken($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->page_token = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/ListUsersResponse.php b/Gax/tests/Conformance/src/V1beta1/ListUsersResponse.php
new file mode 100644
index 000000000000..573070619439
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/ListUsersResponse.php
@@ -0,0 +1,114 @@
+google.showcase.v1beta1.ListUsersResponse
+ */
+class ListUsersResponse extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The list of users.
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.User users = 1;
+ */
+ private $users;
+ /**
+ * A token to retrieve next page of results.
+ * Pass this value in ListUsersRequest.page_token field in the subsequent
+ * call to `google.showcase.v1beta1.Message\ListUsers` method to retrieve the
+ * next page of results.
+ *
+ * Generated from protobuf field string next_page_token = 2;
+ */
+ protected $next_page_token = '';
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type array<\Google\Showcase\V1beta1\User>|\Google\Protobuf\Internal\RepeatedField $users
+ * The list of users.
+ * @type string $next_page_token
+ * A token to retrieve next page of results.
+ * Pass this value in ListUsersRequest.page_token field in the subsequent
+ * call to `google.showcase.v1beta1.Message\ListUsers` method to retrieve the
+ * next page of results.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Identity::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The list of users.
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.User users = 1;
+ * @return \Google\Protobuf\Internal\RepeatedField
+ */
+ public function getUsers()
+ {
+ return $this->users;
+ }
+
+ /**
+ * The list of users.
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.User users = 1;
+ * @param array<\Google\Showcase\V1beta1\User>|\Google\Protobuf\Internal\RepeatedField $var
+ * @return $this
+ */
+ public function setUsers($var)
+ {
+ $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Google\Showcase\V1beta1\User::class);
+ $this->users = $arr;
+
+ return $this;
+ }
+
+ /**
+ * A token to retrieve next page of results.
+ * Pass this value in ListUsersRequest.page_token field in the subsequent
+ * call to `google.showcase.v1beta1.Message\ListUsers` method to retrieve the
+ * next page of results.
+ *
+ * Generated from protobuf field string next_page_token = 2;
+ * @return string
+ */
+ public function getNextPageToken()
+ {
+ return $this->next_page_token;
+ }
+
+ /**
+ * A token to retrieve next page of results.
+ * Pass this value in ListUsersRequest.page_token field in the subsequent
+ * call to `google.showcase.v1beta1.Message\ListUsers` method to retrieve the
+ * next page of results.
+ *
+ * Generated from protobuf field string next_page_token = 2;
+ * @param string $var
+ * @return $this
+ */
+ public function setNextPageToken($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->next_page_token = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/PagedExpandLegacyMappedResponse.php b/Gax/tests/Conformance/src/V1beta1/PagedExpandLegacyMappedResponse.php
new file mode 100644
index 000000000000..4be88d99a26f
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/PagedExpandLegacyMappedResponse.php
@@ -0,0 +1,107 @@
+google.showcase.v1beta1.PagedExpandLegacyMappedResponse
+ */
+class PagedExpandLegacyMappedResponse extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The words that were expanded, indexed by their initial character.
+ * (-- aip.dev/not-precedent: This is a legacy, non-standard pattern that violates
+ * aip.dev/158. Ordinarily, this should be a `repeated` field, as in PagedExpandResponse. --)
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.PagedExpandResponseList alphabetized = 1;
+ */
+ private $alphabetized;
+ /**
+ * The next page token.
+ *
+ * Generated from protobuf field string next_page_token = 2;
+ */
+ protected $next_page_token = '';
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type array<\Google\Showcase\V1beta1\PagedExpandResponseList>|\Google\Protobuf\Internal\RepeatedField $alphabetized
+ * The words that were expanded, indexed by their initial character.
+ * (-- aip.dev/not-precedent: This is a legacy, non-standard pattern that violates
+ * aip.dev/158. Ordinarily, this should be a `repeated` field, as in PagedExpandResponse. --)
+ * @type string $next_page_token
+ * The next page token.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\PBEcho::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The words that were expanded, indexed by their initial character.
+ * (-- aip.dev/not-precedent: This is a legacy, non-standard pattern that violates
+ * aip.dev/158. Ordinarily, this should be a `repeated` field, as in PagedExpandResponse. --)
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.PagedExpandResponseList alphabetized = 1;
+ * @return \Google\Protobuf\Internal\RepeatedField
+ */
+ public function getAlphabetized()
+ {
+ return $this->alphabetized;
+ }
+
+ /**
+ * The words that were expanded, indexed by their initial character.
+ * (-- aip.dev/not-precedent: This is a legacy, non-standard pattern that violates
+ * aip.dev/158. Ordinarily, this should be a `repeated` field, as in PagedExpandResponse. --)
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.PagedExpandResponseList alphabetized = 1;
+ * @param array<\Google\Showcase\V1beta1\PagedExpandResponseList>|\Google\Protobuf\Internal\RepeatedField $var
+ * @return $this
+ */
+ public function setAlphabetized($var)
+ {
+ $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Google\Showcase\V1beta1\PagedExpandResponseList::class);
+ $this->alphabetized = $arr;
+
+ return $this;
+ }
+
+ /**
+ * The next page token.
+ *
+ * Generated from protobuf field string next_page_token = 2;
+ * @return string
+ */
+ public function getNextPageToken()
+ {
+ return $this->next_page_token;
+ }
+
+ /**
+ * The next page token.
+ *
+ * Generated from protobuf field string next_page_token = 2;
+ * @param string $var
+ * @return $this
+ */
+ public function setNextPageToken($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->next_page_token = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/PagedExpandLegacyRequest.php b/Gax/tests/Conformance/src/V1beta1/PagedExpandLegacyRequest.php
new file mode 100644
index 000000000000..680f93f2d89c
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/PagedExpandLegacyRequest.php
@@ -0,0 +1,145 @@
+google.showcase.v1beta1.PagedExpandLegacyRequest
+ */
+class PagedExpandLegacyRequest extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The string to expand.
+ *
+ * Generated from protobuf field string content = 1 [(.google.api.field_behavior) = REQUIRED];
+ */
+ protected $content = '';
+ /**
+ * The number of words to returned in each page.
+ * (-- aip.dev/not-precedent: This is a legacy, non-standard pattern that
+ * violates aip.dev/158. Ordinarily, this should be page_size. --)
+ *
+ * Generated from protobuf field int32 max_results = 2;
+ */
+ protected $max_results = 0;
+ /**
+ * The position of the page to be returned.
+ *
+ * Generated from protobuf field string page_token = 3;
+ */
+ protected $page_token = '';
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $content
+ * The string to expand.
+ * @type int $max_results
+ * The number of words to returned in each page.
+ * (-- aip.dev/not-precedent: This is a legacy, non-standard pattern that
+ * violates aip.dev/158. Ordinarily, this should be page_size. --)
+ * @type string $page_token
+ * The position of the page to be returned.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\PBEcho::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The string to expand.
+ *
+ * Generated from protobuf field string content = 1 [(.google.api.field_behavior) = REQUIRED];
+ * @return string
+ */
+ public function getContent()
+ {
+ return $this->content;
+ }
+
+ /**
+ * The string to expand.
+ *
+ * Generated from protobuf field string content = 1 [(.google.api.field_behavior) = REQUIRED];
+ * @param string $var
+ * @return $this
+ */
+ public function setContent($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->content = $var;
+
+ return $this;
+ }
+
+ /**
+ * The number of words to returned in each page.
+ * (-- aip.dev/not-precedent: This is a legacy, non-standard pattern that
+ * violates aip.dev/158. Ordinarily, this should be page_size. --)
+ *
+ * Generated from protobuf field int32 max_results = 2;
+ * @return int
+ */
+ public function getMaxResults()
+ {
+ return $this->max_results;
+ }
+
+ /**
+ * The number of words to returned in each page.
+ * (-- aip.dev/not-precedent: This is a legacy, non-standard pattern that
+ * violates aip.dev/158. Ordinarily, this should be page_size. --)
+ *
+ * Generated from protobuf field int32 max_results = 2;
+ * @param int $var
+ * @return $this
+ */
+ public function setMaxResults($var)
+ {
+ GPBUtil::checkInt32($var);
+ $this->max_results = $var;
+
+ return $this;
+ }
+
+ /**
+ * The position of the page to be returned.
+ *
+ * Generated from protobuf field string page_token = 3;
+ * @return string
+ */
+ public function getPageToken()
+ {
+ return $this->page_token;
+ }
+
+ /**
+ * The position of the page to be returned.
+ *
+ * Generated from protobuf field string page_token = 3;
+ * @param string $var
+ * @return $this
+ */
+ public function setPageToken($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->page_token = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/PagedExpandRequest.php b/Gax/tests/Conformance/src/V1beta1/PagedExpandRequest.php
new file mode 100644
index 000000000000..22297ce3f18f
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/PagedExpandRequest.php
@@ -0,0 +1,135 @@
+google.showcase.v1beta1.PagedExpandRequest
+ */
+class PagedExpandRequest extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The string to expand.
+ *
+ * Generated from protobuf field string content = 1 [(.google.api.field_behavior) = REQUIRED];
+ */
+ protected $content = '';
+ /**
+ * The number of words to returned in each page.
+ *
+ * Generated from protobuf field int32 page_size = 2;
+ */
+ protected $page_size = 0;
+ /**
+ * The position of the page to be returned.
+ *
+ * Generated from protobuf field string page_token = 3;
+ */
+ protected $page_token = '';
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $content
+ * The string to expand.
+ * @type int $page_size
+ * The number of words to returned in each page.
+ * @type string $page_token
+ * The position of the page to be returned.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\PBEcho::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The string to expand.
+ *
+ * Generated from protobuf field string content = 1 [(.google.api.field_behavior) = REQUIRED];
+ * @return string
+ */
+ public function getContent()
+ {
+ return $this->content;
+ }
+
+ /**
+ * The string to expand.
+ *
+ * Generated from protobuf field string content = 1 [(.google.api.field_behavior) = REQUIRED];
+ * @param string $var
+ * @return $this
+ */
+ public function setContent($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->content = $var;
+
+ return $this;
+ }
+
+ /**
+ * The number of words to returned in each page.
+ *
+ * Generated from protobuf field int32 page_size = 2;
+ * @return int
+ */
+ public function getPageSize()
+ {
+ return $this->page_size;
+ }
+
+ /**
+ * The number of words to returned in each page.
+ *
+ * Generated from protobuf field int32 page_size = 2;
+ * @param int $var
+ * @return $this
+ */
+ public function setPageSize($var)
+ {
+ GPBUtil::checkInt32($var);
+ $this->page_size = $var;
+
+ return $this;
+ }
+
+ /**
+ * The position of the page to be returned.
+ *
+ * Generated from protobuf field string page_token = 3;
+ * @return string
+ */
+ public function getPageToken()
+ {
+ return $this->page_token;
+ }
+
+ /**
+ * The position of the page to be returned.
+ *
+ * Generated from protobuf field string page_token = 3;
+ * @param string $var
+ * @return $this
+ */
+ public function setPageToken($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->page_token = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/PagedExpandResponse.php b/Gax/tests/Conformance/src/V1beta1/PagedExpandResponse.php
new file mode 100644
index 000000000000..cb2a2ef7de2c
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/PagedExpandResponse.php
@@ -0,0 +1,101 @@
+google.showcase.v1beta1.PagedExpandResponse
+ */
+class PagedExpandResponse extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The words that were expanded.
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.EchoResponse responses = 1;
+ */
+ private $responses;
+ /**
+ * The next page token.
+ *
+ * Generated from protobuf field string next_page_token = 2;
+ */
+ protected $next_page_token = '';
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type array<\Google\Showcase\V1beta1\EchoResponse>|\Google\Protobuf\Internal\RepeatedField $responses
+ * The words that were expanded.
+ * @type string $next_page_token
+ * The next page token.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\PBEcho::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The words that were expanded.
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.EchoResponse responses = 1;
+ * @return \Google\Protobuf\Internal\RepeatedField
+ */
+ public function getResponses()
+ {
+ return $this->responses;
+ }
+
+ /**
+ * The words that were expanded.
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.EchoResponse responses = 1;
+ * @param array<\Google\Showcase\V1beta1\EchoResponse>|\Google\Protobuf\Internal\RepeatedField $var
+ * @return $this
+ */
+ public function setResponses($var)
+ {
+ $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Google\Showcase\V1beta1\EchoResponse::class);
+ $this->responses = $arr;
+
+ return $this;
+ }
+
+ /**
+ * The next page token.
+ *
+ * Generated from protobuf field string next_page_token = 2;
+ * @return string
+ */
+ public function getNextPageToken()
+ {
+ return $this->next_page_token;
+ }
+
+ /**
+ * The next page token.
+ *
+ * Generated from protobuf field string next_page_token = 2;
+ * @param string $var
+ * @return $this
+ */
+ public function setNextPageToken($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->next_page_token = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/PagedExpandResponseList.php b/Gax/tests/Conformance/src/V1beta1/PagedExpandResponseList.php
new file mode 100644
index 000000000000..75363cc8d631
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/PagedExpandResponseList.php
@@ -0,0 +1,60 @@
+google.showcase.v1beta1.PagedExpandResponseList
+ */
+class PagedExpandResponseList extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * Generated from protobuf field repeated string words = 1;
+ */
+ private $words;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type array|\Google\Protobuf\Internal\RepeatedField $words
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\PBEcho::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * Generated from protobuf field repeated string words = 1;
+ * @return \Google\Protobuf\Internal\RepeatedField
+ */
+ public function getWords()
+ {
+ return $this->words;
+ }
+
+ /**
+ * Generated from protobuf field repeated string words = 1;
+ * @param array|\Google\Protobuf\Internal\RepeatedField $var
+ * @return $this
+ */
+ public function setWords($var)
+ {
+ $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::STRING);
+ $this->words = $arr;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/PoetryError.php b/Gax/tests/Conformance/src/V1beta1/PoetryError.php
new file mode 100644
index 000000000000..9c8384825033
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/PoetryError.php
@@ -0,0 +1,62 @@
+google.showcase.v1beta1.PoetryError
+ */
+class PoetryError extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * Generated from protobuf field string poem = 1;
+ */
+ protected $poem = '';
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $poem
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\PBEcho::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * Generated from protobuf field string poem = 1;
+ * @return string
+ */
+ public function getPoem()
+ {
+ return $this->poem;
+ }
+
+ /**
+ * Generated from protobuf field string poem = 1;
+ * @param string $var
+ * @return $this
+ */
+ public function setPoem($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->poem = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/RepeatRequest.php b/Gax/tests/Conformance/src/V1beta1/RepeatRequest.php
new file mode 100644
index 000000000000..538e302b2198
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/RepeatRequest.php
@@ -0,0 +1,380 @@
+google.showcase.v1beta1.RepeatRequest
+ */
+class RepeatRequest extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * Generated from protobuf field string name = 1;
+ */
+ protected $name = '';
+ /**
+ * Generated from protobuf field .google.showcase.v1beta1.ComplianceData info = 2;
+ */
+ protected $info = null;
+ /**
+ * If true, the server will verify that the received request matches
+ * the request with the same name in the compliance test suite.
+ *
+ * Generated from protobuf field bool server_verify = 3;
+ */
+ protected $server_verify = false;
+ /**
+ * The URI template this request is expected to be bound to server-side.
+ *
+ * Generated from protobuf field optional string intended_binding_uri = 10;
+ */
+ protected $intended_binding_uri = null;
+ /**
+ * Some top level fields, to test that these are encoded correctly
+ * in query params.
+ *
+ * Generated from protobuf field int32 f_int32 = 4;
+ */
+ protected $f_int32 = 0;
+ /**
+ * Generated from protobuf field int64 f_int64 = 5;
+ */
+ protected $f_int64 = 0;
+ /**
+ * Generated from protobuf field double f_double = 6;
+ */
+ protected $f_double = 0.0;
+ /**
+ * Generated from protobuf field optional int32 p_int32 = 7;
+ */
+ protected $p_int32 = null;
+ /**
+ * Generated from protobuf field optional int64 p_int64 = 8;
+ */
+ protected $p_int64 = null;
+ /**
+ * Generated from protobuf field optional double p_double = 9;
+ */
+ protected $p_double = null;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $name
+ * @type \Google\Showcase\V1beta1\ComplianceData $info
+ * @type bool $server_verify
+ * If true, the server will verify that the received request matches
+ * the request with the same name in the compliance test suite.
+ * @type string $intended_binding_uri
+ * The URI template this request is expected to be bound to server-side.
+ * @type int $f_int32
+ * Some top level fields, to test that these are encoded correctly
+ * in query params.
+ * @type int|string $f_int64
+ * @type float $f_double
+ * @type int $p_int32
+ * @type int|string $p_int64
+ * @type float $p_double
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Compliance::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 .google.showcase.v1beta1.ComplianceData info = 2;
+ * @return \Google\Showcase\V1beta1\ComplianceData|null
+ */
+ public function getInfo()
+ {
+ return $this->info;
+ }
+
+ public function hasInfo()
+ {
+ return isset($this->info);
+ }
+
+ public function clearInfo()
+ {
+ unset($this->info);
+ }
+
+ /**
+ * Generated from protobuf field .google.showcase.v1beta1.ComplianceData info = 2;
+ * @param \Google\Showcase\V1beta1\ComplianceData $var
+ * @return $this
+ */
+ public function setInfo($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Showcase\V1beta1\ComplianceData::class);
+ $this->info = $var;
+
+ return $this;
+ }
+
+ /**
+ * If true, the server will verify that the received request matches
+ * the request with the same name in the compliance test suite.
+ *
+ * Generated from protobuf field bool server_verify = 3;
+ * @return bool
+ */
+ public function getServerVerify()
+ {
+ return $this->server_verify;
+ }
+
+ /**
+ * If true, the server will verify that the received request matches
+ * the request with the same name in the compliance test suite.
+ *
+ * Generated from protobuf field bool server_verify = 3;
+ * @param bool $var
+ * @return $this
+ */
+ public function setServerVerify($var)
+ {
+ GPBUtil::checkBool($var);
+ $this->server_verify = $var;
+
+ return $this;
+ }
+
+ /**
+ * The URI template this request is expected to be bound to server-side.
+ *
+ * Generated from protobuf field optional string intended_binding_uri = 10;
+ * @return string
+ */
+ public function getIntendedBindingUri()
+ {
+ return isset($this->intended_binding_uri) ? $this->intended_binding_uri : '';
+ }
+
+ public function hasIntendedBindingUri()
+ {
+ return isset($this->intended_binding_uri);
+ }
+
+ public function clearIntendedBindingUri()
+ {
+ unset($this->intended_binding_uri);
+ }
+
+ /**
+ * The URI template this request is expected to be bound to server-side.
+ *
+ * Generated from protobuf field optional string intended_binding_uri = 10;
+ * @param string $var
+ * @return $this
+ */
+ public function setIntendedBindingUri($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->intended_binding_uri = $var;
+
+ return $this;
+ }
+
+ /**
+ * Some top level fields, to test that these are encoded correctly
+ * in query params.
+ *
+ * Generated from protobuf field int32 f_int32 = 4;
+ * @return int
+ */
+ public function getFInt32()
+ {
+ return $this->f_int32;
+ }
+
+ /**
+ * Some top level fields, to test that these are encoded correctly
+ * in query params.
+ *
+ * Generated from protobuf field int32 f_int32 = 4;
+ * @param int $var
+ * @return $this
+ */
+ public function setFInt32($var)
+ {
+ GPBUtil::checkInt32($var);
+ $this->f_int32 = $var;
+
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field int64 f_int64 = 5;
+ * @return int|string
+ */
+ public function getFInt64()
+ {
+ return $this->f_int64;
+ }
+
+ /**
+ * Generated from protobuf field int64 f_int64 = 5;
+ * @param int|string $var
+ * @return $this
+ */
+ public function setFInt64($var)
+ {
+ GPBUtil::checkInt64($var);
+ $this->f_int64 = $var;
+
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field double f_double = 6;
+ * @return float
+ */
+ public function getFDouble()
+ {
+ return $this->f_double;
+ }
+
+ /**
+ * Generated from protobuf field double f_double = 6;
+ * @param float $var
+ * @return $this
+ */
+ public function setFDouble($var)
+ {
+ GPBUtil::checkDouble($var);
+ $this->f_double = $var;
+
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field optional int32 p_int32 = 7;
+ * @return int
+ */
+ public function getPInt32()
+ {
+ return isset($this->p_int32) ? $this->p_int32 : 0;
+ }
+
+ public function hasPInt32()
+ {
+ return isset($this->p_int32);
+ }
+
+ public function clearPInt32()
+ {
+ unset($this->p_int32);
+ }
+
+ /**
+ * Generated from protobuf field optional int32 p_int32 = 7;
+ * @param int $var
+ * @return $this
+ */
+ public function setPInt32($var)
+ {
+ GPBUtil::checkInt32($var);
+ $this->p_int32 = $var;
+
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field optional int64 p_int64 = 8;
+ * @return int|string
+ */
+ public function getPInt64()
+ {
+ return isset($this->p_int64) ? $this->p_int64 : 0;
+ }
+
+ public function hasPInt64()
+ {
+ return isset($this->p_int64);
+ }
+
+ public function clearPInt64()
+ {
+ unset($this->p_int64);
+ }
+
+ /**
+ * Generated from protobuf field optional int64 p_int64 = 8;
+ * @param int|string $var
+ * @return $this
+ */
+ public function setPInt64($var)
+ {
+ GPBUtil::checkInt64($var);
+ $this->p_int64 = $var;
+
+ return $this;
+ }
+
+ /**
+ * Generated from protobuf field optional double p_double = 9;
+ * @return float
+ */
+ public function getPDouble()
+ {
+ return isset($this->p_double) ? $this->p_double : 0.0;
+ }
+
+ public function hasPDouble()
+ {
+ return isset($this->p_double);
+ }
+
+ public function clearPDouble()
+ {
+ unset($this->p_double);
+ }
+
+ /**
+ * Generated from protobuf field optional double p_double = 9;
+ * @param float $var
+ * @return $this
+ */
+ public function setPDouble($var)
+ {
+ GPBUtil::checkDouble($var);
+ $this->p_double = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/RepeatResponse.php b/Gax/tests/Conformance/src/V1beta1/RepeatResponse.php
new file mode 100644
index 000000000000..ea268e11b9a6
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/RepeatResponse.php
@@ -0,0 +1,102 @@
+google.showcase.v1beta1.RepeatResponse
+ */
+class RepeatResponse extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * Generated from protobuf field .google.showcase.v1beta1.RepeatRequest request = 1;
+ */
+ protected $request = null;
+ /**
+ * The URI template the request was bound to server-side.
+ *
+ * Generated from protobuf field string binding_uri = 2;
+ */
+ protected $binding_uri = '';
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type \Google\Showcase\V1beta1\RepeatRequest $request
+ * @type string $binding_uri
+ * The URI template the request was bound to server-side.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Compliance::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * Generated from protobuf field .google.showcase.v1beta1.RepeatRequest request = 1;
+ * @return \Google\Showcase\V1beta1\RepeatRequest|null
+ */
+ public function getRequest()
+ {
+ return $this->request;
+ }
+
+ public function hasRequest()
+ {
+ return isset($this->request);
+ }
+
+ public function clearRequest()
+ {
+ unset($this->request);
+ }
+
+ /**
+ * Generated from protobuf field .google.showcase.v1beta1.RepeatRequest request = 1;
+ * @param \Google\Showcase\V1beta1\RepeatRequest $var
+ * @return $this
+ */
+ public function setRequest($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Showcase\V1beta1\RepeatRequest::class);
+ $this->request = $var;
+
+ return $this;
+ }
+
+ /**
+ * The URI template the request was bound to server-side.
+ *
+ * Generated from protobuf field string binding_uri = 2;
+ * @return string
+ */
+ public function getBindingUri()
+ {
+ return $this->binding_uri;
+ }
+
+ /**
+ * The URI template the request was bound to server-side.
+ *
+ * Generated from protobuf field string binding_uri = 2;
+ * @param string $var
+ * @return $this
+ */
+ public function setBindingUri($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->binding_uri = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/ReportSessionRequest.php b/Gax/tests/Conformance/src/V1beta1/ReportSessionRequest.php
new file mode 100644
index 000000000000..93e9295cde8c
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/ReportSessionRequest.php
@@ -0,0 +1,67 @@
+google.showcase.v1beta1.ReportSessionRequest
+ */
+class ReportSessionRequest extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The session to be reported on.
+ *
+ * Generated from protobuf field string name = 1 [(.google.api.resource_reference) = {
+ */
+ protected $name = '';
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $name
+ * The session to be reported on.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Testing::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The session to be reported on.
+ *
+ * Generated from protobuf field string name = 1 [(.google.api.resource_reference) = {
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * The session to be reported on.
+ *
+ * Generated from protobuf field string name = 1 [(.google.api.resource_reference) = {
+ * @param string $var
+ * @return $this
+ */
+ public function setName($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->name = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/ReportSessionResponse.php b/Gax/tests/Conformance/src/V1beta1/ReportSessionResponse.php
new file mode 100644
index 000000000000..cb64be765673
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/ReportSessionResponse.php
@@ -0,0 +1,101 @@
+google.showcase.v1beta1.ReportSessionResponse
+ */
+class ReportSessionResponse extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The state of the report.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.ReportSessionResponse.Result result = 1;
+ */
+ protected $result = 0;
+ /**
+ * The test runs of this session.
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.TestRun test_runs = 2;
+ */
+ private $test_runs;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type int $result
+ * The state of the report.
+ * @type array<\Google\Showcase\V1beta1\TestRun>|\Google\Protobuf\Internal\RepeatedField $test_runs
+ * The test runs of this session.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Testing::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The state of the report.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.ReportSessionResponse.Result result = 1;
+ * @return int
+ */
+ public function getResult()
+ {
+ return $this->result;
+ }
+
+ /**
+ * The state of the report.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.ReportSessionResponse.Result result = 1;
+ * @param int $var
+ * @return $this
+ */
+ public function setResult($var)
+ {
+ GPBUtil::checkEnum($var, \Google\Showcase\V1beta1\ReportSessionResponse\Result::class);
+ $this->result = $var;
+
+ return $this;
+ }
+
+ /**
+ * The test runs of this session.
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.TestRun test_runs = 2;
+ * @return \Google\Protobuf\Internal\RepeatedField
+ */
+ public function getTestRuns()
+ {
+ return $this->test_runs;
+ }
+
+ /**
+ * The test runs of this session.
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.TestRun test_runs = 2;
+ * @param array<\Google\Showcase\V1beta1\TestRun>|\Google\Protobuf\Internal\RepeatedField $var
+ * @return $this
+ */
+ public function setTestRuns($var)
+ {
+ $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Google\Showcase\V1beta1\TestRun::class);
+ $this->test_runs = $arr;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/ReportSessionResponse/Result.php b/Gax/tests/Conformance/src/V1beta1/ReportSessionResponse/Result.php
new file mode 100644
index 000000000000..576b7affe255
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/ReportSessionResponse/Result.php
@@ -0,0 +1,69 @@
+google.showcase.v1beta1.ReportSessionResponse.Result
+ */
+class Result
+{
+ /**
+ * Generated from protobuf enum RESULT_UNSPECIFIED = 0;
+ */
+ const RESULT_UNSPECIFIED = 0;
+ /**
+ * The session is complete, and everything passed.
+ *
+ * Generated from protobuf enum PASSED = 1;
+ */
+ const PASSED = 1;
+ /**
+ * The session had an explicit failure.
+ *
+ * Generated from protobuf enum FAILED = 2;
+ */
+ const FAILED = 2;
+ /**
+ * The session is incomplete. This is a failure response.
+ *
+ * Generated from protobuf enum INCOMPLETE = 3;
+ */
+ const INCOMPLETE = 3;
+
+ private static $valueToName = [
+ self::RESULT_UNSPECIFIED => 'RESULT_UNSPECIFIED',
+ self::PASSED => 'PASSED',
+ self::FAILED => 'FAILED',
+ self::INCOMPLETE => 'INCOMPLETE',
+ ];
+
+ public static function name($value)
+ {
+ if (!isset(self::$valueToName[$value])) {
+ throw new UnexpectedValueException(sprintf(
+ 'Enum %s has no name defined for value %s', __CLASS__, $value));
+ }
+ return self::$valueToName[$value];
+ }
+
+
+ public static function value($name)
+ {
+ $const = __CLASS__ . '::' . strtoupper($name);
+ if (!defined($const)) {
+ throw new UnexpectedValueException(sprintf(
+ 'Enum %s has no value defined for name %s', __CLASS__, $name));
+ }
+ return constant($const);
+ }
+}
+
+// Adding a class alias for backwards compatibility with the previous class name.
+class_alias(Result::class, \Google\Showcase\V1beta1\ReportSessionResponse_Result::class);
+
diff --git a/Gax/tests/Conformance/src/V1beta1/RestError.php b/Gax/tests/Conformance/src/V1beta1/RestError.php
new file mode 100644
index 000000000000..cd2ad04f6009
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/RestError.php
@@ -0,0 +1,71 @@
+google.showcase.v1beta1.RestError
+ */
+class RestError extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * Generated from protobuf field .google.showcase.v1beta1.RestError.Status error = 1;
+ */
+ protected $error = null;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type \Google\Showcase\V1beta1\RestError\Status $error
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\RestError::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * Generated from protobuf field .google.showcase.v1beta1.RestError.Status error = 1;
+ * @return \Google\Showcase\V1beta1\RestError\Status|null
+ */
+ public function getError()
+ {
+ return $this->error;
+ }
+
+ public function hasError()
+ {
+ return isset($this->error);
+ }
+
+ public function clearError()
+ {
+ unset($this->error);
+ }
+
+ /**
+ * Generated from protobuf field .google.showcase.v1beta1.RestError.Status error = 1;
+ * @param \Google\Showcase\V1beta1\RestError\Status $var
+ * @return $this
+ */
+ public function setError($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Showcase\V1beta1\RestError\Status::class);
+ $this->error = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/RestError/Status.php b/Gax/tests/Conformance/src/V1beta1/RestError/Status.php
new file mode 100644
index 000000000000..b0c423c41c29
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/RestError/Status.php
@@ -0,0 +1,170 @@
+google.showcase.v1beta1.RestError.Status
+ */
+class Status extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The HTTP status code that corresponds to `google.rpc.Status.code`.
+ *
+ * Generated from protobuf field int32 code = 1;
+ */
+ protected $code = 0;
+ /**
+ * This corresponds to `google.rpc.Status.message`.
+ *
+ * Generated from protobuf field string message = 2;
+ */
+ protected $message = '';
+ /**
+ * This is the enum version for `google.rpc.Status.code`.
+ *
+ * Generated from protobuf field .google.rpc.Code status = 4;
+ */
+ protected $status = 0;
+ /**
+ * This corresponds to `google.rpc.Status.details`.
+ *
+ * Generated from protobuf field repeated .google.protobuf.Any details = 5;
+ */
+ private $details;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type int $code
+ * The HTTP status code that corresponds to `google.rpc.Status.code`.
+ * @type string $message
+ * This corresponds to `google.rpc.Status.message`.
+ * @type int $status
+ * This is the enum version for `google.rpc.Status.code`.
+ * @type array<\Google\Protobuf\Any>|\Google\Protobuf\Internal\RepeatedField $details
+ * This corresponds to `google.rpc.Status.details`.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\RestError::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The HTTP status code that corresponds to `google.rpc.Status.code`.
+ *
+ * Generated from protobuf field int32 code = 1;
+ * @return int
+ */
+ public function getCode()
+ {
+ return $this->code;
+ }
+
+ /**
+ * The HTTP status code that corresponds to `google.rpc.Status.code`.
+ *
+ * Generated from protobuf field int32 code = 1;
+ * @param int $var
+ * @return $this
+ */
+ public function setCode($var)
+ {
+ GPBUtil::checkInt32($var);
+ $this->code = $var;
+
+ return $this;
+ }
+
+ /**
+ * This corresponds to `google.rpc.Status.message`.
+ *
+ * Generated from protobuf field string message = 2;
+ * @return string
+ */
+ public function getMessage()
+ {
+ return $this->message;
+ }
+
+ /**
+ * This corresponds to `google.rpc.Status.message`.
+ *
+ * Generated from protobuf field string message = 2;
+ * @param string $var
+ * @return $this
+ */
+ public function setMessage($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->message = $var;
+
+ return $this;
+ }
+
+ /**
+ * This is the enum version for `google.rpc.Status.code`.
+ *
+ * Generated from protobuf field .google.rpc.Code status = 4;
+ * @return int
+ */
+ public function getStatus()
+ {
+ return $this->status;
+ }
+
+ /**
+ * This is the enum version for `google.rpc.Status.code`.
+ *
+ * Generated from protobuf field .google.rpc.Code status = 4;
+ * @param int $var
+ * @return $this
+ */
+ public function setStatus($var)
+ {
+ GPBUtil::checkEnum($var, \Google\Rpc\Code::class);
+ $this->status = $var;
+
+ return $this;
+ }
+
+ /**
+ * This corresponds to `google.rpc.Status.details`.
+ *
+ * Generated from protobuf field repeated .google.protobuf.Any details = 5;
+ * @return \Google\Protobuf\Internal\RepeatedField
+ */
+ public function getDetails()
+ {
+ return $this->details;
+ }
+
+ /**
+ * This corresponds to `google.rpc.Status.details`.
+ *
+ * Generated from protobuf field repeated .google.protobuf.Any details = 5;
+ * @param array<\Google\Protobuf\Any>|\Google\Protobuf\Internal\RepeatedField $var
+ * @return $this
+ */
+ public function setDetails($var)
+ {
+ $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Google\Protobuf\Any::class);
+ $this->details = $arr;
+
+ return $this;
+ }
+
+}
+
+// Adding a class alias for backwards compatibility with the previous class name.
+class_alias(Status::class, \Google\Showcase\V1beta1\RestError_Status::class);
+
diff --git a/Gax/tests/Conformance/src/V1beta1/Room.php b/Gax/tests/Conformance/src/V1beta1/Room.php
new file mode 100644
index 000000000000..cb105c98b0e9
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/Room.php
@@ -0,0 +1,223 @@
+google.showcase.v1beta1.Room
+ */
+class Room extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The resource name of the chat room.
+ *
+ * Generated from protobuf field string name = 1;
+ */
+ protected $name = '';
+ /**
+ * The human readable name of the chat room.
+ *
+ * Generated from protobuf field string display_name = 2 [(.google.api.field_behavior) = REQUIRED];
+ */
+ protected $display_name = '';
+ /**
+ * The description of the chat room.
+ *
+ * Generated from protobuf field string description = 3;
+ */
+ protected $description = '';
+ /**
+ * The timestamp at which the room was created.
+ *
+ * Generated from protobuf field .google.protobuf.Timestamp create_time = 4 [(.google.api.field_behavior) = OUTPUT_ONLY];
+ */
+ protected $create_time = null;
+ /**
+ * The latest timestamp at which the room was updated.
+ *
+ * Generated from protobuf field .google.protobuf.Timestamp update_time = 5 [(.google.api.field_behavior) = OUTPUT_ONLY];
+ */
+ protected $update_time = null;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $name
+ * The resource name of the chat room.
+ * @type string $display_name
+ * The human readable name of the chat room.
+ * @type string $description
+ * The description of the chat room.
+ * @type \Google\Protobuf\Timestamp $create_time
+ * The timestamp at which the room was created.
+ * @type \Google\Protobuf\Timestamp $update_time
+ * The latest timestamp at which the room was updated.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Messaging::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The resource name of the chat room.
+ *
+ * Generated from protobuf field string name = 1;
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * The resource name of the chat room.
+ *
+ * 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;
+ }
+
+ /**
+ * The human readable name of the chat room.
+ *
+ * Generated from protobuf field string display_name = 2 [(.google.api.field_behavior) = REQUIRED];
+ * @return string
+ */
+ public function getDisplayName()
+ {
+ return $this->display_name;
+ }
+
+ /**
+ * The human readable name of the chat room.
+ *
+ * Generated from protobuf field string display_name = 2 [(.google.api.field_behavior) = REQUIRED];
+ * @param string $var
+ * @return $this
+ */
+ public function setDisplayName($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->display_name = $var;
+
+ return $this;
+ }
+
+ /**
+ * The description of the chat room.
+ *
+ * Generated from protobuf field string description = 3;
+ * @return string
+ */
+ public function getDescription()
+ {
+ return $this->description;
+ }
+
+ /**
+ * The description of the chat room.
+ *
+ * Generated from protobuf field string description = 3;
+ * @param string $var
+ * @return $this
+ */
+ public function setDescription($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->description = $var;
+
+ return $this;
+ }
+
+ /**
+ * The timestamp at which the room was created.
+ *
+ * Generated from protobuf field .google.protobuf.Timestamp create_time = 4 [(.google.api.field_behavior) = OUTPUT_ONLY];
+ * @return \Google\Protobuf\Timestamp|null
+ */
+ public function getCreateTime()
+ {
+ return $this->create_time;
+ }
+
+ public function hasCreateTime()
+ {
+ return isset($this->create_time);
+ }
+
+ public function clearCreateTime()
+ {
+ unset($this->create_time);
+ }
+
+ /**
+ * The timestamp at which the room was created.
+ *
+ * Generated from protobuf field .google.protobuf.Timestamp create_time = 4 [(.google.api.field_behavior) = OUTPUT_ONLY];
+ * @param \Google\Protobuf\Timestamp $var
+ * @return $this
+ */
+ public function setCreateTime($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Protobuf\Timestamp::class);
+ $this->create_time = $var;
+
+ return $this;
+ }
+
+ /**
+ * The latest timestamp at which the room was updated.
+ *
+ * Generated from protobuf field .google.protobuf.Timestamp update_time = 5 [(.google.api.field_behavior) = OUTPUT_ONLY];
+ * @return \Google\Protobuf\Timestamp|null
+ */
+ public function getUpdateTime()
+ {
+ return $this->update_time;
+ }
+
+ public function hasUpdateTime()
+ {
+ return isset($this->update_time);
+ }
+
+ public function clearUpdateTime()
+ {
+ unset($this->update_time);
+ }
+
+ /**
+ * The latest timestamp at which the room was updated.
+ *
+ * Generated from protobuf field .google.protobuf.Timestamp update_time = 5 [(.google.api.field_behavior) = OUTPUT_ONLY];
+ * @param \Google\Protobuf\Timestamp $var
+ * @return $this
+ */
+ public function setUpdateTime($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Protobuf\Timestamp::class);
+ $this->update_time = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/SearchBlurbsMetadata.php b/Gax/tests/Conformance/src/V1beta1/SearchBlurbsMetadata.php
new file mode 100644
index 000000000000..ec8ff0507c2f
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/SearchBlurbsMetadata.php
@@ -0,0 +1,78 @@
+google.showcase.v1beta1.SearchBlurbsMetadata
+ */
+class SearchBlurbsMetadata extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * This signals to the client when to next poll for response.
+ *
+ * Generated from protobuf field .google.rpc.RetryInfo retry_info = 1;
+ */
+ protected $retry_info = null;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type \Google\Rpc\RetryInfo $retry_info
+ * This signals to the client when to next poll for response.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Messaging::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * This signals to the client when to next poll for response.
+ *
+ * Generated from protobuf field .google.rpc.RetryInfo retry_info = 1;
+ * @return \Google\Rpc\RetryInfo|null
+ */
+ public function getRetryInfo()
+ {
+ return $this->retry_info;
+ }
+
+ public function hasRetryInfo()
+ {
+ return isset($this->retry_info);
+ }
+
+ public function clearRetryInfo()
+ {
+ unset($this->retry_info);
+ }
+
+ /**
+ * This signals to the client when to next poll for response.
+ *
+ * Generated from protobuf field .google.rpc.RetryInfo retry_info = 1;
+ * @param \Google\Rpc\RetryInfo $var
+ * @return $this
+ */
+ public function setRetryInfo($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Rpc\RetryInfo::class);
+ $this->retry_info = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/SearchBlurbsRequest.php b/Gax/tests/Conformance/src/V1beta1/SearchBlurbsRequest.php
new file mode 100644
index 000000000000..984f508bf4cd
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/SearchBlurbsRequest.php
@@ -0,0 +1,215 @@
+google.showcase.v1beta1.SearchBlurbsRequest
+ */
+class SearchBlurbsRequest extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The query used to search for blurbs containing to words of this string.
+ * Only posts that contain an exact match of a queried word will be returned.
+ *
+ * Generated from protobuf field string query = 1 [(.google.api.field_behavior) = REQUIRED];
+ */
+ protected $query = '';
+ /**
+ * The rooms or profiles to search. If unset, `SearchBlurbs` will search all
+ * rooms and all profiles.
+ *
+ * Generated from protobuf field string parent = 2 [(.google.api.resource_reference) = {
+ */
+ protected $parent = '';
+ /**
+ * The maximum number of blurbs return. Server may return fewer
+ * blurbs than requested. If unspecified, server will pick an appropriate
+ * default.
+ *
+ * Generated from protobuf field int32 page_size = 3;
+ */
+ protected $page_size = 0;
+ /**
+ * The value of
+ * google.showcase.v1beta1.SearchBlurbsResponse.next_page_token
+ * returned from the previous call to
+ * `google.showcase.v1beta1.Messaging\SearchBlurbs` method.
+ *
+ * Generated from protobuf field string page_token = 4;
+ */
+ protected $page_token = '';
+
+ /**
+ * @param string $parent The rooms or profiles to search. If unset, `SearchBlurbs` will search all
+ * rooms and all profiles.
+ * @param string $query The query used to search for blurbs containing to words of this string.
+ * Only posts that contain an exact match of a queried word will be returned.
+ *
+ * @return \Google\Showcase\V1beta1\SearchBlurbsRequest
+ *
+ * @experimental
+ */
+ public static function build(string $parent, string $query): self
+ {
+ return (new self())
+ ->setParent($parent)
+ ->setQuery($query);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $query
+ * The query used to search for blurbs containing to words of this string.
+ * Only posts that contain an exact match of a queried word will be returned.
+ * @type string $parent
+ * The rooms or profiles to search. If unset, `SearchBlurbs` will search all
+ * rooms and all profiles.
+ * @type int $page_size
+ * The maximum number of blurbs return. Server may return fewer
+ * blurbs than requested. If unspecified, server will pick an appropriate
+ * default.
+ * @type string $page_token
+ * The value of
+ * google.showcase.v1beta1.SearchBlurbsResponse.next_page_token
+ * returned from the previous call to
+ * `google.showcase.v1beta1.Messaging\SearchBlurbs` method.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Messaging::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The query used to search for blurbs containing to words of this string.
+ * Only posts that contain an exact match of a queried word will be returned.
+ *
+ * Generated from protobuf field string query = 1 [(.google.api.field_behavior) = REQUIRED];
+ * @return string
+ */
+ public function getQuery()
+ {
+ return $this->query;
+ }
+
+ /**
+ * The query used to search for blurbs containing to words of this string.
+ * Only posts that contain an exact match of a queried word will be returned.
+ *
+ * Generated from protobuf field string query = 1 [(.google.api.field_behavior) = REQUIRED];
+ * @param string $var
+ * @return $this
+ */
+ public function setQuery($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->query = $var;
+
+ return $this;
+ }
+
+ /**
+ * The rooms or profiles to search. If unset, `SearchBlurbs` will search all
+ * rooms and all profiles.
+ *
+ * Generated from protobuf field string parent = 2 [(.google.api.resource_reference) = {
+ * @return string
+ */
+ public function getParent()
+ {
+ return $this->parent;
+ }
+
+ /**
+ * The rooms or profiles to search. If unset, `SearchBlurbs` will search all
+ * rooms and all profiles.
+ *
+ * Generated from protobuf field string parent = 2 [(.google.api.resource_reference) = {
+ * @param string $var
+ * @return $this
+ */
+ public function setParent($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->parent = $var;
+
+ return $this;
+ }
+
+ /**
+ * The maximum number of blurbs return. Server may return fewer
+ * blurbs than requested. If unspecified, server will pick an appropriate
+ * default.
+ *
+ * Generated from protobuf field int32 page_size = 3;
+ * @return int
+ */
+ public function getPageSize()
+ {
+ return $this->page_size;
+ }
+
+ /**
+ * The maximum number of blurbs return. Server may return fewer
+ * blurbs than requested. If unspecified, server will pick an appropriate
+ * default.
+ *
+ * Generated from protobuf field int32 page_size = 3;
+ * @param int $var
+ * @return $this
+ */
+ public function setPageSize($var)
+ {
+ GPBUtil::checkInt32($var);
+ $this->page_size = $var;
+
+ return $this;
+ }
+
+ /**
+ * The value of
+ * google.showcase.v1beta1.SearchBlurbsResponse.next_page_token
+ * returned from the previous call to
+ * `google.showcase.v1beta1.Messaging\SearchBlurbs` method.
+ *
+ * Generated from protobuf field string page_token = 4;
+ * @return string
+ */
+ public function getPageToken()
+ {
+ return $this->page_token;
+ }
+
+ /**
+ * The value of
+ * google.showcase.v1beta1.SearchBlurbsResponse.next_page_token
+ * returned from the previous call to
+ * `google.showcase.v1beta1.Messaging\SearchBlurbs` method.
+ *
+ * Generated from protobuf field string page_token = 4;
+ * @param string $var
+ * @return $this
+ */
+ public function setPageToken($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->page_token = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/SearchBlurbsResponse.php b/Gax/tests/Conformance/src/V1beta1/SearchBlurbsResponse.php
new file mode 100644
index 000000000000..b0fc9b703e16
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/SearchBlurbsResponse.php
@@ -0,0 +1,114 @@
+google.showcase.v1beta1.SearchBlurbsResponse
+ */
+class SearchBlurbsResponse extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * Blurbs that matched the search query.
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.Blurb blurbs = 1;
+ */
+ private $blurbs;
+ /**
+ * A token to retrieve next page of results.
+ * Pass this value in SearchBlurbsRequest.page_token field in the subsequent
+ * call to `google.showcase.v1beta1.Blurb\SearchBlurbs` method to
+ * retrieve the next page of results.
+ *
+ * Generated from protobuf field string next_page_token = 2;
+ */
+ protected $next_page_token = '';
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type array<\Google\Showcase\V1beta1\Blurb>|\Google\Protobuf\Internal\RepeatedField $blurbs
+ * Blurbs that matched the search query.
+ * @type string $next_page_token
+ * A token to retrieve next page of results.
+ * Pass this value in SearchBlurbsRequest.page_token field in the subsequent
+ * call to `google.showcase.v1beta1.Blurb\SearchBlurbs` method to
+ * retrieve the next page of results.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Messaging::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * Blurbs that matched the search query.
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.Blurb blurbs = 1;
+ * @return \Google\Protobuf\Internal\RepeatedField
+ */
+ public function getBlurbs()
+ {
+ return $this->blurbs;
+ }
+
+ /**
+ * Blurbs that matched the search query.
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.Blurb blurbs = 1;
+ * @param array<\Google\Showcase\V1beta1\Blurb>|\Google\Protobuf\Internal\RepeatedField $var
+ * @return $this
+ */
+ public function setBlurbs($var)
+ {
+ $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Google\Showcase\V1beta1\Blurb::class);
+ $this->blurbs = $arr;
+
+ return $this;
+ }
+
+ /**
+ * A token to retrieve next page of results.
+ * Pass this value in SearchBlurbsRequest.page_token field in the subsequent
+ * call to `google.showcase.v1beta1.Blurb\SearchBlurbs` method to
+ * retrieve the next page of results.
+ *
+ * Generated from protobuf field string next_page_token = 2;
+ * @return string
+ */
+ public function getNextPageToken()
+ {
+ return $this->next_page_token;
+ }
+
+ /**
+ * A token to retrieve next page of results.
+ * Pass this value in SearchBlurbsRequest.page_token field in the subsequent
+ * call to `google.showcase.v1beta1.Blurb\SearchBlurbs` method to
+ * retrieve the next page of results.
+ *
+ * Generated from protobuf field string next_page_token = 2;
+ * @param string $var
+ * @return $this
+ */
+ public function setNextPageToken($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->next_page_token = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/SendBlurbsResponse.php b/Gax/tests/Conformance/src/V1beta1/SendBlurbsResponse.php
new file mode 100644
index 000000000000..3fd5a005b8b2
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/SendBlurbsResponse.php
@@ -0,0 +1,68 @@
+google.showcase.v1beta1.SendBlurbsResponse
+ */
+class SendBlurbsResponse extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The names of successful blurb creations.
+ *
+ * Generated from protobuf field repeated string names = 1;
+ */
+ private $names;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type array|\Google\Protobuf\Internal\RepeatedField $names
+ * The names of successful blurb creations.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Messaging::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The names of successful blurb creations.
+ *
+ * Generated from protobuf field repeated string names = 1;
+ * @return \Google\Protobuf\Internal\RepeatedField
+ */
+ public function getNames()
+ {
+ return $this->names;
+ }
+
+ /**
+ * The names of successful blurb creations.
+ *
+ * Generated from protobuf field repeated string names = 1;
+ * @param array|\Google\Protobuf\Internal\RepeatedField $var
+ * @return $this
+ */
+ public function setNames($var)
+ {
+ $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::STRING);
+ $this->names = $arr;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/Sequence.php b/Gax/tests/Conformance/src/V1beta1/Sequence.php
new file mode 100644
index 000000000000..b8565b20374c
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/Sequence.php
@@ -0,0 +1,96 @@
+google.showcase.v1beta1.Sequence
+ */
+class Sequence extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = OUTPUT_ONLY];
+ */
+ protected $name = '';
+ /**
+ * Sequence of responses to return in order for each attempt. If empty, the
+ * default response is an immediate OK.
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.Sequence.Response responses = 2;
+ */
+ private $responses;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $name
+ * @type array<\Google\Showcase\V1beta1\Sequence\Response>|\Google\Protobuf\Internal\RepeatedField $responses
+ * Sequence of responses to return in order for each attempt. If empty, the
+ * default response is an immediate OK.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Sequence::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = OUTPUT_ONLY];
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = OUTPUT_ONLY];
+ * @param string $var
+ * @return $this
+ */
+ public function setName($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->name = $var;
+
+ return $this;
+ }
+
+ /**
+ * Sequence of responses to return in order for each attempt. If empty, the
+ * default response is an immediate OK.
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.Sequence.Response responses = 2;
+ * @return \Google\Protobuf\Internal\RepeatedField
+ */
+ public function getResponses()
+ {
+ return $this->responses;
+ }
+
+ /**
+ * Sequence of responses to return in order for each attempt. If empty, the
+ * default response is an immediate OK.
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.Sequence.Response responses = 2;
+ * @param array<\Google\Showcase\V1beta1\Sequence\Response>|\Google\Protobuf\Internal\RepeatedField $var
+ * @return $this
+ */
+ public function setResponses($var)
+ {
+ $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Google\Showcase\V1beta1\Sequence\Response::class);
+ $this->responses = $arr;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/Sequence/Response.php b/Gax/tests/Conformance/src/V1beta1/Sequence/Response.php
new file mode 100644
index 000000000000..5db17b668970
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/Sequence/Response.php
@@ -0,0 +1,124 @@
+google.showcase.v1beta1.Sequence.Response
+ */
+class Response extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The status to return for an individual attempt.
+ *
+ * Generated from protobuf field .google.rpc.Status status = 1;
+ */
+ protected $status = null;
+ /**
+ * The amount of time to delay sending the response.
+ *
+ * Generated from protobuf field .google.protobuf.Duration delay = 2;
+ */
+ protected $delay = null;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type \Google\Rpc\Status $status
+ * The status to return for an individual attempt.
+ * @type \Google\Protobuf\Duration $delay
+ * The amount of time to delay sending the response.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Sequence::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The status to return for an individual attempt.
+ *
+ * Generated from protobuf field .google.rpc.Status status = 1;
+ * @return \Google\Rpc\Status|null
+ */
+ public function getStatus()
+ {
+ return $this->status;
+ }
+
+ public function hasStatus()
+ {
+ return isset($this->status);
+ }
+
+ public function clearStatus()
+ {
+ unset($this->status);
+ }
+
+ /**
+ * The status to return for an individual attempt.
+ *
+ * Generated from protobuf field .google.rpc.Status status = 1;
+ * @param \Google\Rpc\Status $var
+ * @return $this
+ */
+ public function setStatus($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Rpc\Status::class);
+ $this->status = $var;
+
+ return $this;
+ }
+
+ /**
+ * The amount of time to delay sending the response.
+ *
+ * Generated from protobuf field .google.protobuf.Duration delay = 2;
+ * @return \Google\Protobuf\Duration|null
+ */
+ public function getDelay()
+ {
+ return $this->delay;
+ }
+
+ public function hasDelay()
+ {
+ return isset($this->delay);
+ }
+
+ public function clearDelay()
+ {
+ unset($this->delay);
+ }
+
+ /**
+ * The amount of time to delay sending the response.
+ *
+ * Generated from protobuf field .google.protobuf.Duration delay = 2;
+ * @param \Google\Protobuf\Duration $var
+ * @return $this
+ */
+ public function setDelay($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Protobuf\Duration::class);
+ $this->delay = $var;
+
+ return $this;
+ }
+
+}
+
+// Adding a class alias for backwards compatibility with the previous class name.
+class_alias(Response::class, \Google\Showcase\V1beta1\Sequence_Response::class);
+
diff --git a/Gax/tests/Conformance/src/V1beta1/SequenceReport.php b/Gax/tests/Conformance/src/V1beta1/SequenceReport.php
new file mode 100644
index 000000000000..990664340637
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/SequenceReport.php
@@ -0,0 +1,92 @@
+google.showcase.v1beta1.SequenceReport
+ */
+class SequenceReport extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = OUTPUT_ONLY];
+ */
+ protected $name = '';
+ /**
+ * The set of RPC attempts received by the server for a Sequence.
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.SequenceReport.Attempt attempts = 2;
+ */
+ private $attempts;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $name
+ * @type array<\Google\Showcase\V1beta1\SequenceReport\Attempt>|\Google\Protobuf\Internal\RepeatedField $attempts
+ * The set of RPC attempts received by the server for a Sequence.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Sequence::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = OUTPUT_ONLY];
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = OUTPUT_ONLY];
+ * @param string $var
+ * @return $this
+ */
+ public function setName($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->name = $var;
+
+ return $this;
+ }
+
+ /**
+ * The set of RPC attempts received by the server for a Sequence.
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.SequenceReport.Attempt attempts = 2;
+ * @return \Google\Protobuf\Internal\RepeatedField
+ */
+ public function getAttempts()
+ {
+ return $this->attempts;
+ }
+
+ /**
+ * The set of RPC attempts received by the server for a Sequence.
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.SequenceReport.Attempt attempts = 2;
+ * @param array<\Google\Showcase\V1beta1\SequenceReport\Attempt>|\Google\Protobuf\Internal\RepeatedField $var
+ * @return $this
+ */
+ public function setAttempts($var)
+ {
+ $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Google\Showcase\V1beta1\SequenceReport\Attempt::class);
+ $this->attempts = $arr;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/SequenceReport/Attempt.php b/Gax/tests/Conformance/src/V1beta1/SequenceReport/Attempt.php
new file mode 100644
index 000000000000..9142d4c85ab3
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/SequenceReport/Attempt.php
@@ -0,0 +1,254 @@
+google.showcase.v1beta1.SequenceReport.Attempt
+ */
+class Attempt extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The attempt number - starting at 0.
+ *
+ * Generated from protobuf field int32 attempt_number = 1;
+ */
+ protected $attempt_number = 0;
+ /**
+ * The deadline dictated by the attempt to the server.
+ *
+ * Generated from protobuf field .google.protobuf.Timestamp attempt_deadline = 2;
+ */
+ protected $attempt_deadline = null;
+ /**
+ * The time that the server responded to the RPC attempt. Used for
+ * calculating attempt_delay.
+ *
+ * Generated from protobuf field .google.protobuf.Timestamp response_time = 3;
+ */
+ protected $response_time = null;
+ /**
+ * The server perceived delay between sending the last response and
+ * receiving this attempt. Used for validating attempt delay backoff.
+ *
+ * Generated from protobuf field .google.protobuf.Duration attempt_delay = 4;
+ */
+ protected $attempt_delay = null;
+ /**
+ * The status returned to the attempt.
+ *
+ * Generated from protobuf field .google.rpc.Status status = 5;
+ */
+ protected $status = null;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type int $attempt_number
+ * The attempt number - starting at 0.
+ * @type \Google\Protobuf\Timestamp $attempt_deadline
+ * The deadline dictated by the attempt to the server.
+ * @type \Google\Protobuf\Timestamp $response_time
+ * The time that the server responded to the RPC attempt. Used for
+ * calculating attempt_delay.
+ * @type \Google\Protobuf\Duration $attempt_delay
+ * The server perceived delay between sending the last response and
+ * receiving this attempt. Used for validating attempt delay backoff.
+ * @type \Google\Rpc\Status $status
+ * The status returned to the attempt.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Sequence::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The attempt number - starting at 0.
+ *
+ * Generated from protobuf field int32 attempt_number = 1;
+ * @return int
+ */
+ public function getAttemptNumber()
+ {
+ return $this->attempt_number;
+ }
+
+ /**
+ * The attempt number - starting at 0.
+ *
+ * Generated from protobuf field int32 attempt_number = 1;
+ * @param int $var
+ * @return $this
+ */
+ public function setAttemptNumber($var)
+ {
+ GPBUtil::checkInt32($var);
+ $this->attempt_number = $var;
+
+ return $this;
+ }
+
+ /**
+ * The deadline dictated by the attempt to the server.
+ *
+ * Generated from protobuf field .google.protobuf.Timestamp attempt_deadline = 2;
+ * @return \Google\Protobuf\Timestamp|null
+ */
+ public function getAttemptDeadline()
+ {
+ return $this->attempt_deadline;
+ }
+
+ public function hasAttemptDeadline()
+ {
+ return isset($this->attempt_deadline);
+ }
+
+ public function clearAttemptDeadline()
+ {
+ unset($this->attempt_deadline);
+ }
+
+ /**
+ * The deadline dictated by the attempt to the server.
+ *
+ * Generated from protobuf field .google.protobuf.Timestamp attempt_deadline = 2;
+ * @param \Google\Protobuf\Timestamp $var
+ * @return $this
+ */
+ public function setAttemptDeadline($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Protobuf\Timestamp::class);
+ $this->attempt_deadline = $var;
+
+ return $this;
+ }
+
+ /**
+ * The time that the server responded to the RPC attempt. Used for
+ * calculating attempt_delay.
+ *
+ * Generated from protobuf field .google.protobuf.Timestamp response_time = 3;
+ * @return \Google\Protobuf\Timestamp|null
+ */
+ public function getResponseTime()
+ {
+ return $this->response_time;
+ }
+
+ public function hasResponseTime()
+ {
+ return isset($this->response_time);
+ }
+
+ public function clearResponseTime()
+ {
+ unset($this->response_time);
+ }
+
+ /**
+ * The time that the server responded to the RPC attempt. Used for
+ * calculating attempt_delay.
+ *
+ * Generated from protobuf field .google.protobuf.Timestamp response_time = 3;
+ * @param \Google\Protobuf\Timestamp $var
+ * @return $this
+ */
+ public function setResponseTime($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Protobuf\Timestamp::class);
+ $this->response_time = $var;
+
+ return $this;
+ }
+
+ /**
+ * The server perceived delay between sending the last response and
+ * receiving this attempt. Used for validating attempt delay backoff.
+ *
+ * Generated from protobuf field .google.protobuf.Duration attempt_delay = 4;
+ * @return \Google\Protobuf\Duration|null
+ */
+ public function getAttemptDelay()
+ {
+ return $this->attempt_delay;
+ }
+
+ public function hasAttemptDelay()
+ {
+ return isset($this->attempt_delay);
+ }
+
+ public function clearAttemptDelay()
+ {
+ unset($this->attempt_delay);
+ }
+
+ /**
+ * The server perceived delay between sending the last response and
+ * receiving this attempt. Used for validating attempt delay backoff.
+ *
+ * Generated from protobuf field .google.protobuf.Duration attempt_delay = 4;
+ * @param \Google\Protobuf\Duration $var
+ * @return $this
+ */
+ public function setAttemptDelay($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Protobuf\Duration::class);
+ $this->attempt_delay = $var;
+
+ return $this;
+ }
+
+ /**
+ * The status returned to the attempt.
+ *
+ * Generated from protobuf field .google.rpc.Status status = 5;
+ * @return \Google\Rpc\Status|null
+ */
+ public function getStatus()
+ {
+ return $this->status;
+ }
+
+ public function hasStatus()
+ {
+ return isset($this->status);
+ }
+
+ public function clearStatus()
+ {
+ unset($this->status);
+ }
+
+ /**
+ * The status returned to the attempt.
+ *
+ * Generated from protobuf field .google.rpc.Status status = 5;
+ * @param \Google\Rpc\Status $var
+ * @return $this
+ */
+ public function setStatus($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Rpc\Status::class);
+ $this->status = $var;
+
+ return $this;
+ }
+
+}
+
+// Adding a class alias for backwards compatibility with the previous class name.
+class_alias(Attempt::class, \Google\Showcase\V1beta1\SequenceReport_Attempt::class);
+
diff --git a/Gax/tests/Conformance/src/V1beta1/Session.php b/Gax/tests/Conformance/src/V1beta1/Session.php
new file mode 100644
index 000000000000..bf4479084084
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/Session.php
@@ -0,0 +1,108 @@
+google.showcase.v1beta1.Session
+ */
+class Session extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The name of the session. The ID must conform to ^[a-z]+$
+ * If this is not provided, Showcase chooses one at random.
+ *
+ * Generated from protobuf field string name = 1;
+ */
+ protected $name = '';
+ /**
+ * Required. The version this session is using.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Session.Version version = 2;
+ */
+ protected $version = 0;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $name
+ * The name of the session. The ID must conform to ^[a-z]+$
+ * If this is not provided, Showcase chooses one at random.
+ * @type int $version
+ * Required. The version this session is using.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Testing::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The name of the session. The ID must conform to ^[a-z]+$
+ * If this is not provided, Showcase chooses one at random.
+ *
+ * Generated from protobuf field string name = 1;
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * The name of the session. The ID must conform to ^[a-z]+$
+ * If this is not provided, Showcase chooses one at random.
+ *
+ * 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;
+ }
+
+ /**
+ * Required. The version this session is using.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Session.Version version = 2;
+ * @return int
+ */
+ public function getVersion()
+ {
+ return $this->version;
+ }
+
+ /**
+ * Required. The version this session is using.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Session.Version version = 2;
+ * @param int $var
+ * @return $this
+ */
+ public function setVersion($var)
+ {
+ GPBUtil::checkEnum($var, \Google\Showcase\V1beta1\Session\Version::class);
+ $this->version = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/Session/Version.php b/Gax/tests/Conformance/src/V1beta1/Session/Version.php
new file mode 100644
index 000000000000..528e966e3078
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/Session/Version.php
@@ -0,0 +1,65 @@
+google.showcase.v1beta1.Session.Version
+ */
+class Version
+{
+ /**
+ * Unspecified version. If passed on creation, the session will default
+ * to using the latest stable release.
+ *
+ * Generated from protobuf enum VERSION_UNSPECIFIED = 0;
+ */
+ const VERSION_UNSPECIFIED = 0;
+ /**
+ * The latest v1. Currently, this is v1.0.
+ *
+ * Generated from protobuf enum V1_LATEST = 1;
+ */
+ const V1_LATEST = 1;
+ /**
+ * v1.0. (Until the spec is "GA", this will be a moving target.)
+ *
+ * Generated from protobuf enum V1_0 = 2;
+ */
+ const V1_0 = 2;
+
+ private static $valueToName = [
+ self::VERSION_UNSPECIFIED => 'VERSION_UNSPECIFIED',
+ self::V1_LATEST => 'V1_LATEST',
+ self::V1_0 => 'V1_0',
+ ];
+
+ public static function name($value)
+ {
+ if (!isset(self::$valueToName[$value])) {
+ throw new UnexpectedValueException(sprintf(
+ 'Enum %s has no name defined for value %s', __CLASS__, $value));
+ }
+ return self::$valueToName[$value];
+ }
+
+
+ public static function value($name)
+ {
+ $const = __CLASS__ . '::' . strtoupper($name);
+ if (!defined($const)) {
+ throw new UnexpectedValueException(sprintf(
+ 'Enum %s has no value defined for name %s', __CLASS__, $name));
+ }
+ return constant($const);
+ }
+}
+
+// Adding a class alias for backwards compatibility with the previous class name.
+class_alias(Version::class, \Google\Showcase\V1beta1\Session_Version::class);
+
diff --git a/Gax/tests/Conformance/src/V1beta1/Severity.php b/Gax/tests/Conformance/src/V1beta1/Severity.php
new file mode 100644
index 000000000000..bdb57ebf72a0
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/Severity.php
@@ -0,0 +1,60 @@
+google.showcase.v1beta1.Severity
+ */
+class Severity
+{
+ /**
+ * Generated from protobuf enum UNNECESSARY = 0;
+ */
+ const UNNECESSARY = 0;
+ /**
+ * Generated from protobuf enum NECESSARY = 1;
+ */
+ const NECESSARY = 1;
+ /**
+ * Generated from protobuf enum URGENT = 2;
+ */
+ const URGENT = 2;
+ /**
+ * Generated from protobuf enum CRITICAL = 3;
+ */
+ const CRITICAL = 3;
+
+ private static $valueToName = [
+ self::UNNECESSARY => 'UNNECESSARY',
+ self::NECESSARY => 'NECESSARY',
+ self::URGENT => 'URGENT',
+ self::CRITICAL => 'CRITICAL',
+ ];
+
+ public static function name($value)
+ {
+ if (!isset(self::$valueToName[$value])) {
+ throw new UnexpectedValueException(sprintf(
+ 'Enum %s has no name defined for value %s', __CLASS__, $value));
+ }
+ return self::$valueToName[$value];
+ }
+
+
+ public static function value($name)
+ {
+ $const = __CLASS__ . '::' . strtoupper($name);
+ if (!defined($const)) {
+ throw new UnexpectedValueException(sprintf(
+ 'Enum %s has no value defined for name %s', __CLASS__, $name));
+ }
+ return constant($const);
+ }
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/StreamBlurbsRequest.php b/Gax/tests/Conformance/src/V1beta1/StreamBlurbsRequest.php
new file mode 100644
index 000000000000..13fb733751f5
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/StreamBlurbsRequest.php
@@ -0,0 +1,112 @@
+google.showcase.v1beta1.StreamBlurbsRequest
+ */
+class StreamBlurbsRequest extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The resource name of a chat room or user profile whose blurbs to stream.
+ *
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = REQUIRED, (.google.api.resource_reference) = {
+ */
+ protected $name = '';
+ /**
+ * The time at which this stream will close.
+ *
+ * Generated from protobuf field .google.protobuf.Timestamp expire_time = 2 [(.google.api.field_behavior) = REQUIRED];
+ */
+ protected $expire_time = null;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $name
+ * The resource name of a chat room or user profile whose blurbs to stream.
+ * @type \Google\Protobuf\Timestamp $expire_time
+ * The time at which this stream will close.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Messaging::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The resource name of a chat room or user profile whose blurbs to stream.
+ *
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = REQUIRED, (.google.api.resource_reference) = {
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * The resource name of a chat room or user profile whose blurbs to stream.
+ *
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = REQUIRED, (.google.api.resource_reference) = {
+ * @param string $var
+ * @return $this
+ */
+ public function setName($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->name = $var;
+
+ return $this;
+ }
+
+ /**
+ * The time at which this stream will close.
+ *
+ * Generated from protobuf field .google.protobuf.Timestamp expire_time = 2 [(.google.api.field_behavior) = REQUIRED];
+ * @return \Google\Protobuf\Timestamp|null
+ */
+ public function getExpireTime()
+ {
+ return $this->expire_time;
+ }
+
+ public function hasExpireTime()
+ {
+ return isset($this->expire_time);
+ }
+
+ public function clearExpireTime()
+ {
+ unset($this->expire_time);
+ }
+
+ /**
+ * The time at which this stream will close.
+ *
+ * Generated from protobuf field .google.protobuf.Timestamp expire_time = 2 [(.google.api.field_behavior) = REQUIRED];
+ * @param \Google\Protobuf\Timestamp $var
+ * @return $this
+ */
+ public function setExpireTime($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Protobuf\Timestamp::class);
+ $this->expire_time = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/StreamBlurbsResponse.php b/Gax/tests/Conformance/src/V1beta1/StreamBlurbsResponse.php
new file mode 100644
index 000000000000..673ee59d1e78
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/StreamBlurbsResponse.php
@@ -0,0 +1,112 @@
+google.showcase.v1beta1.StreamBlurbsResponse
+ */
+class StreamBlurbsResponse extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The blurb that was either created, updated, or deleted.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Blurb blurb = 1;
+ */
+ protected $blurb = null;
+ /**
+ * The action that triggered the blurb to be returned.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.StreamBlurbsResponse.Action action = 2;
+ */
+ protected $action = 0;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type \Google\Showcase\V1beta1\Blurb $blurb
+ * The blurb that was either created, updated, or deleted.
+ * @type int $action
+ * The action that triggered the blurb to be returned.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Messaging::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The blurb that was either created, updated, or deleted.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Blurb blurb = 1;
+ * @return \Google\Showcase\V1beta1\Blurb|null
+ */
+ public function getBlurb()
+ {
+ return $this->blurb;
+ }
+
+ public function hasBlurb()
+ {
+ return isset($this->blurb);
+ }
+
+ public function clearBlurb()
+ {
+ unset($this->blurb);
+ }
+
+ /**
+ * The blurb that was either created, updated, or deleted.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Blurb blurb = 1;
+ * @param \Google\Showcase\V1beta1\Blurb $var
+ * @return $this
+ */
+ public function setBlurb($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Showcase\V1beta1\Blurb::class);
+ $this->blurb = $var;
+
+ return $this;
+ }
+
+ /**
+ * The action that triggered the blurb to be returned.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.StreamBlurbsResponse.Action action = 2;
+ * @return int
+ */
+ public function getAction()
+ {
+ return $this->action;
+ }
+
+ /**
+ * The action that triggered the blurb to be returned.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.StreamBlurbsResponse.Action action = 2;
+ * @param int $var
+ * @return $this
+ */
+ public function setAction($var)
+ {
+ GPBUtil::checkEnum($var, \Google\Showcase\V1beta1\StreamBlurbsResponse\Action::class);
+ $this->action = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/StreamBlurbsResponse/Action.php b/Gax/tests/Conformance/src/V1beta1/StreamBlurbsResponse/Action.php
new file mode 100644
index 000000000000..d7edf0a6b270
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/StreamBlurbsResponse/Action.php
@@ -0,0 +1,69 @@
+google.showcase.v1beta1.StreamBlurbsResponse.Action
+ */
+class Action
+{
+ /**
+ * Generated from protobuf enum ACTION_UNSPECIFIED = 0;
+ */
+ const ACTION_UNSPECIFIED = 0;
+ /**
+ * Specifies that the blurb was created.
+ *
+ * Generated from protobuf enum CREATE = 1;
+ */
+ const CREATE = 1;
+ /**
+ * Specifies that the blurb was updated.
+ *
+ * Generated from protobuf enum UPDATE = 2;
+ */
+ const UPDATE = 2;
+ /**
+ * Specifies that the blurb was deleted.
+ *
+ * Generated from protobuf enum DELETE = 3;
+ */
+ const DELETE = 3;
+
+ private static $valueToName = [
+ self::ACTION_UNSPECIFIED => 'ACTION_UNSPECIFIED',
+ self::CREATE => 'CREATE',
+ self::UPDATE => 'UPDATE',
+ self::DELETE => 'DELETE',
+ ];
+
+ public static function name($value)
+ {
+ if (!isset(self::$valueToName[$value])) {
+ throw new UnexpectedValueException(sprintf(
+ 'Enum %s has no name defined for value %s', __CLASS__, $value));
+ }
+ return self::$valueToName[$value];
+ }
+
+
+ public static function value($name)
+ {
+ $const = __CLASS__ . '::' . strtoupper($name);
+ if (!defined($const)) {
+ throw new UnexpectedValueException(sprintf(
+ 'Enum %s has no value defined for name %s', __CLASS__, $name));
+ }
+ return constant($const);
+ }
+}
+
+// Adding a class alias for backwards compatibility with the previous class name.
+class_alias(Action::class, \Google\Showcase\V1beta1\StreamBlurbsResponse_Action::class);
+
diff --git a/Gax/tests/Conformance/src/V1beta1/StreamingSequence.php b/Gax/tests/Conformance/src/V1beta1/StreamingSequence.php
new file mode 100644
index 000000000000..4ee0a4047639
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/StreamingSequence.php
@@ -0,0 +1,130 @@
+google.showcase.v1beta1.StreamingSequence
+ */
+class StreamingSequence extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = OUTPUT_ONLY];
+ */
+ protected $name = '';
+ /**
+ * The Content that the stream will send
+ *
+ * Generated from protobuf field string content = 2;
+ */
+ protected $content = '';
+ /**
+ * Sequence of responses to return in order for each attempt. If empty, the
+ * default response is an immediate OK.
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.StreamingSequence.Response responses = 3;
+ */
+ private $responses;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $name
+ * @type string $content
+ * The Content that the stream will send
+ * @type array<\Google\Showcase\V1beta1\StreamingSequence\Response>|\Google\Protobuf\Internal\RepeatedField $responses
+ * Sequence of responses to return in order for each attempt. If empty, the
+ * default response is an immediate OK.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Sequence::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = OUTPUT_ONLY];
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = OUTPUT_ONLY];
+ * @param string $var
+ * @return $this
+ */
+ public function setName($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->name = $var;
+
+ return $this;
+ }
+
+ /**
+ * The Content that the stream will send
+ *
+ * Generated from protobuf field string content = 2;
+ * @return string
+ */
+ public function getContent()
+ {
+ return $this->content;
+ }
+
+ /**
+ * The Content that the stream will send
+ *
+ * Generated from protobuf field string content = 2;
+ * @param string $var
+ * @return $this
+ */
+ public function setContent($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->content = $var;
+
+ return $this;
+ }
+
+ /**
+ * Sequence of responses to return in order for each attempt. If empty, the
+ * default response is an immediate OK.
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.StreamingSequence.Response responses = 3;
+ * @return \Google\Protobuf\Internal\RepeatedField
+ */
+ public function getResponses()
+ {
+ return $this->responses;
+ }
+
+ /**
+ * Sequence of responses to return in order for each attempt. If empty, the
+ * default response is an immediate OK.
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.StreamingSequence.Response responses = 3;
+ * @param array<\Google\Showcase\V1beta1\StreamingSequence\Response>|\Google\Protobuf\Internal\RepeatedField $var
+ * @return $this
+ */
+ public function setResponses($var)
+ {
+ $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Google\Showcase\V1beta1\StreamingSequence\Response::class);
+ $this->responses = $arr;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/StreamingSequence/Response.php b/Gax/tests/Conformance/src/V1beta1/StreamingSequence/Response.php
new file mode 100644
index 000000000000..fde26643cd1e
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/StreamingSequence/Response.php
@@ -0,0 +1,158 @@
+google.showcase.v1beta1.StreamingSequence.Response
+ */
+class Response extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The status to return for an individual attempt.
+ *
+ * Generated from protobuf field .google.rpc.Status status = 1;
+ */
+ protected $status = null;
+ /**
+ * The amount of time to delay sending the response.
+ *
+ * Generated from protobuf field .google.protobuf.Duration delay = 2;
+ */
+ protected $delay = null;
+ /**
+ * The index that the status should be sent
+ *
+ * Generated from protobuf field int32 response_index = 3;
+ */
+ protected $response_index = 0;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type \Google\Rpc\Status $status
+ * The status to return for an individual attempt.
+ * @type \Google\Protobuf\Duration $delay
+ * The amount of time to delay sending the response.
+ * @type int $response_index
+ * The index that the status should be sent
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Sequence::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The status to return for an individual attempt.
+ *
+ * Generated from protobuf field .google.rpc.Status status = 1;
+ * @return \Google\Rpc\Status|null
+ */
+ public function getStatus()
+ {
+ return $this->status;
+ }
+
+ public function hasStatus()
+ {
+ return isset($this->status);
+ }
+
+ public function clearStatus()
+ {
+ unset($this->status);
+ }
+
+ /**
+ * The status to return for an individual attempt.
+ *
+ * Generated from protobuf field .google.rpc.Status status = 1;
+ * @param \Google\Rpc\Status $var
+ * @return $this
+ */
+ public function setStatus($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Rpc\Status::class);
+ $this->status = $var;
+
+ return $this;
+ }
+
+ /**
+ * The amount of time to delay sending the response.
+ *
+ * Generated from protobuf field .google.protobuf.Duration delay = 2;
+ * @return \Google\Protobuf\Duration|null
+ */
+ public function getDelay()
+ {
+ return $this->delay;
+ }
+
+ public function hasDelay()
+ {
+ return isset($this->delay);
+ }
+
+ public function clearDelay()
+ {
+ unset($this->delay);
+ }
+
+ /**
+ * The amount of time to delay sending the response.
+ *
+ * Generated from protobuf field .google.protobuf.Duration delay = 2;
+ * @param \Google\Protobuf\Duration $var
+ * @return $this
+ */
+ public function setDelay($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Protobuf\Duration::class);
+ $this->delay = $var;
+
+ return $this;
+ }
+
+ /**
+ * The index that the status should be sent
+ *
+ * Generated from protobuf field int32 response_index = 3;
+ * @return int
+ */
+ public function getResponseIndex()
+ {
+ return $this->response_index;
+ }
+
+ /**
+ * The index that the status should be sent
+ *
+ * Generated from protobuf field int32 response_index = 3;
+ * @param int $var
+ * @return $this
+ */
+ public function setResponseIndex($var)
+ {
+ GPBUtil::checkInt32($var);
+ $this->response_index = $var;
+
+ return $this;
+ }
+
+}
+
+// Adding a class alias for backwards compatibility with the previous class name.
+class_alias(Response::class, \Google\Showcase\V1beta1\StreamingSequence_Response::class);
+
diff --git a/Gax/tests/Conformance/src/V1beta1/StreamingSequenceReport.php b/Gax/tests/Conformance/src/V1beta1/StreamingSequenceReport.php
new file mode 100644
index 000000000000..6ed3e26d15e6
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/StreamingSequenceReport.php
@@ -0,0 +1,92 @@
+google.showcase.v1beta1.StreamingSequenceReport
+ */
+class StreamingSequenceReport extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = OUTPUT_ONLY];
+ */
+ protected $name = '';
+ /**
+ * The set of RPC attempts received by the server for a Sequence.
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.StreamingSequenceReport.Attempt attempts = 2;
+ */
+ private $attempts;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $name
+ * @type array<\Google\Showcase\V1beta1\StreamingSequenceReport\Attempt>|\Google\Protobuf\Internal\RepeatedField $attempts
+ * The set of RPC attempts received by the server for a Sequence.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Sequence::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = OUTPUT_ONLY];
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Generated from protobuf field string name = 1 [(.google.api.field_behavior) = OUTPUT_ONLY];
+ * @param string $var
+ * @return $this
+ */
+ public function setName($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->name = $var;
+
+ return $this;
+ }
+
+ /**
+ * The set of RPC attempts received by the server for a Sequence.
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.StreamingSequenceReport.Attempt attempts = 2;
+ * @return \Google\Protobuf\Internal\RepeatedField
+ */
+ public function getAttempts()
+ {
+ return $this->attempts;
+ }
+
+ /**
+ * The set of RPC attempts received by the server for a Sequence.
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.StreamingSequenceReport.Attempt attempts = 2;
+ * @param array<\Google\Showcase\V1beta1\StreamingSequenceReport\Attempt>|\Google\Protobuf\Internal\RepeatedField $var
+ * @return $this
+ */
+ public function setAttempts($var)
+ {
+ $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Google\Showcase\V1beta1\StreamingSequenceReport\Attempt::class);
+ $this->attempts = $arr;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/StreamingSequenceReport/Attempt.php b/Gax/tests/Conformance/src/V1beta1/StreamingSequenceReport/Attempt.php
new file mode 100644
index 000000000000..605fceee9e66
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/StreamingSequenceReport/Attempt.php
@@ -0,0 +1,254 @@
+google.showcase.v1beta1.StreamingSequenceReport.Attempt
+ */
+class Attempt extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The attempt number - starting at 0.
+ *
+ * Generated from protobuf field int32 attempt_number = 1;
+ */
+ protected $attempt_number = 0;
+ /**
+ * The deadline dictated by the attempt to the server.
+ *
+ * Generated from protobuf field .google.protobuf.Timestamp attempt_deadline = 2;
+ */
+ protected $attempt_deadline = null;
+ /**
+ * The time that the server responded to the RPC attempt. Used for
+ * calculating attempt_delay.
+ *
+ * Generated from protobuf field .google.protobuf.Timestamp response_time = 3;
+ */
+ protected $response_time = null;
+ /**
+ * The server perceived delay between sending the last response and
+ * receiving this attempt. Used for validating attempt delay backoff.
+ *
+ * Generated from protobuf field .google.protobuf.Duration attempt_delay = 4;
+ */
+ protected $attempt_delay = null;
+ /**
+ * The status returned to the attempt.
+ *
+ * Generated from protobuf field .google.rpc.Status status = 5;
+ */
+ protected $status = null;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type int $attempt_number
+ * The attempt number - starting at 0.
+ * @type \Google\Protobuf\Timestamp $attempt_deadline
+ * The deadline dictated by the attempt to the server.
+ * @type \Google\Protobuf\Timestamp $response_time
+ * The time that the server responded to the RPC attempt. Used for
+ * calculating attempt_delay.
+ * @type \Google\Protobuf\Duration $attempt_delay
+ * The server perceived delay between sending the last response and
+ * receiving this attempt. Used for validating attempt delay backoff.
+ * @type \Google\Rpc\Status $status
+ * The status returned to the attempt.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Sequence::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The attempt number - starting at 0.
+ *
+ * Generated from protobuf field int32 attempt_number = 1;
+ * @return int
+ */
+ public function getAttemptNumber()
+ {
+ return $this->attempt_number;
+ }
+
+ /**
+ * The attempt number - starting at 0.
+ *
+ * Generated from protobuf field int32 attempt_number = 1;
+ * @param int $var
+ * @return $this
+ */
+ public function setAttemptNumber($var)
+ {
+ GPBUtil::checkInt32($var);
+ $this->attempt_number = $var;
+
+ return $this;
+ }
+
+ /**
+ * The deadline dictated by the attempt to the server.
+ *
+ * Generated from protobuf field .google.protobuf.Timestamp attempt_deadline = 2;
+ * @return \Google\Protobuf\Timestamp|null
+ */
+ public function getAttemptDeadline()
+ {
+ return $this->attempt_deadline;
+ }
+
+ public function hasAttemptDeadline()
+ {
+ return isset($this->attempt_deadline);
+ }
+
+ public function clearAttemptDeadline()
+ {
+ unset($this->attempt_deadline);
+ }
+
+ /**
+ * The deadline dictated by the attempt to the server.
+ *
+ * Generated from protobuf field .google.protobuf.Timestamp attempt_deadline = 2;
+ * @param \Google\Protobuf\Timestamp $var
+ * @return $this
+ */
+ public function setAttemptDeadline($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Protobuf\Timestamp::class);
+ $this->attempt_deadline = $var;
+
+ return $this;
+ }
+
+ /**
+ * The time that the server responded to the RPC attempt. Used for
+ * calculating attempt_delay.
+ *
+ * Generated from protobuf field .google.protobuf.Timestamp response_time = 3;
+ * @return \Google\Protobuf\Timestamp|null
+ */
+ public function getResponseTime()
+ {
+ return $this->response_time;
+ }
+
+ public function hasResponseTime()
+ {
+ return isset($this->response_time);
+ }
+
+ public function clearResponseTime()
+ {
+ unset($this->response_time);
+ }
+
+ /**
+ * The time that the server responded to the RPC attempt. Used for
+ * calculating attempt_delay.
+ *
+ * Generated from protobuf field .google.protobuf.Timestamp response_time = 3;
+ * @param \Google\Protobuf\Timestamp $var
+ * @return $this
+ */
+ public function setResponseTime($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Protobuf\Timestamp::class);
+ $this->response_time = $var;
+
+ return $this;
+ }
+
+ /**
+ * The server perceived delay between sending the last response and
+ * receiving this attempt. Used for validating attempt delay backoff.
+ *
+ * Generated from protobuf field .google.protobuf.Duration attempt_delay = 4;
+ * @return \Google\Protobuf\Duration|null
+ */
+ public function getAttemptDelay()
+ {
+ return $this->attempt_delay;
+ }
+
+ public function hasAttemptDelay()
+ {
+ return isset($this->attempt_delay);
+ }
+
+ public function clearAttemptDelay()
+ {
+ unset($this->attempt_delay);
+ }
+
+ /**
+ * The server perceived delay between sending the last response and
+ * receiving this attempt. Used for validating attempt delay backoff.
+ *
+ * Generated from protobuf field .google.protobuf.Duration attempt_delay = 4;
+ * @param \Google\Protobuf\Duration $var
+ * @return $this
+ */
+ public function setAttemptDelay($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Protobuf\Duration::class);
+ $this->attempt_delay = $var;
+
+ return $this;
+ }
+
+ /**
+ * The status returned to the attempt.
+ *
+ * Generated from protobuf field .google.rpc.Status status = 5;
+ * @return \Google\Rpc\Status|null
+ */
+ public function getStatus()
+ {
+ return $this->status;
+ }
+
+ public function hasStatus()
+ {
+ return isset($this->status);
+ }
+
+ public function clearStatus()
+ {
+ unset($this->status);
+ }
+
+ /**
+ * The status returned to the attempt.
+ *
+ * Generated from protobuf field .google.rpc.Status status = 5;
+ * @param \Google\Rpc\Status $var
+ * @return $this
+ */
+ public function setStatus($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Rpc\Status::class);
+ $this->status = $var;
+
+ return $this;
+ }
+
+}
+
+// Adding a class alias for backwards compatibility with the previous class name.
+class_alias(Attempt::class, \Google\Showcase\V1beta1\StreamingSequenceReport_Attempt::class);
+
diff --git a/Gax/tests/Conformance/src/V1beta1/Test.php b/Gax/tests/Conformance/src/V1beta1/Test.php
new file mode 100644
index 000000000000..40235941a98a
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/Test.php
@@ -0,0 +1,187 @@
+google.showcase.v1beta1.Test
+ */
+class Test extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The name of the test.
+ * The tests/* portion of the names are hard-coded, and do not change
+ * from session to session.
+ *
+ * Generated from protobuf field string name = 1;
+ */
+ protected $name = '';
+ /**
+ * The expectation level for this test.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Test.ExpectationLevel expectation_level = 2;
+ */
+ protected $expectation_level = 0;
+ /**
+ * A description of the test.
+ *
+ * Generated from protobuf field string description = 3;
+ */
+ protected $description = '';
+ /**
+ * The blueprints that will satisfy this test. There may be multiple blueprints
+ * that can signal to the server that this test case is being exercised. Although
+ * multiple blueprints are specified, only a single blueprint needs to be run to
+ * signal that the test case was exercised.
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.Test.Blueprint blueprints = 4;
+ */
+ private $blueprints;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $name
+ * The name of the test.
+ * The tests/* portion of the names are hard-coded, and do not change
+ * from session to session.
+ * @type int $expectation_level
+ * The expectation level for this test.
+ * @type string $description
+ * A description of the test.
+ * @type array<\Google\Showcase\V1beta1\Test\Blueprint>|\Google\Protobuf\Internal\RepeatedField $blueprints
+ * The blueprints that will satisfy this test. There may be multiple blueprints
+ * that can signal to the server that this test case is being exercised. Although
+ * multiple blueprints are specified, only a single blueprint needs to be run to
+ * signal that the test case was exercised.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Testing::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The name of the test.
+ * The tests/* portion of the names are hard-coded, and do not change
+ * from session to session.
+ *
+ * Generated from protobuf field string name = 1;
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * The name of the test.
+ * The tests/* portion of the names are hard-coded, and do not change
+ * from session to session.
+ *
+ * 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;
+ }
+
+ /**
+ * The expectation level for this test.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Test.ExpectationLevel expectation_level = 2;
+ * @return int
+ */
+ public function getExpectationLevel()
+ {
+ return $this->expectation_level;
+ }
+
+ /**
+ * The expectation level for this test.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Test.ExpectationLevel expectation_level = 2;
+ * @param int $var
+ * @return $this
+ */
+ public function setExpectationLevel($var)
+ {
+ GPBUtil::checkEnum($var, \Google\Showcase\V1beta1\Test\ExpectationLevel::class);
+ $this->expectation_level = $var;
+
+ return $this;
+ }
+
+ /**
+ * A description of the test.
+ *
+ * Generated from protobuf field string description = 3;
+ * @return string
+ */
+ public function getDescription()
+ {
+ return $this->description;
+ }
+
+ /**
+ * A description of the test.
+ *
+ * Generated from protobuf field string description = 3;
+ * @param string $var
+ * @return $this
+ */
+ public function setDescription($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->description = $var;
+
+ return $this;
+ }
+
+ /**
+ * The blueprints that will satisfy this test. There may be multiple blueprints
+ * that can signal to the server that this test case is being exercised. Although
+ * multiple blueprints are specified, only a single blueprint needs to be run to
+ * signal that the test case was exercised.
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.Test.Blueprint blueprints = 4;
+ * @return \Google\Protobuf\Internal\RepeatedField
+ */
+ public function getBlueprints()
+ {
+ return $this->blueprints;
+ }
+
+ /**
+ * The blueprints that will satisfy this test. There may be multiple blueprints
+ * that can signal to the server that this test case is being exercised. Although
+ * multiple blueprints are specified, only a single blueprint needs to be run to
+ * signal that the test case was exercised.
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.Test.Blueprint blueprints = 4;
+ * @param array<\Google\Showcase\V1beta1\Test\Blueprint>|\Google\Protobuf\Internal\RepeatedField $var
+ * @return $this
+ */
+ public function setBlueprints($var)
+ {
+ $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Google\Showcase\V1beta1\Test\Blueprint::class);
+ $this->blueprints = $arr;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/Test/Blueprint.php b/Gax/tests/Conformance/src/V1beta1/Test/Blueprint.php
new file mode 100644
index 000000000000..fed874389a58
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/Test/Blueprint.php
@@ -0,0 +1,185 @@
+google.showcase.v1beta1.Test.Blueprint
+ */
+class Blueprint extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The name of this blueprint.
+ *
+ * Generated from protobuf field string name = 1;
+ */
+ protected $name = '';
+ /**
+ * A description of this blueprint.
+ *
+ * Generated from protobuf field string description = 2;
+ */
+ protected $description = '';
+ /**
+ * The initial request to trigger this test.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Test.Blueprint.Invocation request = 3;
+ */
+ protected $request = null;
+ /**
+ * An ordered list of method calls that can be called to trigger this test.
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.Test.Blueprint.Invocation additional_requests = 4;
+ */
+ private $additional_requests;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $name
+ * The name of this blueprint.
+ * @type string $description
+ * A description of this blueprint.
+ * @type \Google\Showcase\V1beta1\Test\Blueprint\Invocation $request
+ * The initial request to trigger this test.
+ * @type array<\Google\Showcase\V1beta1\Test\Blueprint\Invocation>|\Google\Protobuf\Internal\RepeatedField $additional_requests
+ * An ordered list of method calls that can be called to trigger this test.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Testing::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The name of this blueprint.
+ *
+ * Generated from protobuf field string name = 1;
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * The name of this blueprint.
+ *
+ * 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;
+ }
+
+ /**
+ * A description of this blueprint.
+ *
+ * Generated from protobuf field string description = 2;
+ * @return string
+ */
+ public function getDescription()
+ {
+ return $this->description;
+ }
+
+ /**
+ * A description of this blueprint.
+ *
+ * Generated from protobuf field string description = 2;
+ * @param string $var
+ * @return $this
+ */
+ public function setDescription($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->description = $var;
+
+ return $this;
+ }
+
+ /**
+ * The initial request to trigger this test.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Test.Blueprint.Invocation request = 3;
+ * @return \Google\Showcase\V1beta1\Test\Blueprint\Invocation|null
+ */
+ public function getRequest()
+ {
+ return $this->request;
+ }
+
+ public function hasRequest()
+ {
+ return isset($this->request);
+ }
+
+ public function clearRequest()
+ {
+ unset($this->request);
+ }
+
+ /**
+ * The initial request to trigger this test.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Test.Blueprint.Invocation request = 3;
+ * @param \Google\Showcase\V1beta1\Test\Blueprint\Invocation $var
+ * @return $this
+ */
+ public function setRequest($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Showcase\V1beta1\Test\Blueprint\Invocation::class);
+ $this->request = $var;
+
+ return $this;
+ }
+
+ /**
+ * An ordered list of method calls that can be called to trigger this test.
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.Test.Blueprint.Invocation additional_requests = 4;
+ * @return \Google\Protobuf\Internal\RepeatedField
+ */
+ public function getAdditionalRequests()
+ {
+ return $this->additional_requests;
+ }
+
+ /**
+ * An ordered list of method calls that can be called to trigger this test.
+ *
+ * Generated from protobuf field repeated .google.showcase.v1beta1.Test.Blueprint.Invocation additional_requests = 4;
+ * @param array<\Google\Showcase\V1beta1\Test\Blueprint\Invocation>|\Google\Protobuf\Internal\RepeatedField $var
+ * @return $this
+ */
+ public function setAdditionalRequests($var)
+ {
+ $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Google\Showcase\V1beta1\Test\Blueprint\Invocation::class);
+ $this->additional_requests = $arr;
+
+ return $this;
+ }
+
+}
+
+// Adding a class alias for backwards compatibility with the previous class name.
+class_alias(Blueprint::class, \Google\Showcase\V1beta1\Test_Blueprint::class);
+
diff --git a/Gax/tests/Conformance/src/V1beta1/Test/Blueprint/Invocation.php b/Gax/tests/Conformance/src/V1beta1/Test/Blueprint/Invocation.php
new file mode 100644
index 000000000000..825b65d320e8
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/Test/Blueprint/Invocation.php
@@ -0,0 +1,104 @@
+google.showcase.v1beta1.Test.Blueprint.Invocation
+ */
+class Invocation extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The fully qualified name of the showcase method to be invoked.
+ *
+ * Generated from protobuf field string method = 1;
+ */
+ protected $method = '';
+ /**
+ * The request to be made if a specific request is necessary.
+ *
+ * Generated from protobuf field bytes serialized_request = 2;
+ */
+ protected $serialized_request = '';
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $method
+ * The fully qualified name of the showcase method to be invoked.
+ * @type string $serialized_request
+ * The request to be made if a specific request is necessary.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Testing::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The fully qualified name of the showcase method to be invoked.
+ *
+ * Generated from protobuf field string method = 1;
+ * @return string
+ */
+ public function getMethod()
+ {
+ return $this->method;
+ }
+
+ /**
+ * The fully qualified name of the showcase method to be invoked.
+ *
+ * Generated from protobuf field string method = 1;
+ * @param string $var
+ * @return $this
+ */
+ public function setMethod($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->method = $var;
+
+ return $this;
+ }
+
+ /**
+ * The request to be made if a specific request is necessary.
+ *
+ * Generated from protobuf field bytes serialized_request = 2;
+ * @return string
+ */
+ public function getSerializedRequest()
+ {
+ return $this->serialized_request;
+ }
+
+ /**
+ * The request to be made if a specific request is necessary.
+ *
+ * Generated from protobuf field bytes serialized_request = 2;
+ * @param string $var
+ * @return $this
+ */
+ public function setSerializedRequest($var)
+ {
+ GPBUtil::checkString($var, False);
+ $this->serialized_request = $var;
+
+ return $this;
+ }
+
+}
+
+// Adding a class alias for backwards compatibility with the previous class name.
+class_alias(Invocation::class, \Google\Showcase\V1beta1\Test_Blueprint_Invocation::class);
+
diff --git a/Gax/tests/Conformance/src/V1beta1/Test/ExpectationLevel.php b/Gax/tests/Conformance/src/V1beta1/Test/ExpectationLevel.php
new file mode 100644
index 000000000000..1910c84e8277
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/Test/ExpectationLevel.php
@@ -0,0 +1,78 @@
+google.showcase.v1beta1.Test.ExpectationLevel
+ */
+class ExpectationLevel
+{
+ /**
+ * Generated from protobuf enum EXPECTATION_LEVEL_UNSPECIFIED = 0;
+ */
+ const EXPECTATION_LEVEL_UNSPECIFIED = 0;
+ /**
+ * This test is strictly required.
+ *
+ * Generated from protobuf enum REQUIRED = 1;
+ */
+ const REQUIRED = 1;
+ /**
+ * This test is recommended.
+ * If a generator explicitly ignores a recommended test (see `DeleteTest`),
+ * then the report may still pass, but with a warning.
+ * If a generator skips a recommended test and does not explicitly
+ * express that intention, the report will fail.
+ *
+ * Generated from protobuf enum RECOMMENDED = 2;
+ */
+ const RECOMMENDED = 2;
+ /**
+ * This test is optional.
+ * If a generator explicitly ignores an optional test (see `DeleteTest`),
+ * then the report may still pass, and no warning will be issued.
+ * If a generator skips an optional test and does not explicitly
+ * express that intention, the report may still pass, but with a
+ * warning.
+ *
+ * Generated from protobuf enum OPTIONAL = 3;
+ */
+ const OPTIONAL = 3;
+
+ private static $valueToName = [
+ self::EXPECTATION_LEVEL_UNSPECIFIED => 'EXPECTATION_LEVEL_UNSPECIFIED',
+ self::REQUIRED => 'REQUIRED',
+ self::RECOMMENDED => 'RECOMMENDED',
+ self::OPTIONAL => 'OPTIONAL',
+ ];
+
+ public static function name($value)
+ {
+ if (!isset(self::$valueToName[$value])) {
+ throw new UnexpectedValueException(sprintf(
+ 'Enum %s has no name defined for value %s', __CLASS__, $value));
+ }
+ return self::$valueToName[$value];
+ }
+
+
+ public static function value($name)
+ {
+ $const = __CLASS__ . '::' . strtoupper($name);
+ if (!defined($const)) {
+ throw new UnexpectedValueException(sprintf(
+ 'Enum %s has no value defined for name %s', __CLASS__, $name));
+ }
+ return constant($const);
+ }
+}
+
+// Adding a class alias for backwards compatibility with the previous class name.
+class_alias(ExpectationLevel::class, \Google\Showcase\V1beta1\Test_ExpectationLevel::class);
+
diff --git a/Gax/tests/Conformance/src/V1beta1/TestRun.php b/Gax/tests/Conformance/src/V1beta1/TestRun.php
new file mode 100644
index 000000000000..7e3ed07637ee
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/TestRun.php
@@ -0,0 +1,119 @@
+google.showcase.v1beta1.TestRun
+ */
+class TestRun extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The name of the test.
+ * The tests/* portion of the names are hard-coded, and do not change
+ * from session to session.
+ *
+ * Generated from protobuf field string test = 1 [(.google.api.resource_reference) = {
+ */
+ protected $test = '';
+ /**
+ * An issue found with the test run. If empty, this test run was successful.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Issue issue = 2;
+ */
+ protected $issue = null;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $test
+ * The name of the test.
+ * The tests/* portion of the names are hard-coded, and do not change
+ * from session to session.
+ * @type \Google\Showcase\V1beta1\Issue $issue
+ * An issue found with the test run. If empty, this test run was successful.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Testing::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The name of the test.
+ * The tests/* portion of the names are hard-coded, and do not change
+ * from session to session.
+ *
+ * Generated from protobuf field string test = 1 [(.google.api.resource_reference) = {
+ * @return string
+ */
+ public function getTest()
+ {
+ return $this->test;
+ }
+
+ /**
+ * The name of the test.
+ * The tests/* portion of the names are hard-coded, and do not change
+ * from session to session.
+ *
+ * Generated from protobuf field string test = 1 [(.google.api.resource_reference) = {
+ * @param string $var
+ * @return $this
+ */
+ public function setTest($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->test = $var;
+
+ return $this;
+ }
+
+ /**
+ * An issue found with the test run. If empty, this test run was successful.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Issue issue = 2;
+ * @return \Google\Showcase\V1beta1\Issue|null
+ */
+ public function getIssue()
+ {
+ return $this->issue;
+ }
+
+ public function hasIssue()
+ {
+ return isset($this->issue);
+ }
+
+ public function clearIssue()
+ {
+ unset($this->issue);
+ }
+
+ /**
+ * An issue found with the test run. If empty, this test run was successful.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Issue issue = 2;
+ * @param \Google\Showcase\V1beta1\Issue $var
+ * @return $this
+ */
+ public function setIssue($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Showcase\V1beta1\Issue::class);
+ $this->issue = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/UpdateBlurbRequest.php b/Gax/tests/Conformance/src/V1beta1/UpdateBlurbRequest.php
new file mode 100644
index 000000000000..e60ef17a0e28
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/UpdateBlurbRequest.php
@@ -0,0 +1,126 @@
+google.showcase.v1beta1.UpdateBlurbRequest
+ */
+class UpdateBlurbRequest extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The blurb to update.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Blurb blurb = 1;
+ */
+ protected $blurb = null;
+ /**
+ * The field mask to determine which fields are to be updated. If empty, the
+ * server will assume all fields are to be updated.
+ *
+ * Generated from protobuf field .google.protobuf.FieldMask update_mask = 2;
+ */
+ protected $update_mask = null;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type \Google\Showcase\V1beta1\Blurb $blurb
+ * The blurb to update.
+ * @type \Google\Protobuf\FieldMask $update_mask
+ * The field mask to determine which fields are to be updated. If empty, the
+ * server will assume all fields are to be updated.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Messaging::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The blurb to update.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Blurb blurb = 1;
+ * @return \Google\Showcase\V1beta1\Blurb|null
+ */
+ public function getBlurb()
+ {
+ return $this->blurb;
+ }
+
+ public function hasBlurb()
+ {
+ return isset($this->blurb);
+ }
+
+ public function clearBlurb()
+ {
+ unset($this->blurb);
+ }
+
+ /**
+ * The blurb to update.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Blurb blurb = 1;
+ * @param \Google\Showcase\V1beta1\Blurb $var
+ * @return $this
+ */
+ public function setBlurb($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Showcase\V1beta1\Blurb::class);
+ $this->blurb = $var;
+
+ return $this;
+ }
+
+ /**
+ * The field mask to determine which fields are to be updated. If empty, the
+ * server will assume all fields are to be updated.
+ *
+ * Generated from protobuf field .google.protobuf.FieldMask update_mask = 2;
+ * @return \Google\Protobuf\FieldMask|null
+ */
+ public function getUpdateMask()
+ {
+ return $this->update_mask;
+ }
+
+ public function hasUpdateMask()
+ {
+ return isset($this->update_mask);
+ }
+
+ public function clearUpdateMask()
+ {
+ unset($this->update_mask);
+ }
+
+ /**
+ * The field mask to determine which fields are to be updated. If empty, the
+ * server will assume all fields are to be updated.
+ *
+ * Generated from protobuf field .google.protobuf.FieldMask update_mask = 2;
+ * @param \Google\Protobuf\FieldMask $var
+ * @return $this
+ */
+ public function setUpdateMask($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Protobuf\FieldMask::class);
+ $this->update_mask = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/UpdateRoomRequest.php b/Gax/tests/Conformance/src/V1beta1/UpdateRoomRequest.php
new file mode 100644
index 000000000000..647556e88178
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/UpdateRoomRequest.php
@@ -0,0 +1,126 @@
+google.showcase.v1beta1.UpdateRoomRequest
+ */
+class UpdateRoomRequest extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The room to update.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Room room = 1;
+ */
+ protected $room = null;
+ /**
+ * The field mask to determine which fields are to be updated. If empty, the
+ * server will assume all fields are to be updated.
+ *
+ * Generated from protobuf field .google.protobuf.FieldMask update_mask = 2;
+ */
+ protected $update_mask = null;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type \Google\Showcase\V1beta1\Room $room
+ * The room to update.
+ * @type \Google\Protobuf\FieldMask $update_mask
+ * The field mask to determine which fields are to be updated. If empty, the
+ * server will assume all fields are to be updated.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Messaging::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The room to update.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Room room = 1;
+ * @return \Google\Showcase\V1beta1\Room|null
+ */
+ public function getRoom()
+ {
+ return $this->room;
+ }
+
+ public function hasRoom()
+ {
+ return isset($this->room);
+ }
+
+ public function clearRoom()
+ {
+ unset($this->room);
+ }
+
+ /**
+ * The room to update.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Room room = 1;
+ * @param \Google\Showcase\V1beta1\Room $var
+ * @return $this
+ */
+ public function setRoom($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Showcase\V1beta1\Room::class);
+ $this->room = $var;
+
+ return $this;
+ }
+
+ /**
+ * The field mask to determine which fields are to be updated. If empty, the
+ * server will assume all fields are to be updated.
+ *
+ * Generated from protobuf field .google.protobuf.FieldMask update_mask = 2;
+ * @return \Google\Protobuf\FieldMask|null
+ */
+ public function getUpdateMask()
+ {
+ return $this->update_mask;
+ }
+
+ public function hasUpdateMask()
+ {
+ return isset($this->update_mask);
+ }
+
+ public function clearUpdateMask()
+ {
+ unset($this->update_mask);
+ }
+
+ /**
+ * The field mask to determine which fields are to be updated. If empty, the
+ * server will assume all fields are to be updated.
+ *
+ * Generated from protobuf field .google.protobuf.FieldMask update_mask = 2;
+ * @param \Google\Protobuf\FieldMask $var
+ * @return $this
+ */
+ public function setUpdateMask($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Protobuf\FieldMask::class);
+ $this->update_mask = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/UpdateUserRequest.php b/Gax/tests/Conformance/src/V1beta1/UpdateUserRequest.php
new file mode 100644
index 000000000000..95b45a4e86a9
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/UpdateUserRequest.php
@@ -0,0 +1,126 @@
+google.showcase.v1beta1.UpdateUserRequest
+ */
+class UpdateUserRequest extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The user to update.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.User user = 1;
+ */
+ protected $user = null;
+ /**
+ * The field mask to determine which fields are to be updated. If empty, the
+ * server will assume all fields are to be updated.
+ *
+ * Generated from protobuf field .google.protobuf.FieldMask update_mask = 2;
+ */
+ protected $update_mask = null;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type \Google\Showcase\V1beta1\User $user
+ * The user to update.
+ * @type \Google\Protobuf\FieldMask $update_mask
+ * The field mask to determine which fields are to be updated. If empty, the
+ * server will assume all fields are to be updated.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Identity::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The user to update.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.User user = 1;
+ * @return \Google\Showcase\V1beta1\User|null
+ */
+ public function getUser()
+ {
+ return $this->user;
+ }
+
+ public function hasUser()
+ {
+ return isset($this->user);
+ }
+
+ public function clearUser()
+ {
+ unset($this->user);
+ }
+
+ /**
+ * The user to update.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.User user = 1;
+ * @param \Google\Showcase\V1beta1\User $var
+ * @return $this
+ */
+ public function setUser($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Showcase\V1beta1\User::class);
+ $this->user = $var;
+
+ return $this;
+ }
+
+ /**
+ * The field mask to determine which fields are to be updated. If empty, the
+ * server will assume all fields are to be updated.
+ *
+ * Generated from protobuf field .google.protobuf.FieldMask update_mask = 2;
+ * @return \Google\Protobuf\FieldMask|null
+ */
+ public function getUpdateMask()
+ {
+ return $this->update_mask;
+ }
+
+ public function hasUpdateMask()
+ {
+ return isset($this->update_mask);
+ }
+
+ public function clearUpdateMask()
+ {
+ unset($this->update_mask);
+ }
+
+ /**
+ * The field mask to determine which fields are to be updated. If empty, the
+ * server will assume all fields are to be updated.
+ *
+ * Generated from protobuf field .google.protobuf.FieldMask update_mask = 2;
+ * @param \Google\Protobuf\FieldMask $var
+ * @return $this
+ */
+ public function setUpdateMask($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Protobuf\FieldMask::class);
+ $this->update_mask = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/User.php b/Gax/tests/Conformance/src/V1beta1/User.php
new file mode 100644
index 000000000000..e0898a7c3d47
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/User.php
@@ -0,0 +1,415 @@
+google.showcase.v1beta1.User
+ */
+class User extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The resource name of the user.
+ *
+ * Generated from protobuf field string name = 1;
+ */
+ protected $name = '';
+ /**
+ * The display_name of the user.
+ *
+ * Generated from protobuf field string display_name = 2 [(.google.api.field_behavior) = REQUIRED];
+ */
+ protected $display_name = '';
+ /**
+ * The email address of the user.
+ *
+ * Generated from protobuf field string email = 3 [(.google.api.field_behavior) = REQUIRED];
+ */
+ protected $email = '';
+ /**
+ * The timestamp at which the user was created.
+ *
+ * Generated from protobuf field .google.protobuf.Timestamp create_time = 4 [(.google.api.field_behavior) = OUTPUT_ONLY];
+ */
+ protected $create_time = null;
+ /**
+ * The latest timestamp at which the user was updated.
+ *
+ * Generated from protobuf field .google.protobuf.Timestamp update_time = 5 [(.google.api.field_behavior) = OUTPUT_ONLY];
+ */
+ protected $update_time = null;
+ /**
+ * The age of the user in years.
+ *
+ * Generated from protobuf field optional int32 age = 6;
+ */
+ protected $age = null;
+ /**
+ * The height of the user in feet.
+ *
+ * Generated from protobuf field optional double height_feet = 7;
+ */
+ protected $height_feet = null;
+ /**
+ * The nickname of the user.
+ * (-- aip.dev/not-precedent: An empty string is a valid nickname.
+ * Ordinarily, proto3_optional should not be used on a `string` field. --)
+ *
+ * Generated from protobuf field optional string nickname = 8;
+ */
+ protected $nickname = null;
+ /**
+ * Enables the receiving of notifications. The default is true if unset.
+ * (-- aip.dev/not-precedent: The default for the feature is true.
+ * Ordinarily, the default for a `bool` field should be false. --)
+ *
+ * Generated from protobuf field optional bool enable_notifications = 9;
+ */
+ protected $enable_notifications = null;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $name
+ * The resource name of the user.
+ * @type string $display_name
+ * The display_name of the user.
+ * @type string $email
+ * The email address of the user.
+ * @type \Google\Protobuf\Timestamp $create_time
+ * The timestamp at which the user was created.
+ * @type \Google\Protobuf\Timestamp $update_time
+ * The latest timestamp at which the user was updated.
+ * @type int $age
+ * The age of the user in years.
+ * @type float $height_feet
+ * The height of the user in feet.
+ * @type string $nickname
+ * The nickname of the user.
+ * (-- aip.dev/not-precedent: An empty string is a valid nickname.
+ * Ordinarily, proto3_optional should not be used on a `string` field. --)
+ * @type bool $enable_notifications
+ * Enables the receiving of notifications. The default is true if unset.
+ * (-- aip.dev/not-precedent: The default for the feature is true.
+ * Ordinarily, the default for a `bool` field should be false. --)
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Identity::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The resource name of the user.
+ *
+ * Generated from protobuf field string name = 1;
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * The resource name of the user.
+ *
+ * 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;
+ }
+
+ /**
+ * The display_name of the user.
+ *
+ * Generated from protobuf field string display_name = 2 [(.google.api.field_behavior) = REQUIRED];
+ * @return string
+ */
+ public function getDisplayName()
+ {
+ return $this->display_name;
+ }
+
+ /**
+ * The display_name of the user.
+ *
+ * Generated from protobuf field string display_name = 2 [(.google.api.field_behavior) = REQUIRED];
+ * @param string $var
+ * @return $this
+ */
+ public function setDisplayName($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->display_name = $var;
+
+ return $this;
+ }
+
+ /**
+ * The email address of the user.
+ *
+ * Generated from protobuf field string email = 3 [(.google.api.field_behavior) = REQUIRED];
+ * @return string
+ */
+ public function getEmail()
+ {
+ return $this->email;
+ }
+
+ /**
+ * The email address of the user.
+ *
+ * Generated from protobuf field string email = 3 [(.google.api.field_behavior) = REQUIRED];
+ * @param string $var
+ * @return $this
+ */
+ public function setEmail($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->email = $var;
+
+ return $this;
+ }
+
+ /**
+ * The timestamp at which the user was created.
+ *
+ * Generated from protobuf field .google.protobuf.Timestamp create_time = 4 [(.google.api.field_behavior) = OUTPUT_ONLY];
+ * @return \Google\Protobuf\Timestamp|null
+ */
+ public function getCreateTime()
+ {
+ return $this->create_time;
+ }
+
+ public function hasCreateTime()
+ {
+ return isset($this->create_time);
+ }
+
+ public function clearCreateTime()
+ {
+ unset($this->create_time);
+ }
+
+ /**
+ * The timestamp at which the user was created.
+ *
+ * Generated from protobuf field .google.protobuf.Timestamp create_time = 4 [(.google.api.field_behavior) = OUTPUT_ONLY];
+ * @param \Google\Protobuf\Timestamp $var
+ * @return $this
+ */
+ public function setCreateTime($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Protobuf\Timestamp::class);
+ $this->create_time = $var;
+
+ return $this;
+ }
+
+ /**
+ * The latest timestamp at which the user was updated.
+ *
+ * Generated from protobuf field .google.protobuf.Timestamp update_time = 5 [(.google.api.field_behavior) = OUTPUT_ONLY];
+ * @return \Google\Protobuf\Timestamp|null
+ */
+ public function getUpdateTime()
+ {
+ return $this->update_time;
+ }
+
+ public function hasUpdateTime()
+ {
+ return isset($this->update_time);
+ }
+
+ public function clearUpdateTime()
+ {
+ unset($this->update_time);
+ }
+
+ /**
+ * The latest timestamp at which the user was updated.
+ *
+ * Generated from protobuf field .google.protobuf.Timestamp update_time = 5 [(.google.api.field_behavior) = OUTPUT_ONLY];
+ * @param \Google\Protobuf\Timestamp $var
+ * @return $this
+ */
+ public function setUpdateTime($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Protobuf\Timestamp::class);
+ $this->update_time = $var;
+
+ return $this;
+ }
+
+ /**
+ * The age of the user in years.
+ *
+ * Generated from protobuf field optional int32 age = 6;
+ * @return int
+ */
+ public function getAge()
+ {
+ return isset($this->age) ? $this->age : 0;
+ }
+
+ public function hasAge()
+ {
+ return isset($this->age);
+ }
+
+ public function clearAge()
+ {
+ unset($this->age);
+ }
+
+ /**
+ * The age of the user in years.
+ *
+ * Generated from protobuf field optional int32 age = 6;
+ * @param int $var
+ * @return $this
+ */
+ public function setAge($var)
+ {
+ GPBUtil::checkInt32($var);
+ $this->age = $var;
+
+ return $this;
+ }
+
+ /**
+ * The height of the user in feet.
+ *
+ * Generated from protobuf field optional double height_feet = 7;
+ * @return float
+ */
+ public function getHeightFeet()
+ {
+ return isset($this->height_feet) ? $this->height_feet : 0.0;
+ }
+
+ public function hasHeightFeet()
+ {
+ return isset($this->height_feet);
+ }
+
+ public function clearHeightFeet()
+ {
+ unset($this->height_feet);
+ }
+
+ /**
+ * The height of the user in feet.
+ *
+ * Generated from protobuf field optional double height_feet = 7;
+ * @param float $var
+ * @return $this
+ */
+ public function setHeightFeet($var)
+ {
+ GPBUtil::checkDouble($var);
+ $this->height_feet = $var;
+
+ return $this;
+ }
+
+ /**
+ * The nickname of the user.
+ * (-- aip.dev/not-precedent: An empty string is a valid nickname.
+ * Ordinarily, proto3_optional should not be used on a `string` field. --)
+ *
+ * Generated from protobuf field optional string nickname = 8;
+ * @return string
+ */
+ public function getNickname()
+ {
+ return isset($this->nickname) ? $this->nickname : '';
+ }
+
+ public function hasNickname()
+ {
+ return isset($this->nickname);
+ }
+
+ public function clearNickname()
+ {
+ unset($this->nickname);
+ }
+
+ /**
+ * The nickname of the user.
+ * (-- aip.dev/not-precedent: An empty string is a valid nickname.
+ * Ordinarily, proto3_optional should not be used on a `string` field. --)
+ *
+ * Generated from protobuf field optional string nickname = 8;
+ * @param string $var
+ * @return $this
+ */
+ public function setNickname($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->nickname = $var;
+
+ return $this;
+ }
+
+ /**
+ * Enables the receiving of notifications. The default is true if unset.
+ * (-- aip.dev/not-precedent: The default for the feature is true.
+ * Ordinarily, the default for a `bool` field should be false. --)
+ *
+ * Generated from protobuf field optional bool enable_notifications = 9;
+ * @return bool
+ */
+ public function getEnableNotifications()
+ {
+ return isset($this->enable_notifications) ? $this->enable_notifications : false;
+ }
+
+ public function hasEnableNotifications()
+ {
+ return isset($this->enable_notifications);
+ }
+
+ public function clearEnableNotifications()
+ {
+ unset($this->enable_notifications);
+ }
+
+ /**
+ * Enables the receiving of notifications. The default is true if unset.
+ * (-- aip.dev/not-precedent: The default for the feature is true.
+ * Ordinarily, the default for a `bool` field should be false. --)
+ *
+ * Generated from protobuf field optional bool enable_notifications = 9;
+ * @param bool $var
+ * @return $this
+ */
+ public function setEnableNotifications($var)
+ {
+ GPBUtil::checkBool($var);
+ $this->enable_notifications = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/VerifyTestRequest.php b/Gax/tests/Conformance/src/V1beta1/VerifyTestRequest.php
new file mode 100644
index 000000000000..6e05fcf74ce6
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/VerifyTestRequest.php
@@ -0,0 +1,133 @@
+google.showcase.v1beta1.VerifyTestRequest
+ */
+class VerifyTestRequest extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The test to have an answer registered to it.
+ *
+ * Generated from protobuf field string name = 1 [(.google.api.resource_reference) = {
+ */
+ protected $name = '';
+ /**
+ * The answer from the test.
+ *
+ * Generated from protobuf field bytes answer = 2;
+ */
+ protected $answer = '';
+ /**
+ * The answers from the test if multiple are to be checked
+ *
+ * Generated from protobuf field repeated bytes answers = 3;
+ */
+ private $answers;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $name
+ * The test to have an answer registered to it.
+ * @type string $answer
+ * The answer from the test.
+ * @type array|\Google\Protobuf\Internal\RepeatedField $answers
+ * The answers from the test if multiple are to be checked
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Testing::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The test to have an answer registered to it.
+ *
+ * Generated from protobuf field string name = 1 [(.google.api.resource_reference) = {
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * The test to have an answer registered to it.
+ *
+ * Generated from protobuf field string name = 1 [(.google.api.resource_reference) = {
+ * @param string $var
+ * @return $this
+ */
+ public function setName($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->name = $var;
+
+ return $this;
+ }
+
+ /**
+ * The answer from the test.
+ *
+ * Generated from protobuf field bytes answer = 2;
+ * @return string
+ */
+ public function getAnswer()
+ {
+ return $this->answer;
+ }
+
+ /**
+ * The answer from the test.
+ *
+ * Generated from protobuf field bytes answer = 2;
+ * @param string $var
+ * @return $this
+ */
+ public function setAnswer($var)
+ {
+ GPBUtil::checkString($var, False);
+ $this->answer = $var;
+
+ return $this;
+ }
+
+ /**
+ * The answers from the test if multiple are to be checked
+ *
+ * Generated from protobuf field repeated bytes answers = 3;
+ * @return \Google\Protobuf\Internal\RepeatedField
+ */
+ public function getAnswers()
+ {
+ return $this->answers;
+ }
+
+ /**
+ * The answers from the test if multiple are to be checked
+ *
+ * Generated from protobuf field repeated bytes answers = 3;
+ * @param array|\Google\Protobuf\Internal\RepeatedField $var
+ * @return $this
+ */
+ public function setAnswers($var)
+ {
+ $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::BYTES);
+ $this->answers = $arr;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/VerifyTestResponse.php b/Gax/tests/Conformance/src/V1beta1/VerifyTestResponse.php
new file mode 100644
index 000000000000..80f2d2009c61
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/VerifyTestResponse.php
@@ -0,0 +1,75 @@
+google.showcase.v1beta1.VerifyTestResponse
+ */
+class VerifyTestResponse extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * An issue if check answer was unsuccessful. This will be empty if the check answer succeeded.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Issue issue = 1;
+ */
+ protected $issue = null;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type \Google\Showcase\V1beta1\Issue $issue
+ * An issue if check answer was unsuccessful. This will be empty if the check answer succeeded.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\Testing::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * An issue if check answer was unsuccessful. This will be empty if the check answer succeeded.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Issue issue = 1;
+ * @return \Google\Showcase\V1beta1\Issue|null
+ */
+ public function getIssue()
+ {
+ return $this->issue;
+ }
+
+ public function hasIssue()
+ {
+ return isset($this->issue);
+ }
+
+ public function clearIssue()
+ {
+ unset($this->issue);
+ }
+
+ /**
+ * An issue if check answer was unsuccessful. This will be empty if the check answer succeeded.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.Issue issue = 1;
+ * @param \Google\Showcase\V1beta1\Issue $var
+ * @return $this
+ */
+ public function setIssue($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Showcase\V1beta1\Issue::class);
+ $this->issue = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/WaitMetadata.php b/Gax/tests/Conformance/src/V1beta1/WaitMetadata.php
new file mode 100644
index 000000000000..83481a02ec1a
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/WaitMetadata.php
@@ -0,0 +1,77 @@
+google.showcase.v1beta1.WaitMetadata
+ */
+class WaitMetadata extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * The time that this operation will complete.
+ *
+ * Generated from protobuf field .google.protobuf.Timestamp end_time = 1;
+ */
+ protected $end_time = null;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type \Google\Protobuf\Timestamp $end_time
+ * The time that this operation will complete.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\PBEcho::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The time that this operation will complete.
+ *
+ * Generated from protobuf field .google.protobuf.Timestamp end_time = 1;
+ * @return \Google\Protobuf\Timestamp|null
+ */
+ public function getEndTime()
+ {
+ return $this->end_time;
+ }
+
+ public function hasEndTime()
+ {
+ return isset($this->end_time);
+ }
+
+ public function clearEndTime()
+ {
+ unset($this->end_time);
+ }
+
+ /**
+ * The time that this operation will complete.
+ *
+ * Generated from protobuf field .google.protobuf.Timestamp end_time = 1;
+ * @param \Google\Protobuf\Timestamp $var
+ * @return $this
+ */
+ public function setEndTime($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Protobuf\Timestamp::class);
+ $this->end_time = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/WaitRequest.php b/Gax/tests/Conformance/src/V1beta1/WaitRequest.php
new file mode 100644
index 000000000000..d90348d2c9cf
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/WaitRequest.php
@@ -0,0 +1,186 @@
+google.showcase.v1beta1.WaitRequest
+ */
+class WaitRequest extends \Google\Protobuf\Internal\Message
+{
+ protected $end;
+ protected $response;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type \Google\Protobuf\Timestamp $end_time
+ * The time that this operation will complete.
+ * @type \Google\Protobuf\Duration $ttl
+ * The duration of this operation.
+ * @type \Google\Rpc\Status $error
+ * The error that will be returned by the server. If this code is specified
+ * to be the OK rpc code, an empty response will be returned.
+ * @type \Google\Showcase\V1beta1\WaitResponse $success
+ * The response to be returned on operation completion.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\PBEcho::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * The time that this operation will complete.
+ *
+ * Generated from protobuf field .google.protobuf.Timestamp end_time = 1;
+ * @return \Google\Protobuf\Timestamp|null
+ */
+ public function getEndTime()
+ {
+ return $this->readOneof(1);
+ }
+
+ public function hasEndTime()
+ {
+ return $this->hasOneof(1);
+ }
+
+ /**
+ * The time that this operation will complete.
+ *
+ * Generated from protobuf field .google.protobuf.Timestamp end_time = 1;
+ * @param \Google\Protobuf\Timestamp $var
+ * @return $this
+ */
+ public function setEndTime($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Protobuf\Timestamp::class);
+ $this->writeOneof(1, $var);
+
+ return $this;
+ }
+
+ /**
+ * The duration of this operation.
+ *
+ * Generated from protobuf field .google.protobuf.Duration ttl = 4;
+ * @return \Google\Protobuf\Duration|null
+ */
+ public function getTtl()
+ {
+ return $this->readOneof(4);
+ }
+
+ public function hasTtl()
+ {
+ return $this->hasOneof(4);
+ }
+
+ /**
+ * The duration of this operation.
+ *
+ * Generated from protobuf field .google.protobuf.Duration ttl = 4;
+ * @param \Google\Protobuf\Duration $var
+ * @return $this
+ */
+ public function setTtl($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Protobuf\Duration::class);
+ $this->writeOneof(4, $var);
+
+ return $this;
+ }
+
+ /**
+ * The error that will be returned by the server. If this code is specified
+ * to be the OK rpc code, an empty response will be returned.
+ *
+ * Generated from protobuf field .google.rpc.Status error = 2;
+ * @return \Google\Rpc\Status|null
+ */
+ public function getError()
+ {
+ return $this->readOneof(2);
+ }
+
+ public function hasError()
+ {
+ return $this->hasOneof(2);
+ }
+
+ /**
+ * The error that will be returned by the server. If this code is specified
+ * to be the OK rpc code, an empty response will be returned.
+ *
+ * Generated from protobuf field .google.rpc.Status error = 2;
+ * @param \Google\Rpc\Status $var
+ * @return $this
+ */
+ public function setError($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Rpc\Status::class);
+ $this->writeOneof(2, $var);
+
+ return $this;
+ }
+
+ /**
+ * The response to be returned on operation completion.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.WaitResponse success = 3;
+ * @return \Google\Showcase\V1beta1\WaitResponse|null
+ */
+ public function getSuccess()
+ {
+ return $this->readOneof(3);
+ }
+
+ public function hasSuccess()
+ {
+ return $this->hasOneof(3);
+ }
+
+ /**
+ * The response to be returned on operation completion.
+ *
+ * Generated from protobuf field .google.showcase.v1beta1.WaitResponse success = 3;
+ * @param \Google\Showcase\V1beta1\WaitResponse $var
+ * @return $this
+ */
+ public function setSuccess($var)
+ {
+ GPBUtil::checkMessage($var, \Google\Showcase\V1beta1\WaitResponse::class);
+ $this->writeOneof(3, $var);
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getEnd()
+ {
+ return $this->whichOneof("end");
+ }
+
+ /**
+ * @return string
+ */
+ public function getResponse()
+ {
+ return $this->whichOneof("response");
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/WaitResponse.php b/Gax/tests/Conformance/src/V1beta1/WaitResponse.php
new file mode 100644
index 000000000000..9d882e3012f9
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/WaitResponse.php
@@ -0,0 +1,67 @@
+google.showcase.v1beta1.WaitResponse
+ */
+class WaitResponse extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * This content of the result.
+ *
+ * Generated from protobuf field string content = 1;
+ */
+ protected $content = '';
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $content
+ * This content of the result.
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Google\Showcase\V1Beta1\PBEcho::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * This content of the result.
+ *
+ * Generated from protobuf field string content = 1;
+ * @return string
+ */
+ public function getContent()
+ {
+ return $this->content;
+ }
+
+ /**
+ * This content of the result.
+ *
+ * Generated from protobuf field string content = 1;
+ * @param string $var
+ * @return $this
+ */
+ public function setContent($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->content = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/Gax/tests/Conformance/src/V1beta1/gapic_metadata.json b/Gax/tests/Conformance/src/V1beta1/gapic_metadata.json
new file mode 100644
index 000000000000..ae1e9b817740
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/gapic_metadata.json
@@ -0,0 +1,333 @@
+{
+ "schema": "1.0",
+ "comment": "This file maps proto services\/RPCs to the corresponding library clients\/methods",
+ "language": "php",
+ "protoPackage": "google.showcase.v1beta1",
+ "libraryPackage": "Google\\Showcase\\V1beta1",
+ "services": {
+ "Compliance": {
+ "clients": {
+ "grpc": {
+ "libraryClient": "ComplianceGapicClient",
+ "rpcs": {
+ "GetEnum": {
+ "methods": [
+ "getEnum"
+ ]
+ },
+ "RepeatDataBody": {
+ "methods": [
+ "repeatDataBody"
+ ]
+ },
+ "RepeatDataBodyInfo": {
+ "methods": [
+ "repeatDataBodyInfo"
+ ]
+ },
+ "RepeatDataBodyPatch": {
+ "methods": [
+ "repeatDataBodyPatch"
+ ]
+ },
+ "RepeatDataBodyPut": {
+ "methods": [
+ "repeatDataBodyPut"
+ ]
+ },
+ "RepeatDataPathResource": {
+ "methods": [
+ "repeatDataPathResource"
+ ]
+ },
+ "RepeatDataPathTrailingResource": {
+ "methods": [
+ "repeatDataPathTrailingResource"
+ ]
+ },
+ "RepeatDataQuery": {
+ "methods": [
+ "repeatDataQuery"
+ ]
+ },
+ "RepeatDataSimplePath": {
+ "methods": [
+ "repeatDataSimplePath"
+ ]
+ },
+ "VerifyEnum": {
+ "methods": [
+ "verifyEnum"
+ ]
+ }
+ }
+ }
+ }
+ },
+ "Echo": {
+ "clients": {
+ "grpc": {
+ "libraryClient": "EchoGapicClient",
+ "rpcs": {
+ "Block": {
+ "methods": [
+ "block"
+ ]
+ },
+ "Chat": {
+ "methods": [
+ "chat"
+ ]
+ },
+ "Collect": {
+ "methods": [
+ "collect"
+ ]
+ },
+ "Echo": {
+ "methods": [
+ "echo"
+ ]
+ },
+ "EchoErrorDetails": {
+ "methods": [
+ "echoErrorDetails"
+ ]
+ },
+ "Expand": {
+ "methods": [
+ "expand"
+ ]
+ },
+ "FailEchoWithDetails": {
+ "methods": [
+ "failEchoWithDetails"
+ ]
+ },
+ "PagedExpand": {
+ "methods": [
+ "pagedExpand"
+ ]
+ },
+ "PagedExpandLegacy": {
+ "methods": [
+ "pagedExpandLegacy"
+ ]
+ },
+ "PagedExpandLegacyMapped": {
+ "methods": [
+ "pagedExpandLegacyMapped"
+ ]
+ },
+ "Wait": {
+ "methods": [
+ "wait"
+ ]
+ }
+ }
+ }
+ }
+ },
+ "Identity": {
+ "clients": {
+ "grpc": {
+ "libraryClient": "IdentityGapicClient",
+ "rpcs": {
+ "CreateUser": {
+ "methods": [
+ "createUser"
+ ]
+ },
+ "DeleteUser": {
+ "methods": [
+ "deleteUser"
+ ]
+ },
+ "GetUser": {
+ "methods": [
+ "getUser"
+ ]
+ },
+ "ListUsers": {
+ "methods": [
+ "listUsers"
+ ]
+ },
+ "UpdateUser": {
+ "methods": [
+ "updateUser"
+ ]
+ }
+ }
+ }
+ }
+ },
+ "Messaging": {
+ "clients": {
+ "grpc": {
+ "libraryClient": "MessagingGapicClient",
+ "rpcs": {
+ "Connect": {
+ "methods": [
+ "connect"
+ ]
+ },
+ "CreateBlurb": {
+ "methods": [
+ "createBlurb"
+ ]
+ },
+ "CreateRoom": {
+ "methods": [
+ "createRoom"
+ ]
+ },
+ "DeleteBlurb": {
+ "methods": [
+ "deleteBlurb"
+ ]
+ },
+ "DeleteRoom": {
+ "methods": [
+ "deleteRoom"
+ ]
+ },
+ "GetBlurb": {
+ "methods": [
+ "getBlurb"
+ ]
+ },
+ "GetRoom": {
+ "methods": [
+ "getRoom"
+ ]
+ },
+ "ListBlurbs": {
+ "methods": [
+ "listBlurbs"
+ ]
+ },
+ "ListRooms": {
+ "methods": [
+ "listRooms"
+ ]
+ },
+ "SearchBlurbs": {
+ "methods": [
+ "searchBlurbs"
+ ]
+ },
+ "SendBlurbs": {
+ "methods": [
+ "sendBlurbs"
+ ]
+ },
+ "StreamBlurbs": {
+ "methods": [
+ "streamBlurbs"
+ ]
+ },
+ "UpdateBlurb": {
+ "methods": [
+ "updateBlurb"
+ ]
+ },
+ "UpdateRoom": {
+ "methods": [
+ "updateRoom"
+ ]
+ }
+ }
+ }
+ }
+ },
+ "SequenceService": {
+ "clients": {
+ "grpc": {
+ "libraryClient": "SequenceServiceGapicClient",
+ "rpcs": {
+ "AttemptSequence": {
+ "methods": [
+ "attemptSequence"
+ ]
+ },
+ "AttemptStreamingSequence": {
+ "methods": [
+ "attemptStreamingSequence"
+ ]
+ },
+ "CreateSequence": {
+ "methods": [
+ "createSequence"
+ ]
+ },
+ "CreateStreamingSequence": {
+ "methods": [
+ "createStreamingSequence"
+ ]
+ },
+ "GetSequenceReport": {
+ "methods": [
+ "getSequenceReport"
+ ]
+ },
+ "GetStreamingSequenceReport": {
+ "methods": [
+ "getStreamingSequenceReport"
+ ]
+ }
+ }
+ }
+ }
+ },
+ "Testing": {
+ "clients": {
+ "grpc": {
+ "libraryClient": "TestingGapicClient",
+ "rpcs": {
+ "CreateSession": {
+ "methods": [
+ "createSession"
+ ]
+ },
+ "DeleteSession": {
+ "methods": [
+ "deleteSession"
+ ]
+ },
+ "DeleteTest": {
+ "methods": [
+ "deleteTest"
+ ]
+ },
+ "GetSession": {
+ "methods": [
+ "getSession"
+ ]
+ },
+ "ListSessions": {
+ "methods": [
+ "listSessions"
+ ]
+ },
+ "ListTests": {
+ "methods": [
+ "listTests"
+ ]
+ },
+ "ReportSession": {
+ "methods": [
+ "reportSession"
+ ]
+ },
+ "VerifyTest": {
+ "methods": [
+ "verifyTest"
+ ]
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gax/tests/Conformance/src/V1beta1/resources/compliance_client_config.json b/Gax/tests/Conformance/src/V1beta1/resources/compliance_client_config.json
new file mode 100644
index 000000000000..106ead7ba366
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/resources/compliance_client_config.json
@@ -0,0 +1,72 @@
+{
+ "interfaces": {
+ "google.showcase.v1beta1.Compliance": {
+ "retry_codes": {
+ "no_retry_codes": []
+ },
+ "retry_params": {
+ "no_retry_params": {
+ "initial_retry_delay_millis": 0,
+ "retry_delay_multiplier": 0.0,
+ "max_retry_delay_millis": 0,
+ "initial_rpc_timeout_millis": 0,
+ "rpc_timeout_multiplier": 1.0,
+ "max_rpc_timeout_millis": 0,
+ "total_timeout_millis": 0
+ }
+ },
+ "methods": {
+ "GetEnum": {
+ "timeout_millis": 60000,
+ "retry_codes_name": "no_retry_codes",
+ "retry_params_name": "no_retry_params"
+ },
+ "RepeatDataBody": {
+ "timeout_millis": 60000,
+ "retry_codes_name": "no_retry_codes",
+ "retry_params_name": "no_retry_params"
+ },
+ "RepeatDataBodyInfo": {
+ "timeout_millis": 60000,
+ "retry_codes_name": "no_retry_codes",
+ "retry_params_name": "no_retry_params"
+ },
+ "RepeatDataBodyPatch": {
+ "timeout_millis": 60000,
+ "retry_codes_name": "no_retry_codes",
+ "retry_params_name": "no_retry_params"
+ },
+ "RepeatDataBodyPut": {
+ "timeout_millis": 60000,
+ "retry_codes_name": "no_retry_codes",
+ "retry_params_name": "no_retry_params"
+ },
+ "RepeatDataPathResource": {
+ "timeout_millis": 60000,
+ "retry_codes_name": "no_retry_codes",
+ "retry_params_name": "no_retry_params"
+ },
+ "RepeatDataPathTrailingResource": {
+ "timeout_millis": 60000,
+ "retry_codes_name": "no_retry_codes",
+ "retry_params_name": "no_retry_params"
+ },
+ "RepeatDataQuery": {
+ "timeout_millis": 60000,
+ "retry_codes_name": "no_retry_codes",
+ "retry_params_name": "no_retry_params"
+ },
+ "RepeatDataSimplePath": {
+ "timeout_millis": 60000,
+ "retry_codes_name": "no_retry_codes",
+ "retry_params_name": "no_retry_params"
+ },
+ "VerifyEnum": {
+ "timeout_millis": 60000,
+ "retry_codes_name": "no_retry_codes",
+ "retry_params_name": "no_retry_params"
+ }
+ }
+ }
+ }
+}
diff --git a/Gax/tests/Conformance/src/V1beta1/resources/compliance_descriptor_config.php b/Gax/tests/Conformance/src/V1beta1/resources/compliance_descriptor_config.php
new file mode 100644
index 000000000000..e1e9126106fd
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/resources/compliance_descriptor_config.php
@@ -0,0 +1,146 @@
+ [
+ 'google.showcase.v1beta1.Compliance' => [
+ 'GetEnum' => [
+ 'callType' => \Google\ApiCore\Call::UNARY_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\EnumResponse',
+ ],
+ 'RepeatDataBody' => [
+ 'callType' => \Google\ApiCore\Call::UNARY_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\RepeatResponse',
+ ],
+ 'RepeatDataBodyInfo' => [
+ 'callType' => \Google\ApiCore\Call::UNARY_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\RepeatResponse',
+ ],
+ 'RepeatDataBodyPatch' => [
+ 'callType' => \Google\ApiCore\Call::UNARY_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\RepeatResponse',
+ ],
+ 'RepeatDataBodyPut' => [
+ 'callType' => \Google\ApiCore\Call::UNARY_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\RepeatResponse',
+ ],
+ 'RepeatDataPathResource' => [
+ 'callType' => \Google\ApiCore\Call::UNARY_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\RepeatResponse',
+ 'headerParams' => [
+ [
+ 'keyName' => 'info.f_child.f_string',
+ 'fieldAccessors' => [
+ 'getInfo',
+ 'getFChild',
+ 'getFString',
+ ],
+ ],
+ [
+ 'keyName' => 'info.f_string',
+ 'fieldAccessors' => [
+ 'getInfo',
+ 'getFString',
+ ],
+ ],
+ [
+ 'keyName' => 'info.f_bool',
+ 'fieldAccessors' => [
+ 'getInfo',
+ 'getFBool',
+ ],
+ ],
+ ],
+ ],
+ 'RepeatDataPathTrailingResource' => [
+ 'callType' => \Google\ApiCore\Call::UNARY_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\RepeatResponse',
+ 'headerParams' => [
+ [
+ 'keyName' => 'info.f_string',
+ 'fieldAccessors' => [
+ 'getInfo',
+ 'getFString',
+ ],
+ ],
+ [
+ 'keyName' => 'info.f_child.f_string',
+ 'fieldAccessors' => [
+ 'getInfo',
+ 'getFChild',
+ 'getFString',
+ ],
+ ],
+ ],
+ ],
+ 'RepeatDataQuery' => [
+ 'callType' => \Google\ApiCore\Call::UNARY_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\RepeatResponse',
+ ],
+ 'RepeatDataSimplePath' => [
+ 'callType' => \Google\ApiCore\Call::UNARY_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\RepeatResponse',
+ 'headerParams' => [
+ [
+ 'keyName' => 'info.f_string',
+ 'fieldAccessors' => [
+ 'getInfo',
+ 'getFString',
+ ],
+ ],
+ [
+ 'keyName' => 'info.f_int32',
+ 'fieldAccessors' => [
+ 'getInfo',
+ 'getFInt32',
+ ],
+ ],
+ [
+ 'keyName' => 'info.f_double',
+ 'fieldAccessors' => [
+ 'getInfo',
+ 'getFDouble',
+ ],
+ ],
+ [
+ 'keyName' => 'info.f_bool',
+ 'fieldAccessors' => [
+ 'getInfo',
+ 'getFBool',
+ ],
+ ],
+ [
+ 'keyName' => 'info.f_kingdom',
+ 'fieldAccessors' => [
+ 'getInfo',
+ 'getFKingdom',
+ ],
+ ],
+ ],
+ ],
+ 'VerifyEnum' => [
+ 'callType' => \Google\ApiCore\Call::UNARY_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\EnumResponse',
+ ],
+ ],
+ ],
+];
diff --git a/Gax/tests/Conformance/src/V1beta1/resources/compliance_rest_client_config.php b/Gax/tests/Conformance/src/V1beta1/resources/compliance_rest_client_config.php
new file mode 100644
index 000000000000..ffdb057e80f8
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/resources/compliance_rest_client_config.php
@@ -0,0 +1,295 @@
+ [
+ 'google.cloud.location.Locations' => [
+ 'ListLocations' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{name=projects/*}/locations',
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'GetLocation' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{name=projects/*/locations/*}',
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ ],
+ 'google.iam.v1.IAMPolicy' => [
+ 'SetIamPolicy' => [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=users/*}:setIamPolicy',
+ 'body' => '*',
+ 'additionalBindings' => [
+ [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=rooms/*}:setIamPolicy',
+ 'body' => '*',
+ ],
+ [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=rooms/*/blurbs/*}:setIamPolicy',
+ 'body' => '*',
+ ],
+ [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=sequences/*}:setIamPolicy',
+ 'body' => '*',
+ ],
+ ],
+ 'placeholders' => [
+ 'resource' => [
+ 'getters' => [
+ 'getResource',
+ ],
+ ],
+ ],
+ ],
+ 'GetIamPolicy' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{resource=users/*}:getIamPolicy',
+ 'additionalBindings' => [
+ [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{resource=rooms/*}:getIamPolicy',
+ ],
+ [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{resource=rooms/*/blurbs/*}:getIamPolicy',
+ ],
+ [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{resource=sequences/*}:getIamPolicy',
+ ],
+ ],
+ 'placeholders' => [
+ 'resource' => [
+ 'getters' => [
+ 'getResource',
+ ],
+ ],
+ ],
+ ],
+ 'TestIamPermissions' => [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=users/*}:testIamPermissions',
+ 'body' => '*',
+ 'additionalBindings' => [
+ [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=rooms/*}:testIamPermissions',
+ 'body' => '*',
+ ],
+ [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=rooms/*/blurbs/*}:testIamPermissions',
+ 'body' => '*',
+ ],
+ [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=sequences/*}:testIamPermissions',
+ 'body' => '*',
+ ],
+ ],
+ 'placeholders' => [
+ 'resource' => [
+ 'getters' => [
+ 'getResource',
+ ],
+ ],
+ ],
+ ],
+ ],
+ 'google.longrunning.Operations' => [
+ 'ListOperations' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/operations',
+ ],
+ 'GetOperation' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{name=operations/**}',
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'DeleteOperation' => [
+ 'method' => 'delete',
+ 'uriTemplate' => '/v1beta1/{name=operations/**}',
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'CancelOperation' => [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{name=operations/**}:cancel',
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ ],
+ 'google.showcase.v1beta1.Compliance' => [
+ 'GetEnum' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/compliance/enum',
+ ],
+ 'RepeatDataBody' => [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/repeat:body',
+ 'body' => '*',
+ ],
+ 'RepeatDataBodyInfo' => [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/repeat:bodyinfo',
+ 'body' => 'info',
+ ],
+ 'RepeatDataBodyPatch' => [
+ 'method' => 'patch',
+ 'uriTemplate' => '/v1beta1/repeat:bodypatch',
+ 'body' => '*',
+ ],
+ 'RepeatDataBodyPut' => [
+ 'method' => 'put',
+ 'uriTemplate' => '/v1beta1/repeat:bodyput',
+ 'body' => '*',
+ ],
+ 'RepeatDataPathResource' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/repeat/{info.f_string=first/*}/{info.f_child.f_string=second/*}/bool/{info.f_bool}:pathresource',
+ 'additionalBindings' => [
+ [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/repeat/{info.f_child.f_string=first/*}/{info.f_string=second/*}/bool/{info.f_bool}:childfirstpathresource',
+ ],
+ ],
+ 'placeholders' => [
+ 'info.f_bool' => [
+ 'getters' => [
+ 'getInfo',
+ 'getFBool',
+ ],
+ ],
+ 'info.f_child.f_string' => [
+ 'getters' => [
+ 'getInfo',
+ 'getFChild',
+ 'getFString',
+ ],
+ ],
+ 'info.f_string' => [
+ 'getters' => [
+ 'getInfo',
+ 'getFString',
+ ],
+ ],
+ ],
+ ],
+ 'RepeatDataPathTrailingResource' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/repeat/{info.f_string=first/*}/{info.f_child.f_string=second/**}:pathtrailingresource',
+ 'placeholders' => [
+ 'info.f_child.f_string' => [
+ 'getters' => [
+ 'getInfo',
+ 'getFChild',
+ 'getFString',
+ ],
+ ],
+ 'info.f_string' => [
+ 'getters' => [
+ 'getInfo',
+ 'getFString',
+ ],
+ ],
+ ],
+ ],
+ 'RepeatDataQuery' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/repeat:query',
+ ],
+ 'RepeatDataSimplePath' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/repeat/{info.f_string}/{info.f_int32}/{info.f_double}/{info.f_bool}/{info.f_kingdom}:simplepath',
+ 'placeholders' => [
+ 'info.f_bool' => [
+ 'getters' => [
+ 'getInfo',
+ 'getFBool',
+ ],
+ ],
+ 'info.f_double' => [
+ 'getters' => [
+ 'getInfo',
+ 'getFDouble',
+ ],
+ ],
+ 'info.f_int32' => [
+ 'getters' => [
+ 'getInfo',
+ 'getFInt32',
+ ],
+ ],
+ 'info.f_kingdom' => [
+ 'getters' => [
+ 'getInfo',
+ 'getFKingdom',
+ ],
+ ],
+ 'info.f_string' => [
+ 'getters' => [
+ 'getInfo',
+ 'getFString',
+ ],
+ ],
+ ],
+ ],
+ 'VerifyEnum' => [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/compliance/enum',
+ ],
+ ],
+ ],
+ 'numericEnums' => true,
+];
diff --git a/Gax/tests/Conformance/src/V1beta1/resources/echo_client_config.json b/Gax/tests/Conformance/src/V1beta1/resources/echo_client_config.json
new file mode 100644
index 000000000000..258aa31371c5
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/resources/echo_client_config.json
@@ -0,0 +1,94 @@
+{
+ "interfaces": {
+ "google.showcase.v1beta1.Echo": {
+ "retry_codes": {
+ "no_retry_codes": [],
+ "no_retry_1_codes": [],
+ "retry_policy_1_codes": [
+ "UNAVAILABLE",
+ "UNKNOWN"
+ ]
+ },
+ "retry_params": {
+ "no_retry_params": {
+ "initial_retry_delay_millis": 0,
+ "retry_delay_multiplier": 0.0,
+ "max_retry_delay_millis": 0,
+ "initial_rpc_timeout_millis": 0,
+ "rpc_timeout_multiplier": 1.0,
+ "max_rpc_timeout_millis": 0,
+ "total_timeout_millis": 0
+ },
+ "no_retry_1_params": {
+ "initial_retry_delay_millis": 0,
+ "retry_delay_multiplier": 0.0,
+ "max_retry_delay_millis": 0,
+ "initial_rpc_timeout_millis": 5000,
+ "rpc_timeout_multiplier": 1.0,
+ "max_rpc_timeout_millis": 5000,
+ "total_timeout_millis": 5000
+ },
+ "retry_policy_1_params": {
+ "initial_retry_delay_millis": 100,
+ "retry_delay_multiplier": 2.0,
+ "max_retry_delay_millis": 3000,
+ "initial_rpc_timeout_millis": 10000,
+ "rpc_timeout_multiplier": 1.0,
+ "max_rpc_timeout_millis": 10000,
+ "total_timeout_millis": 10000
+ }
+ },
+ "methods": {
+ "Block": {
+ "timeout_millis": 5000,
+ "retry_codes_name": "no_retry_1_codes",
+ "retry_params_name": "no_retry_1_params"
+ },
+ "Chat": {
+ "timeout_millis": 5000
+ },
+ "Collect": {
+ "timeout_millis": 5000
+ },
+ "Echo": {
+ "timeout_millis": 10000,
+ "retry_codes_name": "retry_policy_1_codes",
+ "retry_params_name": "retry_policy_1_params"
+ },
+ "EchoErrorDetails": {
+ "timeout_millis": 5000,
+ "retry_codes_name": "no_retry_1_codes",
+ "retry_params_name": "no_retry_1_params"
+ },
+ "Expand": {
+ "timeout_millis": 10000
+ },
+ "FailEchoWithDetails": {
+ "timeout_millis": 5000,
+ "retry_codes_name": "no_retry_1_codes",
+ "retry_params_name": "no_retry_1_params"
+ },
+ "PagedExpand": {
+ "timeout_millis": 10000,
+ "retry_codes_name": "retry_policy_1_codes",
+ "retry_params_name": "retry_policy_1_params"
+ },
+ "PagedExpandLegacy": {
+ "timeout_millis": 5000,
+ "retry_codes_name": "no_retry_1_codes",
+ "retry_params_name": "no_retry_1_params"
+ },
+ "PagedExpandLegacyMapped": {
+ "timeout_millis": 5000,
+ "retry_codes_name": "no_retry_1_codes",
+ "retry_params_name": "no_retry_1_params"
+ },
+ "Wait": {
+ "timeout_millis": 5000,
+ "retry_codes_name": "no_retry_1_codes",
+ "retry_params_name": "no_retry_1_params"
+ }
+ }
+ }
+ }
+}
diff --git a/Gax/tests/Conformance/src/V1beta1/resources/echo_descriptor_config.php b/Gax/tests/Conformance/src/V1beta1/resources/echo_descriptor_config.php
new file mode 100644
index 000000000000..63b503e5c1c5
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/resources/echo_descriptor_config.php
@@ -0,0 +1,161 @@
+ [
+ 'google.showcase.v1beta1.Echo' => [
+ 'Wait' => [
+ 'longRunning' => [
+ 'operationReturnType' => '\Google\Showcase\V1beta1\WaitResponse',
+ 'metadataReturnType' => '\Google\Showcase\V1beta1\WaitMetadata',
+ 'initialPollDelayMillis' => '500',
+ 'pollDelayMultiplier' => '1.5',
+ 'maxPollDelayMillis' => '5000',
+ 'totalPollTimeoutMillis' => '300000',
+ ],
+ 'callType' => \Google\ApiCore\Call::LONGRUNNING_CALL,
+ ],
+ 'Block' => [
+ 'callType' => \Google\ApiCore\Call::UNARY_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\BlockResponse',
+ ],
+ 'Chat' => [
+ 'grpcStreaming' => [
+ 'grpcStreamingType' => 'BidiStreaming',
+ ],
+ 'callType' => \Google\ApiCore\Call::BIDI_STREAMING_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\EchoResponse',
+ ],
+ 'Collect' => [
+ 'grpcStreaming' => [
+ 'grpcStreamingType' => 'ClientStreaming',
+ ],
+ 'callType' => \Google\ApiCore\Call::CLIENT_STREAMING_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\EchoResponse',
+ ],
+ 'Echo' => [
+ 'callType' => \Google\ApiCore\Call::UNARY_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\EchoResponse',
+ 'headerParams' => [
+ [
+ 'keyName' => 'header',
+ 'fieldAccessors' => [
+ 'getHeader',
+ ],
+ ],
+ [
+ 'keyName' => 'routing_id',
+ 'fieldAccessors' => [
+ 'getHeader',
+ ],
+ ],
+ [
+ 'keyName' => 'table_name',
+ 'fieldAccessors' => [
+ 'getHeader',
+ ],
+ 'matchers' => [
+ '/^(?regions\/[^\/]+\/zones\/[^\/]+(?:\/.*)?)$/',
+ '/^(?projects\/[^\/]+\/instances\/[^\/]+(?:\/.*)?)$/',
+ ],
+ ],
+ [
+ 'keyName' => 'super_id',
+ 'fieldAccessors' => [
+ 'getHeader',
+ ],
+ 'matchers' => [
+ '/^(?projects\/[^\/]+)(?:\/.*)?$/',
+ ],
+ ],
+ [
+ 'keyName' => 'instance_id',
+ 'fieldAccessors' => [
+ 'getHeader',
+ ],
+ 'matchers' => [
+ '/^projects\/[^\/]+\/(?instances\/[^\/]+)(?:\/.*)?$/',
+ ],
+ ],
+ [
+ 'keyName' => 'baz',
+ 'fieldAccessors' => [
+ 'getOtherHeader',
+ ],
+ ],
+ [
+ 'keyName' => 'qux',
+ 'fieldAccessors' => [
+ 'getOtherHeader',
+ ],
+ 'matchers' => [
+ '/^(?projects\/[^\/]+)(?:\/.*)?$/',
+ ],
+ ],
+ ],
+ ],
+ 'EchoErrorDetails' => [
+ 'callType' => \Google\ApiCore\Call::UNARY_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\EchoErrorDetailsResponse',
+ ],
+ 'Expand' => [
+ 'grpcStreaming' => [
+ 'grpcStreamingType' => 'ServerStreaming',
+ ],
+ 'callType' => \Google\ApiCore\Call::SERVER_STREAMING_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\EchoResponse',
+ ],
+ 'FailEchoWithDetails' => [
+ 'callType' => \Google\ApiCore\Call::UNARY_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\FailEchoWithDetailsResponse',
+ ],
+ 'PagedExpand' => [
+ 'pageStreaming' => [
+ 'requestPageTokenGetMethod' => 'getPageToken',
+ 'requestPageTokenSetMethod' => 'setPageToken',
+ 'requestPageSizeGetMethod' => 'getPageSize',
+ 'requestPageSizeSetMethod' => 'setPageSize',
+ 'responsePageTokenGetMethod' => 'getNextPageToken',
+ 'resourcesGetMethod' => 'getResponses',
+ ],
+ 'callType' => \Google\ApiCore\Call::PAGINATED_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\PagedExpandResponse',
+ ],
+ 'PagedExpandLegacy' => [
+ 'callType' => \Google\ApiCore\Call::UNARY_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\PagedExpandResponse',
+ ],
+ 'PagedExpandLegacyMapped' => [
+ 'pageStreaming' => [
+ 'requestPageTokenGetMethod' => 'getPageToken',
+ 'requestPageTokenSetMethod' => 'setPageToken',
+ 'requestPageSizeGetMethod' => 'getPageSize',
+ 'requestPageSizeSetMethod' => 'setPageSize',
+ 'responsePageTokenGetMethod' => 'getNextPageToken',
+ 'resourcesGetMethod' => 'getAlphabetized',
+ ],
+ 'callType' => \Google\ApiCore\Call::PAGINATED_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\PagedExpandLegacyMappedResponse',
+ ],
+ ],
+ ],
+];
diff --git a/Gax/tests/Conformance/src/V1beta1/resources/echo_rest_client_config.php b/Gax/tests/Conformance/src/V1beta1/resources/echo_rest_client_config.php
new file mode 100644
index 000000000000..f1a999b60fbf
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/resources/echo_rest_client_config.php
@@ -0,0 +1,222 @@
+ [
+ 'google.cloud.location.Locations' => [
+ 'ListLocations' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{name=projects/*}/locations',
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'GetLocation' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{name=projects/*/locations/*}',
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ ],
+ 'google.iam.v1.IAMPolicy' => [
+ 'SetIamPolicy' => [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=users/*}:setIamPolicy',
+ 'body' => '*',
+ 'additionalBindings' => [
+ [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=rooms/*}:setIamPolicy',
+ 'body' => '*',
+ ],
+ [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=rooms/*/blurbs/*}:setIamPolicy',
+ 'body' => '*',
+ ],
+ [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=sequences/*}:setIamPolicy',
+ 'body' => '*',
+ ],
+ ],
+ 'placeholders' => [
+ 'resource' => [
+ 'getters' => [
+ 'getResource',
+ ],
+ ],
+ ],
+ ],
+ 'GetIamPolicy' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{resource=users/*}:getIamPolicy',
+ 'additionalBindings' => [
+ [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{resource=rooms/*}:getIamPolicy',
+ ],
+ [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{resource=rooms/*/blurbs/*}:getIamPolicy',
+ ],
+ [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{resource=sequences/*}:getIamPolicy',
+ ],
+ ],
+ 'placeholders' => [
+ 'resource' => [
+ 'getters' => [
+ 'getResource',
+ ],
+ ],
+ ],
+ ],
+ 'TestIamPermissions' => [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=users/*}:testIamPermissions',
+ 'body' => '*',
+ 'additionalBindings' => [
+ [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=rooms/*}:testIamPermissions',
+ 'body' => '*',
+ ],
+ [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=rooms/*/blurbs/*}:testIamPermissions',
+ 'body' => '*',
+ ],
+ [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=sequences/*}:testIamPermissions',
+ 'body' => '*',
+ ],
+ ],
+ 'placeholders' => [
+ 'resource' => [
+ 'getters' => [
+ 'getResource',
+ ],
+ ],
+ ],
+ ],
+ ],
+ 'google.longrunning.Operations' => [
+ 'ListOperations' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/operations',
+ ],
+ 'GetOperation' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{name=operations/**}',
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'DeleteOperation' => [
+ 'method' => 'delete',
+ 'uriTemplate' => '/v1beta1/{name=operations/**}',
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'CancelOperation' => [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{name=operations/**}:cancel',
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ ],
+ 'google.showcase.v1beta1.Echo' => [
+ 'Block' => [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/echo:block',
+ 'body' => '*',
+ ],
+ 'Echo' => [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/echo:echo',
+ 'body' => '*',
+ ],
+ 'EchoErrorDetails' => [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/echo:error-details',
+ 'body' => '*',
+ ],
+ 'Expand' => [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/echo:expand',
+ 'body' => '*',
+ ],
+ 'FailEchoWithDetails' => [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/echo:failWithDetails',
+ 'body' => '*',
+ ],
+ 'PagedExpand' => [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/echo:pagedExpand',
+ 'body' => '*',
+ ],
+ 'PagedExpandLegacy' => [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/echo:pagedExpandLegacy',
+ 'body' => '*',
+ ],
+ 'PagedExpandLegacyMapped' => [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/echo:pagedExpandLegacyMapped',
+ 'body' => '*',
+ ],
+ 'Wait' => [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/echo:wait',
+ 'body' => '*',
+ ],
+ ],
+ ],
+ 'numericEnums' => true,
+];
diff --git a/Gax/tests/Conformance/src/V1beta1/resources/identity_client_config.json b/Gax/tests/Conformance/src/V1beta1/resources/identity_client_config.json
new file mode 100644
index 000000000000..348948e0816a
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/resources/identity_client_config.json
@@ -0,0 +1,60 @@
+{
+ "interfaces": {
+ "google.showcase.v1beta1.Identity": {
+ "retry_codes": {
+ "no_retry_codes": [],
+ "retry_policy_2_codes": [
+ "UNAVAILABLE",
+ "UNKNOWN"
+ ]
+ },
+ "retry_params": {
+ "no_retry_params": {
+ "initial_retry_delay_millis": 0,
+ "retry_delay_multiplier": 0.0,
+ "max_retry_delay_millis": 0,
+ "initial_rpc_timeout_millis": 0,
+ "rpc_timeout_multiplier": 1.0,
+ "max_rpc_timeout_millis": 0,
+ "total_timeout_millis": 0
+ },
+ "retry_policy_2_params": {
+ "initial_retry_delay_millis": 200,
+ "retry_delay_multiplier": 2.0,
+ "max_retry_delay_millis": 3000,
+ "initial_rpc_timeout_millis": 5000,
+ "rpc_timeout_multiplier": 1.0,
+ "max_rpc_timeout_millis": 5000,
+ "total_timeout_millis": 5000
+ }
+ },
+ "methods": {
+ "CreateUser": {
+ "timeout_millis": 60000,
+ "retry_codes_name": "no_retry_codes",
+ "retry_params_name": "no_retry_params"
+ },
+ "DeleteUser": {
+ "timeout_millis": 60000,
+ "retry_codes_name": "no_retry_codes",
+ "retry_params_name": "no_retry_params"
+ },
+ "GetUser": {
+ "timeout_millis": 5000,
+ "retry_codes_name": "retry_policy_2_codes",
+ "retry_params_name": "retry_policy_2_params"
+ },
+ "ListUsers": {
+ "timeout_millis": 5000,
+ "retry_codes_name": "retry_policy_2_codes",
+ "retry_params_name": "retry_policy_2_params"
+ },
+ "UpdateUser": {
+ "timeout_millis": 60000,
+ "retry_codes_name": "no_retry_codes",
+ "retry_params_name": "no_retry_params"
+ }
+ }
+ }
+ }
+}
diff --git a/Gax/tests/Conformance/src/V1beta1/resources/identity_descriptor_config.php b/Gax/tests/Conformance/src/V1beta1/resources/identity_descriptor_config.php
new file mode 100644
index 000000000000..b544be013f13
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/resources/identity_descriptor_config.php
@@ -0,0 +1,84 @@
+ [
+ 'google.showcase.v1beta1.Identity' => [
+ 'CreateUser' => [
+ 'callType' => \Google\ApiCore\Call::UNARY_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\User',
+ ],
+ 'DeleteUser' => [
+ 'callType' => \Google\ApiCore\Call::UNARY_CALL,
+ 'responseType' => 'Google\Protobuf\GPBEmpty',
+ 'headerParams' => [
+ [
+ 'keyName' => 'name',
+ 'fieldAccessors' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'GetUser' => [
+ 'callType' => \Google\ApiCore\Call::UNARY_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\User',
+ 'headerParams' => [
+ [
+ 'keyName' => 'name',
+ 'fieldAccessors' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'ListUsers' => [
+ 'pageStreaming' => [
+ 'requestPageTokenGetMethod' => 'getPageToken',
+ 'requestPageTokenSetMethod' => 'setPageToken',
+ 'requestPageSizeGetMethod' => 'getPageSize',
+ 'requestPageSizeSetMethod' => 'setPageSize',
+ 'responsePageTokenGetMethod' => 'getNextPageToken',
+ 'resourcesGetMethod' => 'getUsers',
+ ],
+ 'callType' => \Google\ApiCore\Call::PAGINATED_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\ListUsersResponse',
+ ],
+ 'UpdateUser' => [
+ 'callType' => \Google\ApiCore\Call::UNARY_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\User',
+ 'headerParams' => [
+ [
+ 'keyName' => 'user.name',
+ 'fieldAccessors' => [
+ 'getUser',
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'templateMap' => [
+ 'user' => 'users/{user}',
+ ],
+ ],
+ ],
+];
diff --git a/Gax/tests/Conformance/src/V1beta1/resources/identity_rest_client_config.php b/Gax/tests/Conformance/src/V1beta1/resources/identity_rest_client_config.php
new file mode 100644
index 000000000000..f80afb6d0e25
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/resources/identity_rest_client_config.php
@@ -0,0 +1,221 @@
+ [
+ 'google.cloud.location.Locations' => [
+ 'ListLocations' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{name=projects/*}/locations',
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'GetLocation' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{name=projects/*/locations/*}',
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ ],
+ 'google.iam.v1.IAMPolicy' => [
+ 'SetIamPolicy' => [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=users/*}:setIamPolicy',
+ 'body' => '*',
+ 'additionalBindings' => [
+ [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=rooms/*}:setIamPolicy',
+ 'body' => '*',
+ ],
+ [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=rooms/*/blurbs/*}:setIamPolicy',
+ 'body' => '*',
+ ],
+ [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=sequences/*}:setIamPolicy',
+ 'body' => '*',
+ ],
+ ],
+ 'placeholders' => [
+ 'resource' => [
+ 'getters' => [
+ 'getResource',
+ ],
+ ],
+ ],
+ ],
+ 'GetIamPolicy' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{resource=users/*}:getIamPolicy',
+ 'additionalBindings' => [
+ [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{resource=rooms/*}:getIamPolicy',
+ ],
+ [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{resource=rooms/*/blurbs/*}:getIamPolicy',
+ ],
+ [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{resource=sequences/*}:getIamPolicy',
+ ],
+ ],
+ 'placeholders' => [
+ 'resource' => [
+ 'getters' => [
+ 'getResource',
+ ],
+ ],
+ ],
+ ],
+ 'TestIamPermissions' => [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=users/*}:testIamPermissions',
+ 'body' => '*',
+ 'additionalBindings' => [
+ [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=rooms/*}:testIamPermissions',
+ 'body' => '*',
+ ],
+ [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=rooms/*/blurbs/*}:testIamPermissions',
+ 'body' => '*',
+ ],
+ [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=sequences/*}:testIamPermissions',
+ 'body' => '*',
+ ],
+ ],
+ 'placeholders' => [
+ 'resource' => [
+ 'getters' => [
+ 'getResource',
+ ],
+ ],
+ ],
+ ],
+ ],
+ 'google.longrunning.Operations' => [
+ 'ListOperations' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/operations',
+ ],
+ 'GetOperation' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{name=operations/**}',
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'DeleteOperation' => [
+ 'method' => 'delete',
+ 'uriTemplate' => '/v1beta1/{name=operations/**}',
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'CancelOperation' => [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{name=operations/**}:cancel',
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ ],
+ 'google.showcase.v1beta1.Identity' => [
+ 'CreateUser' => [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/users',
+ 'body' => '*',
+ ],
+ 'DeleteUser' => [
+ 'method' => 'delete',
+ 'uriTemplate' => '/v1beta1/{name=users/*}',
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'GetUser' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{name=users/*}',
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'ListUsers' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/users',
+ ],
+ 'UpdateUser' => [
+ 'method' => 'patch',
+ 'uriTemplate' => '/v1beta1/{user.name=users/*}',
+ 'body' => 'user',
+ 'placeholders' => [
+ 'user.name' => [
+ 'getters' => [
+ 'getUser',
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ ],
+ ],
+ 'numericEnums' => true,
+];
diff --git a/Gax/tests/Conformance/src/V1beta1/resources/messaging_client_config.json b/Gax/tests/Conformance/src/V1beta1/resources/messaging_client_config.json
new file mode 100644
index 000000000000..05a964f8f028
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/resources/messaging_client_config.json
@@ -0,0 +1,109 @@
+{
+ "interfaces": {
+ "google.showcase.v1beta1.Messaging": {
+ "retry_codes": {
+ "no_retry_codes": [],
+ "no_retry_1_codes": [],
+ "retry_policy_1_codes": [
+ "UNAVAILABLE",
+ "UNKNOWN"
+ ]
+ },
+ "retry_params": {
+ "no_retry_params": {
+ "initial_retry_delay_millis": 0,
+ "retry_delay_multiplier": 0.0,
+ "max_retry_delay_millis": 0,
+ "initial_rpc_timeout_millis": 0,
+ "rpc_timeout_multiplier": 1.0,
+ "max_rpc_timeout_millis": 0,
+ "total_timeout_millis": 0
+ },
+ "no_retry_1_params": {
+ "initial_retry_delay_millis": 0,
+ "retry_delay_multiplier": 0.0,
+ "max_retry_delay_millis": 0,
+ "initial_rpc_timeout_millis": 5000,
+ "rpc_timeout_multiplier": 1.0,
+ "max_rpc_timeout_millis": 5000,
+ "total_timeout_millis": 5000
+ },
+ "retry_policy_1_params": {
+ "initial_retry_delay_millis": 100,
+ "retry_delay_multiplier": 2.0,
+ "max_retry_delay_millis": 3000,
+ "initial_rpc_timeout_millis": 10000,
+ "rpc_timeout_multiplier": 1.0,
+ "max_rpc_timeout_millis": 10000,
+ "total_timeout_millis": 10000
+ }
+ },
+ "methods": {
+ "Connect": {
+ "timeout_millis": 10000
+ },
+ "CreateBlurb": {
+ "timeout_millis": 5000,
+ "retry_codes_name": "no_retry_1_codes",
+ "retry_params_name": "no_retry_1_params"
+ },
+ "CreateRoom": {
+ "timeout_millis": 5000,
+ "retry_codes_name": "no_retry_1_codes",
+ "retry_params_name": "no_retry_1_params"
+ },
+ "DeleteBlurb": {
+ "timeout_millis": 5000,
+ "retry_codes_name": "no_retry_1_codes",
+ "retry_params_name": "no_retry_1_params"
+ },
+ "DeleteRoom": {
+ "timeout_millis": 5000,
+ "retry_codes_name": "no_retry_1_codes",
+ "retry_params_name": "no_retry_1_params"
+ },
+ "GetBlurb": {
+ "timeout_millis": 10000,
+ "retry_codes_name": "retry_policy_1_codes",
+ "retry_params_name": "retry_policy_1_params"
+ },
+ "GetRoom": {
+ "timeout_millis": 10000,
+ "retry_codes_name": "retry_policy_1_codes",
+ "retry_params_name": "retry_policy_1_params"
+ },
+ "ListBlurbs": {
+ "timeout_millis": 10000,
+ "retry_codes_name": "retry_policy_1_codes",
+ "retry_params_name": "retry_policy_1_params"
+ },
+ "ListRooms": {
+ "timeout_millis": 10000,
+ "retry_codes_name": "retry_policy_1_codes",
+ "retry_params_name": "retry_policy_1_params"
+ },
+ "SearchBlurbs": {
+ "timeout_millis": 10000,
+ "retry_codes_name": "retry_policy_1_codes",
+ "retry_params_name": "retry_policy_1_params"
+ },
+ "SendBlurbs": {
+ "timeout_millis": 5000
+ },
+ "StreamBlurbs": {
+ "timeout_millis": 5000
+ },
+ "UpdateBlurb": {
+ "timeout_millis": 5000,
+ "retry_codes_name": "no_retry_1_codes",
+ "retry_params_name": "no_retry_1_params"
+ },
+ "UpdateRoom": {
+ "timeout_millis": 5000,
+ "retry_codes_name": "no_retry_1_codes",
+ "retry_params_name": "no_retry_1_params"
+ }
+ }
+ }
+ }
+}
diff --git a/Gax/tests/Conformance/src/V1beta1/resources/messaging_descriptor_config.php b/Gax/tests/Conformance/src/V1beta1/resources/messaging_descriptor_config.php
new file mode 100644
index 000000000000..f29f84658c63
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/resources/messaging_descriptor_config.php
@@ -0,0 +1,216 @@
+ [
+ 'google.showcase.v1beta1.Messaging' => [
+ 'SearchBlurbs' => [
+ 'longRunning' => [
+ 'operationReturnType' => '\Google\Showcase\V1beta1\SearchBlurbsResponse',
+ 'metadataReturnType' => '\Google\Showcase\V1beta1\SearchBlurbsMetadata',
+ 'initialPollDelayMillis' => '500',
+ 'pollDelayMultiplier' => '1.5',
+ 'maxPollDelayMillis' => '5000',
+ 'totalPollTimeoutMillis' => '300000',
+ ],
+ 'callType' => \Google\ApiCore\Call::LONGRUNNING_CALL,
+ 'headerParams' => [
+ [
+ 'keyName' => 'parent',
+ 'fieldAccessors' => [
+ 'getParent',
+ ],
+ ],
+ ],
+ ],
+ 'Connect' => [
+ 'grpcStreaming' => [
+ 'grpcStreamingType' => 'BidiStreaming',
+ ],
+ 'callType' => \Google\ApiCore\Call::BIDI_STREAMING_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\StreamBlurbsResponse',
+ ],
+ 'CreateBlurb' => [
+ 'callType' => \Google\ApiCore\Call::UNARY_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\Blurb',
+ 'headerParams' => [
+ [
+ 'keyName' => 'parent',
+ 'fieldAccessors' => [
+ 'getParent',
+ ],
+ ],
+ ],
+ ],
+ 'CreateRoom' => [
+ 'callType' => \Google\ApiCore\Call::UNARY_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\Room',
+ ],
+ 'DeleteBlurb' => [
+ 'callType' => \Google\ApiCore\Call::UNARY_CALL,
+ 'responseType' => 'Google\Protobuf\GPBEmpty',
+ 'headerParams' => [
+ [
+ 'keyName' => 'name',
+ 'fieldAccessors' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'DeleteRoom' => [
+ 'callType' => \Google\ApiCore\Call::UNARY_CALL,
+ 'responseType' => 'Google\Protobuf\GPBEmpty',
+ 'headerParams' => [
+ [
+ 'keyName' => 'name',
+ 'fieldAccessors' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'GetBlurb' => [
+ 'callType' => \Google\ApiCore\Call::UNARY_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\Blurb',
+ 'headerParams' => [
+ [
+ 'keyName' => 'name',
+ 'fieldAccessors' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'GetRoom' => [
+ 'callType' => \Google\ApiCore\Call::UNARY_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\Room',
+ 'headerParams' => [
+ [
+ 'keyName' => 'name',
+ 'fieldAccessors' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'ListBlurbs' => [
+ 'pageStreaming' => [
+ 'requestPageTokenGetMethod' => 'getPageToken',
+ 'requestPageTokenSetMethod' => 'setPageToken',
+ 'requestPageSizeGetMethod' => 'getPageSize',
+ 'requestPageSizeSetMethod' => 'setPageSize',
+ 'responsePageTokenGetMethod' => 'getNextPageToken',
+ 'resourcesGetMethod' => 'getBlurbs',
+ ],
+ 'callType' => \Google\ApiCore\Call::PAGINATED_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\ListBlurbsResponse',
+ 'headerParams' => [
+ [
+ 'keyName' => 'parent',
+ 'fieldAccessors' => [
+ 'getParent',
+ ],
+ ],
+ ],
+ ],
+ 'ListRooms' => [
+ 'pageStreaming' => [
+ 'requestPageTokenGetMethod' => 'getPageToken',
+ 'requestPageTokenSetMethod' => 'setPageToken',
+ 'requestPageSizeGetMethod' => 'getPageSize',
+ 'requestPageSizeSetMethod' => 'setPageSize',
+ 'responsePageTokenGetMethod' => 'getNextPageToken',
+ 'resourcesGetMethod' => 'getRooms',
+ ],
+ 'callType' => \Google\ApiCore\Call::PAGINATED_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\ListRoomsResponse',
+ ],
+ 'SendBlurbs' => [
+ 'grpcStreaming' => [
+ 'grpcStreamingType' => 'ClientStreaming',
+ ],
+ 'callType' => \Google\ApiCore\Call::CLIENT_STREAMING_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\SendBlurbsResponse',
+ 'headerParams' => [
+ [
+ 'keyName' => 'parent',
+ 'fieldAccessors' => [
+ 'getParent',
+ ],
+ ],
+ ],
+ ],
+ 'StreamBlurbs' => [
+ 'grpcStreaming' => [
+ 'grpcStreamingType' => 'ServerStreaming',
+ ],
+ 'callType' => \Google\ApiCore\Call::SERVER_STREAMING_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\StreamBlurbsResponse',
+ 'headerParams' => [
+ [
+ 'keyName' => 'name',
+ 'fieldAccessors' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'UpdateBlurb' => [
+ 'callType' => \Google\ApiCore\Call::UNARY_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\Blurb',
+ 'headerParams' => [
+ [
+ 'keyName' => 'blurb.name',
+ 'fieldAccessors' => [
+ 'getBlurb',
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'UpdateRoom' => [
+ 'callType' => \Google\ApiCore\Call::UNARY_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\Room',
+ 'headerParams' => [
+ [
+ 'keyName' => 'room.name',
+ 'fieldAccessors' => [
+ 'getRoom',
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'templateMap' => [
+ 'blurb' => 'users/{user}/blurbs/{blurb}',
+ 'room' => 'rooms/{room}',
+ 'roomBlurb' => 'rooms/{room}/blurbs/{blurb}',
+ 'roomLegacyRoom' => 'rooms/{room}/legacy_room/{legacy_room}',
+ 'roomLegacyRoomBlurb' => 'rooms/{room}/legacy_room/{legacy_room}/blurbs/{blurb}',
+ 'user' => 'users/{user}',
+ 'userBlurb' => 'users/{user}/blurbs/{blurb}',
+ 'userBlurbLegacyUser' => 'users/{user}/blurbs/{blurb}/legacy/{legacy_user}',
+ ],
+ ],
+ ],
+];
diff --git a/Gax/tests/Conformance/src/V1beta1/resources/messaging_rest_client_config.php b/Gax/tests/Conformance/src/V1beta1/resources/messaging_rest_client_config.php
new file mode 100644
index 000000000000..4b080078c321
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/resources/messaging_rest_client_config.php
@@ -0,0 +1,349 @@
+ [
+ 'google.cloud.location.Locations' => [
+ 'ListLocations' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{name=projects/*}/locations',
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'GetLocation' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{name=projects/*/locations/*}',
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ ],
+ 'google.iam.v1.IAMPolicy' => [
+ 'SetIamPolicy' => [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=users/*}:setIamPolicy',
+ 'body' => '*',
+ 'additionalBindings' => [
+ [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=rooms/*}:setIamPolicy',
+ 'body' => '*',
+ ],
+ [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=rooms/*/blurbs/*}:setIamPolicy',
+ 'body' => '*',
+ ],
+ [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=sequences/*}:setIamPolicy',
+ 'body' => '*',
+ ],
+ ],
+ 'placeholders' => [
+ 'resource' => [
+ 'getters' => [
+ 'getResource',
+ ],
+ ],
+ ],
+ ],
+ 'GetIamPolicy' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{resource=users/*}:getIamPolicy',
+ 'additionalBindings' => [
+ [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{resource=rooms/*}:getIamPolicy',
+ ],
+ [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{resource=rooms/*/blurbs/*}:getIamPolicy',
+ ],
+ [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{resource=sequences/*}:getIamPolicy',
+ ],
+ ],
+ 'placeholders' => [
+ 'resource' => [
+ 'getters' => [
+ 'getResource',
+ ],
+ ],
+ ],
+ ],
+ 'TestIamPermissions' => [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=users/*}:testIamPermissions',
+ 'body' => '*',
+ 'additionalBindings' => [
+ [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=rooms/*}:testIamPermissions',
+ 'body' => '*',
+ ],
+ [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=rooms/*/blurbs/*}:testIamPermissions',
+ 'body' => '*',
+ ],
+ [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=sequences/*}:testIamPermissions',
+ 'body' => '*',
+ ],
+ ],
+ 'placeholders' => [
+ 'resource' => [
+ 'getters' => [
+ 'getResource',
+ ],
+ ],
+ ],
+ ],
+ ],
+ 'google.longrunning.Operations' => [
+ 'ListOperations' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/operations',
+ ],
+ 'GetOperation' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{name=operations/**}',
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'DeleteOperation' => [
+ 'method' => 'delete',
+ 'uriTemplate' => '/v1beta1/{name=operations/**}',
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'CancelOperation' => [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{name=operations/**}:cancel',
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ ],
+ 'google.showcase.v1beta1.Messaging' => [
+ 'CreateBlurb' => [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{parent=rooms/*}/blurbs',
+ 'body' => '*',
+ 'additionalBindings' => [
+ [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{parent=users/*/profile}/blurbs',
+ 'body' => '*',
+ ],
+ ],
+ 'placeholders' => [
+ 'parent' => [
+ 'getters' => [
+ 'getParent',
+ ],
+ ],
+ ],
+ ],
+ 'CreateRoom' => [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/rooms',
+ 'body' => '*',
+ ],
+ 'DeleteBlurb' => [
+ 'method' => 'delete',
+ 'uriTemplate' => '/v1beta1/{name=rooms/*/blurbs/*}',
+ 'additionalBindings' => [
+ [
+ 'method' => 'delete',
+ 'uriTemplate' => '/v1beta1/{name=users/*/profile/blurbs/*}',
+ ],
+ ],
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'DeleteRoom' => [
+ 'method' => 'delete',
+ 'uriTemplate' => '/v1beta1/{name=rooms/*}',
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'GetBlurb' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{name=rooms/*/blurbs/*}',
+ 'additionalBindings' => [
+ [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{name=users/*/profile/blurbs/*}',
+ ],
+ ],
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'GetRoom' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{name=rooms/*}',
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'ListBlurbs' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{parent=rooms/*}/blurbs',
+ 'additionalBindings' => [
+ [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{parent=users/*/profile}/blurbs',
+ ],
+ ],
+ 'placeholders' => [
+ 'parent' => [
+ 'getters' => [
+ 'getParent',
+ ],
+ ],
+ ],
+ ],
+ 'ListRooms' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/rooms',
+ ],
+ 'SearchBlurbs' => [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{parent=rooms/*}/blurbs:search',
+ 'body' => '*',
+ 'additionalBindings' => [
+ [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{parent=users/*/profile}/blurbs:search',
+ 'body' => '*',
+ ],
+ ],
+ 'placeholders' => [
+ 'parent' => [
+ 'getters' => [
+ 'getParent',
+ ],
+ ],
+ ],
+ ],
+ 'StreamBlurbs' => [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{name=rooms/*}/blurbs:stream',
+ 'body' => '*',
+ 'additionalBindings' => [
+ [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{name=users/*/profile}/blurbs:stream',
+ 'body' => '*',
+ ],
+ ],
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'UpdateBlurb' => [
+ 'method' => 'patch',
+ 'uriTemplate' => '/v1beta1/{blurb.name=rooms/*/blurbs/*}',
+ 'body' => 'blurb',
+ 'additionalBindings' => [
+ [
+ 'method' => 'patch',
+ 'uriTemplate' => '/v1beta1/{blurb.name=users/*/profile/blurbs/*}',
+ 'body' => 'blurb',
+ ],
+ ],
+ 'placeholders' => [
+ 'blurb.name' => [
+ 'getters' => [
+ 'getBlurb',
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'UpdateRoom' => [
+ 'method' => 'patch',
+ 'uriTemplate' => '/v1beta1/{room.name=rooms/*}',
+ 'body' => 'room',
+ 'placeholders' => [
+ 'room.name' => [
+ 'getters' => [
+ 'getRoom',
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ ],
+ ],
+ 'numericEnums' => true,
+];
diff --git a/Gax/tests/Conformance/src/V1beta1/resources/sequence_service_client_config.json b/Gax/tests/Conformance/src/V1beta1/resources/sequence_service_client_config.json
new file mode 100644
index 000000000000..01b283c7943d
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/resources/sequence_service_client_config.json
@@ -0,0 +1,73 @@
+{
+ "interfaces": {
+ "google.showcase.v1beta1.SequenceService": {
+ "retry_codes": {
+ "no_retry_codes": [],
+ "no_retry_1_codes": [],
+ "retry_policy_1_codes": [
+ "UNAVAILABLE",
+ "UNKNOWN"
+ ]
+ },
+ "retry_params": {
+ "no_retry_params": {
+ "initial_retry_delay_millis": 0,
+ "retry_delay_multiplier": 0.0,
+ "max_retry_delay_millis": 0,
+ "initial_rpc_timeout_millis": 0,
+ "rpc_timeout_multiplier": 1.0,
+ "max_rpc_timeout_millis": 0,
+ "total_timeout_millis": 0
+ },
+ "no_retry_1_params": {
+ "initial_retry_delay_millis": 0,
+ "retry_delay_multiplier": 0.0,
+ "max_retry_delay_millis": 0,
+ "initial_rpc_timeout_millis": 5000,
+ "rpc_timeout_multiplier": 1.0,
+ "max_rpc_timeout_millis": 5000,
+ "total_timeout_millis": 5000
+ },
+ "retry_policy_1_params": {
+ "initial_retry_delay_millis": 100,
+ "retry_delay_multiplier": 2.0,
+ "max_retry_delay_millis": 3000,
+ "initial_rpc_timeout_millis": 10000,
+ "rpc_timeout_multiplier": 1.0,
+ "max_rpc_timeout_millis": 10000,
+ "total_timeout_millis": 10000
+ }
+ },
+ "methods": {
+ "AttemptSequence": {
+ "timeout_millis": 10000,
+ "retry_codes_name": "retry_policy_1_codes",
+ "retry_params_name": "retry_policy_1_params"
+ },
+ "AttemptStreamingSequence": {
+ "timeout_millis": 5000
+ },
+ "CreateSequence": {
+ "timeout_millis": 5000,
+ "retry_codes_name": "no_retry_1_codes",
+ "retry_params_name": "no_retry_1_params"
+ },
+ "CreateStreamingSequence": {
+ "timeout_millis": 5000,
+ "retry_codes_name": "no_retry_1_codes",
+ "retry_params_name": "no_retry_1_params"
+ },
+ "GetSequenceReport": {
+ "timeout_millis": 5000,
+ "retry_codes_name": "no_retry_1_codes",
+ "retry_params_name": "no_retry_1_params"
+ },
+ "GetStreamingSequenceReport": {
+ "timeout_millis": 5000,
+ "retry_codes_name": "no_retry_1_codes",
+ "retry_params_name": "no_retry_1_params"
+ }
+ }
+ }
+ }
+}
diff --git a/Gax/tests/Conformance/src/V1beta1/resources/sequence_service_descriptor_config.php b/Gax/tests/Conformance/src/V1beta1/resources/sequence_service_descriptor_config.php
new file mode 100644
index 000000000000..64096e0fe8bf
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/resources/sequence_service_descriptor_config.php
@@ -0,0 +1,93 @@
+ [
+ 'google.showcase.v1beta1.SequenceService' => [
+ 'AttemptSequence' => [
+ 'callType' => \Google\ApiCore\Call::UNARY_CALL,
+ 'responseType' => 'Google\Protobuf\GPBEmpty',
+ 'headerParams' => [
+ [
+ 'keyName' => 'name',
+ 'fieldAccessors' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'AttemptStreamingSequence' => [
+ 'grpcStreaming' => [
+ 'grpcStreamingType' => 'ServerStreaming',
+ ],
+ 'callType' => \Google\ApiCore\Call::SERVER_STREAMING_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\AttemptStreamingSequenceResponse',
+ 'headerParams' => [
+ [
+ 'keyName' => 'name',
+ 'fieldAccessors' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'CreateSequence' => [
+ 'callType' => \Google\ApiCore\Call::UNARY_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\Sequence',
+ ],
+ 'CreateStreamingSequence' => [
+ 'callType' => \Google\ApiCore\Call::UNARY_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\StreamingSequence',
+ ],
+ 'GetSequenceReport' => [
+ 'callType' => \Google\ApiCore\Call::UNARY_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\SequenceReport',
+ 'headerParams' => [
+ [
+ 'keyName' => 'name',
+ 'fieldAccessors' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'GetStreamingSequenceReport' => [
+ 'callType' => \Google\ApiCore\Call::UNARY_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\StreamingSequenceReport',
+ 'headerParams' => [
+ [
+ 'keyName' => 'name',
+ 'fieldAccessors' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'templateMap' => [
+ 'sequence' => 'sequences/{sequence}',
+ 'sequenceReport' => 'sequences/{sequence}/sequenceReport',
+ 'streamingSequence' => 'streamingSequences/{streaming_sequence}',
+ 'streamingSequenceReport' => 'streamingSequences/{streaming_sequence}/streamingSequenceReport',
+ ],
+ ],
+ ],
+];
diff --git a/Gax/tests/Conformance/src/V1beta1/resources/sequence_service_rest_client_config.php b/Gax/tests/Conformance/src/V1beta1/resources/sequence_service_rest_client_config.php
new file mode 100644
index 000000000000..51d8310bc9fe
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/resources/sequence_service_rest_client_config.php
@@ -0,0 +1,233 @@
+ [
+ 'google.cloud.location.Locations' => [
+ 'ListLocations' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{name=projects/*}/locations',
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'GetLocation' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{name=projects/*/locations/*}',
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ ],
+ 'google.iam.v1.IAMPolicy' => [
+ 'SetIamPolicy' => [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=users/*}:setIamPolicy',
+ 'body' => '*',
+ 'additionalBindings' => [
+ [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=rooms/*}:setIamPolicy',
+ 'body' => '*',
+ ],
+ [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=rooms/*/blurbs/*}:setIamPolicy',
+ 'body' => '*',
+ ],
+ [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=sequences/*}:setIamPolicy',
+ 'body' => '*',
+ ],
+ ],
+ 'placeholders' => [
+ 'resource' => [
+ 'getters' => [
+ 'getResource',
+ ],
+ ],
+ ],
+ ],
+ 'GetIamPolicy' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{resource=users/*}:getIamPolicy',
+ 'additionalBindings' => [
+ [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{resource=rooms/*}:getIamPolicy',
+ ],
+ [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{resource=rooms/*/blurbs/*}:getIamPolicy',
+ ],
+ [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{resource=sequences/*}:getIamPolicy',
+ ],
+ ],
+ 'placeholders' => [
+ 'resource' => [
+ 'getters' => [
+ 'getResource',
+ ],
+ ],
+ ],
+ ],
+ 'TestIamPermissions' => [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=users/*}:testIamPermissions',
+ 'body' => '*',
+ 'additionalBindings' => [
+ [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=rooms/*}:testIamPermissions',
+ 'body' => '*',
+ ],
+ [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=rooms/*/blurbs/*}:testIamPermissions',
+ 'body' => '*',
+ ],
+ [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=sequences/*}:testIamPermissions',
+ 'body' => '*',
+ ],
+ ],
+ 'placeholders' => [
+ 'resource' => [
+ 'getters' => [
+ 'getResource',
+ ],
+ ],
+ ],
+ ],
+ ],
+ 'google.longrunning.Operations' => [
+ 'ListOperations' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/operations',
+ ],
+ 'GetOperation' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{name=operations/**}',
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'DeleteOperation' => [
+ 'method' => 'delete',
+ 'uriTemplate' => '/v1beta1/{name=operations/**}',
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'CancelOperation' => [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{name=operations/**}:cancel',
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ ],
+ 'google.showcase.v1beta1.SequenceService' => [
+ 'AttemptSequence' => [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{name=sequences/*}',
+ 'body' => '*',
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'AttemptStreamingSequence' => [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{name=streamingSequences/*}:stream',
+ 'body' => '*',
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'CreateSequence' => [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/sequences',
+ 'body' => 'sequence',
+ ],
+ 'CreateStreamingSequence' => [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/streamingSequences',
+ 'body' => 'streaming_sequence',
+ ],
+ 'GetSequenceReport' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{name=sequences/*/sequenceReport}',
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'GetStreamingSequenceReport' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{name=streamingSequences/*/streamingSequenceReport}',
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ ],
+ ],
+ 'numericEnums' => true,
+];
diff --git a/Gax/tests/Conformance/src/V1beta1/resources/testing_client_config.json b/Gax/tests/Conformance/src/V1beta1/resources/testing_client_config.json
new file mode 100644
index 000000000000..9a1757d4408d
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/resources/testing_client_config.json
@@ -0,0 +1,62 @@
+{
+ "interfaces": {
+ "google.showcase.v1beta1.Testing": {
+ "retry_codes": {
+ "no_retry_codes": []
+ },
+ "retry_params": {
+ "no_retry_params": {
+ "initial_retry_delay_millis": 0,
+ "retry_delay_multiplier": 0.0,
+ "max_retry_delay_millis": 0,
+ "initial_rpc_timeout_millis": 0,
+ "rpc_timeout_multiplier": 1.0,
+ "max_rpc_timeout_millis": 0,
+ "total_timeout_millis": 0
+ }
+ },
+ "methods": {
+ "CreateSession": {
+ "timeout_millis": 60000,
+ "retry_codes_name": "no_retry_codes",
+ "retry_params_name": "no_retry_params"
+ },
+ "DeleteSession": {
+ "timeout_millis": 60000,
+ "retry_codes_name": "no_retry_codes",
+ "retry_params_name": "no_retry_params"
+ },
+ "DeleteTest": {
+ "timeout_millis": 60000,
+ "retry_codes_name": "no_retry_codes",
+ "retry_params_name": "no_retry_params"
+ },
+ "GetSession": {
+ "timeout_millis": 60000,
+ "retry_codes_name": "no_retry_codes",
+ "retry_params_name": "no_retry_params"
+ },
+ "ListSessions": {
+ "timeout_millis": 60000,
+ "retry_codes_name": "no_retry_codes",
+ "retry_params_name": "no_retry_params"
+ },
+ "ListTests": {
+ "timeout_millis": 60000,
+ "retry_codes_name": "no_retry_codes",
+ "retry_params_name": "no_retry_params"
+ },
+ "ReportSession": {
+ "timeout_millis": 60000,
+ "retry_codes_name": "no_retry_codes",
+ "retry_params_name": "no_retry_params"
+ },
+ "VerifyTest": {
+ "timeout_millis": 60000,
+ "retry_codes_name": "no_retry_codes",
+ "retry_params_name": "no_retry_params"
+ }
+ }
+ }
+ }
+}
diff --git a/Gax/tests/Conformance/src/V1beta1/resources/testing_descriptor_config.php b/Gax/tests/Conformance/src/V1beta1/resources/testing_descriptor_config.php
new file mode 100644
index 000000000000..6f62206cbe47
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/resources/testing_descriptor_config.php
@@ -0,0 +1,128 @@
+ [
+ 'google.showcase.v1beta1.Testing' => [
+ 'CreateSession' => [
+ 'callType' => \Google\ApiCore\Call::UNARY_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\Session',
+ ],
+ 'DeleteSession' => [
+ 'callType' => \Google\ApiCore\Call::UNARY_CALL,
+ 'responseType' => 'Google\Protobuf\GPBEmpty',
+ 'headerParams' => [
+ [
+ 'keyName' => 'name',
+ 'fieldAccessors' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'DeleteTest' => [
+ 'callType' => \Google\ApiCore\Call::UNARY_CALL,
+ 'responseType' => 'Google\Protobuf\GPBEmpty',
+ 'headerParams' => [
+ [
+ 'keyName' => 'name',
+ 'fieldAccessors' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'GetSession' => [
+ 'callType' => \Google\ApiCore\Call::UNARY_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\Session',
+ 'headerParams' => [
+ [
+ 'keyName' => 'name',
+ 'fieldAccessors' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'ListSessions' => [
+ 'pageStreaming' => [
+ 'requestPageTokenGetMethod' => 'getPageToken',
+ 'requestPageTokenSetMethod' => 'setPageToken',
+ 'requestPageSizeGetMethod' => 'getPageSize',
+ 'requestPageSizeSetMethod' => 'setPageSize',
+ 'responsePageTokenGetMethod' => 'getNextPageToken',
+ 'resourcesGetMethod' => 'getSessions',
+ ],
+ 'callType' => \Google\ApiCore\Call::PAGINATED_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\ListSessionsResponse',
+ ],
+ 'ListTests' => [
+ 'pageStreaming' => [
+ 'requestPageTokenGetMethod' => 'getPageToken',
+ 'requestPageTokenSetMethod' => 'setPageToken',
+ 'requestPageSizeGetMethod' => 'getPageSize',
+ 'requestPageSizeSetMethod' => 'setPageSize',
+ 'responsePageTokenGetMethod' => 'getNextPageToken',
+ 'resourcesGetMethod' => 'getTests',
+ ],
+ 'callType' => \Google\ApiCore\Call::PAGINATED_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\ListTestsResponse',
+ 'headerParams' => [
+ [
+ 'keyName' => 'parent',
+ 'fieldAccessors' => [
+ 'getParent',
+ ],
+ ],
+ ],
+ ],
+ 'ReportSession' => [
+ 'callType' => \Google\ApiCore\Call::UNARY_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\ReportSessionResponse',
+ 'headerParams' => [
+ [
+ 'keyName' => 'name',
+ 'fieldAccessors' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'VerifyTest' => [
+ 'callType' => \Google\ApiCore\Call::UNARY_CALL,
+ 'responseType' => 'Google\Showcase\V1beta1\VerifyTestResponse',
+ 'headerParams' => [
+ [
+ 'keyName' => 'name',
+ 'fieldAccessors' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'templateMap' => [
+ 'session' => 'sessions/{session}',
+ 'test' => 'sessions/{session}/tests/{test}',
+ ],
+ ],
+ ],
+];
diff --git a/Gax/tests/Conformance/src/V1beta1/resources/testing_rest_client_config.php b/Gax/tests/Conformance/src/V1beta1/resources/testing_rest_client_config.php
new file mode 100644
index 000000000000..a0e0e150aa41
--- /dev/null
+++ b/Gax/tests/Conformance/src/V1beta1/resources/testing_rest_client_config.php
@@ -0,0 +1,252 @@
+ [
+ 'google.cloud.location.Locations' => [
+ 'ListLocations' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{name=projects/*}/locations',
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'GetLocation' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{name=projects/*/locations/*}',
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ ],
+ 'google.iam.v1.IAMPolicy' => [
+ 'SetIamPolicy' => [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=users/*}:setIamPolicy',
+ 'body' => '*',
+ 'additionalBindings' => [
+ [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=rooms/*}:setIamPolicy',
+ 'body' => '*',
+ ],
+ [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=rooms/*/blurbs/*}:setIamPolicy',
+ 'body' => '*',
+ ],
+ [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=sequences/*}:setIamPolicy',
+ 'body' => '*',
+ ],
+ ],
+ 'placeholders' => [
+ 'resource' => [
+ 'getters' => [
+ 'getResource',
+ ],
+ ],
+ ],
+ ],
+ 'GetIamPolicy' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{resource=users/*}:getIamPolicy',
+ 'additionalBindings' => [
+ [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{resource=rooms/*}:getIamPolicy',
+ ],
+ [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{resource=rooms/*/blurbs/*}:getIamPolicy',
+ ],
+ [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{resource=sequences/*}:getIamPolicy',
+ ],
+ ],
+ 'placeholders' => [
+ 'resource' => [
+ 'getters' => [
+ 'getResource',
+ ],
+ ],
+ ],
+ ],
+ 'TestIamPermissions' => [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=users/*}:testIamPermissions',
+ 'body' => '*',
+ 'additionalBindings' => [
+ [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=rooms/*}:testIamPermissions',
+ 'body' => '*',
+ ],
+ [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=rooms/*/blurbs/*}:testIamPermissions',
+ 'body' => '*',
+ ],
+ [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{resource=sequences/*}:testIamPermissions',
+ 'body' => '*',
+ ],
+ ],
+ 'placeholders' => [
+ 'resource' => [
+ 'getters' => [
+ 'getResource',
+ ],
+ ],
+ ],
+ ],
+ ],
+ 'google.longrunning.Operations' => [
+ 'ListOperations' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/operations',
+ ],
+ 'GetOperation' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{name=operations/**}',
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'DeleteOperation' => [
+ 'method' => 'delete',
+ 'uriTemplate' => '/v1beta1/{name=operations/**}',
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'CancelOperation' => [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{name=operations/**}:cancel',
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ ],
+ 'google.showcase.v1beta1.Testing' => [
+ 'CreateSession' => [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/sessions',
+ 'body' => 'session',
+ ],
+ 'DeleteSession' => [
+ 'method' => 'delete',
+ 'uriTemplate' => '/v1beta1/{name=sessions/*}',
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'DeleteTest' => [
+ 'method' => 'delete',
+ 'uriTemplate' => '/v1beta1/{name=sessions/*/tests/*}',
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'GetSession' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{name=sessions/*}',
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'ListSessions' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/sessions',
+ ],
+ 'ListTests' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1beta1/{parent=sessions/*}/tests',
+ 'placeholders' => [
+ 'parent' => [
+ 'getters' => [
+ 'getParent',
+ ],
+ ],
+ ],
+ ],
+ 'ReportSession' => [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{name=sessions/*}:report',
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ 'VerifyTest' => [
+ 'method' => 'post',
+ 'uriTemplate' => '/v1beta1/{name=sessions/*/tests/*}:check',
+ 'placeholders' => [
+ 'name' => [
+ 'getters' => [
+ 'getName',
+ ],
+ ],
+ ],
+ ],
+ ],
+ ],
+ 'numericEnums' => true,
+];
diff --git a/Gax/tests/Unit/AgentHeaderTest.php b/Gax/tests/Unit/AgentHeaderTest.php
new file mode 100644
index 000000000000..69dc6c74b2c6
--- /dev/null
+++ b/Gax/tests/Unit/AgentHeaderTest.php
@@ -0,0 +1,167 @@
+ [
+ 'gl-php/' . phpversion() .
+ ' gapic/' .
+ ' gax/' . Version::getApiCoreVersion() .
+ ' grpc/' . phpversion('grpc') .
+ ' rest/' . Version::getApiCoreVersion() .
+ ' pb/' . (phpversion('protobuf') ? phpversion('protobuf') . '+c' : '+n')
+ ]];
+
+ $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 rest/3.3.3 pb/6.6.6+n'
+ ]];
+
+ $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',
+ 'protobufVersion' => '6.6.6+n',
+ ]);
+
+ $this->assertSame($expectedHeader, $header);
+ }
+
+ public function testWithoutVersionInput()
+ {
+ $expectedHeader = [AgentHeader::AGENT_HEADER_KEY => [
+ 'gl-php/' . phpversion() .
+ ' gccl/ gapic/ gax/' . Version::getApiCoreVersion() .
+ ' grpc/' . phpversion('grpc') .
+ ' rest/' . Version::getApiCoreVersion() .
+ ' pb/' . (phpversion('protobuf') ? phpversion('protobuf') . '+c' : '+n')
+ ]];
+
+ $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') .
+ ' rest/' . Version::getApiCoreVersion() .
+ ' pb/' . (phpversion('protobuf') ? phpversion('protobuf') . '+c' : '+n')
+ ]];
+
+ $header = AgentHeader::buildAgentHeader([
+ 'libName' => 'gccl',
+ 'libVersion' => null,
+ 'gapicVersion' => null,
+ 'apiCoreVersion' => null,
+ 'phpVersion' => null,
+ 'grpcVersion' => null,
+ 'protobufVersion' => null,
+ ]);
+
+ $this->assertSame($expectedHeader, $header);
+ }
+
+ public function testGetGapicVersionWithVersionFile()
+ {
+ require_once __DIR__ . '/testdata/mocks/src/GapicClientStub.php';
+ $expectedVersion = '1.2.3-dev';
+ $actualVersion = AgentHeader::readGapicVersionFromFile(GapicClientStub::class);
+ $this->assertEquals($expectedVersion, $actualVersion);
+ }
+
+ public function testGetGapicVersionWithNoAvailableVersion()
+ {
+ $this->assertSame('', AgentHeader::readGapicVersionFromFile(__CLASS__));
+ }
+
+ public function testWithGrpcAndRest()
+ {
+ $expectedHeader = [AgentHeader::AGENT_HEADER_KEY => [
+ 'gl-php/' . phpversion() .
+ ' gapic/' .
+ ' gax/3.3.3' .
+ ' grpc/4.4.4' .
+ ' rest/5.5.5' .
+ ' pb/6.6.6+n'
+ ]];
+
+ $header = AgentHeader::buildAgentHeader([
+ 'apiCoreVersion' => '3.3.3',
+ 'grpcVersion' => '4.4.4',
+ 'restVersion' => '5.5.5',
+ 'protobufVersion' => '6.6.6+n',
+ ]);
+
+ $this->assertSame($expectedHeader, $header);
+ }
+
+ public function testWithRestAndGaxFallback()
+ {
+ $expectedHeader = [AgentHeader::AGENT_HEADER_KEY => [
+ 'gl-php/' . phpversion() .
+ ' gapic/' .
+ ' gax/3.3.3' .
+ ' grpc/' . phpversion('grpc') .
+ ' rest/3.3.3' .
+ ' pb/6.6.6+n'
+ ]];
+
+ $header = AgentHeader::buildAgentHeader([
+ 'apiCoreVersion' => '3.3.3',
+ 'restVersion' => null,
+ 'protobufVersion' => '6.6.6+n',
+ ]);
+
+ $this->assertSame($expectedHeader, $header);
+ }
+}
diff --git a/Gax/tests/Unit/ApiExceptionTest.php b/Gax/tests/Unit/ApiExceptionTest.php
new file mode 100644
index 000000000000..b4ce68be5876
--- /dev/null
+++ b/Gax/tests/Unit/ApiExceptionTest.php
@@ -0,0 +1,825 @@
+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());
+ $this->assertEmpty($apiException->getErrorDetails());
+ }
+
+ /**
+ * @dataProvider getMetadata
+ */
+ public function testWithMetadataWithoutErrorInfo($metadata, $metadataArray, $unserializedErrorsCount)
+ {
+ $status = new \stdClass();
+ $status->code = Code::OK;
+ $status->details = 'testWithMetadata';
+ $status->metadata = $metadata;
+
+ $apiException = ApiException::createFromStdClass($status);
+
+ $expectedMessageWithoutErrorDetails = json_encode([
+ 'message' => 'testWithMetadata',
+ 'code' => Code::OK,
+ 'status' => 'OK',
+ 'details' => $metadataArray
+ ], JSON_PRETTY_PRINT);
+
+ $this->assertSame(Code::OK, $apiException->getCode());
+ $this->assertSame($expectedMessageWithoutErrorDetails, $apiException->getMessage());
+ $this->assertSame($metadata, $apiException->getMetadata());
+ $this->assertCount($unserializedErrorsCount, $apiException->getErrorDetails());
+ }
+
+ /**
+ * Test without ErrorInfo in Metadata
+ * @dataProvider getMetadata
+ */
+ public function testCreateFromApiResponse($metadata, $metadataArray, $unserializedErrorsCount)
+ {
+ $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());
+ $this->assertCount($unserializedErrorsCount, $apiException->getErrorDetails());
+ }
+
+ 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' => '',
+ ],
+ ];
+
+ // Format: Error, metadataArray, unserialized errors count
+ return [
+ [['unknown-bin' => ['some-data-that-should-not-appear']], $unknownBinData, 0],
+ [['ascii' => ['ascii-data']], $asciiData, 0],
+ [
+ ['google.rpc.retryinfo-bin' => [$retryInfo->serializeToString()]],
+ $retryInfoData,
+ 1
+ ],
+ [
+ [
+ '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,
+ 8
+ ]
+ ];
+ }
+
+ /**
+ * @dataProvider getMetadataWithErrorInfo
+ */
+ public function testWithMetadataWithErrorInfo($metadata, $metadataArray)
+ {
+ $status = new \stdClass();
+ $status->code = Code::OK;
+ $status->details = 'testWithMetadataWithErrorInfo';
+ $status->metadata = $metadata;
+
+ $apiException = ApiException::createFromStdClass($status);
+
+ $expectedMessage = json_encode(
+ [
+ 'reason' => '',
+ 'domain' => '',
+ 'errorInfoMetadata' => [],
+ 'message' => 'testWithMetadataWithErrorInfo',
+ '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());
+ }
+
+ /**
+ * Test with ErrorInfo in Metadata
+ * @dataProvider getMetadataWithErrorInfo
+ */
+ public function testCreateFromApiResponseWithErrorInfo($metadata, $metadataArray)
+ {
+ $basicMessage = 'testWithMetadataWithErrorInfo';
+ $code = Code::OK;
+ $status = 'OK';
+
+ $apiException = ApiException::createFromApiResponse($basicMessage, $code, $metadata);
+
+ $expectedMessage = json_encode([
+ 'reason' => '',
+ 'domain' => '',
+ 'errorInfoMetadata' => [],
+ '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());
+ $this->assertSame('', $apiException->getReason());
+ $this->assertSame('', $apiException->getDomain());
+ $this->assertSame([], $apiException->getErrorInfoMetadata());
+ }
+
+ public function getMetadataWithErrorInfo()
+ {
+ $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.errorinfo-bin',
+ 'reason' => '',
+ 'domain' => '',
+ 'metadata' => [],
+ ],
+ [
+ '@type' => 'google.rpc.help-bin',
+ 'links' => [],
+ ],
+ [
+ '@type' => 'google.rpc.localizedmessage-bin',
+ 'locale' => '',
+ 'message' => '',
+ ],
+ ];
+ return [
+ [[
+ '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.errorinfo-bin' => [(new ErrorInfo())->serializeToString()],
+ 'google.rpc.help-bin' => [(new Help())->serializeToString()],
+ 'google.rpc.localizedmessage-bin' => [(new LocalizedMessage())->serializeToString()],
+ ], $allKnownTypesData],
+ ];
+ }
+
+ public function testGetErrorDetails()
+ {
+ $metadata = [
+ [
+ '@type' => 'type.googleapis.com/google.rpc.RetryInfo',
+ 'retry_delay' => '1s'
+ ],
+ [
+ '@type' => 'type.googleapis.com/google.rpc.DebugInfo',
+ 'stackEntries' => [],
+ 'detail' => ''
+ ],
+ [
+ '@type' => 'type.googleapis.com/google.rpc.QuotaFailure',
+ 'violations' => [],
+ ],
+ [
+ '@type' => 'type.googleapis.com/google.rpc.BadRequest',
+ 'fieldViolations' => []
+ ],
+ [
+ '@type' => 'type.googleapis.com/google.rpc.RequestInfo',
+ 'requestId' => '',
+ 'servingData' => '',
+ ],
+ [
+ '@type' => 'type.googleapis.com/google.rpc.ResourceInfo',
+ 'resourceType' => '',
+ 'resourceName' => '',
+ 'owner' => '',
+ 'description' => '',
+ ],
+ [
+ '@type' => 'type.googleapis.com/google.rpc.ErrorInfo',
+ 'reason' => 'Error Info',
+ 'domain' => 'Error Info Domain',
+ 'metadata' => [
+ 'test' => 'Error Info Test'
+ ],
+ ],
+ [
+ '@type' => 'type.googleapis.com/google.rpc.Help',
+ 'links' => [],
+ ],
+ [
+ '@type' => 'type.googleapis.com/google.rpc.LocalizedMessage',
+ 'locale' => '',
+ 'message' => '',
+ ],
+ ];
+
+ $basicMessage = 'testWithRestMetadata';
+ $code = Code::OK;
+ $status = 'OK';
+
+ $apiException = ApiException::createFromRestApiResponse($basicMessage, $code, $metadata);
+
+ $expectedMessage = json_encode([
+ 'reason' => 'Error Info',
+ 'domain' => 'Error Info Domain',
+ 'errorInfoMetadata' => [
+ 'test' => 'Error Info Test'
+ ],
+ 'message' => $basicMessage,
+ 'code' => $code,
+ 'status' => $status,
+ 'details' => $metadata
+ ], JSON_PRETTY_PRINT);
+
+ $this->assertSame($expectedMessage, $apiException->getMessage());
+ $this->assertCount(9, $apiException->getErrorDetails());
+ $this->assertSame('Error Info', $apiException->getReason());
+ $this->assertSame('Error Info Domain', $apiException->getDomain());
+ $this->assertSame(
+ [
+ 'test' => 'Error Info Test'
+ ],
+ $apiException->getErrorInfoMetadata()
+ );
+ }
+
+ /**
+ * @dataProvider getRestMetadata
+ */
+ public function testCreateFromRestApiResponse($metadata)
+ {
+ $basicMessage = 'testWithRestMetadata';
+ $code = Code::OK;
+ $status = 'OK';
+
+ $apiException = ApiException::createFromRestApiResponse($basicMessage, $code, $metadata);
+
+ $expectedMessage = json_encode([
+ 'message' => $basicMessage,
+ 'code' => $code,
+ 'status' => $status,
+ 'details' => $metadata
+ ], JSON_PRETTY_PRINT);
+
+ $this->assertSame($expectedMessage, $apiException->getMessage());
+ $this->assertSame(null, $apiException->getReason());
+ $this->assertSame(null, $apiException->getDomain());
+ $this->assertSame(null, $apiException->getErrorInfoMetadata());
+ }
+
+ public function getRestMetadata()
+ {
+ $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 [
+ [
+ [[]],
+ [[null]],
+ [$unknownBinData],
+ [$asciiData],
+ [$retryInfoData],
+ [$allKnownTypesData]
+ ]
+ ];
+ }
+
+ /**
+ * @dataProvider getRestMetadataWithErrorInfo
+ */
+ public function testCreateFromRestApiResponseWithErrorInfo($metadata)
+ {
+ $basicMessage = 'testWithRestMetadataWithErrorInfo';
+ $code = Code::OK;
+ $status = 'OK';
+
+ $apiException = ApiException::createFromRestApiResponse($basicMessage, $code, $metadata);
+
+ $expectedMessage = json_encode([
+ 'reason' => '',
+ 'domain' => '',
+ 'errorInfoMetadata' => [],
+ 'message' => $basicMessage,
+ 'code' => $code,
+ 'status' => $status,
+ 'details' => $metadata
+ ], JSON_PRETTY_PRINT);
+
+ $this->assertSame($expectedMessage, $apiException->getMessage());
+ $this->assertSame('', $apiException->getReason());
+ $this->assertSame('', $apiException->getDomain());
+ $this->assertSame([], $apiException->getErrorInfoMetadata());
+ }
+
+ public function getRestMetadataWithErrorInfo()
+ {
+ $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' => '',
+ ],
+ [
+ '@type' => 'google.rpc.errorinfo-bin',
+ 'reason' => '',
+ 'domain' => '',
+ 'metadata' => [],
+ ],
+ ];
+
+ return [
+ [$allKnownTypesData]
+ ];
+ }
+
+ /**
+ * Test without ErrorInfo
+ * @dataProvider getRpcStatusData
+ */
+ public function testCreateFromRpcStatus($status, $expectedApiException)
+ {
+ $actualApiException = ApiException::createFromRpcStatus($status);
+ $this->assertEquals($expectedApiException, $actualApiException);
+ $this->assertEquals(null, $actualApiException->getReason());
+ $this->assertSame(null, $actualApiException->getDomain());
+ $this->assertSame(null, $actualApiException->getErrorInfoMetadata());
+ }
+
+ 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' => [$any],
+ 'basicMessage' => $status->getMessage(),
+ ]
+ )
+ ]
+ ];
+ }
+
+ /**
+ * Test with ErrorInfo
+ * @dataProvider getRpcStatusDataWithErrorInfo
+ */
+ public function testCreateFromRpcStatusWithErrorInfo($status, $expectedApiException)
+ {
+ $actualApiException = ApiException::createFromRpcStatus($status);
+ $this->assertEquals($expectedApiException, $actualApiException);
+ $this->assertSame('SERVICE_DISABLED', $actualApiException->getReason());
+ $this->assertSame('googleapis.com', $actualApiException->getDomain());
+ $this->assertSame([], $actualApiException->getErrorInfoMetadata());
+ }
+
+ public function getRpcStatusDataWithErrorInfo()
+ {
+ $errorInfo = new ErrorInfo();
+ $errorInfo->setDomain('googleapis.com');
+ $errorInfo->setReason('SERVICE_DISABLED');
+ $any = new Any();
+ $any->pack($errorInfo);
+
+ $status = new Status();
+ $status->setMessage('status string');
+ $status->setCode(Code::OK);
+ $status->setDetails([$any]);
+
+ $expectedMessage = json_encode([
+ 'reason' => $errorInfo->getReason(),
+ 'domain' => $errorInfo->getDomain(),
+ 'errorInfoMetadata' => [],
+ 'message' => $status->getMessage(),
+ 'code' => $status->getCode(),
+ 'status' => 'OK',
+ 'details' => [
+ [
+ 'reason' => 'SERVICE_DISABLED',
+ 'domain' => 'googleapis.com',
+ 'metadata' => []],
+ ]
+ ], JSON_PRETTY_PRINT);
+
+ return [
+ [
+ $status,
+ new ApiException(
+ $expectedMessage,
+ Code::OK,
+ 'OK',
+ [
+ 'metadata' => [$any],
+ 'basicMessage' => $status->getMessage(),
+ ]
+ )
+ ]
+ ];
+ }
+
+ /**
+ * @dataProvider buildRequestExceptions
+ */
+ public function testCreateFromRequestException($re, $stream, $expectedCode)
+ {
+ $ae = ApiException::createFromRequestException($re, $stream);
+ $this->assertSame($expectedCode, $ae->getCode());
+ }
+
+ public function buildRequestExceptions()
+ {
+ $error = [
+ 'error' => [
+ 'status' => 'NOT_FOUND',
+ 'message' => 'Ruh-roh.',
+ ]
+ ];
+ $protoError = [
+ 'error' => [
+ 'code' => ApiStatus::INVALID_ARGUMENT,
+ 'message' => 'error',
+ 'status' => 'INVALID_ARGUMENT',
+ 'details' => [
+ [[
+ '@type' => 'type.googleapis.com/google.rpc.BadRequest',
+ 'fieldViolations' => [
+ 'field' => 'target_language_code',
+ 'description' => 'Target language: invalid language'
+ ]
+ ]]
+ ]
+ ]
+ ];
+ $stream = RequestException::create(
+ new Request('POST', 'http://www.example.com'),
+ new Response(
+ 404,
+ [],
+ json_encode([$error])
+ )
+ );
+ $unary = RequestException::create(
+ new Request('POST', 'http://www.example.com'),
+ new Response(
+ 404,
+ [],
+ json_encode([$error])
+ )
+ );
+ unset($error['error']['message']);
+ $withoutErrorMessageStream = RequestException::create(
+ new Request('POST', 'http://www.example.com'),
+ new Response(
+ 404,
+ [],
+ json_encode([$error])
+ )
+ );
+ $withoutErrorMessageUnary = RequestException::create(
+ new Request('POST', 'http://www.example.com'),
+ new Response(
+ 404,
+ [],
+ json_encode($error)
+ )
+ );
+ $withProtoError = RequestException::create(
+ new Request('POST', 'http://www.example.com'),
+ new Response(
+ 400,
+ [],
+ json_encode($protoError)
+ )
+ );
+ return [
+ [$stream, true, Code::NOT_FOUND],
+ [$unary, false, Code::NOT_FOUND],
+ [$withoutErrorMessageStream, true, Code::NOT_FOUND],
+ [$withoutErrorMessageUnary, true, Code::NOT_FOUND],
+ [$withProtoError, false, Code::INVALID_ARGUMENT]
+ ];
+ }
+
+ public function testGrpcStatusDetailsBinUnknownType()
+ {
+ $link = new Link(['url' => 'foo.com', 'description' => 'a helpful link']);
+ $help = new Help(['links' => [$link]]);
+ $validAny = new Any();
+ $validAny->setTypeUrl('type.googleapis.com/google.rpc.Help');
+ $validAny->setValue($help->serializeToString());
+
+ $invalidAny = new Any();
+ $invalidAny->setTypeUrl('type.googleapis.com/invalid.url');
+
+ $statusBin = new Status();
+ $statusBin->setDetails([$validAny, $invalidAny]);
+
+ $status = new stdClass();
+ $status->metadata = ['grpc-status-details-bin' => [$statusBin->serializeToString()]];
+ $status->details = 'exception message';
+ $status->code = 123;
+
+ $exception = ApiException::createFromStdClass($status);
+ $this->assertCount(2, $exception->getErrorDetails());
+
+ [$detail1, $detail2] = $exception->getErrorDetails();
+ $this->assertEquals($help, $detail1); // verify the valid detail was decoded
+ $this->assertEquals($invalidAny, $detail2); // verify the invalid detail did not change
+ }
+}
diff --git a/Gax/tests/Unit/ApiStatusTest.php b/Gax/tests/Unit/ApiStatusTest.php
new file mode 100644
index 000000000000..1a293e5b646a
--- /dev/null
+++ b/Gax/tests/Unit/ApiStatusTest.php
@@ -0,0 +1,154 @@
+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]
+ ];
+ }
+
+ /**
+ * @dataProvider getHttpCodeAndStatus
+ */
+ public function testRpcCodeFromHttpStatus($httpCode, $rpcCode)
+ {
+ $this->assertSame($rpcCode, ApiStatus::rpcCodeFromHttpStatusCode($httpCode));
+ }
+
+ public function getHttpCodeAndStatus()
+ {
+ return [
+ [400, Code::INVALID_ARGUMENT],
+ [401, Code::UNAUTHENTICATED],
+ [403, Code::PERMISSION_DENIED],
+ [404, Code::NOT_FOUND],
+ [409, Code::ABORTED],
+ [416, Code::OUT_OF_RANGE],
+ [429, Code::RESOURCE_EXHAUSTED],
+ [499, Code::CANCELLED],
+ [501, Code::UNIMPLEMENTED],
+ [503, Code::UNAVAILABLE],
+ [504, Code::DEADLINE_EXCEEDED],
+ // Unmapped 2xx returns Status::OK
+ [201, Code::OK],
+ // Unmapped 4xx returns Status::FAILED_PRECONDITION
+ [405, Code::FAILED_PRECONDITION],
+ // Unmapped 5xx returns Status::INTERNAL
+ [505, Code::INTERNAL],
+ // Anything else returns Status::UNRECOGNIZED_CODE
+ [-1, ApiStatus::UNRECOGNIZED_CODE],
+ [100, ApiStatus::UNRECOGNIZED_CODE],
+ [300, ApiStatus::UNRECOGNIZED_CODE],
+ ];
+ }
+}
diff --git a/Gax/tests/Unit/ArrayTraitTest.php b/Gax/tests/Unit/ArrayTraitTest.php
new file mode 100644
index 000000000000..c11da2613fa8
--- /dev/null
+++ b/Gax/tests/Unit/ArrayTraitTest.php
@@ -0,0 +1,193 @@
+implementation = new class() {
+ use ArrayTrait {
+ arrayFilterRemoveNull as public;
+ isAssoc as public;
+ pluck as public;
+ pluckArray as public;
+ subsetArray as public;
+ arrayMergeRecursive as public;
+ }
+ };
+ }
+
+ public function testPluck()
+ {
+ $value = '123';
+ $key = 'key';
+ $array = [$key => $value];
+ $actualValue = $this->implementation->pluck($key, $array);
+
+ $this->assertEquals($value, $actualValue);
+ $this->assertEquals([], $array);
+ }
+
+ public function testPluckThrowsExceptionWithInvalidKey()
+ {
+ $array = [];
+ $this->expectException(InvalidArgumentException::class);
+ $this->implementation->pluck('not_here', $array);
+ }
+
+ public function testPluckArray()
+ {
+ $keys = ['key1', 'key2'];
+ $array = [
+ 'key1' => 'test',
+ 'key2' => 'test'
+ ];
+ $expectedArray = $array;
+
+ $actualValues = $this->implementation->pluckArray($keys, $array);
+
+ $this->assertEquals($expectedArray, $actualValues);
+ $this->assertEquals([], $array);
+ }
+
+ public function testIsAssocTrue()
+ {
+ $actual = $this->implementation->isAssoc([
+ 'test' => 1,
+ 'test' => 2
+ ]);
+
+ $this->assertTrue($actual);
+ }
+
+ public function testIsAssocFalse()
+ {
+ $actual = $this->implementation->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->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->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',
+ ],
+ ]
+ ];
+ }
+
+ public function testArrayMergeRecursive()
+ {
+ $array1 = [
+ 'a' => [
+ 'b' => 'c'
+ ],
+ 'e' => 'f'
+ ];
+
+ $array2 = [
+ 'a' => [
+ 'b' => 'd'
+ ],
+ 'g' => 'h'
+ ];
+
+ $expected = [
+ 'a' => [
+ 'b' => 'd'
+ ],
+ 'e' => 'f',
+ 'g' => 'h'
+ ];
+
+ $res = $this->implementation->arrayMergeRecursive($array1, $array2);
+ $this->assertEquals($expected, $res);
+ }
+}
diff --git a/Gax/tests/Unit/BidiStreamTest.php b/Gax/tests/Unit/BidiStreamTest.php
new file mode 100644
index 000000000000..d9453e578ed6
--- /dev/null
+++ b/Gax/tests/Unit/BidiStreamTest.php
@@ -0,0 +1,379 @@
+assertSame($call, $stream->getBidiStreamingCall());
+ $this->assertSame([], iterator_to_array($stream->closeWriteAndReadAll()));
+ }
+
+ 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();
+
+ $this->expectException(ApiException::class);
+ $this->expectExceptionMessage('empty failure read');
+
+ $stream->read();
+ }
+
+ public function testEmptyFailureReadAll()
+ {
+ $call = new MockBidiStreamingCall([], null, new MockStatus(Code::INTERNAL, 'empty failure readall'));
+ $stream = new BidiStream($call);
+
+ $this->assertSame($call, $stream->getBidiStreamingCall());
+
+ $this->expectException(ApiException::class);
+ $this->expectExceptionMessage('empty failure readall');
+
+ iterator_to_array($stream->closeWriteAndReadAll());
+ }
+
+ public function testReadAfterComplete()
+ {
+ $call = new MockBidiStreamingCall([]);
+ $stream = new BidiStream($call);
+
+ $this->assertSame($call, $stream->getBidiStreamingCall());
+ $stream->closeWrite();
+ $this->assertNull($stream->read());
+
+ $this->expectException(ValidationException::class);
+ $this->expectExceptionMessage('Cannot call read() after streaming call is complete.');
+
+ $stream->read();
+ }
+
+ public function testWriteAfterComplete()
+ {
+ $call = new MockBidiStreamingCall([]);
+ $stream = new BidiStream($call);
+
+ $this->assertSame($call, $stream->getBidiStreamingCall());
+ $stream->closeWrite();
+ $this->assertNull($stream->read());
+
+ $this->expectException(ValidationException::class);
+ $this->expectExceptionMessage('Cannot call write() after streaming call is complete.');
+
+ $stream->write('request');
+ }
+
+ public function testWriteAfterCloseWrite()
+ {
+ $call = new MockBidiStreamingCall([]);
+ $stream = new BidiStream($call);
+
+ $this->assertSame($call, $stream->getBidiStreamingCall());
+ $stream->closeWrite();
+
+ $this->expectException(ValidationException::class);
+ $this->expectExceptionMessage('Cannot call write() after calling 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++;
+ }
+ }
+
+ 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());
+
+ $this->expectException(ApiException::class);
+ $this->expectExceptionMessage('read failure');
+
+ $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());
+ }
+
+ 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);
+
+ $this->expectException(ApiException::class);
+ $this->expectExceptionMessage('write failure without close');
+
+ 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()));
+ }
+
+ public function testWriteCallsLogger()
+ {
+ $logger = $this->prophesize(StdOutLogger::class);
+ $logger->debug(Argument::cetera())
+ ->shouldBeCalledTimes(2);
+
+ $requests = [
+ $this->createStatus(Code::OK, 'request1'),
+ $this->createStatus(Code::OK, 'request2')
+ ];
+ $responses = [];
+ $call = new MockBidiStreamingCall($responses, ['\Google\Rpc\Status', 'mergeFromString']);
+ $stream = new BidiStream($call, logger: $logger->reveal());
+
+ $stream->writeAll($requests);
+ }
+
+ public function testWriteStringDoesNotCallLogger()
+ {
+ $logger = $this->prophesize(StdOutLogger::class);
+ $logger->debug(Argument::cetera())
+ ->shouldNotBeCalled();
+ $logger->info(Argument::cetera())
+ ->shouldNotBeCalled();
+
+ $requests = ['request1', 'request2'];
+ $responses = [];
+ $call = new MockBidiStreamingCall($responses);
+ $stream = new BidiStream($call, logger: $logger->reveal());
+
+ $stream->writeAll($requests);
+ }
+
+ public function testReadCallsLogger()
+ {
+ $logger = $this->prophesize(StdOutLogger::class);
+ $logger->debug(Argument::cetera())
+ ->shouldBeCalledTimes(3);
+
+ $requests = [
+ $this->createStatus(Code::OK, 'request1'),
+ $this->createStatus(Code::OK, 'request2')
+ ];
+ $responses = [];
+ $call = new MockBidiStreamingCall($responses, ['\Google\Rpc\Status', 'mergeFromString']);
+ $stream = new BidiStream($call, logger: $logger->reveal());
+
+ $stream->writeAll($requests);
+
+ foreach ($stream->closeWriteAndReadAll() as $_) {
+ }
+ }
+}
diff --git a/Gax/tests/Unit/ClientOptionsTraitTest.php b/Gax/tests/Unit/ClientOptionsTraitTest.php
new file mode 100644
index 000000000000..a5c9d964a57f
--- /dev/null
+++ b/Gax/tests/Unit/ClientOptionsTraitTest.php
@@ -0,0 +1,750 @@
+clientStub = new class() {
+ use ClientOptionsTrait {
+ buildClientOptions as public;
+ createCredentialsWrapper as public;
+ determineMtlsEndpoint as public;
+ getGapicVersion as public;
+ shouldUseMtlsEndpoint as public;
+ }
+
+ private const SERVICE_NAME = 'TEST_SERVICE_NAME';
+
+ 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 static function getClientDefaults()
+ {
+ return [
+ 'apiEndpoint' => 'test.address.com:443',
+ 'gcpApiConfigPath' => __DIR__ . '/testdata/resources/test_service_grpc_config.json',
+ ];
+ }
+ };
+
+ $this->universeDomainClientStub = new class() {
+ use ClientOptionsTrait {
+ buildClientOptions as public;
+ }
+
+ private const SERVICE_ADDRESS_TEMPLATE = 'stub.UNIVERSE_DOMAIN';
+
+ public static function getClientDefaults()
+ {
+ return [
+ 'apiEndpoint' => 'test.address.com:443',
+ ];
+ }
+ };
+ }
+
+ public function tearDown(): void
+ {
+ // Reset the static gapicVersion field between tests
+ $this->clientStub->set('gapicVersionFromFile', null, true);
+ }
+
+ public function testGetGapicVersionWithVersionFile()
+ {
+ require_once __DIR__ . '/testdata/mocks/src/GapicClientStub.php';
+ $version = '1.2.3-dev';
+ $client = new GapicClientStub();
+ $this->assertEquals($version, $client::getGapicVersion([]));
+ }
+
+ public function testGetGapicVersionWithNoAvailableVersion()
+ {
+ $this->assertSame('', $this->clientStub::getGapicVersion([]));
+ }
+
+ public function testGetGapicVersionWithLibVersion()
+ {
+ $version = '1.2.3-dev';
+ $this->clientStub->set('gapicVersionFromFile', $version, true);
+ $options = ['libVersion' => $version];
+ $this->assertEquals($version, $this->clientStub::getGapicVersion(
+ $options
+ ));
+ }
+
+ /**
+ * @dataProvider createCredentialsWrapperData
+ */
+ public function testCreateCredentialsWrapper($auth, $authConfig, $expectedCredentialsWrapper)
+ {
+ $actualCredentialsWrapper = $this->clientStub->createCredentialsWrapper(
+ $auth,
+ $authConfig,
+ GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN
+ );
+
+ $this->assertEquals($expectedCredentialsWrapper, $actualCredentialsWrapper);
+ }
+
+ public function createCredentialsWrapperData()
+ {
+ $keyFilePath = __DIR__ . '/testdata/creds/json-key-file.json';
+ $keyFile = json_decode(file_get_contents($keyFilePath), true);
+
+ $fetcher = $this->prophesize(FetchAuthTokenInterface::class)->reveal();
+ $credentialsWrapper = new CredentialsWrapper($fetcher);
+
+ return [
+ [$keyFilePath, [], CredentialsWrapper::build(['keyFile' => $keyFile])],
+ [$keyFile, [], CredentialsWrapper::build(['keyFile' => $keyFile])],
+ [$fetcher, [], new CredentialsWrapper($fetcher)],
+ [$credentialsWrapper, [], $credentialsWrapper],
+ ];
+ }
+
+ /**
+ * @runInSeparateProcess
+ */
+ public function testCreateCredentialsWrapperFromEnv()
+ {
+ $keyFilePath = __DIR__ . '/testdata/creds/json-key-file.json';
+ putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $keyFilePath);
+
+ $expectedCredentialsWrapper = CredentialsWrapper::build();
+ $actualCredentialsWrapper = $this->clientStub->createCredentialsWrapper(
+ null,
+ [],
+ GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN
+ );
+
+ $this->assertEquals($expectedCredentialsWrapper, $actualCredentialsWrapper);
+ }
+
+
+ /**
+ * @dataProvider createCredentialsWrapperValidationExceptionData
+ */
+ public function testCreateCredentialsWrapperValidationException($auth, $authConfig)
+ {
+
+ $this->expectException(ValidationException::class);
+
+ $this->clientStub->createCredentialsWrapper(
+ $auth,
+ $authConfig,
+ ''
+ );
+ }
+
+ public function createCredentialsWrapperValidationExceptionData()
+ {
+ return [
+ ['not a json string', []],
+ [new \stdClass(), []],
+ ];
+ }
+
+ /**
+ * @dataProvider createCredentialsWrapperInvalidArgumentExceptionData
+ */
+ public function testCreateCredentialsWrapperInvalidArgumentException($auth, $authConfig)
+ {
+
+ $this->expectException(InvalidArgumentException::class);
+
+ $this->clientStub->createCredentialsWrapper(
+ $auth,
+ $authConfig,
+ ''
+ );
+ }
+
+ public function createCredentialsWrapperInvalidArgumentExceptionData()
+ {
+ return [
+ [['array' => 'without right keys'], []],
+ ];
+ }
+
+ /**
+ * @dataProvider buildClientOptionsProvider
+ */
+ public function testBuildClientOptions($options, $expectedUpdatedOptions)
+ {
+ if (!extension_loaded('sysvshm')) {
+ $this->markTestSkipped('The sysvshm extension must be installed to execute this test.');
+ }
+ $updatedOptions = $this->clientStub->buildClientOptions($options);
+ $this->assertEquals($expectedUpdatedOptions, $updatedOptions);
+ }
+
+ public function buildClientOptionsProvider()
+ {
+ $apiConfig = new ApiConfig();
+ $apiConfig->mergeFromJsonString(
+ file_get_contents(__DIR__ . '/testdata/resources/test_service_grpc_config.json')
+ );
+ $grpcGcpConfig = new Config('test.address.com:443', $apiConfig);
+
+ $defaultOptions = [
+ 'apiEndpoint' => 'test.address.com:443',
+ 'gcpApiConfigPath' => __DIR__ . '/testdata/resources/test_service_grpc_config.json',
+ 'disableRetries' => false,
+ 'transport' => null,
+ 'transportConfig' => [
+ 'grpc' => [
+ 'stubOpts' => [
+ 'grpc_call_invoker' => $grpcGcpConfig->callInvoker(),
+ 'grpc.service_config_disable_resolution' => 1,
+ ],
+ 'logger' => null,
+ ],
+ 'rest' => [
+ 'logger' => null,
+ ],
+ 'grpc-fallback' => [
+ 'logger' => null,
+ ],
+ ],
+ 'credentials' => null,
+ 'credentialsConfig' => [],
+ 'gapicVersion' => '',
+ 'libName' => null,
+ 'libVersion' => null,
+ 'clientCertSource' => null,
+ 'logger' => null,
+ 'universeDomain' => 'googleapis.com',
+ ];
+
+ $restConfigOptions = $defaultOptions;
+ $restConfigOptions['transportConfig']['rest'] += [
+ 'customRestConfig' => 'value',
+ 'logger' => null,
+ ];
+ $grpcConfigOptions = $defaultOptions;
+ $grpcConfigOptions['transportConfig']['grpc'] += [
+ 'customGrpcConfig' => 'value',
+ 'logger' => null,
+ ];
+ return [
+ [[], $defaultOptions],
+ [
+ [
+ 'transportConfig' => [
+ 'rest' => [
+ 'customRestConfig' => 'value'
+ ]
+ ]
+ ], $restConfigOptions
+ ],
+ [
+ [
+ 'transportConfig' => [
+ 'grpc' => [
+ 'customGrpcConfig' => 'value'
+ ]
+ ]
+ ], $grpcConfigOptions
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider buildClientOptionsProviderRestOnly
+ */
+ public function testBuildClientOptionsRestOnly($options, $expectedUpdatedOptions)
+ {
+ if (!extension_loaded('sysvshm')) {
+ $this->markTestSkipped('The sysvshm extension must be installed to execute this test.');
+ }
+ $restOnlyClient = new class() {
+ use ClientOptionsTrait {
+ buildClientOptions as public;
+ }
+
+ private static function supportedTransports()
+ {
+ return ['rest', 'fake-transport'];
+ }
+
+ private static function defaultTransport()
+ {
+ return 'rest';
+ }
+ };
+ $updatedOptions = $restOnlyClient->buildClientOptions($options);
+ $this->assertEquals($expectedUpdatedOptions, $updatedOptions);
+ }
+
+ public function buildClientOptionsProviderRestOnly()
+ {
+ $defaultOptions = [
+ 'apiEndpoint' => null,
+ 'disableRetries' => false,
+ 'transport' => null,
+ 'transportConfig' => [
+ 'rest' => [
+ 'logger' => null,
+ ],
+ 'fake-transport' => []
+ ],
+ 'credentials' => null,
+ 'credentialsConfig' => [],
+ 'gapicVersion' => '',
+ 'libName' => null,
+ 'libVersion' => null,
+ 'clientCertSource' => null,
+ 'universeDomain' => 'googleapis.com',
+ 'logger' => null
+ ];
+
+ $restConfigOptions = $defaultOptions;
+ $restConfigOptions['transportConfig']['rest'] += [
+ 'customRestConfig' => 'value'
+ ];
+
+ $fakeTransportConfigOptions = $defaultOptions;
+ $fakeTransportConfigOptions['transportConfig']['fake-transport'] += [
+ 'customRestConfig' => 'value'
+ ];
+ return [
+ [[], $defaultOptions],
+ [
+ [
+ 'transportConfig' => [
+ 'rest' => [
+ 'customRestConfig' => 'value'
+ ]
+ ]
+ ], $restConfigOptions
+ ],
+ [
+ [
+ 'transportConfig' => [
+ 'fake-transport' => [
+ 'customRestConfig' => 'value'
+ ]
+ ]
+ ], $fakeTransportConfigOptions
+ ],
+ ];
+ }
+
+ public function testDefaultScopes()
+ {
+ $defaultScopeClient = new class() {
+ use ClientOptionsTrait {
+ buildClientOptions as public;
+ }
+
+ const SERVICE_ADDRESS = 'service-address';
+
+ public static $serviceScopes = [
+ 'default-scope-1',
+ 'default-scope-2',
+ ];
+
+ public static function getClientDefaults()
+ {
+ return [
+ 'credentialsConfig' => [
+ 'defaultScopes' => self::$serviceScopes,
+ ],
+ ];
+ }
+ };
+
+ // verify scopes are not set by default
+ $defaultOptions = $defaultScopeClient->buildClientOptions([]);
+ $this->assertArrayNotHasKey('scopes', $defaultOptions['credentialsConfig']);
+
+ // verify scopes are set when a custom api endpoint is used
+ $defaultOptions = $defaultScopeClient->buildClientOptions([
+ 'apiEndpoint' => 'www.someotherendpoint.com',
+ ]);
+ $this->assertArrayHasKey('scopes', $defaultOptions['credentialsConfig']);
+ $this->assertEquals(
+ $defaultScopeClient::$serviceScopes,
+ $defaultOptions['credentialsConfig']['scopes']
+ );
+
+ // verify user-defined scopes override default scopes
+ $defaultOptions = $defaultScopeClient->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
+ $defaultScopeClient::$serviceScopes = null;
+ $defaultOptions = $defaultScopeClient->buildClientOptions([
+ 'apiEndpoint' => 'www.someotherendpoint.com',
+ ]);
+ $this->assertArrayNotHasKey('scopes', $defaultOptions['credentialsConfig']);
+ }
+
+ /** @dataProvider provideDetermineMtlsEndpoint */
+ public function testDetermineMtlsEndpoint($apiEndpoint, $expected)
+ {
+ $this->assertEquals(
+ $expected,
+ $this->clientStub::determineMtlsEndpoint($apiEndpoint)
+ );
+ }
+
+ public function provideDetermineMtlsEndpoint()
+ {
+ return [
+ ['foo', 'foo'], // invalid no-op
+ ['api.dev', 'api.dev'], // invalid no-op
+ // normal endpoint
+ ['vision.googleapis.com', 'vision.mtls.googleapis.com'],
+ // endpoint with protocol
+ ['https://vision.googleapis.com', 'https://vision.mtls.googleapis.com'],
+ // endpoint with protocol and path
+ ['https://vision.googleapis.com/foo', 'https://vision.mtls.googleapis.com/foo'],
+ // regional endpoint
+ ['us-documentai.googleapis.com', 'us-documentai.mtls.googleapis.com'],
+ ];
+ }
+
+ /**
+ * @runInSeparateProcess
+ * @dataProvider provideShouldUseMtlsEndpoint
+ */
+ public function testShouldUseMtlsEndpoint($envVarValue, $options, $expected)
+ {
+
+ putenv('GOOGLE_API_USE_MTLS_ENDPOINT=' . $envVarValue);
+ $this->assertEquals(
+ $expected,
+ $this->clientStub->shouldUseMtlsEndpoint($options)
+ );
+ }
+
+ public function provideShouldUseMtlsEndpoint()
+ {
+ return [
+ ['', [], false],
+ ['always', [], true],
+ ['never', [], false],
+ ['never', ['clientCertSource' => true], false],
+ ['auto', ['clientCertSource' => true], true],
+ ['invalid', ['clientCertSource' => true], true],
+ ['', ['clientCertSource' => true], true],
+ ];
+ }
+
+ /**
+ * @runInSeparateProcess
+ * @dataProvider provideMtlsClientOptions
+ */
+ public function testMtlsClientOptions($envVars, $options, $expected)
+ {
+ foreach ($envVars as $envVar) {
+ putenv($envVar);
+ }
+
+ $options = $this->clientStub->buildClientOptions($options);
+
+ // Only check the keys we care about
+ $options = array_intersect_key(
+ $options,
+ array_flip(['apiEndpoint', 'clientCertSource'])
+ );
+
+ $this->assertEquals($expected, $options);
+ }
+
+ public function provideMtlsClientOptions()
+ {
+ $defaultEndpoint = 'test.address.com:443';
+ $mtlsEndpoint = 'test.mtls.address.com:443';
+ $homeDir = PHP_OS_FAMILY === 'Windows' ? 'APPDATA' : 'HOME';
+
+ return [
+ [
+ [],
+ [],
+ ['apiEndpoint' => $defaultEndpoint, 'clientCertSource' => null]
+ ],
+ [
+ ['GOOGLE_API_USE_MTLS_ENDPOINT=always'],
+ [],
+ ['apiEndpoint' => $mtlsEndpoint, 'clientCertSource' => null]
+ ],
+ [
+ ['GOOGLE_API_USE_MTLS_ENDPOINT=always'],
+ ['apiEndpoint' => 'user.supplied.endpoint:443'],
+ ['apiEndpoint' => 'user.supplied.endpoint:443', 'clientCertSource' => null]
+ ],
+ [
+ ['GOOGLE_API_USE_MTLS_ENDPOINT=never'],
+ ['clientCertSource' => true],
+ ['apiEndpoint' => $defaultEndpoint, 'clientCertSource' => true]
+ ],
+ [
+ [
+ 'GOOGLE_API_USE_MTLS_ENDPOINT=auto'
+ ],
+ ['clientCertSource' => true],
+ ['apiEndpoint' => $mtlsEndpoint, 'clientCertSource' => true]
+ ],
+ [
+ [
+ $homeDir . '=' . __DIR__ . '/testdata/nonexistant',
+ 'GOOGLE_API_USE_MTLS_ENDPOINT', // no env var
+ CredentialsLoader::MTLS_CERT_ENV_VAR . '=true',
+ ],
+ [],
+ ['apiEndpoint' => $defaultEndpoint, 'clientCertSource' => null]
+ ],
+ ];
+ }
+
+ /**
+ * @runInSeparateProcess
+ */
+ public function testMtlsClientOptionWithDefaultClientCertSource()
+ {
+ $homeDir = PHP_OS_FAMILY === 'Windows' ? 'APPDATA' : 'HOME';
+ putenv($homeDir . '=' . __DIR__ . '/testdata/creds/mtls');
+ putenv('GOOGLE_API_USE_MTLS_ENDPOINT=auto');
+ putenv(CredentialsLoader::MTLS_CERT_ENV_VAR . '=true');
+
+ $options = $this->clientStub->buildClientOptions([]);
+
+ $this->assertSame('test.mtls.address.com:443', $options['apiEndpoint']);
+ $this->assertTrue(is_callable($options['clientCertSource']));
+ $this->assertEquals(['foo', 'foo'], $options['clientCertSource']());
+ }
+
+ /**
+ * @dataProvider provideServiceAddressTemplate
+ * @runInSeparateProcess
+ */
+ public function testServiceAddressTemplate(array $options, string $expectedEndpoint, ?string $envVar = null)
+ {
+ if ($envVar) {
+ putenv($envVar);
+ }
+ $updatedOptions = $this->universeDomainClientStub->buildClientOptions($options);
+
+ $this->assertEquals($expectedEndpoint, $updatedOptions['apiEndpoint']);
+ }
+
+ public function provideServiceAddressTemplate()
+ {
+ return [
+ [
+ [],
+ 'stub.googleapis.com', // defaults to "googleapis.com"
+ ],
+ [
+ ['apiEndpoint' => 'new.test.address.com'],
+ 'new.test.address.com', // set through api endpoint
+ ],
+ [
+ ['universeDomain' => 'foo.com'],
+ 'stub.foo.com', // set through universe domain
+ ],
+ [
+ ['universeDomain' => 'foo.com', 'apiEndpoint' => 'new.test.address.com'],
+ 'new.test.address.com', // set through api endpoint (universe domain is not used)
+ ],
+ [
+ [],
+ 'stub.googleapis.com',
+ 'GOOGLE_CLOUD_UNIVERSE_DOMAIN=', // env var is ignored when empty
+ ],
+ [
+ ['universeDomain' => 'foo.com'],
+ 'stub.foo.com',
+ 'GOOGLE_CLOUD_UNIVERSE_DOMAIN=bar.com', // env var is ignored when client option is set
+ ],
+ [
+ [],
+ 'stub.bar.com',
+ 'GOOGLE_CLOUD_UNIVERSE_DOMAIN=bar.com', // env var is used when client option isn't set
+ ],
+ ];
+ }
+
+ public function testMtlsWithUniverseDomainThrowsException()
+ {
+ $this->expectException(ValidationException::class);
+ $this->expectExceptionMessage('mTLS is not supported outside the "googleapis.com" universe');
+
+ $this->universeDomainClientStub->buildClientOptions([
+ 'universeDomain' => 'foo.com',
+ 'clientCertSource' => function () {
+ $this->fail('this should not be called');
+ },
+ ]);
+ }
+
+ public function testBuildClientOptionsTwice()
+ {
+ $options = $this->clientStub->buildClientOptions([]);
+ $options2 = $this->clientStub->buildClientOptions($options);
+ $this->assertEquals($options, $options2);
+ }
+
+ public function testBuildClientOptionsWithClientOptions()
+ {
+ $clientOptions = new ClientOptions([]);
+ $clientOptions->setApiEndpoint('TestEndpoint.com');
+ $builtOptions = $this->clientStub->buildClientOptions($clientOptions);
+
+ $this->assertEquals($clientOptions['apiEndpoint'], $builtOptions['apiEndpoint']);
+ }
+
+ /**
+ * @runInSeparateProcess
+ */
+ public function testLoggerIsNullWhenFalseIsPassed()
+ {
+ putenv('GOOGLE_SDK_PHP_LOGGING=true');
+
+ $optionsArray = [
+ 'logger' => false,
+ ];
+ $options = $this->clientStub->buildClientOptions($optionsArray);
+
+ $this->assertFalse($options['transportConfig']['rest']['logger']);
+ $this->assertFalse($options['transportConfig']['grpc']['logger']);
+ $this->assertFalse($options['transportConfig']['grpc-fallback']['logger']);
+ }
+
+ /**
+ * @runInSeparateProcess
+ */
+ public function testLoggerIsNotNullIfFlagIsEmptyAndEnvVarSet()
+ {
+ putenv('GOOGLE_SDK_PHP_LOGGING=true');
+
+ $optionsArray = [];
+ $options = $this->clientStub->buildClientOptions($optionsArray);
+
+ $this->assertInstanceOf(StdOutLogger::class, $options['transportConfig']['rest']['logger']);
+ $this->assertInstanceOf(StdOutLogger::class, $options['transportConfig']['grpc']['logger']);
+ }
+
+ /**
+ * @runInSeparateProcess
+ */
+ public function testLogConfiguration()
+ {
+ putenv('GOOGLE_SDK_PHP_LOGGING=true');
+
+ $options = $this->clientStub->buildClientOptions([
+ 'apiEndpoint' => 'test'
+ ]);
+ $parsedOutput = json_decode($this->getActualOutputForAssertion(), true);
+
+ $this->assertNotNull($parsedOutput);
+ $this->assertArrayHasKey('timestamp', $parsedOutput);
+ $this->assertEquals($parsedOutput['severity'], strtoupper(LogLevel::DEBUG));
+ $this->assertArrayHasKey('jsonPayload', $parsedOutput);
+ }
+
+ public function testLoggerIsNullIfFlagIsEmptyAndNoEnvVar()
+ {
+ $optionsArray = [];
+ $options = $this->clientStub->buildClientOptions($optionsArray);
+
+ $this->assertNull($options['transportConfig']['rest']['logger']);
+ $this->assertNull($options['transportConfig']['grpc']['logger']);
+ }
+
+ public function testLoggerIsSetWhenALoggerIsPassed()
+ {
+ $logger = new StdOutLogger();
+ $optionsArray = [
+ 'logger' => $logger
+ ];
+ $options = $this->clientStub->buildClientOptions($optionsArray);
+
+ $this->assertEquals($options['transportConfig']['rest']['logger'], $logger);
+ $this->assertEquals($options['transportConfig']['grpc']['logger'], $logger);
+ }
+
+ public function testExceptionIsRaisedIfOptionsIsInvalid()
+ {
+ $this->expectException(ValidationException::class);
+ $this->expectExceptionMessage(
+ 'The "logger" option in the options array should be PSR-3 LoggerInterface compatible'
+ );
+
+ $optionsArray = [
+ 'logger' => 'nonValidOption'
+ ];
+ $this->clientStub->buildClientOptions($optionsArray);
+ }
+}
diff --git a/Gax/tests/Unit/ClientStreamTest.php b/Gax/tests/Unit/ClientStreamTest.php
new file mode 100644
index 000000000000..129915e1b6de
--- /dev/null
+++ b/Gax/tests/Unit/ClientStreamTest.php
@@ -0,0 +1,201 @@
+assertSame($call, $stream->getClientStreamingCall());
+ $this->assertSame($response, $stream->readResponse());
+ $this->assertSame([], $call->popReceivedCalls());
+ }
+
+ 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());
+
+ $this->expectException(ApiException::class);
+ $this->expectExceptionMessage('no writes failure');
+
+ $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());
+ }
+
+ 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());
+
+ $this->expectException(ApiException::class);
+ $this->expectExceptionMessage('manual writes failure');
+
+ $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());
+ }
+
+ 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);
+
+ $this->expectException(ApiException::class);
+ $this->expectExceptionMessage('write all failure');
+
+ try {
+ $stream->writeAllAndReadResponse($requests);
+ } finally {
+ $this->assertSame($call, $stream->getClientStreamingCall());
+ $this->assertEquals($requests, $call->popReceivedCalls());
+ }
+ }
+
+ public function testWriteCallsLogger()
+ {
+ $logger = $this->prophesize(StdOutLogger::class);
+ $logger->debug(Argument::cetera())
+ ->shouldBeCalledTimes(2);
+
+ $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, logger: $logger->reveal());
+
+ $stream->write($requests[0]);
+ $stream->write($requests[1]);
+ }
+
+ public function testReadCallsLogger()
+ {
+ $logger = $this->prophesize(StdOutLogger::class);
+ $logger->debug(Argument::cetera())
+ ->shouldBeCalledTimes(2);
+
+ $requests = [
+ $this->createStatus(Code::OK, 'request1'),
+ ];
+ $response = $this->createStatus(Code::OK, 'response');
+ $call = new MockClientStreamingCall($response->serializeToString(), ['\Google\Rpc\Status', 'mergeFromString']);
+ $stream = new ClientStream($call, logger: $logger->reveal());
+
+ $stream->write($requests[0]);
+ $stream->readResponse();
+ }
+}
diff --git a/Gax/tests/Unit/CredentialsWrapperTest.php b/Gax/tests/Unit/CredentialsWrapperTest.php
new file mode 100644
index 000000000000..d7d5a1c0979b
--- /dev/null
+++ b/Gax/tests/Unit/CredentialsWrapperTest.php
@@ -0,0 +1,628 @@
+setEnv('GOOGLE_APPLICATION_CREDENTIALS', __DIR__ . '/testdata/creds/json-key-file.json');
+
+ $actualCredentialsWrapper = CredentialsWrapper::build($args);
+ $this->assertEquals($expectedCredentialsWrapper, $actualCredentialsWrapper);
+
+ $this->setEnv('GOOGLE_APPLICATION_CREDENTIALS', $appDefaultCreds);
+ }
+
+ /**
+ * @dataProvider buildDataWithKeyFile
+ */
+ public function testBuildWithKeyFile($args, $expectedCredentialsWrapper)
+ {
+ $actualCredentialsWrapper = CredentialsWrapper::build($args);
+ $this->assertEquals($expectedCredentialsWrapper, $actualCredentialsWrapper);
+ }
+
+ public function buildDataWithoutExplicitKeyFile()
+ {
+ $appDefaultCreds = getenv('GOOGLE_APPLICATION_CREDENTIALS');
+ $this->setEnv('GOOGLE_APPLICATION_CREDENTIALS', __DIR__ . '/testdata/creds/json-key-file.json');
+ $scopes = ['myscope'];
+ $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';
+
+ $testData = [
+ [
+ [],
+ new CredentialsWrapper(ApplicationDefaultCredentials::getCredentials(
+ httpHandler: $authHttpHandler,
+ cache: $defaultAuthCache
+ )),
+ ],
+ [
+ ['scopes' => $scopes],
+ new CredentialsWrapper(ApplicationDefaultCredentials::getCredentials(
+ scope: $scopes,
+ httpHandler: $authHttpHandler,
+ cache: $defaultAuthCache
+ )),
+ ],
+ [
+ ['scopes' => $scopes, 'authHttpHandler' => $asyncAuthHttpHandler],
+ new CredentialsWrapper(ApplicationDefaultCredentials::getCredentials(
+ $scopes,
+ $asyncAuthHttpHandler,
+ cache: $defaultAuthCache
+ ), $asyncAuthHttpHandler),
+ ],
+ [
+ ['enableCaching' => false],
+ new CredentialsWrapper(ApplicationDefaultCredentials::getCredentials(
+ httpHandler: $authHttpHandler
+ )),
+ ],
+ [
+ ['authCacheOptions' => $authCacheOptions],
+ new CredentialsWrapper(ApplicationDefaultCredentials::getCredentials(
+ httpHandler: $authHttpHandler,
+ cacheConfig: $authCacheOptions,
+ cache: $defaultAuthCache
+ )),
+ ],
+ [
+ ['authCache' => $authCache],
+ new CredentialsWrapper(ApplicationDefaultCredentials::getCredentials(
+ httpHandler: $authHttpHandler,
+ cache: $authCache
+ )),
+ ],
+ [
+ ['quotaProject' => $quotaProject],
+ new CredentialsWrapper(ApplicationDefaultCredentials::getCredentials(
+ httpHandler: $authHttpHandler,
+ cache: $defaultAuthCache,
+ quotaProject: $quotaProject
+ )),
+ ],
+ ];
+
+ $this->setEnv('GOOGLE_APPLICATION_CREDENTIALS', $appDefaultCreds);
+
+ return $testData;
+ }
+
+ public function buildDataWithKeyFile()
+ {
+ $keyFilePath = __DIR__ . '/testdata/creds/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 provideCheckUniverseDomainFails
+ */
+ public function testCheckUniverseDomainFails(
+ ?string $universeDomain,
+ ?string $credentialsUniverse,
+ ?string $message = null
+ ) {
+ $this->expectException(ValidationException::class);
+ $this->expectExceptionMessage($message ?: sprintf(
+ 'The configured universe domain (%s) does not match the credential universe domain (%s)',
+ is_null($universeDomain) ? GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN : $universeDomain,
+ is_null($credentialsUniverse) ? GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN : $credentialsUniverse,
+ ));
+ $fetcher = $this->prophesize(FetchAuthTokenInterface::class);
+ // When the $credentialsUniverse is null, the fetcher doesn't implement GetUniverseDomainInterface
+ if (!is_null($credentialsUniverse)) {
+ $fetcher->willImplement(GetUniverseDomainInterface::class);
+ $fetcher->getUniverseDomain()->willReturn($credentialsUniverse);
+ }
+ $fetcher->getLastReceivedToken()->willReturn(null);
+ // When $universeDomain is null, it means no $universeDomain argument was provided
+ if (is_null($universeDomain)) {
+ $credentialsWrapper = new CredentialsWrapper($fetcher->reveal());
+ } else {
+ $credentialsWrapper = new CredentialsWrapper($fetcher->reveal(), null, $universeDomain);
+ }
+ // Check authorization callback
+ $credentialsWrapper->getAuthorizationHeaderCallback()();
+ }
+
+ /**
+ * Same test as above, but calls the deprecated CredentialsWrapper::getBearerString method
+ * instead of CredentialsWrapper::getAuthorizationHeaderCallback
+ * @dataProvider provideCheckUniverseDomainFails
+ */
+ public function testCheckUniverseDomainOnGetBearerStringFails(
+ ?string $universeDomain,
+ ?string $credentialsUniverse,
+ ?string $message = null
+ ) {
+ $this->expectException(ValidationException::class);
+ $this->expectExceptionMessage($message ?: sprintf(
+ 'The configured universe domain (%s) does not match the credential universe domain (%s)',
+ is_null($universeDomain) ? GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN : $universeDomain,
+ is_null($credentialsUniverse) ? GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN : $credentialsUniverse,
+ ));
+ $fetcher = $this->prophesize(FetchAuthTokenInterface::class);
+ // When the $credentialsUniverse is null, the fetcher doesn't implement GetUniverseDomainInterface
+ if (!is_null($credentialsUniverse)) {
+ $fetcher->willImplement(GetUniverseDomainInterface::class);
+ $fetcher->getUniverseDomain()->willReturn($credentialsUniverse);
+ }
+ $fetcher->getLastReceivedToken()->willReturn(null);
+ // When $universeDomain is null, it means no $universeDomain argument was provided
+ if (is_null($universeDomain)) {
+ $credentialsWrapper = new CredentialsWrapper($fetcher->reveal());
+ } else {
+ $credentialsWrapper = new CredentialsWrapper($fetcher->reveal(), null, $universeDomain);
+ }
+ // Check getBearerString (deprecated)
+ $credentialsWrapper->getBearerString();
+ }
+
+ public function provideCheckUniverseDomainFails()
+ {
+ return [
+ ['foo.com', 'googleapis.com'],
+ ['googleapis.com', 'foo.com'],
+ ['googleapis.com', ''],
+ ['', 'googleapis.com', 'The universe domain cannot be empty'],
+ [null, 'foo.com'], // null in CredentialsWrapper will default to "googleapis.com"
+ ['foo.com', null], // Credentials not implementing GetUniverseDomainInterface will default to googleapis.com
+ ];
+ }
+
+ /**
+ * @dataProvider provideCheckUniverseDomainPasses
+ */
+ public function testCheckUniverseDomainPasses(?string $universeDomain, ?string $credentialsUniverse)
+ {
+ $fetcher = $this->prophesize(FetchAuthTokenInterface::class);
+ // When the $credentialsUniverse is null, the fetcher doesn't implement GetUniverseDomainInterface
+ if (!is_null($credentialsUniverse)) {
+ $fetcher->willImplement(GetUniverseDomainInterface::class);
+ $fetcher->getUniverseDomain()->shouldBeCalledOnce()->willReturn($credentialsUniverse);
+ }
+ $fetcher->getLastReceivedToken()->willReturn(null);
+ $fetcher->fetchAuthToken(Argument::any())->willReturn(['access_token' => 'abc']);
+ if (is_null($universeDomain)) {
+ $credentialsWrapper = new CredentialsWrapper($fetcher->reveal());
+ } else {
+ $credentialsWrapper = new CredentialsWrapper($fetcher->reveal(), null, $universeDomain);
+ }
+ // Check authorization callback
+ $this->assertEquals(
+ ['authorization' => ['Bearer abc']],
+ $credentialsWrapper->getAuthorizationHeaderCallback()()
+ );
+ // Check getBearerString (deprecated)
+ $this->assertEquals(
+ 'Bearer abc',
+ $credentialsWrapper->getBearerString()
+ );
+ }
+
+ public function provideCheckUniverseDomainPasses()
+ {
+ return [
+ [null, 'googleapis.com'], // null will default to "googleapis.com"
+ ['foo.com', 'foo.com'],
+ ['googleapis.com', 'googleapis.com'],
+ ['googleapis.com', null],
+ ];
+ }
+
+ public function testCheckUniverseDomainOnGceCredentialsDoesNotCheck()
+ {
+ $fetcher = $this->prophesize(GCECredentials::class);
+ $fetcher->getUniverseDomain()->shouldNotBeCalled();
+ $credentialsWrapper = new CredentialsWrapper(
+ $fetcher->reveal(),
+ null,
+ 'some-random-universe-domain'
+ );
+
+ $credentialsWrapper->checkUniverseDomain();
+ }
+
+ /**
+ * @dataProvider getBearerStringData
+ * @runInSeparateProcess
+ */
+ public function testGetBearerString(string $fetcherFunc, $expectedBearerString)
+ {
+ $fetcher = $this->$fetcherFunc();
+ $credentialsWrapper = new CredentialsWrapper($fetcher);
+ $bearerString = $credentialsWrapper->getBearerString();
+ $this->assertSame($expectedBearerString, $bearerString);
+ }
+
+ public function getBearerStringData()
+ {
+ return [
+ ['getExpiredFetcher', 'Bearer 456'],
+ ['getEagerExpiredFetcher', 'Bearer 456'],
+ ['getUnexpiredFetcher', 'Bearer 123'],
+ ['getInsecureFetcher', ''],
+ ['getNullFetcher', ''],
+ ];
+ }
+
+ /**
+ * @dataProvider getAuthorizationHeaderCallbackData
+ * @runInSeparateProcess
+ */
+ public function testGetAuthorizationHeaderCallback(string $fetcherFunc, $expectedCallbackResponse)
+ {
+ $fetcher = $this->$fetcherFunc();
+ $httpHandler = function () {
+ };
+ $credentialsWrapper = new CredentialsWrapper($fetcher, $httpHandler);
+ $callback = $credentialsWrapper->getAuthorizationHeaderCallback('audience');
+ $actualResponse = $callback();
+ $this->assertSame($expectedCallbackResponse, $actualResponse);
+ }
+
+ public function getAuthorizationHeaderCallbackData()
+ {
+ return [
+ ['getExpiredFetcher', ['authorization' => ['Bearer 456']]],
+ ['getExpiredInvalidFetcher', []],
+ ['getUnexpiredFetcher', ['authorization' => ['Bearer 123']]],
+ ['getInsecureFetcher', []],
+ ['getNullFetcher', []],
+ ['getCustomFetcher', ['authorization' => ['Bearer 123']]],
+ ];
+ }
+
+ private function getExpiredFetcher()
+ {
+ $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
+ ]);
+ return $expiredFetcher->reveal();
+ }
+
+ private function getExpiredInvalidFetcher()
+ {
+ $expiredInvalidFetcher = $this->prophesize(FetchAuthTokenInterface::class);
+ $expiredInvalidFetcher->getLastReceivedToken()
+ ->willReturn([
+ 'access_token' => 123,
+ 'expires_at' => time() - 1
+ ]);
+ $expiredInvalidFetcher->fetchAuthToken(Argument::any())
+ ->willReturn(['not-a' => 'valid-token']);
+ return $expiredInvalidFetcher->reveal();
+ }
+
+ private function getEagerExpiredFetcher()
+ {
+ $eagerExpiredFetcher = $this->prophesize(FetchAuthTokenInterface::class);
+ $eagerExpiredFetcher->getLastReceivedToken()
+ ->willReturn([
+ 'access_token' => 123,
+ 'expires_at' => time() + 1
+ ]);
+ $eagerExpiredFetcher->fetchAuthToken(Argument::any())
+ ->willReturn([
+ 'access_token' => 456,
+ 'expires_at' => time() + 10 // within 10 second eager threshold
+ ]);
+ return $eagerExpiredFetcher->reveal();
+ }
+
+ private function getUnexpiredFetcher()
+ {
+ $unexpiredFetcher = $this->prophesize(FetchAuthTokenInterface::class);
+ $unexpiredFetcher->getLastReceivedToken()
+ ->willReturn([
+ 'access_token' => 123,
+ 'expires_at' => time() + 100,
+ ]);
+ $unexpiredFetcher->fetchAuthToken(Argument::any())
+ ->shouldNotBeCalled();
+ return $unexpiredFetcher->reveal();
+ }
+
+ private function getInsecureFetcher()
+ {
+ $insecureFetcher = $this->prophesize(FetchAuthTokenInterface::class);
+ $insecureFetcher->getLastReceivedToken()->willReturn(null);
+ $insecureFetcher->fetchAuthToken(Argument::any())
+ ->willReturn([
+ 'access_token' => '',
+ ]);
+ return $insecureFetcher->reveal();
+ }
+
+ private function getNullFetcher()
+ {
+ $nullFetcher = $this->prophesize(FetchAuthTokenInterface::class);
+ $nullFetcher->getLastReceivedToken()->willReturn(null);
+ $nullFetcher->fetchAuthToken(Argument::any())
+ ->willReturn([
+ 'access_token' => null,
+ ]);
+ return $nullFetcher->reveal();
+ }
+
+ private function getCustomFetcher()
+ {
+ $customFetcher = $this->prophesize(FetchAuthTokenInterface::class);
+ $customFetcher->getLastReceivedToken()->willReturn(null);
+ $customFetcher->fetchAuthToken(Argument::any())
+ ->willReturn([
+ 'access_token' => 123,
+ 'expires_at' => time() + 100,
+ ]);
+ return $customFetcher->reveal();
+ }
+
+ /**
+ * @runInSeparateProcess
+ */
+ public function testApplicationDefaultCredentialsWithOnGCECacheTrue()
+ {
+ $this->setEnv('HOME', __DIR__ . '/not_exist_fixtures');
+ $this->setEnv(ServiceAccountCredentials::ENV_VAR); // removes it from the environment
+
+ $mockCacheItem = $this->prophesize('Psr\Cache\CacheItemInterface');
+ $mockCacheItem->isHit()
+ ->willReturn(true);
+ // mock being on GCE
+ $mockCacheItem->get()
+ ->shouldBeCalledTimes(1)
+ ->willReturn(true);
+
+ $mockCache = $this->prophesize('Psr\Cache\CacheItemPoolInterface');
+ $mockCache->getItem(GCECache::GCE_CACHE_KEY)
+ ->shouldBeCalledTimes(1)
+ ->willReturn($mockCacheItem->reveal());
+
+ $wrapper = CredentialsWrapper::build([
+ 'authCache' => $mockCache->reveal(),
+ ]);
+ $reflectionClass = new \ReflectionClass($wrapper);
+ $reflectionProperty = $reflectionClass->getProperty('credentialsFetcher');
+ $this->assertInstanceOf(GCECredentials::class, $reflectionProperty->getValue($wrapper)->getFetcher());
+ }
+
+ /**
+ * @runInSeparateProcess
+ */
+ public function testApplicationDefaultCredentialsWithOnGCECacheFalse()
+ {
+ $this->setEnv('HOME', __DIR__ . '/not_exist_fixtures');
+ $this->setEnv(ServiceAccountCredentials::ENV_VAR); // removes it from the environment
+
+ $this->expectException(ValidationException::class);
+ $this->expectExceptionMessage('Could not construct ApplicationDefaultCredentials');
+
+ $mockCacheItem = $this->prophesize('Psr\Cache\CacheItemInterface');
+ $mockCacheItem->isHit()
+ ->willReturn(true);
+ // mock not being on GCE
+ $mockCacheItem->get()
+ ->shouldBeCalledTimes(1)
+ ->willReturn(false);
+
+ $mockCache = $this->prophesize('Psr\Cache\CacheItemPoolInterface');
+ $mockCache->getItem(GCECache::GCE_CACHE_KEY)
+ ->shouldBeCalledTimes(1)
+ ->willReturn($mockCacheItem->reveal());
+
+ $wrapper = CredentialsWrapper::build([
+ 'authCache' => $mockCache->reveal(),
+ ]);
+ }
+
+ /**
+ * @runInSeparateProcess
+ */
+ public function testApplicationDefaultCredentialsWithOnGCECacheOptions()
+ {
+ $this->setEnv('HOME', __DIR__ . '/not_exist_fixtures');
+ $this->setEnv(ServiceAccountCredentials::ENV_VAR); // removes it from the environment
+
+ $mockCacheItem = $this->prophesize('Psr\Cache\CacheItemInterface');
+ $mockCacheItem->isHit()
+ ->willReturn(true);
+ // mock being on GCE
+ $mockCacheItem->get()
+ ->shouldBeCalledTimes(1)
+ ->willReturn(true);
+
+ $mockCache = $this->prophesize('Psr\Cache\CacheItemPoolInterface');
+ $mockCache->getItem('prefix_' . GCECache::GCE_CACHE_KEY)
+ ->shouldBeCalledTimes(1)
+ ->willReturn($mockCacheItem->reveal());
+
+ $wrapper = CredentialsWrapper::build([
+ 'authCache' => $mockCache->reveal(),
+ 'authCacheOptions' => ['gce_prefix' => 'prefix_'],
+ ]);
+ $reflectionClass = new \ReflectionClass($wrapper);
+ $reflectionProperty = $reflectionClass->getProperty('credentialsFetcher');
+ $this->assertInstanceOf(GCECredentials::class, $reflectionProperty->getValue($wrapper)->getFetcher());
+ }
+
+ public function testGetProjectId()
+ {
+ $credentials = $this->prophesize(FetchAuthTokenInterface::class)
+ ->willImplement(ProjectIdProviderInterface::class);
+ $credentials
+ ->getProjectId(null)
+ ->shouldBeCalledOnce()
+ ->willReturn('my-project-id');
+ $credentialsWrapper = new CredentialsWrapper($credentials->reveal());
+ $this->assertEquals('my-project-id', $credentialsWrapper->getProjectId());
+ }
+
+ public function testGetProjectIdWithFetchAuthTokenCache()
+ {
+ // Ensure credentials which do NOT implement ProjectIdProviderInterface return null
+ $credentials = $this->prophesize(FetchAuthTokenInterface::class);
+ $cache = new FetchAuthTokenCache($credentials->reveal(), [], new MemoryCacheItemPool());
+ $credentialsWrapper = new CredentialsWrapper($cache);
+ $this->assertNull($credentialsWrapper->getProjectId());
+
+ // Ensure credentials which DO implement ProjectIdProviderInterface return the project ID
+ $credentials = $this->prophesize(FetchAuthTokenInterface::class)
+ ->willImplement(ProjectIdProviderInterface::class);
+ $credentials
+ ->getProjectId(null)
+ ->shouldBeCalledOnce()
+ ->willReturn('my-project-id');
+ $cache = new FetchAuthTokenCache($credentials->reveal(), [], new MemoryCacheItemPool());
+ $credentialsWrapper = new CredentialsWrapper($cache);
+ $this->assertEquals('my-project-id', $credentialsWrapper->getProjectId());
+ }
+
+ public function testSerializeCredentialsWrapper()
+ {
+ $credentialsWrapper = CredentialsWrapper::build([
+ 'keyFile' => __DIR__ . '/testdata/creds/json-key-file.json',
+ ]);
+ $serialized = serialize($credentialsWrapper);
+ $this->assertIsString($serialized);
+ }
+
+ private function setEnv(string $env, ?string $value = null)
+ {
+ if ($value === null) {
+ putenv($env);
+ unset($_ENV[$env]);
+ } else {
+ putenv($env . '=' . $value);
+ $_ENV[$env] = $value;
+ }
+ }
+}
diff --git a/Gax/tests/Unit/FixedSizeCollectionTest.php b/Gax/tests/Unit/FixedSizeCollectionTest.php
new file mode 100644
index 000000000000..00771d074ca5
--- /dev/null
+++ b/Gax/tests/Unit/FixedSizeCollectionTest.php
@@ -0,0 +1,203 @@
+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) = $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);
+ }
+
+ 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, '')],
+ ]);
+
+ $this->expectException(LengthException::class);
+ $this->expectExceptionMessage('API returned a number of elements exceeding the specified page size limit');
+
+ new FixedSizeCollection($page, 3);
+ }
+
+ public function testEmptyCollectionThrowsException()
+ {
+ $collectionSize = 0;
+ $page = $this->prophesize(Page::class);
+
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('collectionSize must be > 0.');
+
+ new FixedSizeCollection($page->reveal(), $collectionSize);
+ }
+
+ public function testInvalidPageCount()
+ {
+ $collectionSize = 1;
+ $page = $this->prophesize(Page::class);
+
+ $page->getPageElementCount()
+ ->shouldBeCalledTimes(2)
+ ->willReturn(2);
+
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage(
+ 'collectionSize must be greater than or equal to the number of elements in initialPage'
+ );
+
+ new FixedSizeCollection($page->reveal(), $collectionSize);
+ }
+}
diff --git a/Gax/tests/Unit/GapicClientTraitTest.php b/Gax/tests/Unit/GapicClientTraitTest.php
new file mode 100644
index 000000000000..ae62e75396e0
--- /dev/null
+++ b/Gax/tests/Unit/GapicClientTraitTest.php
@@ -0,0 +1,1985 @@
+set('gapicVersionFromFile', null, true);
+ putenv('GOOGLE_APPLICATION_CREDENTIALS=');
+ }
+
+ public function testHeadersOverwriteBehavior()
+ {
+ $unaryDescriptors = [
+ 'callType' => Call::UNARY_CALL,
+ 'responseType' => 'decodeType',
+ 'headerParams' => [
+ [
+ 'fieldAccessors' => ['getName'],
+ 'keyName' => 'name'
+ ]
+ ]
+ ];
+ $request = new MockRequestBody(['name' => 'foos/123/bars/456']);
+ $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',
+ 'protobufVersion' => '6.6.6',
+ ]);
+ $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 rest/1.0.0 pb/6.6.6'],
+ 'new-header' => ['this-should-be-used'],
+ 'x-goog-request-params' => ['name=foos%2F123%2Fbars%2F456']
+ ];
+ $transport = $this->prophesize(TransportInterface::class);
+ $credentialsWrapper = CredentialsWrapper::build([
+ 'keyFile' => __DIR__ . '/testdata/creds/json-key-file.json'
+ ]);
+ $transport->startUnaryCall(
+ Argument::type(Call::class),
+ [
+ 'headers' => $expectedHeaders,
+ 'credentialsWrapper' => $credentialsWrapper,
+ ]
+ )
+ ->shouldBeCalledOnce()
+ ->willReturn($this->prophesize(PromiseInterface::class)->reveal());
+ $client = new StubGapicClient();
+ $client->set('agentHeader', $header);
+ $client->set(
+ 'retrySettings',
+ ['method' => $this->prophesize(RetrySettings::class)->reveal()]
+ );
+ $client->set('transport', $transport->reveal());
+ $client->set('credentialsWrapper', $credentialsWrapper);
+ $client->set('descriptors', ['method' => $unaryDescriptors]);
+ $client->startApiCall(
+ 'method',
+ $request,
+ ['headers' => $headers]
+ );
+ }
+
+ public function testVersionedHeadersOverwriteBehavior()
+ {
+ $unaryDescriptors = [
+ 'callType' => Call::UNARY_CALL,
+ 'responseType' => 'decodeType',
+ 'headerParams' => [
+ [
+ 'fieldAccessors' => ['getName'],
+ 'keyName' => 'name'
+ ]
+ ]
+ ];
+ $request = new MockRequestBody(['name' => 'foos/123/bars/456']);
+ $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',
+ 'protobufVersion' => '6.6.6',
+ ]);
+ $headers = [
+ 'x-goog-api-client' => ['this-should-not-be-used'],
+ 'new-header' => ['this-should-be-used'],
+ 'X-Goog-Api-Version' => ['this-should-not-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 rest/1.0.0 pb/6.6.6'],
+ 'new-header' => ['this-should-be-used'],
+ 'X-Goog-Api-Version' => ['20240418'],
+ 'x-goog-request-params' => ['name=foos%2F123%2Fbars%2F456'],
+ ];
+ $transport = $this->prophesize(TransportInterface::class);
+ $credentialsWrapper = CredentialsWrapper::build([
+ 'keyFile' => __DIR__ . '/testdata/creds/json-key-file.json'
+ ]);
+ $transport->startUnaryCall(
+ Argument::type(Call::class),
+ [
+ 'headers' => $expectedHeaders,
+ 'credentialsWrapper' => $credentialsWrapper,
+ ]
+ )
+ ->shouldBeCalledOnce()
+ ->willReturn($this->prophesize(PromiseInterface::class)->reveal());
+ $client = new VersionedStubGapicClient();
+ $client->set('agentHeader', $header);
+ $client->set(
+ 'retrySettings',
+ ['method' => $this->prophesize(RetrySettings::class)->reveal()]
+ );
+ $client->set('transport', $transport->reveal());
+ $client->set('credentialsWrapper', $credentialsWrapper);
+ $client->set('descriptors', ['method' => $unaryDescriptors]);
+ $client->startApiCall(
+ 'method',
+ $request,
+ ['headers' => $headers]
+ );
+ }
+
+ public function testConfigureCallConstructionOptions()
+ {
+ $client = new StubGapicClient();
+ $client->setClientOptions($client->buildClientOptions([]));
+ $retrySettings = RetrySettings::constructDefault();
+ $expected = [
+ 'retrySettings' => $retrySettings,
+ 'autoPopulationSettings' => [
+ 'pageToken' => \Google\Api\FieldInfo\Format::UUID4,
+ ],
+ ];
+ $actual = $client->configureCallConstructionOptions('PageStreamingMethod', ['retrySettings' => $retrySettings]);
+ $this->assertEquals($expected, $actual);
+ }
+
+ public function testConfigureCallConstructionOptionsAcceptsRetryObjectOrArray()
+ {
+ $defaultRetrySettings = RetrySettings::constructDefault();
+ $client = new StubGapicClient();
+ $client->set('retrySettings', ['method' => $defaultRetrySettings]);
+ $expectedOptions = [
+ 'retrySettings' => $defaultRetrySettings
+ ->with(['rpcTimeoutMultiplier' => 5]),
+ 'autoPopulationSettings' => []
+ ];
+ $actualOptionsWithObject = $client->configureCallConstructionOptions(
+ 'method',
+ [
+ 'retrySettings' => $defaultRetrySettings
+ ->with(['rpcTimeoutMultiplier' => 5])
+ ]
+ );
+ $actualOptionsWithArray = $client->configureCallConstructionOptions(
+ 'method',
+ [
+ 'retrySettings' => ['rpcTimeoutMultiplier' => 5]
+ ]
+ );
+
+ $this->assertEquals($expectedOptions, $actualOptionsWithObject);
+ $this->assertEquals($expectedOptions, $actualOptionsWithArray);
+ }
+
+ public function testStartOperationsCall()
+ {
+ $header = AgentHeader::buildAgentHeader([]);
+ $retrySettings = $this->prophesize(RetrySettings::class);
+ $longRunningDescriptors = [
+ 'longRunning' => [
+ 'operationReturnType' => 'operationType',
+ 'metadataReturnType' => 'metadataType',
+ 'initialPollDelayMillis' => 100,
+ 'pollDelayMultiplier' => 1.0,
+ 'maxPollDelayMillis' => 200,
+ 'totalPollTimeoutMillis' => 300,
+ ]
+ ];
+ $expectedPromise = new FulfilledPromise(new Operation());
+ $transport = $this->prophesize(TransportInterface::class);
+ $transport->startUnaryCall(Argument::cetera())
+ ->shouldBeCalledOnce()
+ ->willReturn($expectedPromise);
+ $credentialsWrapper = CredentialsWrapper::build([]);
+ $client = new StubGapicClient();
+ $client->set('transport', $transport->reveal());
+ $client->set('credentialsWrapper', $credentialsWrapper);
+ $client->set('agentHeader', $header);
+ $client->set('retrySettings', ['method' => $retrySettings->reveal()]);
+ $client->set('descriptors', ['method' => $longRunningDescriptors]);
+ $message = new MockRequest();
+ $operationsClient = $this->prophesize(OperationsClient::class);
+
+ $response = $client->startOperationsCall(
+ 'method',
+ [],
+ $message,
+ $operationsClient->reveal()
+ )->wait();
+
+ $expectedResponse = new OperationResponse(
+ '',
+ $operationsClient->reveal(),
+ $longRunningDescriptors['longRunning'] + ['lastProtoResponse' => new Operation()]
+ );
+
+ $this->assertEquals($expectedResponse, $response);
+ }
+
+ public function testStartApiCallOperation()
+ {
+ $header = AgentHeader::buildAgentHeader([]);
+ $retrySettings = $this->prophesize(RetrySettings::class);
+
+ $longRunningDescriptors = [
+ 'callType' => Call::LONGRUNNING_CALL,
+ 'longRunning' => [
+ 'operationReturnType' => 'operationType',
+ 'metadataReturnType' => 'metadataType',
+ 'initialPollDelayMillis' => 100,
+ 'pollDelayMultiplier' => 1.0,
+ 'maxPollDelayMillis' => 200,
+ 'totalPollTimeoutMillis' => 300,
+ ]
+ ];
+ $expectedPromise = new FulfilledPromise(new Operation());
+ $transport = $this->prophesize(TransportInterface::class);
+ $transport->startUnaryCall(Argument::cetera())
+ ->shouldBeCalledOnce()
+ ->willReturn($expectedPromise);
+ $credentialsWrapper = CredentialsWrapper::build([]);
+ $client = new OperationsGapicClient();
+ $client->set('transport', $transport->reveal());
+ $client->set('credentialsWrapper', $credentialsWrapper);
+ $client->set('agentHeader', $header);
+ $client->set('retrySettings', ['method' => $retrySettings->reveal()]);
+ $client->set('descriptors', ['method' => $longRunningDescriptors]);
+ $operationsClient = $this->prophesize(OperationsClient::class);
+ $client->set('operationsClient', $operationsClient->reveal());
+
+ $request = new MockRequest();
+ $response = $client->startApiCall(
+ 'method',
+ $request
+ )->wait();
+
+ $expectedResponse = new OperationResponse(
+ '',
+ $operationsClient->reveal(),
+ $longRunningDescriptors['longRunning'] + ['lastProtoResponse' => new Operation()]
+ );
+
+ $this->assertEquals($expectedResponse, $response);
+ }
+
+ public function testStartApiCallCustomOperation()
+ {
+ $header = AgentHeader::buildAgentHeader([]);
+ $retrySettings = $this->prophesize(RetrySettings::class);
+
+ $longRunningDescriptors = [
+ 'callType' => Call::LONGRUNNING_CALL,
+ 'responseType' => 'Google\ApiCore\Testing\MockResponse',
+ 'longRunning' => [
+ 'operationReturnType' => 'operationType',
+ 'metadataReturnType' => 'metadataType',
+ 'initialPollDelayMillis' => 100,
+ 'pollDelayMultiplier' => 1.0,
+ 'maxPollDelayMillis' => 200,
+ 'totalPollTimeoutMillis' => 300,
+ ]
+ ];
+ $expectedPromise = new FulfilledPromise(new MockResponse());
+ $transport = $this->prophesize(TransportInterface::class);
+ $transport->startUnaryCall(Argument::cetera())
+ ->shouldBeCalledOnce()
+ ->willReturn($expectedPromise);
+ $credentialsWrapper = CredentialsWrapper::build([]);
+ $client = new OperationsGapicClient();
+ $client->set('transport', $transport->reveal());
+ $client->set('credentialsWrapper', $credentialsWrapper);
+ $client->set('agentHeader', $header);
+ $client->set('retrySettings', ['method' => $retrySettings->reveal()]);
+ $client->set('descriptors', ['method' => $longRunningDescriptors]);
+ $operationsClient = $this->prophesize(OperationsClient::class)->reveal();
+ $client->set('operationsClient', $operationsClient);
+
+ $request = new MockRequest();
+ $response = $client->startApiCall(
+ 'method',
+ $request,
+ )->wait();
+
+ $expectedResponse = new OperationResponse(
+ '',
+ $operationsClient,
+ $longRunningDescriptors['longRunning'] + ['lastProtoResponse' => new MockResponse()]
+ );
+
+ $this->assertEquals($expectedResponse, $response);
+ }
+
+ /**
+ * @dataProvider startApiCallExceptions
+ */
+ public function testStartApiCallException($descriptor, $expected)
+ {
+ $client = new StubGapicClient();
+ $client->set('descriptors', $descriptor);
+
+ // All descriptor config checks throw Validation exceptions
+ $this->expectException(ValidationException::class);
+ // Check that the proper exception is being thrown for the given descriptor.
+ $this->expectExceptionMessage($expected);
+
+ $client->startApiCall(
+ 'method',
+ new MockRequest()
+ )->wait();
+ }
+
+ public function startApiCallExceptions()
+ {
+ return [
+ [
+ [],
+ 'does not exist'
+ ],
+ [
+ [
+ 'method' => []
+ ],
+ 'does not have a callType'
+ ],
+ [
+ [
+ 'method' => ['callType' => Call::LONGRUNNING_CALL]
+ ],
+ 'does not have a longRunning config'
+ ],
+ [
+ [
+ 'method' => ['callType' => Call::LONGRUNNING_CALL, 'longRunning' => []]
+ ],
+ 'missing required getOperationsClient'
+ ],
+ [
+ [
+ 'method' => ['callType' => Call::UNARY_CALL]
+ ],
+ 'does not have a responseType'
+ ],
+ [
+ [
+ 'method' => ['callType' => Call::PAGINATED_CALL, 'responseType' => 'foo']
+ ],
+ 'does not have a pageStreaming'
+ ],
+ ];
+ }
+
+ public function testStartApiCallUnary()
+ {
+ $header = AgentHeader::buildAgentHeader([]);
+ $retrySettings = $this->prophesize(RetrySettings::class);
+ $unaryDescriptors = [
+ 'callType' => Call::UNARY_CALL,
+ 'responseType' => 'Google\Longrunning\Operation',
+ 'interfaceOverride' => 'google.cloud.foo.v1.Foo'
+ ];
+ $expectedPromise = new FulfilledPromise(new Operation());
+ $transport = $this->prophesize(TransportInterface::class);
+ $transport->startUnaryCall(
+ Argument::that(function ($call) use ($unaryDescriptors) {
+ return strpos($call->getMethod(), $unaryDescriptors['interfaceOverride']) !== false;
+ }),
+ Argument::any()
+ )
+ ->shouldBeCalledOnce()
+ ->willReturn($expectedPromise);
+ $credentialsWrapper = CredentialsWrapper::build([]);
+ $client = new StubGapicClient();
+ $client->set('transport', $transport->reveal());
+ $client->set('credentialsWrapper', $credentialsWrapper);
+ $client->set('agentHeader', $header);
+ $client->set('retrySettings', ['method' => $retrySettings->reveal()]);
+ $client->set('descriptors', ['method' => $unaryDescriptors]);
+
+ $request = new MockRequest();
+ $client->startApiCall(
+ 'method',
+ $request
+ )->wait();
+ }
+
+ public function testStartApiCallPaged()
+ {
+ $header = AgentHeader::buildAgentHeader([]);
+ $retrySettings = $this->prophesize(RetrySettings::class);
+ $pagedDescriptors = [
+ 'callType' => Call::PAGINATED_CALL,
+ 'responseType' => 'Google\Longrunning\ListOperationsResponse',
+ 'pageStreaming' => [
+ 'requestPageTokenGetMethod' => 'getPageToken',
+ 'requestPageTokenSetMethod' => 'setPageToken',
+ 'requestPageSizeGetMethod' => 'getPageSize',
+ 'requestPageSizeSetMethod' => 'setPageSize',
+ 'responsePageTokenGetMethod' => 'getNextPageToken',
+ 'resourcesGetMethod' => 'getOperations',
+ ],
+ ];
+ $expectedPromise = new FulfilledPromise(new Operation());
+ $transport = $this->prophesize(TransportInterface::class);
+ $transport->startUnaryCall(Argument::cetera())
+ ->shouldBeCalledOnce()
+ ->willReturn($expectedPromise);
+ $credentialsWrapper = CredentialsWrapper::build([]);
+ $client = new StubGapicClient();
+ $client->set('transport', $transport->reveal());
+ $client->set('credentialsWrapper', $credentialsWrapper);
+ $client->set('agentHeader', $header);
+ $client->set('retrySettings', ['method' => $retrySettings->reveal()]);
+ $client->set('descriptors', ['method' => $pagedDescriptors]);
+
+ $request = new MockRequest();
+ $client->startApiCall(
+ 'method',
+ $request
+ );
+ }
+
+ public function testStartAsyncCall()
+ {
+ $header = AgentHeader::buildAgentHeader([]);
+ $retrySettings = $this->prophesize(RetrySettings::class);
+ $unaryDescriptors = [
+ 'callType' => Call::UNARY_CALL,
+ 'responseType' => 'Google\Longrunning\Operation'
+ ];
+ $expectedPromise = new FulfilledPromise(new Operation());
+ $transport = $this->prophesize(TransportInterface::class);
+ $transport->startUnaryCall(Argument::cetera())
+ ->shouldBeCalledOnce()
+ ->willReturn($expectedPromise);
+ $credentialsWrapper = CredentialsWrapper::build([]);
+ $client = new StubGapicClient();
+ $client->set('transport', $transport->reveal());
+ $client->set('credentialsWrapper', $credentialsWrapper);
+ $client->set('agentHeader', $header);
+ $client->set('retrySettings', ['Method' => $retrySettings->reveal()]);
+ $client->set('descriptors', ['Method' => $unaryDescriptors]);
+
+ $request = new MockRequest();
+ $client->startAsyncCall(
+ 'method',
+ $request
+ )->wait();
+ }
+
+ public function testStartAsyncCallPaged()
+ {
+ $header = AgentHeader::buildAgentHeader([]);
+ $retrySettings = $this->prophesize(RetrySettings::class);
+ $pagedDescriptors = [
+ 'callType' => Call::PAGINATED_CALL,
+ 'responseType' => 'Google\Longrunning\ListOperationsResponse',
+ 'interfaceOverride' => 'google.cloud.foo.v1.Foo',
+ 'pageStreaming' => [
+ 'requestPageTokenGetMethod' => 'getPageToken',
+ 'requestPageTokenSetMethod' => 'setPageToken',
+ 'requestPageSizeGetMethod' => 'getPageSize',
+ 'requestPageSizeSetMethod' => 'setPageSize',
+ 'responsePageTokenGetMethod' => 'getNextPageToken',
+ 'resourcesGetMethod' => 'getOperations',
+ ],
+ ];
+ $expectedPromise = new FulfilledPromise(new Operation());
+ $transport = $this->prophesize(TransportInterface::class);
+ $transport->startUnaryCall(
+ Argument::that(function ($call) use ($pagedDescriptors) {
+ return strpos($call->getMethod(), $pagedDescriptors['interfaceOverride']) !== false;
+ }),
+ Argument::any()
+ )
+ ->shouldBeCalledOnce()
+ ->willReturn($expectedPromise);
+ $credentialsWrapper = CredentialsWrapper::build([]);
+ $client = new StubGapicClient();
+ $client->set('transport', $transport->reveal());
+ $client->set('credentialsWrapper', $credentialsWrapper);
+ $client->set('agentHeader', $header);
+ $client->set('retrySettings', ['Method' => $retrySettings->reveal()]);
+ $client->set('descriptors', ['Method' => $pagedDescriptors]);
+
+ $request = new MockRequest();
+ $client->startAsyncCall(
+ 'method',
+ $request
+ )->wait();
+ }
+
+ /**
+ * @dataProvider startAsyncCallExceptions
+ */
+ public function testStartAsyncCallException($descriptor, $expected)
+ {
+ $client = new StubGapicClient();
+ $client->set('descriptors', $descriptor);
+
+ // All descriptor config checks throw Validation exceptions
+ $this->expectException(ValidationException::class);
+ // Check that the proper exception is being thrown for the given descriptor.
+ $this->expectExceptionMessage($expected);
+
+ $client->startAsyncCall(
+ 'method',
+ new MockRequest()
+ )->wait();
+ }
+
+ public function startAsyncCallExceptions()
+ {
+ return [
+ [
+ [],
+ 'does not exist'
+ ],
+ [
+ [
+ 'Method' => []
+ ],
+ 'does not have a callType'
+ ],
+ [
+ [
+ 'Method' => [
+ 'callType' => Call::SERVER_STREAMING_CALL,
+ 'responseType' => 'Google\Longrunning\Operation'
+ ]
+ ],
+ 'not supported for async execution'
+ ],
+ [
+ [
+ 'Method' => [
+ 'callType' => Call::CLIENT_STREAMING_CALL, 'longRunning' => [],
+ 'responseType' => 'Google\Longrunning\Operation'
+ ]
+ ],
+ 'not supported for async execution'
+ ],
+ [
+ [
+ 'Method' => [
+ 'callType' => Call::BIDI_STREAMING_CALL,
+ 'responseType' => 'Google\Longrunning\Operation'
+ ]
+ ],
+ 'not supported for async execution'
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider createTransportData
+ */
+ public function testCreateTransport($apiEndpoint, $transport, $transportConfig, $expectedTransportClass)
+ {
+ if ($expectedTransportClass == GrpcTransport::class) {
+ self::requiresGrpcExtension();
+ }
+ $client = new StubGapicClient();
+ $transport = $client->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/resources/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
+ */
+ public function testCreateTransportInvalid($apiEndpoint, $transport, $transportConfig)
+ {
+ $client = new StubGapicClient();
+
+ $this->expectException(ValidationException::class);
+
+ $client->createTransport(
+ $apiEndpoint,
+ $transport,
+ $transportConfig
+ );
+ }
+
+ public function createTransportDataInvalid()
+ {
+ $apiEndpoint = 'address:443';
+ $transportConfig = [
+ 'rest' => [
+ 'restConfigPath' => __DIR__ . '/testdata/resources/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 StubGapicClient();
+ $apiEndpoint = 'test.address.com:443';
+ $updatedOptions = $client->buildClientOptions(
+ ['serviceAddress' => $apiEndpoint]
+ );
+ $client->setClientOptions($updatedOptions);
+
+ $this->assertEquals($apiEndpoint, $updatedOptions['apiEndpoint']);
+ $this->assertArrayNotHasKey('serviceAddress', $updatedOptions);
+ }
+
+ public function testOperationClientClassOption()
+ {
+ $options = ['operationsClientClass' => CustomOperationsClient::class];
+ $client = new StubGapicClient();
+ $operationsClient = $client->createOperationsClient($options);
+ $this->assertInstanceOf(CustomOperationsClient::class, $operationsClient);
+ }
+
+ public function testAdditionalArgumentMethods()
+ {
+ $client = new StubGapicClient();
+
+ // Set the LRO descriptors we are testing.
+ $longRunningDescriptors = [
+ 'longRunning' => [
+ 'additionalArgumentMethods' => [
+ 'getPageToken',
+ 'getPageSize',
+ ]
+ ]
+ ];
+ $client->set('descriptors', ['method.name' => $longRunningDescriptors]);
+
+ // Set our mock transport.
+ $expectedOperation = new Operation(['name' => 'test-123']);
+ $transport = $this->prophesize(TransportInterface::class);
+ $transport->startUnaryCall(Argument::any(), Argument::any())
+ ->shouldBeCalledOnce()
+ ->willReturn(new FulfilledPromise($expectedOperation));
+ $client->set('transport', $transport->reveal());
+
+ // Set up things for the mock call to work.
+ $client->set('credentialsWrapper', CredentialsWrapper::build([]));
+ $client->set('agentHeader', []);
+ $retrySettings = $this->prophesize(RetrySettings::class);
+ $client->set('retrySettings', [
+ 'method.name' => RetrySettings::constructDefault()
+ ]);
+
+ // Create the mock request object which will have additional argument
+ // methods called on it.
+ $request = new MockRequest([
+ 'page_token' => 'abc',
+ 'page_size' => 100,
+ ]);
+
+ // Create mock operations client to test the additional arguments from
+ // the request object are used.
+ $operationsClient = $this->prophesize(CustomOperationsClient::class);
+ $operationsClient->getOperation('test-123', 'abc', 100)
+ ->shouldBeCalledOnce();
+
+ $operationResponse = $client->startOperationsCall(
+ 'method.name',
+ [],
+ $request,
+ $operationsClient->reveal()
+ )->wait();
+
+ // This will invoke $operationsClient->getOperation with values from
+ // the additional argument methods.
+ $operationResponse->reload();
+ }
+
+ /**
+ * @dataProvider setClientOptionsData
+ */
+ public function testSetClientOptions($options, $expectedProperties)
+ {
+ $client = new StubGapicClient();
+ $updatedOptions = $client->buildClientOptions($options);
+ $client->setClientOptions($updatedOptions);
+ foreach ($expectedProperties as $propertyName => $expectedValue) {
+ $actualValue = $client->get($propertyName);
+ $this->assertEquals($expectedValue, $actualValue);
+ }
+ }
+
+ public function setClientOptionsData()
+ {
+ $clientDefaults = StubGapicClient::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([]) + ['User-Agent' => ['gcloud-php-legacy/']],
+ 'retrySettings' => $expectedRetrySettings,
+ ];
+ return [
+ [[], $expectedProperties],
+ [['disableRetries' => true], ['retrySettings' => $disabledRetrySettings] + $expectedProperties],
+ ];
+ }
+
+ /**
+ * @dataProvider buildRequestHeaderParams
+ */
+ public function testBuildRequestHeaders($headerParams, $request, $expected)
+ {
+ $client = new StubGapicClient();
+ $actual = $client->buildRequestParamsHeader($headerParams, $request);
+ $this->assertEquals($actual[RequestParamsHeaderDescriptor::HEADER_KEY], $expected);
+ }
+
+ public function buildRequestHeaderParams()
+ {
+ $simple = new MockRequestBody([
+ 'name' => 'foos/123/bars/456'
+ ]);
+ $simpleNull = new MockRequestBody();
+ $nested = new MockRequestBody([
+ 'nested_message' => new MockRequestBody([
+ 'name' => 'foos/123/bars/456'
+ ])
+ ]);
+ $unsetNested = new MockRequestBody([]);
+ $nestedNull = new MockRequestBody([
+ 'nested_message' => new MockRequestBody()
+ ]);
+
+ return [
+ [
+ /* $headerParams */ [
+ [
+ 'fieldAccessors' => ['getName'],
+ 'keyName' => 'name_field'
+ ],
+ ],
+ /* $request */ $simple,
+ /* $expected */ ['name_field=foos%2F123%2Fbars%2F456']
+ ],
+ [
+ /* $headerParams */ [
+ [
+ 'fieldAccessors' => ['getName'],
+ 'keyName' => 'name_field'
+ ],
+ ],
+ /* $request */ $simpleNull,
+
+ // For some reason RequestParamsHeaderDescriptor creates an array
+ // with an empty string if there are no headers set in it.
+ /* $expected */ ['']
+ ],
+ [
+ /* $headerParams */ [
+ [
+ 'fieldAccessors' => ['getNestedMessage', 'getName'],
+ 'keyName' => 'name_field'
+ ],
+ ],
+ /* $request */ $nested,
+ /* $expected */ ['name_field=foos%2F123%2Fbars%2F456']
+ ],
+ [
+ /* $headerParams */ [
+ [
+ 'fieldAccessors' => ['getNestedMessage', 'getName'],
+ 'keyName' => 'name_field'
+ ],
+ ],
+ /* $request */ $unsetNested,
+ /* $expected */ ['']
+ ],
+ [
+ /* $headerParams */ [
+ [
+ 'fieldAccessors' => ['getNestedMessage', 'getName'],
+ 'keyName' => 'name_field'
+ ],
+ ],
+ /* $request */ $nestedNull,
+ /* $expected */ ['']
+ ],
+ ];
+ }
+
+ public function testModifyClientOptions()
+ {
+ $options = [];
+ $client = new StubGapicClientExtension();
+ $updatedOptions = $client->buildClientOptions($options);
+ $client->setClientOptions($updatedOptions);
+
+ $this->assertArrayHasKey('addNewOption', $updatedOptions);
+ $this->assertTrue($updatedOptions['disableRetries']);
+ $this->assertEquals('abc123', $updatedOptions['apiEndpoint']);
+ }
+
+ private function buildClientToTestModifyCallMethods($clientClass = null)
+ {
+ $header = AgentHeader::buildAgentHeader([]);
+ $retrySettings = $this->prophesize(RetrySettings::class);
+
+ $longRunningDescriptors = [
+ 'longRunning' => [
+ 'operationReturnType' => 'operationType',
+ 'metadataReturnType' => 'metadataType',
+ ]
+ ];
+ $pageStreamingDescriptors = [
+ 'pageStreaming' => [
+ 'requestPageTokenGetMethod' => 'getPageToken',
+ 'requestPageTokenSetMethod' => 'setPageToken',
+ 'requestPageSizeGetMethod' => 'getPageSize',
+ 'requestPageSizeSetMethod' => 'setPageSize',
+ 'responsePageTokenGetMethod' => 'getNextPageToken',
+ 'resourcesGetMethod' => 'getResources',
+ ],
+ ];
+ $transport = $this->prophesize(TransportInterface::class);
+ $credentialsWrapper = CredentialsWrapper::build([
+ 'keyFile' => __DIR__ . '/testdata/creds/json-key-file.json'
+ ]);
+ $clientClass = $clientClass ?: StubGapicClientExtension::class;
+ $client = new $clientClass();
+ $client->set('transport', $transport->reveal());
+ $client->set('credentialsWrapper', $credentialsWrapper);
+ $client->set('agentHeader', $header);
+ $client->set('retrySettings', [
+ 'simpleMethod' => $retrySettings->reveal(),
+ 'longRunningMethod' => $retrySettings->reveal(),
+ 'pagedMethod' => $retrySettings->reveal(),
+ 'bidiStreamingMethod' => $retrySettings->reveal(),
+ 'clientStreamingMethod' => $retrySettings->reveal(),
+ 'serverStreamingMethod' => $retrySettings->reveal(),
+ ]);
+ $client->set('descriptors', [
+ 'longRunningMethod' => $longRunningDescriptors,
+ 'pagedMethod' => $pageStreamingDescriptors,
+ ]);
+ return [$client, $transport];
+ }
+
+ public function testModifyUnaryCallFromStartCall()
+ {
+ list($client, $transport) = $this->buildClientToTestModifyCallMethods();
+ $transport->startUnaryCall(
+ Argument::type(Call::class),
+ [
+ 'transportOptions' => [
+ 'custom' => ['addModifyUnaryCallableOption' => true]
+ ],
+ 'headers' => AgentHeader::buildAgentHeader([]),
+ 'credentialsWrapper' => CredentialsWrapper::build([
+ 'keyFile' => __DIR__ . '/testdata/creds/json-key-file.json'
+ ])
+ ]
+ )
+ ->shouldBeCalledOnce()
+ ->willReturn(new FulfilledPromise(new Operation()));
+
+ $client->startCall(
+ 'simpleMethod',
+ 'decodeType',
+ [],
+ new MockRequest(),
+ )->wait();
+ }
+
+ public function testModifyUnaryCallFromOperationsCall()
+ {
+ list($client, $transport) = $this->buildClientToTestModifyCallMethods();
+ $transport->startUnaryCall(
+ Argument::type(Call::class),
+ [
+ 'transportOptions' => [
+ 'custom' => ['addModifyUnaryCallableOption' => true]
+ ],
+ 'headers' => AgentHeader::buildAgentHeader([]),
+ 'credentialsWrapper' => CredentialsWrapper::build([
+ 'keyFile' => __DIR__ . '/testdata/creds/json-key-file.json'
+ ]),
+ 'metadataReturnType' => 'metadataType'
+ ]
+ )
+ ->shouldBeCalledOnce()
+ ->willReturn(new FulfilledPromise(new Operation()));
+ $operationsClient = $this->prophesize(OperationsClient::class);
+
+ $client->startOperationsCall(
+ 'longRunningMethod',
+ [],
+ new MockRequest(),
+ $operationsClient->reveal()
+ )->wait();
+ }
+
+ public function testModifyUnaryCallFromGetPagedListResponse()
+ {
+ list($client, $transport) = $this->buildClientToTestModifyCallMethods();
+ $transport->startUnaryCall(
+ Argument::type(Call::class),
+ [
+ 'transportOptions' => [
+ 'custom' => ['addModifyUnaryCallableOption' => true]
+ ],
+ 'headers' => AgentHeader::buildAgentHeader([]),
+ 'credentialsWrapper' => CredentialsWrapper::build([
+ 'keyFile' => __DIR__ . '/testdata/creds/json-key-file.json'
+ ])
+ ]
+ )
+ ->shouldBeCalledOnce()
+ ->willReturn(new FulfilledPromise(new Operation()));
+ $client->getPagedListResponse(
+ 'pagedMethod',
+ [],
+ 'decodeType',
+ new MockRequest(),
+ );
+ }
+
+ /**
+ * @dataProvider modifyStreamingCallFromStartCallData
+ */
+ public function testModifyStreamingCallFromStartCall($callArgs, $expectedMethod, $expectedResponse)
+ {
+ list($client, $transport) = $this->buildClientToTestModifyCallMethods();
+ $transport->$expectedMethod(
+ Argument::type(Call::class),
+ [
+ 'transportOptions' => [
+ 'custom' => ['addModifyStreamingCallable' => true]
+ ],
+ 'headers' => AgentHeader::buildAgentHeader([]),
+ 'credentialsWrapper' => CredentialsWrapper::build([
+ 'keyFile' => __DIR__ . '/testdata/creds/json-key-file.json'
+ ])
+ ]
+ )
+ ->shouldBeCalledOnce()
+ ->willReturn($expectedResponse);
+ $client->startCall(...$callArgs);
+ }
+
+ public function modifyStreamingCallFromStartCallData()
+ {
+ return [
+ [
+ [
+ 'bidiStreamingMethod',
+ '',
+ [],
+ null,
+ Call::BIDI_STREAMING_CALL
+ ],
+ 'startBidiStreamingCall',
+ $this->prophesize(BidiStream::class)->reveal()
+ ],
+ [
+ [
+ 'clientStreamingMethod',
+ '',
+ [],
+ null,
+ Call::CLIENT_STREAMING_CALL
+ ],
+ 'startClientStreamingCall',
+ $this->prophesize(ClientStream::class)->reveal()
+ ],
+ [
+ [
+ 'serverStreamingMethod',
+ '',
+ [],
+ new MockRequest(),
+ Call::SERVER_STREAMING_CALL
+ ],
+ 'startServerStreamingCall',
+ $this->prophesize(ServerStream::class)->reveal()
+ ],
+ ];
+ }
+
+ public function testGetTransport()
+ {
+ $transport = $this->prophesize(TransportInterface::class)->reveal();
+ $client = new StubGapicClient();
+ $client->set('transport', $transport);
+ $this->assertEquals($transport, $client->getTransport());
+ }
+
+ public function testGetCredentialsWrapper()
+ {
+ $credentialsWrapper = $this->prophesize(CredentialsWrapper::class)->reveal();
+ $client = new StubGapicClient();
+ $client->set('credentialsWrapper', $credentialsWrapper);
+ $this->assertEquals($credentialsWrapper, $client->getCredentialsWrapper());
+ }
+
+ public function testUserProjectHeaderIsSetWhenProvidingQuotaProject()
+ {
+ $quotaProject = 'test-quota-project';
+ $credentialsWrapper = $this->prophesize(CredentialsWrapper::class);
+ $credentialsWrapper->getQuotaProject()
+ ->shouldBeCalledOnce()
+ ->willReturn($quotaProject);
+ $transport = $this->prophesize(TransportInterface::class);
+ $transport->startUnaryCall(
+ Argument::type(Call::class),
+ [
+ 'headers' => AgentHeader::buildAgentHeader([]) + [
+ 'X-Goog-User-Project' => [$quotaProject],
+ 'User-Agent' => ['gcloud-php-legacy/']
+ ],
+ 'credentialsWrapper' => $credentialsWrapper->reveal()
+ ]
+ )
+ ->shouldBeCalledOnce()
+ ->willReturn($this->prophesize(PromiseInterface::class)->reveal());
+ $client = new StubGapicClient();
+ $updatedOptions = $client->buildClientOptions(
+ [
+ 'transport' => $transport->reveal(),
+ 'credentials' => $credentialsWrapper->reveal(),
+ ]
+ );
+ $client->setClientOptions($updatedOptions);
+ $client->set(
+ 'retrySettings',
+ ['method' => $this->prophesize(RetrySettings::class)->reveal()]
+ );
+ $client->startCall(
+ 'method',
+ 'decodeType'
+ );
+ }
+
+ 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()
+ ->willReturn($this->prophesize(PromiseInterface::class)->reveal());
+
+ $client = new DefaultScopeAndAudienceGapicClient();
+ $client->set('credentialsWrapper', $credentialsWrapper);
+ $client->set('agentHeader', []);
+ $client->set(
+ 'retrySettings',
+ ['method.name' => $retrySettings->reveal()]
+ );
+ $client->set('transport', $transport->reveal());
+ $client->startCall('method.name', 'decodeType');
+
+ $transport
+ ->startUnaryCall(
+ Argument::any(),
+ [
+ 'audience' => 'custom-audience',
+ 'headers' => [],
+ 'credentialsWrapper' => $credentialsWrapper,
+ ]
+ )
+ ->shouldBeCalledOnce()
+ ->willReturn($this->prophesize(PromiseInterface::class)->reveal());
+
+ $client->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,
+ 'metadataReturnType' => 'metadataType'
+ ]
+ )
+ ->shouldBeCalledOnce()
+ ->willReturn(new FulfilledPromise(new Operation()));
+
+ $longRunningDescriptors = [
+ 'longRunning' => [
+ 'operationReturnType' => 'operationType',
+ 'metadataReturnType' => 'metadataType',
+ 'initialPollDelayMillis' => 100,
+ 'pollDelayMultiplier' => 1.0,
+ 'maxPollDelayMillis' => 200,
+ 'totalPollTimeoutMillis' => 300,
+ ]
+ ];
+ $client = new DefaultScopeAndAudienceGapicClient();
+ $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->prophesize(OperationsClient::class)->reveal();
+
+ // Test startOperationsCall with default audience
+ $client->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 DefaultScopeAndAudienceGapicClient();
+ $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->getPagedListResponse(
+ 'method.name',
+ [],
+ 'decodeType',
+ new MockRequest(),
+ );
+ }
+
+ public function testSupportedTransportOverrideWithInvalidTransport()
+ {
+ $this->expectException(ValidationException::class);
+ $this->expectExceptionMessage('Unexpected transport option "grpc". Supported transports: rest');
+
+ new RestOnlyGapicClient(['transport' => 'grpc']);
+ }
+
+ public function testSupportedTransportOverrideWithDefaultTransport()
+ {
+ $client = new RestOnlyGapicClient();
+ $this->assertInstanceOf(RestTransport::class, $client->getTransport());
+ }
+
+ public function testSupportedTransportOverrideWithExplicitTransport()
+ {
+ $client = new RestOnlyGapicClient(['transport' => 'rest']);
+ $this->assertInstanceOf(RestTransport::class, $client->getTransport());
+ }
+
+ public function testAddMiddlewares()
+ {
+ list($client, $transport) = $this->buildClientToTestModifyCallMethods();
+
+ $m1Called = false;
+ $m2Called = false;
+ $middleware1 = function (MiddlewareInterface $handler) use (&$m1Called) {
+ return new class($handler, $m1Called) implements MiddlewareInterface {
+ private MiddlewareInterface $handler;
+ private bool $m1Called;
+ public function __construct(
+ MiddlewareInterface $handler,
+ bool &$m1Called
+ ) {
+ $this->handler = $handler;
+ $this->m1Called = &$m1Called;
+ }
+ public function __invoke(Call $call, array $options)
+ {
+ $this->m1Called = true;
+ return ($this->handler)($call, $options);
+ }
+ };
+ };
+ $middleware2 = function (MiddlewareInterface $handler) use (&$m2Called) {
+ return new class($handler, $m2Called) implements MiddlewareInterface {
+ private MiddlewareInterface $handler;
+ private bool $m2Called;
+ public function __construct(
+ MiddlewareInterface $handler,
+ bool &$m2Called
+ ) {
+ $this->handler = $handler;
+ $this->m2Called = &$m2Called;
+ }
+ public function __invoke(Call $call, array $options)
+ {
+ $this->m2Called = true;
+ return ($this->handler)($call, $options);
+ }
+ };
+ };
+ $client->addMiddleware($middleware1);
+ $client->addMiddleware($middleware2);
+
+ $transport->startUnaryCall(
+ Argument::type(Call::class),
+ [
+ 'transportOptions' => [
+ 'custom' => ['addModifyUnaryCallableOption' => true]
+ ],
+ 'headers' => AgentHeader::buildAgentHeader([]),
+ 'credentialsWrapper' => CredentialsWrapper::build([
+ 'keyFile' => __DIR__ . '/testdata/creds/json-key-file.json'
+ ])
+ ]
+ )
+ ->shouldBeCalledOnce()
+ ->willReturn(new FulfilledPromise(new Operation()));
+
+ $client->startCall(
+ 'simpleMethod',
+ 'decodeType',
+ [],
+ new MockRequest(),
+ )->wait();
+
+ $this->assertTrue($m1Called);
+ $this->assertTrue($m2Called);
+ }
+
+ public function testPrependMiddleware()
+ {
+ list($client, $transport) = $this->buildClientToTestModifyCallMethods();
+
+ $callOrder = [];
+ $middleware1 = function (callable $handler) use (&$callOrder) {
+ return new class($handler, $callOrder) implements MiddlewareInterface {
+ private $handler;
+ private array $callOrder;
+ public function __construct(
+ callable $handler,
+ array &$callOrder
+ ) {
+ $this->handler = $handler;
+ $this->callOrder = &$callOrder;
+ }
+ public function __invoke(Call $call, array $options)
+ {
+ $this->callOrder[] = 'middleware1';
+ return ($this->handler)($call, $options);
+ }
+ };
+ };
+ $middleware2 = function (callable $handler) use (&$callOrder) {
+ return new class($handler, $callOrder) implements MiddlewareInterface {
+ private $handler;
+ private array $callOrder;
+ public function __construct(
+ callable $handler,
+ array &$callOrder
+ ) {
+ $this->handler = $handler;
+ $this->callOrder = &$callOrder;
+ }
+ public function __invoke(Call $call, array $options)
+ {
+ $this->callOrder[] = 'middleware2';
+ return ($this->handler)($call, $options);
+ }
+ };
+ };
+ $client->addMiddleware($middleware1);
+ $client->prependMiddleware($middleware2);
+
+ $transport->startUnaryCall(
+ Argument::type(Call::class),
+ [
+ 'transportOptions' => [
+ 'custom' => ['addModifyUnaryCallableOption' => true]
+ ],
+ 'headers' => AgentHeader::buildAgentHeader([]),
+ 'credentialsWrapper' => CredentialsWrapper::build([
+ 'keyFile' => __DIR__ . '/testdata/creds/json-key-file.json'
+ ])
+ ]
+ )
+ ->shouldBeCalledOnce()
+ ->willReturn(new FulfilledPromise(new Operation()));
+
+ $client->startCall(
+ 'simpleMethod',
+ 'decodeType',
+ [],
+ new MockRequest(),
+ )->wait();
+
+ $this->assertEquals(['middleware1', 'middleware2'], $callOrder);
+ }
+
+ public function testPrependMiddlewareOrder()
+ {
+ list($client, $transport) = $this->buildClientToTestModifyCallMethods();
+
+ $callOrder = [];
+ $middleware1 = function (callable $handler) use (&$callOrder) {
+ return new class($handler, $callOrder) implements MiddlewareInterface {
+ private $handler;
+ private array $callOrder;
+ public function __construct(
+ callable $handler,
+ array &$callOrder
+ ) {
+ $this->handler = $handler;
+ $this->callOrder = &$callOrder;
+ }
+ public function __invoke(Call $call, array $options)
+ {
+ $this->callOrder[] = 'middleware1';
+ return ($this->handler)($call, $options);
+ }
+ };
+ };
+ $middleware2 = function (callable $handler) use (&$callOrder) {
+ return new class($handler, $callOrder) implements MiddlewareInterface {
+ private $handler;
+ private array $callOrder;
+ public function __construct(
+ callable $handler,
+ array &$callOrder
+ ) {
+ $this->handler = $handler;
+ $this->callOrder = &$callOrder;
+ }
+ public function __invoke(Call $call, array $options)
+ {
+ $this->callOrder[] = 'middleware2';
+ return ($this->handler)($call, $options);
+ }
+ };
+ };
+
+ $client->prependMiddleware($middleware1);
+ $client->prependMiddleware($middleware2);
+
+ $transport->startUnaryCall(
+ Argument::type(Call::class),
+ [
+ 'transportOptions' => [
+ 'custom' => ['addModifyUnaryCallableOption' => true]
+ ],
+ 'headers' => AgentHeader::buildAgentHeader([]),
+ 'credentialsWrapper' => CredentialsWrapper::build([
+ 'keyFile' => __DIR__ . '/testdata/creds/json-key-file.json'
+ ])
+ ]
+ )
+ ->shouldBeCalledOnce()
+ ->willReturn(new FulfilledPromise(new Operation()));
+
+ $client->startCall(
+ 'simpleMethod',
+ 'decodeType',
+ [],
+ new MockRequest(),
+ )->wait();
+
+ $this->assertEquals(['middleware2', 'middleware1'], $callOrder);
+ }
+
+ public function testInvalidClientOptionsTypeThrowsExceptionForV2SurfaceOnly()
+ {
+ // v1 client
+ new StubGapicClient(['apiEndpoint' => ['foo']]);
+ $this->assertTrue(true, 'Test made it to here without throwing an exception');
+
+ $this->expectException(\TypeError::class);
+ $this->expectExceptionMessage(
+ PHP_MAJOR_VERSION < 8
+ ? 'Argument 1 passed to Google\ApiCore\Options\ClientOptions::setApiEndpoint() '
+ . 'must be of the type string or null, array given'
+ : 'Google\ApiCore\Options\ClientOptions::setApiEndpoint(): Argument #1 '
+ . '($apiEndpoint) must be of type ?string, array given'
+ );
+
+ // v2 client
+ new GapicV2SurfaceClient(['apiEndpoint' => ['foo']]);
+ }
+
+ public function testCallOptionsForV2Surface()
+ {
+ list($client, $transport) = $this->buildClientToTestModifyCallMethods(
+ GapicV2SurfaceClient::class
+ );
+
+ $transport->startUnaryCall(
+ Argument::type(Call::class),
+ [
+ 'headers' => AgentHeader::buildAgentHeader([]) + ['Foo' => 'Bar'],
+ 'credentialsWrapper' => CredentialsWrapper::build([
+ 'keyFile' => __DIR__ . '/testdata/creds/json-key-file.json'
+ ]),
+ 'timeoutMillis' => null, // adds null timeoutMillis,
+ 'transportOptions' => [],
+ ]
+ )
+ ->shouldBeCalledOnce()
+ ->willReturn(new FulfilledPromise(new Operation()));
+
+ $callOptions = [
+ 'headers' => ['Foo' => 'Bar'],
+ 'invalidOption' => 'wont-be-passed'
+ ];
+ $client->startCall(
+ 'simpleMethod',
+ 'decodeType',
+ $callOptions,
+ new MockRequest(),
+ )->wait();
+ }
+
+ public function testInvalidCallOptionsTypeForV1SurfaceDoesNotThrowException()
+ {
+ list($client, $transport) = $this->buildClientToTestModifyCallMethods();
+
+ $transport->startUnaryCall(
+ Argument::type(Call::class),
+ [
+ 'transportOptions' => ['custom' => ['addModifyUnaryCallableOption' => true]],
+ 'headers' => AgentHeader::buildAgentHeader([]),
+ 'credentialsWrapper' => CredentialsWrapper::build([
+ 'keyFile' => __DIR__ . '/testdata/creds/json-key-file.json'
+ ]),
+ 'timeoutMillis' => 'blue', // invalid type, this is ignored
+ ]
+ )
+ ->shouldBeCalledOnce()
+ ->willReturn(new FulfilledPromise(new Operation()));
+
+ $client->startCall(
+ 'simpleMethod',
+ 'decodeType',
+ ['timeoutMillis' => 'blue'],
+ new MockRequest(),
+ )->wait();
+ }
+
+ public function testInvalidCallOptionsTypeForV2SurfaceThrowsException()
+ {
+ $this->expectException(\TypeError::class);
+ $this->expectExceptionMessage(
+ PHP_MAJOR_VERSION < 8
+ ? 'Argument 1 passed to Google\ApiCore\Options\CallOptions::setTimeoutMillis() '
+ . 'must be of the type int or null, string given'
+ : 'Google\ApiCore\Options\CallOptions::setTimeoutMillis(): Argument #1 '
+ . '($timeoutMillis) must be of type ?int, string given'
+ );
+
+ list($client, $_) = $this->buildClientToTestModifyCallMethods(GapicV2SurfaceClient::class);
+
+ $client->startCall(
+ 'simpleMethod',
+ 'decodeType',
+ ['timeoutMillis' => 'blue'], // invalid type, will throw exception
+ new MockRequest(),
+ )->wait();
+ }
+
+ public function testSurfaceAgentHeaders()
+ {
+ // V1 does not contain new headers
+ $client = new RestOnlyGapicClient([
+ 'gapicVersion' => '0.0.2',
+ ]);
+ $agentHeader = $client->getAgentHeader();
+ $this->assertStringContainsString(' gapic/0.0.2 ', $agentHeader['x-goog-api-client'][0]);
+ $this->assertEquals('gcloud-php-legacy/0.0.2', $agentHeader['User-Agent'][0]);
+
+ // V2 contains new headers
+ $client = new GapicV2SurfaceClient([
+ 'gapicVersion' => '0.0.1',
+ ]);
+ $agentHeader = $client->getAgentHeader();
+ $this->assertStringContainsString(' gapic/0.0.1 ', $agentHeader['x-goog-api-client'][0]);
+ $this->assertEquals('gcloud-php-new/0.0.1', $agentHeader['User-Agent'][0]);
+ }
+
+ public function testApiKeyOption()
+ {
+ $transport = $this->prophesize(TransportInterface::class);
+ $transport->startUnaryCall(
+ Argument::type(Call::class),
+ Argument::that(function ($options) {
+ // assert API key
+ $this->assertArrayHasKey('credentialsWrapper', $options);
+ $headers = $options['credentialsWrapper']->getAuthorizationHeaderCallback()();
+ $this->assertArrayHasKey('x-goog-api-key', $headers);
+ $this->assertEquals(['abc-123'], $headers['x-goog-api-key']);
+
+ return true;
+ })
+ )
+ ->shouldBeCalledOnce()
+ ->willReturn(new FulfilledPromise(new MockResponse()));
+
+ $client = new GapicV2SurfaceClient([
+ 'transport' => $transport->reveal(),
+ 'apiKey' => 'abc-123',
+ ]);
+
+ $response = $client->startCall('SimpleMethod', 'decodeType');
+ }
+
+ public function testApiKeyOptionThrowsExceptionWhenCredentialsAreSupplied()
+ {
+ $this->expectException(ValidationException::class);
+ $this->expectExceptionMessage(
+ 'API Keys and Credentials are mutually exclusive authentication methods and cannot be used together.'
+ );
+
+ $credentials = $this->prophesize(FetchAuthTokenInterface::class);
+ $client = new GapicV2SurfaceClient([
+ 'apiKey' => 'abc-123',
+ 'credentials' => $credentials->reveal(),
+ ]);
+ }
+
+ public function testKeyFileIsIgnoredWhenApiKeyOptionIsSupplied()
+ {
+ $credentials = $this->prophesize(FetchAuthTokenInterface::class);
+ $client = new GapicV2SurfaceClient([
+ 'apiKey' => 'abc-123',
+ 'credentialsConfig' => [
+ 'keyFile' => __DIR__ . '/testdata/creds/json-key-file.json',
+ ],
+ ]);
+
+ $prop = new \ReflectionProperty($client, 'credentialsWrapper');
+ $this->assertInstanceOf(ApiKeyHeaderCredentials::class, $prop->getValue($client));
+ }
+
+ public function testApiKeyOptionAndQuotaProject()
+ {
+ $transport = $this->prophesize(TransportInterface::class);
+ $transport->startUnaryCall(
+ Argument::type(Call::class),
+ Argument::that(function ($options) {
+ // assert API key
+ $this->assertArrayHasKey('credentialsWrapper', $options);
+ $headers = $options['credentialsWrapper']->getAuthorizationHeaderCallback()();
+ $this->assertArrayHasKey('x-goog-api-key', $headers);
+ $this->assertEquals(['abc-123'], $headers['x-goog-api-key']);
+
+ // assert quota project
+ $this->assertArrayHasKey('headers', $options);
+ $this->assertArrayHasKey('X-Goog-User-Project', $options['headers']);
+ $this->assertEquals(['def-456'], $options['headers']['X-Goog-User-Project']);
+
+ return true;
+ })
+ )
+ ->shouldBeCalledOnce()
+ ->willReturn(new FulfilledPromise(new MockResponse()));
+
+ $client = new GapicV2SurfaceClient([
+ 'transport' => $transport->reveal(),
+ 'apiKey' => 'abc-123',
+ 'credentialsConfig' => ['quotaProject' => 'def-456']
+ ]);
+
+ $response = $client->startCall('SimpleMethod', 'decodeType');
+ }
+
+ public function testHasEmulatorOption()
+ {
+ $gapic = new class() {
+ public bool $hasEmulator;
+
+ use GapicClientTrait {
+ buildClientOptions as public;
+ setClientOptions as public;
+ }
+ use ClientDefaultsTrait {
+ ClientDefaultsTrait::getClientDefaults insteadof GapicClientTrait;
+ }
+
+ private function createTransport(
+ string $apiEndpoint,
+ $transport,
+ $transportConfig,
+ ?callable $clientCertSource = null,
+ bool $hasEmulator = false
+ ) {
+ $this->hasEmulator = $hasEmulator;
+ }
+ };
+
+ $options = $gapic->buildClientOptions(['hasEmulator' => true]);
+ $gapic->setClientOptions($options);
+
+ $this->assertTrue($gapic->hasEmulator);
+ }
+}
+
+class StubGapicClient
+{
+ use GapicClientTrait {
+ buildClientOptions as public;
+ buildRequestParamsHeader as public;
+ configureCallConstructionOptions as public;
+ createCredentialsWrapper as public;
+ createOperationsClient as public;
+ createTransport as public;
+ determineMtlsEndpoint as public;
+ getGapicVersion as public;
+ getCredentialsWrapper as public;
+ getPagedListResponse as public;
+ getTransport as public;
+ setClientOptions as public;
+ shouldUseMtlsEndpoint as public;
+ startApiCall as public;
+ startAsyncCall as public;
+ startCall as public;
+ startOperationsCall as public;
+ }
+ use GapicClientStubTrait;
+ use ClientDefaultsTrait {
+ ClientDefaultsTrait::getClientDefaults insteadof GapicClientTrait;
+ }
+}
+
+trait ClientDefaultsTrait
+{
+ public static function getClientDefaults()
+ {
+ return [
+ 'apiEndpoint' => 'test.address.com:443',
+ 'serviceName' => 'test.interface.v1.api',
+ 'clientConfig' => __DIR__ . '/testdata/resources/test_service_client_config.json',
+ 'descriptorsConfigPath' => __DIR__ . '/testdata/resources/test_service_descriptor_config.php',
+ 'disableRetries' => false,
+ 'auth' => null,
+ 'authConfig' => null,
+ 'transport' => null,
+ 'transportConfig' => [
+ 'rest' => [
+ 'restClientConfigPath' => __DIR__ . '/testdata/resources/test_service_rest_client_config.php',
+ ]
+ ],
+ ];
+ }
+}
+
+class VersionedStubGapicClient extends StubGapicClient
+{
+ // The API version will come from the client library who uses the GapicClientTrait
+ // so in that one it will be private vs Protected. In this case I used protected so
+ // the parent class can see it.
+ protected $apiVersion = '20240418';
+}
+
+trait GapicClientStubTrait
+{
+ 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 StubGapicClientExtension extends StubGapicClient
+{
+ protected function modifyClientOptions(array &$options)
+ {
+ $options['disableRetries'] = true;
+ $options['addNewOption'] = true;
+ $options['apiEndpoint'] = 'abc123';
+ }
+
+ 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 DefaultScopeAndAudienceGapicClient
+{
+ use GapicClientTrait {
+ buildClientOptions as public;
+ startCall as public;
+ startOperationsCall as public;
+ getPagedListResponse as public;
+ }
+ use GapicClientStubTrait;
+
+ 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,
+ ],
+ ];
+ }
+}
+
+class RestOnlyGapicClient
+{
+ use GapicClientTrait {
+ buildClientOptions as public;
+ getTransport as public;
+ }
+ use ClientDefaultsTrait {
+ ClientDefaultsTrait::getClientDefaults insteadof GapicClientTrait;
+ }
+ public function __construct($options = [])
+ {
+ $options['apiEndpoint'] = 'api.example.com';
+ $this->setClientOptions($this->buildClientOptions($options));
+ }
+
+ private static function supportedTransports()
+ {
+ return ['rest', 'fake-transport'];
+ }
+
+ private static function defaultTransport()
+ {
+ return 'rest';
+ }
+
+ public function getAgentHeader()
+ {
+ return $this->agentHeader;
+ }
+}
+
+class OperationsGapicClient extends StubGapicClient
+{
+ public $operationsClient;
+
+ public function getOperationsClient()
+ {
+ return $this->operationsClient;
+ }
+}
+
+class CustomOperationsClient
+{
+ public function getOperation($name, $arg1, $arg2)
+ {
+ }
+}
+
+class GapicV2SurfaceClient
+{
+ use GapicClientTrait {
+ startCall as public;
+ }
+ use GapicClientStubTrait;
+ use ClientDefaultsTrait {
+ ClientDefaultsTrait::getClientDefaults insteadof GapicClientTrait;
+ }
+
+ public function __construct(array $options = [])
+ {
+ $clientOptions = $this->buildClientOptions($options);
+ $this->setClientOptions($clientOptions);
+ }
+
+ public function getAgentHeader()
+ {
+ return $this->agentHeader;
+ }
+}
diff --git a/Gax/tests/Unit/GeneratedTestTest.php b/Gax/tests/Unit/GeneratedTestTest.php
new file mode 100644
index 000000000000..fd2235f6d7c7
--- /dev/null
+++ b/Gax/tests/Unit/GeneratedTestTest.php
@@ -0,0 +1,128 @@
+assertProtobufEquals($expected, $actual);
+ }
+
+ /**
+ * @dataProvider getFailureCases
+ */
+ public function testFailure($expected, $actual)
+ {
+ try {
+ $this->assertProtobufEquals($expected, $actual);
+ } catch (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 MonitoringDestination();
+ $monitoringA->setMonitoredResource('type');
+ $monitoringB = new MonitoringDestination();
+ $monitoringB->setMonitoredResource('type');
+
+ $emptyRepeatedA = $monitoringA->getMetrics();
+ $emptyRepeatedB = $monitoringB->getMetrics();
+
+ $monitoringC = new MonitoringDestination();
+ $monitoringD = new 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 MonitoringDestination();
+ $monitoringA->setMonitoredResource('typeA');
+ $monitoringB = new MonitoringDestination();
+ $monitoringB->setMonitoredResource('typeB');
+
+ $emptyRepeatedA = $monitoringA->getMetrics();
+ $emptyRepeatedB = $monitoringB->getMetrics();
+
+ $monitoringC = new MonitoringDestination();
+ $monitoringD = new 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],
+ ];
+ }
+}
diff --git a/Gax/tests/Unit/GrpcSupportTraitTest.php b/Gax/tests/Unit/GrpcSupportTraitTest.php
new file mode 100644
index 000000000000..c2697f742688
--- /dev/null
+++ b/Gax/tests/Unit/GrpcSupportTraitTest.php
@@ -0,0 +1,54 @@
+assertTrue(true);
+ }
+
+ public function testValidateGrpcSupportFailure()
+ {
+ self::$hasGrpc = false;
+
+ $this->expectException(ValidationException::class);
+ $this->expectExceptionMessage('gRPC support has been requested');
+
+ self::validateGrpcSupport();
+ }
+
+ /**
+ * "Override" existing trait method using late static binding
+ */
+ private static function getGrpcDependencyStatus()
+ {
+ return self::$hasGrpc;
+ }
+}
diff --git a/Gax/tests/Unit/InsecureCredentialsWrapperTest.php b/Gax/tests/Unit/InsecureCredentialsWrapperTest.php
new file mode 100644
index 000000000000..556ed69cd414
--- /dev/null
+++ b/Gax/tests/Unit/InsecureCredentialsWrapperTest.php
@@ -0,0 +1,83 @@
+ new InsecureCredentialsWrapper(),
+ ]);
+
+ $this->assertEmpty($headers);
+ }
+
+ public function testInsecureCredentialsWrapperWithGrpcTransport()
+ {
+ self::requiresGrpcExtension();
+
+ $message = $this->createMockRequest();
+ $call = $this->prophesize(Call::class);
+ $call->getMessage()->willReturn($message);
+ $call->getMethod()->shouldBeCalled();
+ $call->getDecodeType()->shouldBeCalled();
+
+ $grpc = new GrpcTransport('', ['credentials' => ChannelCredentials::createInsecure()]);
+
+ $response = $grpc->startUnaryCall(
+ $call->reveal(),
+ ['credentialsWrapper' => new InsecureCredentialsWrapper()]
+ );
+
+ $this->assertInstanceOf(Promise::class, $response);
+ }
+}
diff --git a/Gax/tests/Unit/InsecureRequestBuilderTest.php b/Gax/tests/Unit/InsecureRequestBuilderTest.php
new file mode 100644
index 000000000000..084ad3f70f1f
--- /dev/null
+++ b/Gax/tests/Unit/InsecureRequestBuilderTest.php
@@ -0,0 +1,74 @@
+builder = new InsecureRequestBuilder(
+ 'www.example.com',
+ __DIR__ . '/testdata/resources/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->assertSame('http', $uri->getScheme());
+ $this->assertEmpty($uri->getQuery());
+ $this->assertEmpty((string) $request->getBody());
+ $this->assertSame('/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->assertSame('http', $uri->getScheme());
+ $this->assertEmpty($uri->getQuery());
+ $this->assertSame('/v1/message/foo', $uri->getPath());
+ $this->assertEquals(
+ ['name' => 'message/foo', 'nestedMessage' => ['name' => 'nested/foo']],
+ json_decode($request->getBody(), true)
+ );
+ }
+}
diff --git a/Gax/tests/Unit/Middleware/FixedHeaderMiddlewareTest.php b/Gax/tests/Unit/Middleware/FixedHeaderMiddlewareTest.php
new file mode 100644
index 000000000000..b11252a510db
--- /dev/null
+++ b/Gax/tests/Unit/Middleware/FixedHeaderMiddlewareTest.php
@@ -0,0 +1,112 @@
+prophesize(Call::class);
+ $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 pb/6.6.6']
+ ];
+ $handlerCalled = false;
+ $callable = function (Call $call, $options) use ($fixedHeader, &$handlerCalled) {
+ $this->assertEquals($fixedHeader, $options['headers']);
+ $handlerCalled = true;
+ };
+ $middleware = new FixedHeaderMiddleware($callable, $fixedHeader);
+ $middleware($call->reveal(), []);
+ $this->assertTrue($handlerCalled);
+ }
+
+ public function testCustomHeaderNoOverride()
+ {
+ $call = $this->prophesize(Call::class);
+ $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->reveal(), ['headers' => $userHeader]);
+ $this->assertTrue($handlerCalled);
+ }
+
+ public function testCustomHeaderOverride()
+ {
+ $call = $this->prophesize(Call::class);
+
+ $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->reveal(), ['headers' => $userHeader]);
+ $this->assertTrue($handlerCalled);
+ }
+}
diff --git a/Gax/tests/Unit/Middleware/OperationsMiddlewareTest.php b/Gax/tests/Unit/Middleware/OperationsMiddlewareTest.php
new file mode 100644
index 000000000000..7d7dac94189e
--- /dev/null
+++ b/Gax/tests/Unit/Middleware/OperationsMiddlewareTest.php
@@ -0,0 +1,69 @@
+prophesize(Call::class);
+ $operationsClient = $this->prophesize(OperationsClient::class);
+
+ $descriptor = [
+ 'operationNameMethod' => 'getNumber'
+ ];
+ $handler = function (Call $call, $options) use (&$callCount) {
+ return $promise = new Promise(function () use (&$promise) {
+ $response = new MockResponse(['number' => 123]);
+ $promise->resolve($response);
+ });
+ };
+ $middleware = new OperationsMiddleware($handler, $operationsClient->reveal(), $descriptor);
+ $response = $middleware(
+ $call->reveal(),
+ []
+ )->wait();
+
+ $this->assertEquals(123, $response->getName());
+ }
+}
diff --git a/Gax/tests/Unit/Middleware/RequestAutoPopulationMiddlewareTest.php b/Gax/tests/Unit/Middleware/RequestAutoPopulationMiddlewareTest.php
new file mode 100644
index 000000000000..624ae9bfd036
--- /dev/null
+++ b/Gax/tests/Unit/Middleware/RequestAutoPopulationMiddlewareTest.php
@@ -0,0 +1,78 @@
+assertTrue(Uuid::isValid($call->getMessage()->getPageToken()));
+ return true;
+ };
+ $call = new Call('GetExample', 'Example', $request);
+ $middleware = new RequestAutoPopulationMiddleware(
+ $next,
+ ['pageToken' => Format::UUID4]
+ );
+ $this->assertTrue($middleware->__invoke($call, []));
+ }
+
+ public function testRequestAutoPopulatedThrowsForInvalidValueType()
+ {
+ $this->expectException(\UnexpectedValueException::class);
+ $this->expectExceptionMessage(
+ 'Value type Google\Api\FieldInfo\Format::FORMAT_UNSPECIFIED not '
+ . 'supported for auto population of the field pageToken'
+ );
+ $request = new MockRequest();
+ $next = function ($call, $options) {
+ $this->assertTrue(empty($call->getMessage()->getPageToken()));
+ return true;
+ };
+ $call = new Call('GetExample', 'Example', $request);
+ $middleware = new RequestAutoPopulationMiddleware(
+ $next,
+ ['pageToken' => Format::FORMAT_UNSPECIFIED]
+ );
+ $middleware->__invoke($call, []);
+ }
+}
diff --git a/Gax/tests/Unit/Middleware/RetryMiddlewareTest.php b/Gax/tests/Unit/Middleware/RetryMiddlewareTest.php
new file mode 100644
index 000000000000..f9e07853ec70
--- /dev/null
+++ b/Gax/tests/Unit/Middleware/RetryMiddlewareTest.php
@@ -0,0 +1,529 @@
+prophesize(Call::class);
+ $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);
+
+ $this->expectException(ApiException::class);
+ $this->expectExceptionMessage('Call Count: 1');
+
+ $middleware($call->reveal(), [])->wait();
+ }
+
+ public function testRetryBackoff()
+ {
+ $call = $this->prophesize(Call::class);
+ $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->reveal(),
+ []
+ )->wait();
+
+ $this->assertSame('Ok!', $response);
+ $this->assertEquals(3, $callCount);
+ }
+
+ public function testRetryTimeoutExceedsMaxTimeout()
+ {
+ $call = $this->prophesize(Call::class);
+ $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);
+
+ $this->expectException(ApiException::class);
+ $this->expectExceptionMessage('Retry total timeout exceeded.');
+
+ $middleware($call->reveal(), [])->wait();
+ }
+
+ public function testRetryTimeoutExceedsRealTime()
+ {
+ $call = $this->prophesize(Call::class);
+ $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(intval($options['timeoutMillis'] * 1000));
+ }
+ throw new ApiException('Cancelled!', Code::CANCELLED, ApiStatus::CANCELLED);
+ });
+ };
+ $middleware = new RetryMiddleware($handler, $retrySettings);
+
+ $this->expectException(ApiException::class);
+ $this->expectExceptionMessage('Retry total timeout exceeded.');
+
+ $middleware($call->reveal(), [])->wait();
+ }
+
+ public function testRetryTimeoutIsInteger()
+ {
+ $call = $this->prophesize(Call::class);
+ $retrySettings = RetrySettings::constructDefault()
+ ->with([
+ 'retriesEnabled' => true,
+ 'retryableCodes' => [ApiStatus::CANCELLED],
+ 'initialRpcTimeoutMillis' => 10000,
+ 'totalTimeoutMillis' => 10000,
+ ]);
+ $callCount = 0;
+ $observedTimeouts = [];
+ $handler = function (Call $call, $options) use (&$callCount, &$observedTimeouts) {
+ $observedTimeouts[] = $options['timeoutMillis'];
+ $callCount += 1;
+ return $promise = new Promise(function () use (&$promise, $callCount) {
+ if ($callCount < 2) {
+ throw new ApiException('Cancelled!', Code::CANCELLED, ApiStatus::CANCELLED);
+ }
+ $promise->resolve('Ok!');
+ });
+ };
+ $middleware = new RetryMiddleware($handler, $retrySettings);
+ $middleware($call->reveal(), [])->wait();
+
+ $this->assertCount(2, $observedTimeouts, 'Expect 2 attempts');
+ $this->assertSame(10000, $observedTimeouts[0], 'First timeout matches config');
+ $this->assertIsInt($observedTimeouts[1], 'Second timeout is an int');
+ }
+
+ public function testTimeoutMillisCallSettingsOverwrite()
+ {
+ $handlerCalled = false;
+ $timeout = 1234;
+ $handler = function (Call $call, array $options) use (&$handlerCalled, $timeout) {
+ $handlerCalled = true;
+ $this->assertEquals($timeout, $options['timeoutMillis']);
+ $promise = $this->prophesize(Promise::class);
+ $promise->then(Argument::cetera())->willReturn($promise->reveal());
+ return $promise->reveal();
+ };
+ $retrySettings = RetrySettings::constructDefault()
+ ->with([
+ 'retriesEnabled' => true,
+ 'retryableCodes' => [ApiStatus::CANCELLED],
+ ]);
+ $middleware = new RetryMiddleware($handler, $retrySettings);
+
+ $call = $this->prophesize(Call::class);
+ $options = ['timeoutMillis' => $timeout];
+ $middleware($call->reveal(), $options);
+ $this->assertTrue($handlerCalled);
+ }
+
+ public function testRetryLogicalTimeout()
+ {
+ $timeout = 2000;
+ $call = $this->prophesize(Call::class);
+ $retrySettings = RetrySettings::constructDefault()
+ ->with([
+ 'retriesEnabled' => true,
+ 'retryableCodes' => [ApiStatus::CANCELLED],
+ ])
+ ->with(RetrySettings::logicalTimeout($timeout));
+ $callCount = 0;
+ $observedTimeouts = [];
+ $handler = function (Call $call, $options) use (&$callCount, &$observedTimeouts) {
+ $callCount += 1;
+ $observedTimeouts[] = $options['timeoutMillis'];
+ return $promise = new Promise(function () use (&$promise, $callCount) {
+ // each call needs to take at least 1 millisecond otherwise the rounded timeout will not decrease
+ // with each step of the test.
+ usleep(1000);
+ if ($callCount < 3) {
+ throw new ApiException('Cancelled!', Code::CANCELLED, ApiStatus::CANCELLED);
+ }
+ $promise->resolve('Ok!');
+ });
+ };
+ $middleware = new RetryMiddleware($handler, $retrySettings);
+ $response = $middleware(
+ $call->reveal(),
+ []
+ )->wait();
+
+ $this->assertSame('Ok!', $response);
+ $this->assertEquals(3, $callCount);
+ $this->assertCount(3, $observedTimeouts);
+ $this->assertEquals($observedTimeouts[0], $timeout);
+ for ($i = 1; $i < count($observedTimeouts); $i++) {
+ $this->assertTrue($observedTimeouts[$i - 1] > $observedTimeouts[$i]);
+ }
+ }
+
+ public function testNoRetryLogicalTimeout()
+ {
+ $timeout = 2000;
+ $call = $this->prophesize(Call::class);
+ $retrySettings = RetrySettings::constructDefault()
+ ->with(RetrySettings::logicalTimeout($timeout));
+ $observedTimeout = 0;
+ $handler = function (Call $call, $options) use (&$observedTimeout) {
+ $observedTimeout = $options['timeoutMillis'];
+ return $promise = new Promise(function () use (&$promise) {
+ $promise->resolve('Ok!');
+ });
+ };
+ $middleware = new RetryMiddleware($handler, $retrySettings);
+ $response = $middleware(
+ $call->reveal(),
+ []
+ )->wait();
+
+ $this->assertSame('Ok!', $response);
+ $this->assertEquals($observedTimeout, $timeout);
+ }
+
+ public function testCustomRetry()
+ {
+ $call = $this->prophesize(Call::class);
+
+ $maxAttempts = 3;
+ $currentAttempt = 0;
+ $retrySettings = RetrySettings::constructDefault()
+ ->with([
+ 'retriesEnabled' => true,
+ 'retryFunction' => function ($ex, $options) use ($maxAttempts, &$currentAttempt) {
+ $currentAttempt++;
+ if ($currentAttempt < $maxAttempts) {
+ return true;
+ }
+
+ return false;
+ }
+ ]);
+ $callCount = 0;
+ $handler = function (Call $call, $options) use (&$callCount) {
+ return new Promise(function () use (&$callCount) {
+ ++$callCount;
+ throw new ApiException('Call Count: ' . $callCount, 0, '');
+ });
+ };
+ $middleware = new RetryMiddleware($handler, $retrySettings);
+
+ $this->expectException(ApiException::class);
+ // test if the custom retry func threw an exception after $maxAttempts
+ $this->expectExceptionMessage('Call Count: ' . ($maxAttempts));
+
+ $middleware($call->reveal(), [])->wait();
+ }
+
+ public function testCustomRetryRespectsRetriesEnabled()
+ {
+ $call = $this->prophesize(Call::class);
+
+ $retrySettings = RetrySettings::constructDefault()
+ ->with([
+ 'retriesEnabled' => false,
+ 'retryFunction' => function ($ex, $options) {
+ // This should not run as retriesEnabled is false
+ $this->fail('Custom retry function shouldn\'t have run.');
+ return true;
+ }
+ ]);
+
+ $handler = function (Call $call, $options) {
+ return new Promise(function () {
+ throw new ApiException('Exception msg', 0, '');
+ });
+ };
+ $middleware = new RetryMiddleware($handler, $retrySettings);
+
+ $this->expectException(ApiException::class);
+ $middleware($call->reveal(), [])->wait();
+ }
+
+ public function testCustomRetryRespectsTimeout()
+ {
+ $call = $this->prophesize(Call::class);
+
+ $retrySettings = RetrySettings::constructDefault()
+ ->with([
+ 'retriesEnabled' => true,
+ 'totalTimeoutMillis' => 1,
+ 'retryFunction' => function ($ex, $options) {
+ usleep(900);
+ return true;
+ }
+ ]);
+
+ $callCount = 0;
+ $handler = function (Call $call, $options) use (&$callCount) {
+ return new Promise(function () use (&$callCount) {
+ ++$callCount;
+ throw new ApiException('Call count: ' . $callCount, 0, '');
+ });
+ };
+ $middleware = new RetryMiddleware($handler, $retrySettings);
+
+ try {
+ $middleware($call->reveal(), [])->wait();
+ $this->fail('Expected an exception, but didn\'t receive any');
+ } catch (ApiException $e) {
+ $this->assertEquals('Retry total timeout exceeded.', $e->getMessage());
+ // we used a total timeout of 1 ms and every retry sleeps for .9 ms
+ // This means that the call count should be 2(original call and 1 retry)
+ $this->assertEquals(2, $callCount);
+ }
+ }
+
+ public function testMaxRetries()
+ {
+ $call = $this->prophesize(Call::class);
+
+ $maxRetries = 2;
+ $retrySettings = RetrySettings::constructDefault()
+ ->with([
+ 'retriesEnabled' => true,
+ 'retryableCodes' => [ApiStatus::CANCELLED],
+ 'maxRetries' => $maxRetries
+ ]);
+ $callCount = 0;
+ $handler = function (Call $call, $options) use (&$callCount) {
+ return new Promise(function () use (&$callCount) {
+ ++$callCount;
+ throw new ApiException('Call Count: ' . $callCount, 0, ApiStatus::CANCELLED);
+ });
+ };
+ $middleware = new RetryMiddleware($handler, $retrySettings);
+
+ $this->expectException(ApiException::class);
+ // test if the custom retry func threw an exception after $maxRetries + 1 calls
+ $this->expectExceptionMessage('Call Count: ' . ($maxRetries + 1));
+
+ $middleware($call->reveal(), [])->wait();
+ }
+
+ public function testRetriesAreIncludedInTheOptionsArray()
+ {
+ $call = $this->prophesize(Call::class)->reveal();
+
+ $maxRetries = 1;
+ $reportedRetries = 0;
+ $retrySettings = RetrySettings::constructDefault()
+ ->with([
+ 'retriesEnabled' => true,
+ 'retryableCodes' => [ApiStatus::CANCELLED],
+ 'maxRetries' => $maxRetries
+ ]);
+
+ $callCount = 0;
+ $handler = function (Call $call, $options) use (&$callCount, &$reportedRetries) {
+ $promise = new Promise(function () use (&$callCount, &$reportedRetries, $options, &$promise) {
+ if ($callCount === 0) {
+ ++$callCount;
+ throw new ApiException('Call Count: ' . $callCount, 0, ApiStatus::CANCELLED);
+ }
+
+ if (array_key_exists('retryAttempt', $options)) {
+ $reportedRetries = $options['retryAttempt'];
+ }
+
+ $promise->resolve(null);
+ });
+
+ return $promise;
+ };
+
+ $middleware = new RetryMiddleware($handler, $retrySettings);
+ $middleware($call, [])->wait();
+
+ $this->assertEquals($callCount, $reportedRetries);
+ }
+
+ /**
+ * Tests for maxRetries to be evaluated before the retry function.
+ */
+ public function testMaxRetriesOverridesCustomRetryFunc()
+ {
+ $call = $this->prophesize(Call::class);
+
+ $maxRetries = 2;
+ $callCount = 0;
+ $retrySettings = RetrySettings::constructDefault()
+ ->with([
+ 'retriesEnabled' => true,
+ 'retryableCodes' => [ApiStatus::CANCELLED],
+ 'maxRetries' => $maxRetries,
+ 'retryFunction' => function ($ex, $options) use (&$callCount) {
+ // The retryFunction will signal a retry until the total call count reaches 5.
+ return $callCount < 5 ? true : false;
+ }
+ ]);
+ $handler = function (Call $call, $options) use (&$callCount) {
+ return new Promise(function () use (&$callCount) {
+ ++$callCount;
+ throw new ApiException('Call Count: ' . $callCount, 0, ApiStatus::CANCELLED);
+ });
+ };
+ $middleware = new RetryMiddleware($handler, $retrySettings);
+
+ $this->expectException(ApiException::class);
+ // Even though our custom retry function wants 4 retries
+ // the exception should be thrown after $maxRetries + 1 calls
+ $this->expectExceptionMessage('Call Count: ' . ($maxRetries + 1));
+
+ $middleware($call->reveal(), [])->wait();
+ }
+
+ /**
+ * Tests for custom retry function returning false, before we reach maxRetries.
+ */
+ public function testCustomRetryThrowsExceptionBeforeMaxRetries()
+ {
+ $call = $this->prophesize(Call::class);
+
+ $maxRetries = 10;
+ $customRetryMaxCalls = 4;
+ $callCount = 0;
+ $retrySettings = RetrySettings::constructDefault()
+ ->with([
+ 'retriesEnabled' => true,
+ 'retryableCodes' => [ApiStatus::CANCELLED],
+ 'maxRetries' => $maxRetries,
+ 'retryFunction' => function ($ex, $options) use (&$callCount, $customRetryMaxCalls) {
+ // The retryFunction will signal a retry until the total call count reaches $customRetryMaxCalls.
+ return $callCount < $customRetryMaxCalls ? true : false;
+ }
+ ]);
+ $handler = function (Call $call, $options) use (&$callCount) {
+ return new Promise(function () use (&$callCount) {
+ ++$callCount;
+ throw new ApiException('Call Count: ' . $callCount, 0, ApiStatus::CANCELLED);
+ });
+ };
+ $middleware = new RetryMiddleware($handler, $retrySettings);
+
+ $this->expectException(ApiException::class);
+ // Even though our maxRetries hasn't reached
+ // the exception should be thrown after $customRetryMaxCalls
+ // because the custom retry function would return false.
+ $this->expectExceptionMessage('Call Count: ' . ($customRetryMaxCalls));
+
+ $middleware($call->reveal(), [])->wait();
+ }
+
+ public function testUnlimitedMaxRetries()
+ {
+ $call = $this->prophesize(Call::class);
+
+ $customRetryMaxCalls = 4;
+ $callCount = 0;
+ $retrySettings = RetrySettings::constructDefault()
+ ->with([
+ 'retriesEnabled' => true,
+ 'retryableCodes' => [ApiStatus::CANCELLED],
+ 'maxRetries' => 0,
+ 'retryFunction' => function ($ex, $options) use (&$callCount, $customRetryMaxCalls) {
+ // The retryFunction will signal a retry until the total call count reaches $customRetryMaxCalls.
+ return $callCount < $customRetryMaxCalls ? true : false;
+ }
+ ]);
+ $handler = function (Call $call, $options) use (&$callCount) {
+ return new Promise(function () use (&$callCount) {
+ ++$callCount;
+ throw new ApiException('Call Count: ' . $callCount, 0, ApiStatus::CANCELLED);
+ });
+ };
+ $middleware = new RetryMiddleware($handler, $retrySettings);
+
+ $this->expectException(ApiException::class);
+ // Since the maxRetries is set to 0(unlimited),
+ // the exception should be thrown after $customRetryMaxCalls
+ // because then the custom retry function would return false.
+ $this->expectExceptionMessage('Call Count: ' . ($customRetryMaxCalls));
+
+ $middleware($call->reveal(), [])->wait();
+ }
+}
diff --git a/Gax/tests/Unit/OperationResponseTest.php b/Gax/tests/Unit/OperationResponseTest.php
new file mode 100644
index 000000000000..43ab160a45d6
--- /dev/null
+++ b/Gax/tests/Unit/OperationResponseTest.php
@@ -0,0 +1,572 @@
+assertSame($opName, $op->getName());
+ $this->assertSame($opClient, $op->getOperationsClient());
+ }
+
+ public function provideOperationsClients()
+ {
+ $keyFilePath = __DIR__ . '/testdata/creds/json-key-file.json';
+ putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $keyFilePath);
+
+ return [
+ [$this->createOperationsClient()],
+ [$this->prophesize(LROOperationsClient::class)->reveal()],
+ ];
+ }
+
+ /**
+ * @dataProvider provideOperationsClients
+ */
+ public function testWithoutResponse($opClient)
+ {
+ $opName = 'operations/opname';
+ $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());
+ }
+
+ /**
+ * @dataProvider provideOperationsClients
+ */
+ public function testWithResponse($opClient)
+ {
+ $opName = 'operations/opname';
+ $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());
+ }
+
+ /**
+ * @dataProvider provideOperationsClients
+ */
+ public function testWithOptions($opClient)
+ {
+ $opName = 'operations/opname';
+ $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
+ ];
+ }
+
+ public function testCustomOperation()
+ {
+ $operationName = 'test-123';
+ $operation = $this->prophesize(CustomOperation::class);
+ $operation->isThisOperationDoneOrWhat()
+ ->shouldBeCalledTimes(2)
+ ->willReturn('Yes, it is!');
+ $operation->getError()
+ ->shouldBeCalledOnce()
+ ->willReturn(null);
+ $operationClient = $this->prophesize(CustomOperationClient::class);
+ $operationClient->getMyOperationPlease($operationName, 'arg1', 'arg2')
+ ->shouldBeCalledOnce()
+ ->willReturn($operation->reveal());
+ $operationClient->cancelMyOperationPlease($operationName, 'arg1', 'arg2')
+ ->shouldBeCalledOnce()
+ ->willReturn(true);
+ $operationClient->deleteMyOperationPlease($operationName, 'arg1', 'arg2')
+ ->shouldBeCalledOnce()
+ ->willReturn(true);
+ $options = [
+ 'getOperationMethod' => 'getMyOperationPlease',
+ 'cancelOperationMethod' => 'cancelMyOperationPlease',
+ 'deleteOperationMethod' => 'deleteMyOperationPlease',
+ 'additionalOperationArguments' => ['arg1', 'arg2'],
+ 'operationStatusMethod' => 'isThisOperationDoneOrWhat',
+ 'operationStatusDoneValue' => 'Yes, it is!',
+ ];
+ $operationResponse = new OperationResponse($operationName, $operationClient->reveal(), $options);
+
+ // Test getOperationMethod
+ $operationResponse->reload();
+
+ // Test operationStatusMethod and operationStatusDoneValue
+ $this->assertTrue($operationResponse->isDone());
+
+ $this->assertTrue($operationResponse->operationSucceeded());
+
+ // test cancelOperationMethod
+ $operationResponse->cancel();
+
+ // test deleteOperationMethod
+ $operationResponse->delete();
+ }
+
+ public function testNewSurfaceCustomOperation()
+ {
+ $phpunit = $this;
+ $operationName = 'test-123';
+ $operationClient = $this->prophesize(NewSurfaceCustomOperationClient::class);
+ $operationClient->getNewSurfaceOperation(Argument::type(CustomGetOperationRequest::class))
+ ->shouldBeCalledOnce()
+ ->will(function ($args) use ($phpunit) {
+ list($request) = $args;
+ $phpunit->assertEquals('test-123', $request->name);
+ $phpunit->assertEquals('arg2', $request->arg2);
+ $phpunit->assertEquals('arg3', $request->arg3);
+ return new class {
+ public function getDone()
+ {
+ return true;
+ }
+ public function getError()
+ {
+ return false;
+ }
+ };
+ });
+ $operationClient->cancelNewSurfaceOperation(Argument::type(CustomCancelOperationRequest::class))
+ ->shouldBeCalledOnce()
+ ->will(function ($args) use ($phpunit) {
+ list($request) = $args;
+ $phpunit->assertEquals('test-123', $request->name);
+ $phpunit->assertEquals('arg2', $request->arg2);
+ $phpunit->assertEquals('arg3', $request->arg3);
+ return true;
+ });
+ $operationClient->deleteNewSurfaceOperation(Argument::type(CustomDeleteOperationRequest::class))
+ ->shouldBeCalledOnce()
+ ->will(function ($args) use ($phpunit) {
+ list($request) = $args;
+ $phpunit->assertEquals('test-123', $request->name);
+ $phpunit->assertEquals('arg2', $request->arg2);
+ $phpunit->assertEquals('arg3', $request->arg3);
+ return true;
+ });
+ $options = [
+ 'getOperationMethod' => 'getNewSurfaceOperation',
+ 'cancelOperationMethod' => 'cancelNewSurfaceOperation',
+ 'deleteOperationMethod' => 'deleteNewSurfaceOperation',
+ 'additionalOperationArguments' => [
+ 'setArgumentTwo' => 'arg2',
+ 'setArgumentThree' => 'arg3'
+ ],
+ 'getOperationRequest' => CustomGetOperationRequest::class,
+ 'cancelOperationRequest' => CustomCancelOperationRequest::class,
+ 'deleteOperationRequest' => CustomDeleteOperationRequest::class,
+ ];
+ $operationResponse = new OperationResponse($operationName, $operationClient->reveal(), $options);
+
+ // Test getOperationMethod
+ $operationResponse->reload();
+
+ // Test operationStatusMethod and operationStatusDoneValue
+ $this->assertTrue($operationResponse->isDone());
+
+ $this->assertTrue($operationResponse->operationSucceeded());
+
+ // test cancelOperationMethod
+ $operationResponse->cancel();
+
+ // test deleteOperationMethod
+ $operationResponse->delete();
+ }
+
+ public function testRequestClassWithoutBuildThrowsException()
+ {
+ $this->expectException(LogicException::class);
+ $this->expectExceptionMessage('Request class must support the static build method');
+
+ $operationClient = $this->prophesize(NewSurfaceCustomOperationClient::class);
+ $options = [
+ 'getOperationRequest' => \stdClass::class, // a class that does not have a "build" method.
+ ];
+ $operationResponse = new OperationResponse('test-123', $operationClient->reveal(), $options);
+
+ // Test getOperationMethod
+ $operationResponse->reload();
+ }
+
+ /**
+ * @dataProvider provideOperationsClients
+ */
+ public function testCustomOperationError($operationClient)
+ {
+ $operationName = 'test-123';
+ $operation = $this->prophesize(CustomOperationWithErrorAnnotations::class);
+ $operation->isThisOperationDoneOrWhat()
+ ->shouldBeCalledTimes(2)
+ ->willReturn('Yes, it is!');
+ $operation->getTheErrorCode()
+ ->shouldBeCalledTimes(2)
+ ->willReturn(500);
+ $operation->getTheErrorMessage()
+ ->shouldBeCalledOnce()
+ ->willReturn('It failed, sorry :(');
+ $options = [
+ 'operationStatusMethod' => 'isThisOperationDoneOrWhat',
+ 'operationStatusDoneValue' => 'Yes, it is!',
+ 'operationErrorCodeMethod' => 'getTheErrorCode',
+ 'operationErrorMessageMethod' => 'getTheErrorMessage',
+ 'lastProtoResponse' => $operation->reveal(),
+ ];
+ $operationResponse = new OperationResponse($operationName, $operationClient, $options);
+
+ $this->assertFalse($operationResponse->operationSucceeded());
+
+ $error = $operationResponse->getError();
+
+ $this->assertNotNull($error);
+ $this->assertEquals(Code::INTERNAL, $error->getCode());
+ $this->assertSame('It failed, sorry :(', $error->getMessage());
+ }
+
+ /**
+ * @dataProvider provideOperationsClients
+ */
+ public function testEmptyCustomOperationErrorIsSuccessful($operationClient)
+ {
+ $operationName = 'test-123';
+ $operation = $this->prophesize(CustomOperationWithErrorAnnotations::class);
+ $operation->isThisOperationDoneOrWhat()
+ ->shouldBeCalledOnce()
+ ->willReturn('Yes, it is!');
+ $operation->getTheErrorCode()
+ ->shouldBeCalledOnce()
+ ->willReturn(null);
+ $options = [
+ 'operationStatusMethod' => 'isThisOperationDoneOrWhat',
+ 'operationStatusDoneValue' => 'Yes, it is!',
+ 'operationErrorCodeMethod' => 'getTheErrorCode',
+ 'lastProtoResponse' => $operation->reveal(),
+ ];
+ $operationResponse = new OperationResponse($operationName, $operationClient, $options);
+
+ $this->assertTrue($operationResponse->operationSucceeded());
+ }
+
+ /**
+ * @dataProvider provideOperationsClients
+ */
+ public function testMisconfiguredCustomOperationThrowsException($operationClient)
+ {
+ $operationName = 'test-123';
+ $operation = $this->prophesize(CustomOperationWithErrorAnnotations::class);
+ $operation->isThisOperationDoneOrWhat()
+ ->shouldBeCalledOnce()
+ ->willReturn('Yes, it is!');
+ $options = [
+ 'operationStatusMethod' => 'isThisOperationDoneOrWhat',
+ 'operationStatusDoneValue' => 'Yes, it is!',
+ 'operationErrorCodeMethod' => null, // The OperationResponse has no way to determine error status
+ 'lastProtoResponse' => $operation->reveal(),
+ ];
+ $operationResponse = new OperationResponse($operationName, $operationClient, $options);
+
+ $this->expectException(LogicException::class);
+ $this->expectExceptionMessage('Unable to determine operation error status for this service');
+
+ $operationResponse->operationSucceeded();
+ }
+
+ /**
+ * @dataProvider provideOperationsClients
+ */
+ public function testNoCancelOperation($operationClient)
+ {
+ $options = [
+ 'cancelOperationMethod' => null,
+ ];
+ $operationResponse = new OperationResponse('test-123', $operationClient, $options);
+
+ $this->expectException(LogicException::class);
+ $this->expectExceptionMessage('The cancel operation is not supported by this API');
+
+ $operationResponse->cancel();
+ }
+
+ /**
+ * @dataProvider provideOperationsClients
+ */
+ public function testNoDeleteOperation($operationClient)
+ {
+ $options = [
+ 'deleteOperationMethod' => null,
+ ];
+ $operationResponse = new OperationResponse('test-123', $operationClient, $options);
+
+ $this->expectException(LogicException::class);
+ $this->expectExceptionMessage('The delete operation is not supported by this API');
+ $operationResponse->delete();
+ }
+
+ public function testPollingCastToInt()
+ {
+ $op = $this->createOperationResponse([], 3);
+ $op->pollUntilComplete([
+ 'initialPollDelayMillis' => 3.0,
+ 'pollDelayMultiplier' => 1.5,
+ ]);
+
+ $this->assertEquals($op->isDone(), true);
+ $this->assertEquals($op->getSleeps(), [3, 4, 6]);
+ }
+
+ public function testReloadWithLROOperationsClient()
+ {
+ $operationClient = $this->prophesize(LROOperationsClient::class);
+ $request = new GetOperationRequest(['name' => 'test-123']);
+ $operationClient->getOperation($request)
+ ->shouldBeCalledOnce()
+ ->willReturn(new Operation());
+
+ $operationResponse = new OperationResponse('test-123', $operationClient->reveal());
+ $operationResponse->reload();
+ }
+
+ public function testCancelWithLROOperationsClient()
+ {
+ $operationClient = $this->prophesize(LROOperationsClient::class);
+ $request = new CancelOperationRequest(['name' => 'test-123']);
+ $operationClient->cancelOperation($request)
+ ->shouldBeCalledOnce();
+
+ $operationResponse = new OperationResponse('test-123', $operationClient->reveal());
+ $operationResponse->cancel();
+ }
+
+ public function testDeleteWithLROOperationsClient()
+ {
+ $operationClient = $this->prophesize(LROOperationsClient::class);
+ $request = new DeleteOperationRequest(['name' => 'test-123']);
+ $operationClient->deleteOperation($request)
+ ->shouldBeCalledOnce();
+
+ $operationResponse = new OperationResponse('test-123', $operationClient->reveal());
+ $operationResponse->delete();
+ }
+
+ private function createOperationResponse($options, $reloadCount)
+ {
+ $opName = 'operations/opname';
+ return new class($opName, $this->createOperationClient($reloadCount), $options) extends OperationResponse {
+ private $currentTime = 0;
+ private $sleeps;
+
+ public function getSleeps()
+ {
+ return $this->sleeps;
+ }
+
+ public function sleepMillis(int $millis)
+ {
+ $this->currentTime += $millis;
+ $this->sleeps[] = $millis;
+ }
+
+ public function setTimes($times)
+ {
+ $this->times = $times;
+ }
+
+ public function getCurrentTimeMillis()
+ {
+ return $this->currentTime;
+ }
+ };
+ }
+
+ private function createOperationClient($reloadCount)
+ {
+ $consecutiveCalls = [];
+ for ($i = 0; $i < $reloadCount - 1; $i++) {
+ $consecutiveCalls[] = new Operation();
+ }
+ $consecutiveCalls[] = new Operation(['done' => true]);
+
+ $opClient = $this->prophesize(OperationsClient::class);
+
+ $opClient->getOperation(Argument::type('string'))
+ ->shouldBeCalledTimes($reloadCount)
+ ->willReturn(...$consecutiveCalls);
+
+ return $opClient->reveal();
+ }
+}
diff --git a/Gax/tests/Unit/Options/OptionsTraitTest.php b/Gax/tests/Unit/Options/OptionsTraitTest.php
new file mode 100644
index 000000000000..1e24517ba9f9
--- /dev/null
+++ b/Gax/tests/Unit/Options/OptionsTraitTest.php
@@ -0,0 +1,137 @@
+newStub([
+ 'option3' => 'foo',
+ 'option4' => 'bar',
+ ]);
+
+ $this->assertArrayHasKey('option1', $options->toArray());
+ $this->assertArrayHasKey('option2', $options->toArray());
+ $this->assertArrayNotHasKey('option3', $options->toArray());
+ $this->assertArrayNotHasKey('option4', $options->toArray());
+ }
+
+ public function testInvalidTypesThrowException()
+ {
+ $this->expectException(TypeError::class);
+ $this->expectExceptionMessage('Cannot assign string to property ArrayAccess@anonymous::$option2 of type ?int');
+
+ $this->newStub([
+ 'option1' => 123, // this is okay because it is cast to a string
+ 'option2' => 'bar', // this will throw an exception
+ ]);
+ }
+
+ public function testArrayGet()
+ {
+ $options = $this->newStub(['option1' => 'abc']);
+ $this->assertEquals('abc', $options['option1']);
+ }
+
+ public function testArrayIsset()
+ {
+ $options = $this->newStub(['option1' => 'abc']);
+ $this->assertTrue(isset($options['option1']));
+ $this->assertFalse(isset($options['option2'])); // valid option
+ $this->assertFalse(isset($options['option3'])); // invalid option
+ }
+
+ public function testArraySetThrowsException()
+ {
+ $this->expectException(BadMethodCallException::class);
+ $options = $this->newStub([]);
+ $options['option1'] = 'abc';
+ }
+
+ public function testArrayUnsetThrowsException()
+ {
+ $this->expectException(BadMethodCallException::class);
+ $options = $this->newStub([]);
+ unset($options['option1']);
+ }
+
+ public function testInvalidFilePathThrowsException()
+ {
+ $this->expectException(ValidationException::class);
+ $this->expectExceptionMessage('Could not find specified file: does/not/exist.php');
+ $this->newStub(['file' => 'does/not/exist.php']);
+ }
+
+ public function testValidateFileExists()
+ {
+ $options = $this->newStub(['option1' => 'foo', 'file' => __FILE__]);
+ $this->assertEquals(__FILE__, $options['file']);
+ }
+
+ private function newStub(array $options)
+ {
+ return new class($options) implements ArrayAccess {
+ use OptionsTrait;
+
+ private ?string $option1;
+ private ?int $option2;
+ private ?string $file;
+
+ public function __construct(array $options)
+ {
+ $this->option1 = $options['option1'] ?? null;
+ $this->option2 = $options['option2'] ?? null;
+ $this->setFile($options['file'] ?? null);
+ }
+
+ private function setFile(?string $file)
+ {
+ if (!is_null($file)) {
+ self::validateFileExists($file);
+ }
+ $this->file = $file;
+ }
+ };
+ }
+}
diff --git a/Gax/tests/Unit/PageStreamingDescriptorTest.php b/Gax/tests/Unit/PageStreamingDescriptorTest.php
new file mode 100644
index 000000000000..d6c9a526893b
--- /dev/null
+++ b/Gax/tests/Unit/PageStreamingDescriptorTest.php
@@ -0,0 +1,52 @@
+expectException(InvalidArgumentException::class);
+ new PageStreamingDescriptor([
+ 'requestPageTokenField' => 'getNextPageToken',
+ // Missing field
+ // 'responsePageTokenField' => 'getNextPageToken',
+ 'resourceField' => 'getResourcesList'
+ ]);
+ }
+}
diff --git a/Gax/tests/Unit/PageTest.php b/Gax/tests/Unit/PageTest.php
new file mode 100644
index 000000000000..8e8b7edea05c
--- /dev/null
+++ b/Gax/tests/Unit/PageTest.php
@@ -0,0 +1,141 @@
+createMockRequest('token');
+
+ $pageStreamingDescriptor = PageStreamingDescriptor::createFromFields([
+ 'requestPageTokenField' => 'pageToken',
+ 'responsePageTokenField' => 'nextPageToken',
+ 'resourceField' => 'resourcesList'
+ ]);
+
+ $internalCall = $this->createCallWithResponseSequence($responseSequence);
+ $callable = function () use ($internalCall) {
+ list($response, $status) = $internalCall->takeAction(...func_get_args());
+ return $promise = new \GuzzleHttp\Promise\Promise(function () use (&$promise, $response) {
+ $promise->resolve($response);
+ });
+ };
+
+ $call = new Call('method', null, $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();
+ }
+
+ public function testNextPageMethodsFailWithNoNextPage()
+ {
+ $responseA = $this->createMockResponse('', ['resource1']);
+ $page = $this->createPage([
+ [$responseA, new MockStatus(Code::OK, '')],
+ ]);
+
+ $this->assertEquals($page->hasNextPage(), false);
+
+ $this->expectException(ValidationException::class);
+ $this->expectExceptionMessage('Could not complete getNextPage operation');
+
+ $page->getNextPage();
+ }
+
+ 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, '')],
+ ]);
+
+ $this->expectException(ValidationException::class);
+ $this->expectExceptionMessage('pageSize argument was defined, but the method does not');
+
+ $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']);
+ }
+}
diff --git a/Gax/tests/Unit/PagedListResponseTest.php b/Gax/tests/Unit/PagedListResponseTest.php
new file mode 100644
index 000000000000..0fd9468df8bc
--- /dev/null
+++ b/Gax/tests/Unit/PagedListResponseTest.php
@@ -0,0 +1,149 @@
+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 $resourceField
+ * @return PagedListResponse
+ */
+ private function makeMockPagedCall($mockRequest, $mockResponse, $resourceField = 'resourcesList')
+ {
+ $pageStreamingDescriptor = PageStreamingDescriptor::createFromFields([
+ 'requestPageTokenField' => 'pageToken',
+ 'responsePageTokenField' => 'nextPageToken',
+ 'resourceField' => $resourceField,
+ ]);
+
+ $callable = function () use ($mockResponse) {
+ return $promise = new \GuzzleHttp\Promise\Promise(function () use (&$promise, $mockResponse) {
+ $promise->resolve($mockResponse);
+ });
+ };
+
+ $call = new Call('method', null, $mockRequest);
+ $options = [];
+
+ $response = $callable($call, $options)->wait();
+
+ $page = new Page($call, $options, $callable, $pageStreamingDescriptor, $response);
+ $pageAccessor = new PagedListResponse($page);
+
+ return $pageAccessor;
+ }
+
+ public function testMapFieldNextPageToken()
+ {
+ $mockRequest = $this->createMockRequest('mockToken');
+ $mockResponse = $this->createMockResponse('nextPageToken1');
+ $mockResponse->setResourcesMap(['key1' => 'resource1']);
+
+ $pageAccessor = $this->makeMockPagedCall($mockRequest, $mockResponse, 'resourcesMap');
+
+ $page = $pageAccessor->getPage();
+ $this->assertEquals($page->getNextPageToken(), 'nextPageToken1');
+ $this->assertEquals(iterator_to_array($page->getIterator()), ['key1' => 'resource1']);
+ }
+
+ public function testMapFieldIterateAllElements()
+ {
+ $mockRequest = $this->createMockRequest('mockToken');
+ $mockResponse = $this->createMockResponse();
+ $mockResponse->setResourcesMap(['key1' => 'resource1']);
+
+ $pageAccessor = $this->makeMockPagedCall($mockRequest, $mockResponse, 'resourcesMap');
+
+ $result = iterator_to_array($pageAccessor->iterateAllElements());
+
+ $this->assertEquals(['key1' => 'resource1'], $result);
+ }
+
+ public function testMapFieldIterator()
+ {
+ $mockRequest = $this->createMockRequest('mockToken');
+ $mockResponse = $this->createMockResponse();
+ $mockResponse->setResourcesMap(['key1' => 'resource1']);
+
+ $pageAccessor = $this->makeMockPagedCall($mockRequest, $mockResponse, 'resourcesMap');
+
+ $result = iterator_to_array($pageAccessor);
+
+ $this->assertEquals(['key1' => 'resource1'], $result);
+ }
+}
diff --git a/Gax/tests/Unit/PathTemplateTest.php b/Gax/tests/Unit/PathTemplateTest.php
new file mode 100644
index 000000000000..a53c89e1b981
--- /dev/null
+++ b/Gax/tests/Unit/PathTemplateTest.php
@@ -0,0 +1,272 @@
+expectException(ValidationException::class);
+ $this->expectExceptionMessage('Unexpected characters in literal segment');
+
+ new PathTemplate('hello/wor*ld');
+ }
+
+ public function testFailWhenImpossibleMatch01()
+ {
+ $template = new PathTemplate('hello/world');
+
+ $this->expectException(ValidationException::class);
+ $this->expectExceptionMessage('Could not match path');
+
+ $template->match('hello');
+ }
+
+ public function testFailWhenImpossibleMatch02()
+ {
+ $template = new PathTemplate('hello/world');
+
+ $this->expectException(ValidationException::class);
+ $this->expectExceptionMessage('Could not match path');
+
+ $template->match('hello/world/fail');
+ }
+
+ public function testFailMismatchedLiteral()
+ {
+ $template = new PathTemplate('hello/world');
+
+ $this->expectException(ValidationException::class);
+ $this->expectExceptionMessage('Could not match path');
+
+ $template->match('hello/world2');
+ }
+
+ public function testFailWhenMultiplePathWildcards()
+ {
+ $this->expectException(ValidationException::class);
+ $this->expectExceptionMessage('Cannot parse');
+
+ new PathTemplate('buckets/*/**/**/objects/*');
+ }
+
+ public function testFailIfInnerBinding()
+ {
+ $this->expectException(ValidationException::class);
+ $this->expectExceptionMessage("Unexpected '{'");
+
+ new PathTemplate('buckets/{hello={world}}');
+ }
+
+ public function testFailUnexpectedEof()
+ {
+ $this->expectException(ValidationException::class);
+ $this->expectExceptionMessage("Expected '}'");
+
+ new PathTemplate('a/{hello=world');
+ }
+
+ public function testFailNullString()
+ {
+ $this->expectException(ValidationException::class);
+ $this->expectExceptionMessage('Cannot construct PathTemplate from empty string');
+
+ new PathTemplate(null);
+ }
+
+ public function testFailEmptyString()
+ {
+ $this->expectException(ValidationException::class);
+ $this->expectExceptionMessage('Cannot construct PathTemplate from empty string');
+
+ 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')
+ );
+ }
+
+ public function testFailMatchWildcardWithColonInMiddle()
+ {
+ $this->expectException(ValidationException::class);
+
+ 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')
+ );
+ }
+
+ public function testFailMatchUnboundedWildcardWithColonInMiddle()
+ {
+ $this->expectException(ValidationException::class);
+
+ 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');
+ }
+
+ public function testRenderFailWhenTooFewVariables()
+ {
+ $this->expectException(ValidationException::class);
+ $this->expectExceptionMessage('Error rendering');
+
+ $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<>,.?[]'
+ );
+ }
+}
diff --git a/Gax/tests/Unit/ProtobufBandaidTest.php b/Gax/tests/Unit/ProtobufBandaidTest.php
new file mode 100644
index 000000000000..8666235701cd
--- /dev/null
+++ b/Gax/tests/Unit/ProtobufBandaidTest.php
@@ -0,0 +1,46 @@
+assertEquals($expected, $actual);
+ }
+
+ public function protobufMessageProvider()
+ {
+ $this->autoloadTestdata('generated');
+ $this->autoloadTestdata('generated/metadata', 'GPBMetadata\\' . __NAMESPACE__);
+
+ $msg1 = new MyMessage();
+ $msg2 = new Mymessage();
+ return [
+ [$msg1, $msg2],
+ [[$msg1, $msg2], [$msg1, $msg2]]
+ ];
+ }
+}
diff --git a/Gax/tests/Unit/ProtobufExtensionTest.php b/Gax/tests/Unit/ProtobufExtensionTest.php
new file mode 100644
index 000000000000..9c02293b26f7
--- /dev/null
+++ b/Gax/tests/Unit/ProtobufExtensionTest.php
@@ -0,0 +1,51 @@
+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);
+ }
+}
diff --git a/Gax/tests/Unit/RequestBuilderTest.php b/Gax/tests/Unit/RequestBuilderTest.php
new file mode 100644
index 000000000000..a587be4f8de5
--- /dev/null
+++ b/Gax/tests/Unit/RequestBuilderTest.php
@@ -0,0 +1,483 @@
+builder = new RequestBuilder(
+ 'www.example.com',
+ __DIR__ . '/testdata/resources/test_service_rest_client_config.php'
+ );
+ $this->numericEnumsBuilder = new RequestBuilder(
+ 'www.example.com',
+ __DIR__ . '/testdata/resources/test_numeric_enums_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->assertSame('https', $uri->getScheme());
+ $this->assertEmpty($uri->getQuery());
+ $this->assertEmpty((string) $request->getBody());
+ $this->assertSame('/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->assertSame('https', $uri->getScheme());
+ $this->assertEmpty($uri->getQuery());
+ $this->assertSame('/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->assertSame('https', $uri->getScheme());
+ $this->assertEmpty($uri->getQuery());
+ $this->assertSame('/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->assertSame('https', $uri->getScheme());
+ $this->assertEmpty($uri->getQuery());
+ $this->assertSame('/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->assertSame('https', $uri->getScheme());
+ $this->assertSame('/v1/message/foo', $uri->getPath());
+ $this->assertSame('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->assertSame('value1', $request->getHeaderLine('header1'));
+ $this->assertSame('value2', $request->getHeaderLine('header2'));
+ $this->assertSame('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->assertSame('https', $uri->getScheme());
+ $this->assertEmpty($uri->getQuery());
+ $this->assertSame('/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->assertSame('https', $uri->getScheme());
+ $this->assertEmpty($uri->getQuery());
+ $this->assertSame('/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->assertSame('https', $uri->getScheme());
+ $this->assertSame('/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->assertSame('/v1/message/foo/additional/bindings', $request->getUri()->getPath());
+
+ $message->setName('different/format/foo');
+ $request = $this->builder->build(self::SERVICE_NAME . '/MethodWithAdditionalBindings', $message);
+
+ $this->assertSame('/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->assertSame('/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->assertStringContainsString('listValue=val1&listValue=val2', (string) $uri);
+
+ $query = Query::parse($uri->getQuery());
+
+ $this->assertSame('XDAwMA==', $query['bytesValue']);
+ $this->assertSame('9001.000500s', $query['durationValue']);
+ $this->assertSame('path1,path2', $query['fieldMask']);
+ $this->assertEquals(100, $query['int64Value']);
+ $this->assertEquals(['val1', 'val2'], $query['listValue']);
+ $this->assertSame('some-value', $query['stringValue']);
+ $this->assertSame('val5', $query['structValue.test']);
+ $this->assertSame('1970-01-01T02:30:01Z', $query['timestampValue']);
+ $this->assertSame('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 = Query::parse($request->getUri()->getQuery());
+
+ $this->assertSame('path1,path2', $query['fieldMask']);
+ $this->assertSame('some-value', $query['stringValue']);
+ }
+
+ public function testMethodWithRequiredQueryParametersAndDefaultValues()
+ {
+ $message = (new MockRequestBody())
+ ->setName('')
+ ->setNumber(0);
+
+ $request = $this->builder->build(self::SERVICE_NAME . '/MethodWithRequiredQueryParameters', $message);
+ $query = Query::parse($request->getUri()->getQuery());
+
+ $this->assertSame('', $query['name']);
+ $this->assertSame('0', $query['number']);
+ }
+
+ public function testMethodWithRequiredNestedQueryParameters()
+ {
+ $nestedMessage = (new MockRequestBody())
+ ->setName('some-name')
+ ->setNumber(123);
+ $message = (new MockRequestBody())
+ ->setNestedMessage($nestedMessage);
+
+ $request = $this->builder->build(self::SERVICE_NAME . '/MethodWithRequiredNestedQueryParameters', $message);
+ $query = Query::parse($request->getUri()->getQuery());
+
+ $this->assertSame('some-name', $query['nestedMessage.name']);
+ $this->assertSame('123', $query['nestedMessage.number']);
+ }
+
+ public function testMethodWithRequiredTimestampQueryParameters()
+ {
+ $message = (new MockRequestBody())
+ ->setTimestampValue(new Timestamp(['seconds' => 1234567]));
+
+ $request = $this->builder->build(self::SERVICE_NAME . '/MethodWithRequiredTimestampQueryParameters', $message);
+ $query = Query::parse($request->getUri()->getQuery());
+
+ $dateTime = (new \DateTime())->setTimestamp(1234567);
+ $this->assertSame($dateTime->format('Y-m-d\TH:i:s\Z'), $query['timestampValue']);
+ }
+
+ public function testMethodWithRequiredDoubleNestedQueryParameter()
+ {
+ $doubleNestedMessage = (new MockRequestBody())
+ ->setName('double-nested-name');
+ $nestedMessage = (new MockRequestBody())
+ ->setName('some-name')
+ ->setNestedMessage($doubleNestedMessage);
+ $message = (new MockRequestBody())
+ ->setNestedMessage($nestedMessage);
+
+ $request = $this->builder->build(self::SERVICE_NAME . '/MethodWithRequiredNestedQueryParameters', $message);
+ $query = Query::parse($request->getUri()->getQuery());
+
+ $this->assertSame('some-name', $query['nestedMessage.name']);
+ $this->assertSame('double-nested-name', $query['nestedMessage.nestedMessage']);
+ }
+
+ public function testMethodWithRequiredDoubleNestedQueryParameterArray()
+ {
+ // Adding another property decodes it as array
+ $doubleNestedMessage = (new MockRequestBody())
+ ->setName('double-nested-name')
+ ->setNumber(123);
+ $nestedMessage = (new MockRequestBody())
+ ->setName('some-name')
+ ->setNestedMessage($doubleNestedMessage);
+ $message = (new MockRequestBody())
+ ->setNestedMessage($nestedMessage);
+
+ $request = $this->builder->build(self::SERVICE_NAME . '/MethodWithRequiredNestedQueryParameters', $message);
+ $query = Query::parse($request->getUri()->getQuery());
+
+ $this->assertSame(['double-nested-name', '123'], $query['nestedMessage.nestedMessage']);
+ }
+
+ public function testMethodWithComplexMessageInQueryString()
+ {
+ $message = (new MockRequestBody())
+ ->setNestedMessage(
+ (new MockRequestBody())
+ ->setName('some-name')
+ ->setNumber(10)
+ );
+
+ $request = $this->builder->build(self::SERVICE_NAME . '/MethodWithoutPlaceholders', $message);
+ $query = Query::parse($request->getUri()->getQuery());
+
+ $this->assertSame('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 = Query::parse($request->getUri()->getQuery());
+
+ $this->assertSame('some-value', $query['field1']);
+ }
+
+ public function testMethodWithNumericEnumsQueryParam()
+ {
+ $request = $this->numericEnumsBuilder->build(
+ self::SERVICE_NAME . '/MethodWithNumericEnumsQueryParam',
+ new MockRequestBody()
+ );
+ $query = Query::parse($request->getUri()->getQuery());
+
+ $this->assertEquals('json;enum-encoding=int', $query['$alt']);
+ }
+
+ public function testThrowsExceptionWithNonMatchingFormat()
+ {
+ $message = new MockRequestBody();
+ $message->setName('invalid/name/format');
+
+ $this->expectException(ValidationException::class);
+ $this->expectExceptionMessage(
+ 'Could not map bindings for test.interface.v1.api/MethodWithAdditionalBindings to any Uri template.'
+ );
+
+ $this->builder->build(self::SERVICE_NAME . '/MethodWithAdditionalBindings', $message);
+ }
+
+ public function testThrowsExceptionWithNonExistantMethod()
+ {
+ $message = new MockRequestBody();
+
+ $this->expectException(ValidationException::class);
+ $this->expectExceptionMessage(
+ 'Failed to build request, as the provided path (myResource/doesntExist) was not found in the configuration.'
+ );
+
+ $this->builder->build('myResource/doesntExist', $message);
+ }
+}
diff --git a/Gax/tests/Unit/RequestParamsHeaderDescriptorTest.php b/Gax/tests/Unit/RequestParamsHeaderDescriptorTest.php
new file mode 100644
index 000000000000..5d84ab8ed93c
--- /dev/null
+++ b/Gax/tests/Unit/RequestParamsHeaderDescriptorTest.php
@@ -0,0 +1,87 @@
+ ['']];
+
+ $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 testNonAsciiChars()
+ {
+ $val = 'ă“ă‚“ă«ăˇăŻ';
+ $expectedHeader = [
+ RequestParamsHeaderDescriptor::HEADER_KEY => ['field1=' . urlencode($val)]
+ ];
+
+ $agentHeaderDescriptor = new RequestParamsHeaderDescriptor(['field1' => $val]);
+ $header = $agentHeaderDescriptor->getHeader();
+
+ $this->assertSame($expectedHeader, $header);
+ }
+}
diff --git a/Gax/tests/Unit/ResourceHelperTraitTest.php b/Gax/tests/Unit/ResourceHelperTraitTest.php
new file mode 100644
index 000000000000..6123561671d8
--- /dev/null
+++ b/Gax/tests/Unit/ResourceHelperTraitTest.php
@@ -0,0 +1,115 @@
+stub = new class() {
+ use ResourceHelperTrait;
+
+ const CONFIG_PATH = __DIR__ . '/testdata/resources/test_service_descriptor_config.php';
+ const SERVICE_NAME = 'test.interface.v1.api';
+
+ private static function getClientDefaults()
+ {
+ return ['descriptorsConfigPath' => self::CONFIG_PATH];
+ }
+
+ public static function parseName($formattedName, $template = null)
+ {
+ return self::parseFormattedName($formattedName, $template);
+ }
+
+ public static function testRegisterPathTemplates()
+ {
+ self::registerPathTemplates();
+ return self::$templateMap;
+ }
+
+ public static function testGetPathTemplate($key)
+ {
+ return self::getPathTemplate($key);
+ }
+ };
+ }
+ public function testRegisterPathTemplates()
+ {
+ $got = $this->stub::testRegisterPathTemplates();
+ $this->assertEquals(count($got), 4);
+ $this->assertTrue($got['project'] instanceof PathTemplate);
+ }
+
+ public function testGetPathTemplate()
+ {
+ $got = $this->stub::testGetPathTemplate('project');
+ $this->assertNotNull($got);
+ $this->assertTrue($got instanceof PathTemplate);
+ }
+
+ public function testGetPathTemplateNull()
+ {
+ $got = $this->stub::testGetPathTemplate('does_not_exist');
+ $this->assertNull($got);
+ }
+
+ public function testParseName()
+ {
+ $got = $this->stub::parseName('projects/abc123', 'project');
+ $this->assertEquals(count($got), 1);
+ $this->assertEquals($got['project'], 'abc123');
+ }
+
+ public function testParseNameInvalidTemplate()
+ {
+ $this->expectException(ValidationException::class);
+ $this->expectExceptionMessage('Template name does_not_exist does not exist');
+
+ $this->stub::parseName('projects/abc123', 'does_not_exist');
+ }
+
+ public function testParseNameNoMatchingPattern()
+ {
+ $this->expectException(ValidationException::class);
+ $this->expectExceptionMessage('Input did not match any known format. Input: no/matching/pattern');
+
+ $this->stub::parseName('no/matching/pattern');
+ }
+}
diff --git a/Gax/tests/Unit/ResourceTemplate/AbsoluteResourceTemplateTest.php b/Gax/tests/Unit/ResourceTemplate/AbsoluteResourceTemplateTest.php
new file mode 100644
index 000000000000..65c84ffa4b99
--- /dev/null
+++ b/Gax/tests/Unit/ResourceTemplate/AbsoluteResourceTemplateTest.php
@@ -0,0 +1,308 @@
+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
+ * @param string $path
+ */
+ public function testInvalidPaths($path)
+ {
+ $this->expectException(ValidationException::class);
+ new AbsoluteResourceTemplate($path);
+ }
+
+ public function invalidPathProvider()
+ {
+ return [
+ [''], // 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
+ */
+ public function testFailMatch($pathTemplate, $path)
+ {
+ $template = new AbsoluteResourceTemplate($pathTemplate);
+
+ $this->expectException(ValidationException::class);
+
+ $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
+ */
+ public function testFailRender($pathTemplate, $bindings)
+ {
+ $template = new AbsoluteResourceTemplate($pathTemplate);
+
+ $this->expectException(ValidationException::class);
+
+ $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
+ ],
+ ];
+ }
+}
diff --git a/Gax/tests/Unit/ResourceTemplate/ParserTest.php b/Gax/tests/Unit/ResourceTemplate/ParserTest.php
new file mode 100644
index 000000000000..e3a479fef1b5
--- /dev/null
+++ b/Gax/tests/Unit/ResourceTemplate/ParserTest.php
@@ -0,0 +1,438 @@
+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
+ * @param string $path
+ */
+ public function testParseInvalid($path)
+ {
+ $this->expectException(ValidationException::class);
+
+ 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 [
+ [''],
+ ['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 [
+ [''],
+ ['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 [
+ [''],
+ ];
+ }
+
+ private function invokeStaticMethod($class, $methodName, array $parameters)
+ {
+ $reflection = new \ReflectionClass($class);
+ $method = $reflection->getMethod($methodName);
+
+ return $method->invokeArgs(null, $parameters);
+ }
+}
diff --git a/Gax/tests/Unit/ResourceTemplate/RelativeResourceTemplateTest.php b/Gax/tests/Unit/ResourceTemplate/RelativeResourceTemplateTest.php
new file mode 100644
index 000000000000..d29142eb82c6
--- /dev/null
+++ b/Gax/tests/Unit/ResourceTemplate/RelativeResourceTemplateTest.php
@@ -0,0 +1,380 @@
+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
+ * @param string $path
+ */
+ public function testInvalidPaths($path, $expectedExceptionMessage = null)
+ {
+ $this->expectException(ValidationException::class);
+ if (isset($expectedExceptionMessage)) {
+ $this->expectExceptionMessage($expectedExceptionMessage);
+ }
+ new RelativeResourceTemplate($path);
+ }
+
+ public function invalidPathProvider()
+ {
+ return [
+ [
+ '', // 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
+ */
+ public function testFailMatch($pathTemplate, $path)
+ {
+ $template = new RelativeResourceTemplate($pathTemplate);
+
+ $this->expectException(ValidationException::class);
+
+ $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
+ */
+ public function testFailRender($pathTemplate, $bindings, $expectedExceptionMessage = null)
+ {
+ $this->expectException(ValidationException::class);
+ if (isset($expectedExceptionMessage)) {
+ $this->expectExceptionMessage($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 ''\nProvided 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\nProvided 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 " .
+ "''\nProvided 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\nProvided 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
+ ],
+ ];
+ }
+}
diff --git a/Gax/tests/Unit/RetrySettingsTest.php b/Gax/tests/Unit/RetrySettingsTest.php
new file mode 100644
index 000000000000..4d44ca373535
--- /dev/null
+++ b/Gax/tests/Unit/RetrySettingsTest.php
@@ -0,0 +1,398 @@
+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());
+ $this->assertEquals(RetrySettings::DEFAULT_MAX_RETRIES, $simpleMethod->getMaxRetries());
+ }
+
+ public function testLoadInvalid()
+ {
+ $inputConfig = RetrySettingsTest::buildInvalidInputConfig();
+
+ $this->expectException(ValidationException::class);
+
+ 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());
+ }
+
+ public function testRetrySettingsMissingFields()
+ {
+ $this->expectException(ValidationException::class);
+
+ 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);
+ }
+
+ public function testLogicalTimeout()
+ {
+ $timeout = 10000;
+ $expectedValues = [
+ 'initialRpcTimeoutMillis' => $timeout,
+ 'maxRpcTimeoutMillis' => $timeout,
+ 'totalTimeoutMillis' => $timeout,
+ 'noRetriesRpcTimeoutMillis' => $timeout,
+ 'rpcTimeoutMultiplier' => 1.0
+ ];
+ $timeoutSettings = RetrySettings::logicalTimeout($timeout);
+ $this->assertSame(
+ $expectedValues,
+ $timeoutSettings
+ );
+ }
+
+ 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,
+ 'maxRetries' => RetrySettings::DEFAULT_MAX_RETRIES,
+ 'retryFunction' => null,
+ ];
+ 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
+ ],
+ [
+ // Test with a custom retry function
+ [
+ 'retryFunction' => function ($ex, $options) {
+ return true;
+ }
+ ] + $defaultSettings,
+ [
+ 'retryFunction' => function ($ex, $options) {
+ return true;
+ }
+ ] + $defaultExpectedValues
+ ],
+ [
+ // Test with a maxRetries value
+ [
+ 'maxRetries' => 2
+ ] + $defaultSettings,
+ [
+ 'maxRetries' => 2
+ ] + $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,
+ 'maxRetries' => RetrySettings::DEFAULT_MAX_RETRIES,
+ 'retryFunction' => null,
+ ];
+ 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,
+ ]
+ ],
+ [
+ // Test with a custom retry function
+ $defaultSettings,
+ [
+ 'retryFunction' => function ($ex, $options) {
+ return true;
+ }
+ ],
+ [
+ 'retryFunction' => function ($ex, $options) {
+ return true;
+ }
+ ] + $defaultExpectedValues
+ ],
+ [
+ // Test with a maxRetries value
+ $defaultSettings,
+ [
+ 'maxRetries' => 2
+ ],
+ [
+ 'maxRetries' => 2
+ ] + $defaultExpectedValues
+ ]
+ ];
+ }
+}
diff --git a/Gax/tests/Unit/SerializerTest.php b/Gax/tests/Unit/SerializerTest.php
new file mode 100644
index 000000000000..d8e0c227efcb
--- /dev/null
+++ b/Gax/tests/Unit/SerializerTest.php
@@ -0,0 +1,393 @@
+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 testDecodeMetadataReturnsErrorsWithArrayPointer()
+ {
+ $expectedError = new BadRequest();
+ $metadata = [
+ 'google.rpc.badrequest-bin' => [$expectedError->serializeToString()]
+ ];
+
+ $protobufErrors = [];
+ $expectedProtbufErrors = [
+ $expectedError
+ ];
+
+ Serializer::decodeMetadata($metadata, $protobufErrors);
+
+ $this->assertCount(1, $protobufErrors);
+ $this->assertEquals($expectedProtbufErrors, $protobufErrors);
+ }
+
+ public function testDecodeMetadataDoesNotAddUnknownsToErrors()
+ {
+ $metadata = [
+ 'unknown' => ['random string']
+ ];
+
+ $protobufErrors = [];
+ Serializer::decodeMetadata($metadata, $protobufErrors);
+
+ $this->assertCount(0, $protobufErrors);
+ }
+
+ 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->assertEqualsWithDelta(0.1, $color->getRed(), 0.0000001);
+ $this->assertEqualsWithDelta(0.3, $color->getGreen(), 0.0000001);
+ $this->assertEqualsWithDelta(0.4, $color->getBlue(), 0.0000001);
+ $alpha = $color->getAlpha();
+ $this->assertEqualsWithDelta(1.0, $alpha->getValue(), 0.0000001);
+
+ $array = $serializer->encodeMessage($color);
+ $this->assertEqualsWithDelta($data['red'], $array['red'], 0.0000001);
+ $this->assertEqualsWithDelta($data['green'], $array['green'], 0.0000001);
+ $this->assertEqualsWithDelta($data['blue'], $array['blue'], 0.0000001);
+ $this->assertEqualsWithDelta($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->assertEqualsWithDelta(0.2, $color->getRed(), 0.0000001);
+ $this->assertEqualsWithDelta(0.3, $color->getGreen(), 0.0000001);
+ $this->assertEqualsWithDelta(0.4, $color->getBlue(), 0.0000001);
+ $alpha = $color->getAlpha();
+ $this->assertEqualsWithDelta(0.5, $alpha->getValue(), 0.0000001);
+
+ $array = $serializer->encodeMessage($color);
+ $this->assertEqualsWithDelta($data['red'], $array['red'], 0.0000001);
+ $this->assertEqualsWithDelta($data['green'], $array['green'], 0.0000001);
+ $this->assertEqualsWithDelta($data['blue'], $array['blue'], 0.0000001);
+ $this->assertEqualsWithDelta($data['alpha']['value'], $array['alpha']['value'], 0.0000001);
+ }
+
+ /**
+ * @dataProvider customEncoderProvider
+ */
+ public function testCustomEncoderForEncodeMessage($customEncoder, $protoObj, $expectedData)
+ {
+ $serializer = new Serializer([], [], [], [], $customEncoder);
+
+ $data = $serializer->encodeMessage($protoObj);
+ array_walk($expectedData, function ($val, $key, $expectedData) {
+ $this->assertEquals($val, $expectedData[$key]);
+ }, $expectedData);
+ }
+
+ public function customEncoderProvider()
+ {
+ $original = [
+ 'red' => 50.0,
+ 'blue' => 50.0
+ ];
+
+ $expected = [
+ 'red' => 100.0,
+ 'blue' => 100.0
+ ];
+
+ return [
+ [
+ [
+ Color::class => function ($message) {
+ return [
+ 'red' => $message->getRed() * 2,
+ 'blue' => $message->getBlue() * 2
+ ];
+ }
+ ],
+ new Color($original),
+ $expected
+ ],
+ [
+ // When no custom encoder is supplied, the encodeMessage will return the data
+ // that is passed into the proto
+ [
+ ],
+ new Color($original),
+ $original
+ ],
+ [
+ // When a custom encoder for a different protois supplied,
+ // the encodeMessage will return the data as if no custom encoder was supplied
+ [
+ Status::class => function ($message) {
+ return ['foo' => 'bar'];
+ }
+ ],
+ new Color($original),
+ $original
+ ]
+ ];
+ }
+}
diff --git a/Gax/tests/Unit/ServerStreamTest.php b/Gax/tests/Unit/ServerStreamTest.php
new file mode 100644
index 000000000000..a792563d13af
--- /dev/null
+++ b/Gax/tests/Unit/ServerStreamTest.php
@@ -0,0 +1,236 @@
+assertSame($call, $stream->getServerStreamingCall());
+ $this->assertSame([], iterator_to_array($stream->readAll()));
+ }
+
+ public function testEmptyFailure()
+ {
+ $call = new MockServerStreamingCall([], null, new MockStatus(Code::INTERNAL, 'empty failure'));
+ $stream = new ServerStream($call);
+
+ $this->assertSame($call, $stream->getServerStreamingCall());
+
+ $this->expectException(ApiException::class);
+ $this->expectExceptionMessage('empty failure');
+
+ 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()));
+ }
+
+ 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());
+
+ $this->expectException(ApiException::class);
+ $this->expectExceptionMessage('strings failure');
+
+ $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()));
+ }
+
+ 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());
+
+ $this->expectException(ApiException::class);
+ $this->expectExceptionMessage('objects failure');
+
+ $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()));
+ }
+
+ 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());
+
+ $this->expectException(ApiException::class);
+ $this->expectExceptionMessage('resources failure');
+
+ $index = 0;
+ try {
+ foreach ($stream->readAll() as $response) {
+ $this->assertSame($response, $resources[$index]);
+ $index++;
+ }
+ } finally {
+ $this->assertSame(3, $index);
+ }
+ }
+
+ public function testReadCallsLogger()
+ {
+ $logger = $this->prophesize(StdOutLogger::class);
+
+ // Two Debugs expected, once per response
+ $logger->debug(Argument::cetera())
+ ->shouldBeCalledTimes(3);
+
+ $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, logger: $logger->reveal());
+
+ // Loop to read the responses
+ foreach ($stream->readAll() as $response) {
+ }
+ }
+}
diff --git a/Gax/tests/Unit/ServiceAddressTraitTest.php b/Gax/tests/Unit/ServiceAddressTraitTest.php
new file mode 100644
index 000000000000..ba5cf81c7a64
--- /dev/null
+++ b/Gax/tests/Unit/ServiceAddressTraitTest.php
@@ -0,0 +1,69 @@
+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
+ */
+ public function testNormalizeServiceAddressInvalid($serviceAddressString)
+ {
+ $this->expectException(ValidationException::class);
+ $this->expectExceptionMessage('Invalid apiEndpoint');
+
+ self::normalizeServiceAddress($serviceAddressString);
+ }
+
+ public function normalizeServiceAddressInvalidData()
+ {
+ return [
+ ['too.many:colons:123'],
+ ['too:many:colons'],
+ ];
+ }
+}
diff --git a/Gax/tests/Unit/TestTrait.php b/Gax/tests/Unit/TestTrait.php
new file mode 100644
index 000000000000..82132a51b2ec
--- /dev/null
+++ b/Gax/tests/Unit/TestTrait.php
@@ -0,0 +1,145 @@
+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 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($this->onConsecutiveCalls(...$sequence));
+
+ return $mockCall;
+ }
+
+ public function createOperationsClient($transport = null)
+ {
+ self::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.');
+ }
+ }
+
+ private static function autoloadTestdata(string $dir, string $namespace = __NAMESPACE__)
+ {
+ // This is required for tests to pass on Windows with PHP 8.1
+ // @TODO remove this once we drop PHP 8.1 support
+ if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' && PHP_VERSION_ID < 80200) {
+ self::markTestSkipped('Skip on Windows + PHP 8.1 to avoid gRPC shutdown crash');
+ }
+
+ // add mocks to autoloader
+ $loader = file_exists(__DIR__ . '/../../../vendor/autoload.php')
+ ? require __DIR__ . '/../../../vendor/autoload.php'
+ : require __DIR__ . '/../../vendor/autoload.php';
+
+ $loader->addPsr4($namespace . '\\', __DIR__ . '/testdata/' . $dir);
+ }
+}
diff --git a/Gax/tests/Unit/Transport/Grpc/ForwardingServerStreamCallTest.php b/Gax/tests/Unit/Transport/Grpc/ForwardingServerStreamCallTest.php
new file mode 100644
index 000000000000..6c4a0461b374
--- /dev/null
+++ b/Gax/tests/Unit/Transport/Grpc/ForwardingServerStreamCallTest.php
@@ -0,0 +1,63 @@
+prophesize(ServerStreamingCall::class);
+ $serverStreamingCall->getMetadata()->shouldBeCalledOnce();
+ $serverStreamingCall->getTrailingMetadata()->shouldBeCalledOnce();
+ $serverStreamingCall->getPeer()->shouldBeCalledOnce();
+ $serverStreamingCall->cancel()->shouldBeCalledOnce();
+ $serverStreamingCall->responses()->shouldBeCalledOnce();
+ $serverStreamingCall->getStatus()->shouldBeCalledOnce();
+
+ $forwardingCall = new ForwardingServerStreamingCall($serverStreamingCall->reveal());
+
+ $forwardingCall->getMetadata();
+ $forwardingCall->getTrailingMetadata();
+ $forwardingCall->getPeer();
+ $forwardingCall->cancel();
+ $forwardingCall->responses();
+ $forwardingCall->getStatus();
+ }
+}
diff --git a/Gax/tests/Unit/Transport/Grpc/ForwardingUnaryCallTest.php b/Gax/tests/Unit/Transport/Grpc/ForwardingUnaryCallTest.php
new file mode 100644
index 000000000000..3a7f4280886f
--- /dev/null
+++ b/Gax/tests/Unit/Transport/Grpc/ForwardingUnaryCallTest.php
@@ -0,0 +1,61 @@
+prophesize(UnaryCall::class);
+ $unaryCall->getMetadata()->shouldBeCalledOnce();
+ $unaryCall->getTrailingMetadata()->shouldBeCalledOnce();
+ $unaryCall->getPeer()->shouldBeCalledOnce();
+ $unaryCall->cancel()->shouldBeCalledOnce();
+ $unaryCall->wait()->shouldBeCalledOnce();
+
+ $forwardingCall = new ForwardingUnaryCall($unaryCall->reveal());
+
+ $forwardingCall->getMetadata();
+ $forwardingCall->getTrailingMetadata();
+ $forwardingCall->getPeer();
+ $forwardingCall->cancel();
+ $forwardingCall->wait();
+ }
+}
diff --git a/Gax/tests/Unit/Transport/GrpcFallbackTransportTest.php b/Gax/tests/Unit/Transport/GrpcFallbackTransportTest.php
new file mode 100644
index 000000000000..205f26ab5a7b
--- /dev/null
+++ b/Gax/tests/Unit/Transport/GrpcFallbackTransportTest.php
@@ -0,0 +1,241 @@
+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) use ($expectedResponse, $expectedRequest) {
+ $this->assertEquals($expectedRequest, $request);
+
+ return Create::promiseFor(
+ 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()],
+ ];
+ }
+
+ public function testStartUnaryCallThrowsException()
+ {
+ $httpHandler = function (RequestInterface $request, array $options = []) {
+ return Create::rejectionFor(new Exception());
+ };
+
+ $this->expectException(Exception::class);
+
+ $this->getTransport($httpHandler)
+ ->startUnaryCall($this->call, [])
+ ->wait();
+ }
+
+ public function testStartUnaryCallThrowsRequestException()
+ {
+ $httpHandler = function (RequestInterface $request, array $options = []) {
+ $status = new Status();
+ $status->setCode(Code::NOT_FOUND);
+ $status->setMessage('Ruh-roh');
+ return Create::rejectionFor(
+ RequestException::create(
+ new Request('POST', 'http://www.example.com'),
+ new Response(
+ 404,
+ [],
+ $status->serializeToString()
+ )
+ )
+ );
+ };
+
+ $this->expectException(Exception::class);
+
+ $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
+ * @param $apiEndpoint
+ * @param $args
+ */
+ public function testBuildInvalid($apiEndpoint, $args)
+ {
+ $this->expectException(ValidationException::class);
+
+ GrpcFallbackTransport::build($apiEndpoint, $args);
+ }
+
+ public function buildInvalidData()
+ {
+ return [
+ [
+ 'addresswithtoo:many:segments',
+ [],
+ ],
+ ];
+ }
+
+ public function testNonBinaryProtobufResponseException()
+ {
+ $httpHandler = function (RequestInterface $request, array $options = []) {
+ return Create::rejectionFor(
+ RequestException::create(
+ new Request('POST', 'http://www.example.com'),
+ new Response(
+ 404,
+ [],
+ 'This is an HTML response'
+ )
+ )
+ );
+ };
+
+ $this->expectException(ApiException::class);
+ $this->expectExceptionCode(5);
+ $this->expectExceptionMessage('This is an HTML response<\/body><\/html>');
+
+ $this->getTransport($httpHandler)
+ ->startUnaryCall($this->call, [])
+ ->wait();
+ }
+}
diff --git a/Gax/tests/Unit/Transport/GrpcTransportTest.php b/Gax/tests/Unit/Transport/GrpcTransportTest.php
new file mode 100644
index 000000000000..6eafcb667648
--- /dev/null
+++ b/Gax/tests/Unit/Transport/GrpcTransportTest.php
@@ -0,0 +1,667 @@
+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->prophesize(ClientStreamingCall::class);
+ $clientStreamingCall->wait()
+ ->shouldBeCalledOnce()
+ ->willReturn([$response, $status]);
+
+ $transport = new MockGrpcTransport($clientStreamingCall->reveal());
+
+ $stream = $transport->startClientStreamingCall(
+ new Call('method', null),
+ []
+ );
+
+ /* @var $stream \Google\ApiCore\ClientStream */
+ $actualResponse = $stream->writeAllAndReadResponse([]);
+ $this->assertEquals($response, $actualResponse);
+ }
+
+ public function testClientStreamingFailure()
+ {
+ $request = 'request';
+ $response = 'response';
+
+ $status = new stdClass();
+ $status->code = Code::INTERNAL;
+ $status->details = 'client streaming failure';
+
+ $clientStreamingCall = $this->prophesize(ClientStreamingCall::class);
+ $clientStreamingCall->wait()
+ ->shouldBeCalledOnce()
+ ->willReturn([$response, $status]);
+
+ $transport = new MockGrpcTransport($clientStreamingCall->reveal());
+
+ $stream = $transport->startClientStreamingCall(
+ new Call('takeAction', null),
+ []
+ );
+
+ $this->expectException(ApiException::class);
+ $this->expectExceptionMessage('client streaming failure');
+
+ $stream->readResponse();
+ }
+
+ public function testServerStreamingSuccess()
+ {
+ $response = 'response';
+
+ $status = new stdClass();
+ $status->code = Code::OK;
+
+ $message = $this->createMockRequest();
+
+ $serverStreamingCall = $this->prophesize(\Grpc\ServerStreamingCall::class);
+ $serverStreamingCall->responses()
+ ->shouldBeCalledOnce()
+ ->willReturn([$response]);
+ $serverStreamingCall->getStatus()
+ ->shouldBeCalledOnce()
+ ->willReturn($status);
+
+ $transport = new MockGrpcTransport($serverStreamingCall->reveal());
+
+ /* @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->prophesize(\Grpc\ServerStreamingCall::class);
+ $call->responses()
+ ->shouldBeCalledOnce()
+ ->willReturn([$response]);
+ $call->getStatus()
+ ->shouldBeCalledOnce()
+ ->willReturn($status);
+
+ $transport = new MockGrpcTransport($call->reveal());
+
+ $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);
+ }
+
+ public function testServerStreamingFailure()
+ {
+ $status = new stdClass();
+ $status->code = Code::INTERNAL;
+ $status->details = 'server streaming failure';
+
+ $message = $this->createMockRequest();
+
+ $serverStreamingCall = $this->prophesize(\Grpc\ServerStreamingCall::class);
+ $serverStreamingCall->responses()
+ ->shouldBeCalledOnce()
+ ->willReturn(['response1']);
+ $serverStreamingCall->getStatus()
+ ->shouldBeCalledOnce()
+ ->willReturn($status);
+
+ $transport = new MockGrpcTransport($serverStreamingCall->reveal());
+
+ /* @var $stream \Google\ApiCore\ServerStream */
+ $stream = $transport->startServerStreamingCall(
+ new Call('takeAction', null, $message),
+ []
+ );
+
+ $this->expectException(ApiException::class);
+ $this->expectExceptionMessage('server streaming failure');
+
+ 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->prophesize(\Grpc\BidiStreamingCall::class);
+ $bidiStreamingCall->read()
+ ->shouldBeCalled()
+ ->willReturn($response, null);
+ $bidiStreamingCall->getStatus()
+ ->shouldBeCalled()
+ ->willReturn($status);
+ $bidiStreamingCall->writesDone()
+ ->shouldBeCalledOnce();
+
+ $transport = new MockGrpcTransport($bidiStreamingCall->reveal());
+
+ /* @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 testBidiOpeningRequestLogsRpcName()
+ {
+ $status = new stdClass();
+ $status->code = Code::OK;
+ $rpcName = 'takeAction';
+
+ $bidiStreamingCall = $this->prophesize(\Grpc\BidiStreamingCall::class);
+
+ $transport = new MockGrpcTransport(
+ $bidiStreamingCall->reveal(),
+ logger: new StdOutLogger()
+ );
+
+ /* @var \Google\ApiCore\BidiStream $stream*/
+ $stream = $transport->startBidiStreamingCall(
+ new Call($rpcName, null),
+ ['headers' => [
+ ['thisis' => 'a header']
+ ]]
+ );
+
+ $buffer = $this->getActualOutput();
+ $unserializedBuffer = json_decode($buffer, true);
+
+ $this->assertNotEmpty($unserializedBuffer);
+ $this->assertNotEmpty($unserializedBuffer['rpcName']);
+ $this->assertEquals($rpcName, $unserializedBuffer['rpcName']);
+ }
+
+ public function testBidiStreamingSuccessObject()
+ {
+ $response = new Status();
+ $response->setCode(Code::OK);
+ $response->setMessage('response');
+
+ $status = new stdClass();
+ $status->code = Code::OK;
+
+ $bidiStreamingCall = $this->prophesize(\Grpc\BidiStreamingCall::class);
+ $bidiStreamingCall->read()
+ ->shouldBeCalled()
+ ->willReturn($response, null);
+ $bidiStreamingCall->getStatus()
+ ->shouldBeCalled()
+ ->willReturn($status);
+ $bidiStreamingCall->writesDone()
+ ->shouldBeCalledOnce();
+
+ $transport = new MockGrpcTransport($bidiStreamingCall->reveal());
+
+ /* @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->prophesize(\Grpc\BidiStreamingCall::class);
+ $bidiStreamingCall->read()
+ ->shouldBeCalled()
+ ->willReturn($response, null);
+ $bidiStreamingCall->getStatus()
+ ->shouldBeCalled()
+ ->willReturn($status);
+ $bidiStreamingCall->writesDone()
+ ->shouldBeCalledOnce();
+
+ $transport = new MockGrpcTransport($bidiStreamingCall->reveal());
+
+ $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);
+ }
+
+ public function testBidiStreamingFailure()
+ {
+ $response = 'response';
+ $status = new stdClass();
+ $status->code = Code::INTERNAL;
+ $status->details = 'bidi failure';
+
+ $bidiStreamingCall = $this->prophesize(\Grpc\BidiStreamingCall::class);
+ $bidiStreamingCall->read()
+ ->shouldBeCalled()
+ ->willReturn($response, null);
+ $bidiStreamingCall->getStatus()
+ ->shouldBeCalled()
+ ->willReturn($status);
+ $bidiStreamingCall->writesDone()
+ ->shouldBeCalledOnce();
+
+ $transport = new MockGrpcTransport($bidiStreamingCall->reveal());
+
+ /* @var $stream \Google\ApiCore\BidiStream */
+ $stream = $transport->startBidiStreamingCall(
+ new Call('takeAction', null),
+ []
+ );
+
+ $this->expectException(ApiException::class);
+ $this->expectExceptionMessage('bidi failure');
+
+ 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()->shouldBeCalledOnce();
+ $call->getDecodeType()->shouldBeCalledOnce();
+
+ $credentialsWrapper = $this->prophesize(CredentialsWrapper::class);
+ $credentialsWrapper->checkUniverseDomain()
+ ->shouldBeCalledOnce();
+ $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);
+ }
+
+ public function testClientCertSourceOptionValid()
+ {
+ $mockClientCertSource = function () {
+ return ['MOCK_KEY', 'MOCK_CERT'];
+ };
+ $transport = GrpcTransport::build(
+ 'address.com:123',
+ ['clientCertSource' => $mockClientCertSource]
+ );
+
+ $this->assertNotNull($transport);
+ }
+
+ public function testClientCertSourceOptionInvalid()
+ {
+ $mockClientCertSource = 'foo';
+
+ $this->expectException(TypeError::class);
+ $this->expectExceptionMessageMatches('/must be.+callable/i');
+
+ GrpcTransport::build(
+ 'address.com:123',
+ ['clientCertSource' => $mockClientCertSource]
+ );
+ }
+
+ /**
+ * @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
+ */
+ public function testBuildInvalid($apiEndpoint, $args)
+ {
+ $this->expectException(ValidationException::class);
+
+ GrpcTransport::build($apiEndpoint, $args);
+ }
+
+ public function buildInvalidData()
+ {
+ return [
+ [
+ 'addresswithtoo:many:segments',
+ [],
+ ],
+ [
+ 'example.com',
+ [
+ 'channel' => 'not a channel',
+ ]
+ ]
+ ];
+ }
+
+ /**
+ * @dataProvider interceptorDataProvider
+ */
+ public function testExperimentalInterceptors($callType, $interceptor)
+ {
+ $mockCallInvoker = new class($this->buildMockCallForInterceptor($callType)) {
+ private $called = false;
+ private $mockCall;
+
+ public function __construct($mockCall)
+ {
+ $this->mockCall = $mockCall;
+ }
+
+ public function createChannelFactory($hostname, $opts)
+ {
+ // no-op
+ }
+
+ public function UnaryCall($channel, $method, $deserialize, $options)
+ {
+ $this->called = true;
+ return $this->mockCall;
+ }
+
+ public function ServerStreamingCall($channel, $method, $deserialize, $options)
+ {
+ $this->called = true;
+ return $this->mockCall;
+ }
+
+ public function ClientStreamingCall($channel, $method, $deserialize, $options)
+ {
+ // no-op
+ }
+
+ public function BidiStreamingCall($channel, $method, $deserialize, $options)
+ {
+ // no-op
+ }
+
+ public function wasCalled()
+ {
+ return $this->called;
+ }
+ };
+
+ $transport = new GrpcTransport(
+ 'example.com',
+ [
+ 'credentials' => ChannelCredentials::createInsecure()
+ ],
+ null,
+ [$interceptor]
+ );
+
+ $r = new \ReflectionProperty(BaseStub::class, 'call_invoker');
+ $r->setValue(
+ $transport,
+ $mockCallInvoker
+ );
+
+ $call = new Call('method1', '', new MockRequest());
+
+ $callMethod = $callType == UnaryCall::class ? 'startUnaryCall' : 'startServerStreamingCall';
+ $transport->$callMethod($call, [
+ 'transportOptions' => [
+ 'grpcOptions' => [
+ 'call-option' => 'call-option-value'
+ ]
+ ]
+ ]);
+
+ $this->assertTrue($mockCallInvoker->wasCalled());
+ }
+
+ public function interceptorDataProvider()
+ {
+ $this->autoloadTestdata('mocks', __NAMESPACE__);
+
+ $deprecatedInterceptors = (new \ReflectionClass(Interceptor::class))
+ ->getMethod('interceptUnaryUnary')
+ ->getParameters()[3]
+ ->getName() === 'metadata';
+
+ $interceptor = $deprecatedInterceptors ? new DeprecatedTestInterceptor(): new TestInterceptor();
+ $unaryInterceptor = $deprecatedInterceptors ? new DeprecatedTestUnaryInterceptor(): new TestUnaryInterceptor();
+
+ return [
+ [
+ UnaryCall::class,
+ $unaryInterceptor
+ ],
+ [
+ UnaryCall::class,
+ $interceptor
+ ],
+ [
+ ServerStreamingCall::class,
+ $interceptor
+ ]
+ ];
+ }
+
+ private function buildMockCallForInterceptor($callType)
+ {
+ $mockCall = $this->prophesize($callType);
+ $mockCall->start(
+ Argument::type(Message::class),
+ [],
+ [
+ 'call-option' => 'call-option-value',
+ 'test-interceptor-insert' => 'inserted-value'
+ ]
+ )->shouldBeCalled();
+
+ if ($callType === UnaryCall::class) {
+ $mockCall->wait()
+ ->willReturn([
+ null,
+ Code::OK
+ ]);
+ }
+
+ return $mockCall->reveal();
+ }
+}
diff --git a/Gax/tests/Unit/Transport/Rest/JsonStreamDecoderTest.php b/Gax/tests/Unit/Transport/Rest/JsonStreamDecoderTest.php
new file mode 100644
index 000000000000..04a7619408ac
--- /dev/null
+++ b/Gax/tests/Unit/Transport/Rest/JsonStreamDecoderTest.php
@@ -0,0 +1,221 @@
+ $readChunkSizeBytes]);
+ $num = 0;
+ foreach ($decoder->decode() as $op) {
+ $this->assertEquals($responses[$num], $op);
+ $num++;
+ }
+ $this->assertEquals(count($responses), $num);
+ }
+
+ public function buildResponseStreams()
+ {
+ $any = new Any();
+ $any->pack(new Operation([
+ 'name' => 'any_metadata',
+ ]));
+ $operations = [
+ new Operation([
+ 'name' => 'foo',
+ 'done' => true,
+ 'metadata' => $any,
+ ]),
+ new Operation([
+ 'name' => 'bar',
+ 'done' => true,
+ 'error' => new Status([
+ 'code' => 1,
+ 'message' => "This contains an \"escaped string\" and\n'single quotes' on a new \line",
+ ]),
+ ]),
+ new Operation([
+ 'name' => 'foo',
+ 'done' => true,
+ 'error' => new Status([
+ 'code' => 1,
+ 'message' => 'This contains \\escaped slashes\\',
+ ]),
+ ]),
+ new Operation([
+ 'name' => 'foo',
+ 'done' => true,
+ 'error' => new Status([
+ 'code' => 1,
+ 'message' => 'This contains [brackets]',
+ ]),
+ ]),
+ new Operation([
+ 'name' => 'foo',
+ 'done' => true,
+ 'error' => new Status([
+ 'code' => 1,
+ 'message' => 'This contains {braces}',
+ ]),
+ ]),
+ new Operation([
+ 'name' => 'foo',
+ 'done' => true,
+ 'error' => new Status([
+ 'code' => 1,
+ 'message' => "This contains everything \\\"{['\'",
+ ]),
+ ]),
+ ];
+
+ $stream = function ($data) {
+ return $this->messagesToStream($data);
+ };
+ return [
+ [$operations, Operation::class, $stream($operations), /*readChunkSizeBytes*/ 10],
+ [$operations, Operation::class, $stream($operations), /*readChunkSizeBytes*/ 1024],
+ [$operations, Operation::class, $stream($operations), /*readChunkSizeBytes*/ 1]
+ ];
+ }
+
+ private function messagesToStream(array $messages)
+ {
+ $data = [];
+ foreach ($messages as $message) {
+ $data[] = $message->serializeToJsonString();
+ }
+ return Psr7\Utils::streamFor('[' . implode(',', $data) . ']');
+ }
+
+ /**
+ * @dataProvider buildAlternateStreams
+ */
+ public function testJsonStreamDecoderAlternate(array $responses, $decodeType, $stream)
+ {
+ $decoder = new JsonStreamDecoder($stream, $decodeType);
+ $num = 0;
+ foreach ($decoder->decode() as $op) {
+ $this->assertEquals($responses[$num], $op);
+ $num++;
+ }
+ $this->assertEquals(count($responses), $num);
+ }
+
+ public function buildAlternateStreams()
+ {
+ $res1 = new MockResponse(['name' => 'foo']);
+ $res1Str = $res1->serializeToJsonString();
+ $res2 = new MockResponse(['name' => 'bar']);
+ $res2Str = $res2->serializeToJsonString();
+ $responses = [$res1, $res2];
+
+ $newlines = "[\n\n\n" . $res1Str . "\n\n\n,\n\n\n" . $res2Str . "\n\n\n]";
+ $commas = '[' . $res1Str . ",\n,\n,\n," . $res2Str . ']';
+ $blankspace = '[' . $res1Str . ', ' . $res2Str . ']';
+
+ return [
+ [$responses, MockResponse::class, $this->initBufferStream($newlines)],
+ [$responses, MockResponse::class, $this->initBufferStream($commas)],
+ [$responses, MockResponse::class, $this->initBufferStream($blankspace)]
+ ];
+ }
+
+ /**
+ * @dataProvider buildBadPayloads
+ */
+ public function testJsonStreamDecoderBadClose($payload)
+ {
+ $stream = $this->initBufferStream($payload);
+ $decoder = new JsonStreamDecoder($stream, Operation::class, ['readChunkSizeBytes' => 10]);
+
+ $this->expectException(RuntimeException::class);
+
+ try {
+ // Just iterating the stream will throw the exception
+ foreach ($decoder->decode() as $op) {
+ }
+ } finally {
+ $stream->close();
+ }
+ }
+
+ public function buildBadPayloads()
+ {
+ return
+ [
+ ['[{"name": "foo"},{'],
+ ['[{"name": "foo"},'],
+ ['[{"name": "foo"},{"name":'],
+ ['[{"name": "foo"},{]'],
+ ];
+ }
+
+ public function testJsonStreamDecoderClosed()
+ {
+ $stream = new BufferStream();
+ $stream->write('[{"name": "foo"},{');
+ $decoder = new JsonStreamDecoder($stream, Operation::class, ['readChunkSizeBytes' => 10]);
+ $count = 0;
+ foreach ($decoder->decode() as $op) {
+ $this->assertEquals('foo', $op->getName());
+ $count++;
+ $decoder->close();
+ }
+
+ $this->assertEquals(1, $count);
+ }
+
+ private function initBufferStream($data)
+ {
+ $stream = new BufferStream();
+ $stream->write($data);
+ return $stream;
+ }
+}
diff --git a/Gax/tests/Unit/Transport/RestTransportTest.php b/Gax/tests/Unit/Transport/RestTransportTest.php
new file mode 100644
index 000000000000..b4daa40c6713
--- /dev/null
+++ b/Gax/tests/Unit/Transport/RestTransportTest.php
@@ -0,0 +1,588 @@
+call = new Call(
+ 'Testing123',
+ MockResponse::class,
+ new MockRequest()
+ );
+ }
+
+ private function getTransport(?callable $httpHandler = null, $apiEndpoint = 'http://www.example.com')
+ {
+ $request = new Request('POST', $apiEndpoint);
+ $requestBuilder = $this->prophesize(RequestBuilder::class);
+ $requestBuilder->build(Argument::cetera())
+ ->willReturn($request);
+ $requestBuilder->pathExists(Argument::type('string'))
+ ->willReturn(true);
+
+ return new RestTransport(
+ $requestBuilder->reveal(),
+ $httpHandler ?: HttpHandlerFactory::build()
+ );
+ }
+
+ /**
+ * @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 Create::promiseFor(
+ 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'],
+ ];
+ }
+
+ public function testStartUnaryCallThrowsException()
+ {
+ $httpHandler = function (RequestInterface $request, array $options = []) {
+ return Create::rejectionFor(new Exception());
+ };
+
+ $this->expectException(Exception::class);
+
+ $this->getTransport($httpHandler)
+ ->startUnaryCall($this->call, [])
+ ->wait();
+ }
+
+ /**
+ * @runInSeparateProcess
+ */
+ public function testStartUnaryCallWithValidProtoNotLoadedInDescPool()
+ {
+ $endpoint = 'www.example.com';
+ $expectedRequest = new Request(
+ 'POST',
+ $endpoint,
+ [],
+ ''
+ );
+ $body = [
+ 'name' => 'projects/my-project/locations/us-central1/operations/my-operation',
+ 'metadata' => [
+ // This type is arbitrarily chosen and should not exist within the descriptor pool
+ // upon instantation of this test.
+ '@type' => 'type.googleapis.com/google.type.DateTime'
+ ]
+ ];
+ $httpHandler = function (RequestInterface $request) use ($body, $expectedRequest) {
+ $this->assertEquals($expectedRequest, $request);
+ return Create::promiseFor(
+ new Response(
+ 200,
+ [],
+ json_encode($body)
+ )
+ );
+ };
+ $call = new Call(
+ 'Testing123',
+ Operation::class,
+ new MockRequest()
+ );
+
+ $response = $this->getTransport($httpHandler, $endpoint)
+ ->startUnaryCall($call, [
+ 'metadataReturnType' => DateTime::class
+ ])
+ ->wait();
+
+ $this->assertInstanceOf(Operation::class, $response);
+ $this->assertEquals(
+ $body['metadata']['@type'],
+ $response->getMetadata()->getTypeUrl()
+ );
+ }
+
+ /**
+ * @runInSeparateProcess
+ */
+ public function testStartUnaryCallWithValidProtoNotLoadedInDescPoolThrowsExWithoutMetadataType()
+ {
+ $endpoint = 'www.example.com';
+ $expectedRequest = new Request(
+ 'POST',
+ $endpoint,
+ [],
+ ''
+ );
+ $body = [
+ 'name' => 'projects/my-project/locations/us-central1/operations/my-operation',
+ 'metadata' => [
+ // This type is arbitrarily chosen and should not exist within the descriptor pool
+ // upon instantation of this test.
+ '@type' => 'type.googleapis.com/google.type.DateTime'
+ ]
+ ];
+ $httpHandler = function (RequestInterface $request) use ($body, $expectedRequest) {
+ $this->assertEquals($expectedRequest, $request);
+ return Create::promiseFor(
+ new Response(
+ 200,
+ [],
+ json_encode($body)
+ )
+ );
+ };
+ $call = new Call(
+ 'Testing123',
+ Operation::class,
+ new MockRequest()
+ );
+ $this->expectException(\Exception::class);
+ $this->expectExceptionMessageMatches('/^Error occurred during parsing:/');
+ $this->getTransport($httpHandler, $endpoint)
+ ->startUnaryCall($call, [])
+ ->wait();
+ }
+
+ public function testServerStreamingCallThrowsBadMethodCallException()
+ {
+ $request = new Request('POST', 'http://www.example.com');
+ $requestBuilder = $this->prophesize(RequestBuilder::class);
+ $requestBuilder->pathExists(Argument::type('string'))
+ ->willReturn(false);
+
+ $transport = new RestTransport($requestBuilder->reveal(), HttpHandlerFactory::build());
+
+ $this->expectException(BadMethodCallException::class);
+ $transport->startServerStreamingCall($this->call, []);
+ }
+
+ public function testStartUnaryCallThrowsRequestException()
+ {
+ $httpHandler = function (RequestInterface $request, array $options = []) {
+ return Create::rejectionFor(
+ RequestException::create(
+ new Request('POST', 'http://www.example.com'),
+ new Response(
+ 404,
+ [],
+ json_encode([
+ 'error' => [
+ 'status' => 'NOT_FOUND',
+ 'message' => 'Ruh-roh.'
+ ]
+ ])
+ )
+ )
+ );
+ };
+
+ $this->expectException(ApiException::class);
+
+ $this->getTransport($httpHandler)
+ ->startUnaryCall($this->call, [])
+ ->wait();
+ }
+ /**
+ * @dataProvider buildServerStreamMessages
+ */
+ public function testStartServerStreamingCall($messages)
+ {
+ $apiEndpoint = 'www.example.com';
+ $expectedRequest = new Request(
+ 'POST',
+ $apiEndpoint,
+ [],
+ ''
+ );
+
+ $httpHandler = function (RequestInterface $request, array $options = []) use ($messages, $expectedRequest) {
+ $this->assertEquals($expectedRequest, $request);
+ return Create::promiseFor(
+ new Response(
+ 200,
+ [],
+ $this->encodeMessages($messages)
+ )
+ );
+ };
+
+ $stream = $this->getTransport($httpHandler, $apiEndpoint)
+ ->startServerStreamingCall($this->call, []);
+
+ $num = 0;
+ foreach ($stream->readAll() as $m) {
+ $this->assertEquals($messages[$num], $m);
+ $num++;
+ }
+ $this->assertEquals(count($messages), $num);
+ }
+
+ /**
+ * @dataProvider buildServerStreamMessages
+ */
+ public function testCancelServerStreamingCall($messages)
+ {
+ $apiEndpoint = 'www.example.com';
+ $expectedRequest = new Request(
+ 'POST',
+ $apiEndpoint,
+ [],
+ ''
+ );
+
+ $httpHandler = function (RequestInterface $request, array $options = []) use ($messages, $expectedRequest) {
+ $this->assertEquals($expectedRequest, $request);
+ return Create::promiseFor(
+ new Response(
+ 200,
+ [],
+ $this->encodeMessages($messages)
+ )
+ );
+ };
+
+ $stream = $this->getTransport($httpHandler, $apiEndpoint)
+ ->startServerStreamingCall($this->call, []);
+
+ $num = 0;
+ foreach ($stream->readAll() as $m) {
+ $this->assertEquals($messages[$num], $m);
+ $num++;
+
+ // Intentionally cancel the stream mid way through processing.
+ $stream->getServerStreamingCall()->cancel();
+ }
+
+ // Ensure only one message was ever yielded.
+ $this->assertEquals(1, $num);
+ }
+
+ private function encodeMessages(array $messages)
+ {
+ $data = [];
+ foreach ($messages as $message) {
+ $data[] = $message->serializeToJsonString();
+ }
+ return '[' . implode(',', $data) . ']';
+ }
+
+ public function buildServerStreamMessages()
+ {
+ return [
+ [
+ [
+ new MockResponse([
+ 'name' => 'foo',
+ 'number' => 1,
+ ]),
+ new MockResponse([
+ 'name' => 'bar',
+ 'number' => 2,
+ ]),
+ new MockResponse([
+ 'name' => 'baz',
+ 'number' => 3,
+ ]),
+ ]
+ ]
+ ];
+ }
+
+ public function testStartServerStreamingCallThrowsRequestException()
+ {
+ $apiEndpoint = 'http://www.example.com';
+ $errorInfo = new Any();
+ $errorInfo->pack(new ErrorInfo(['domain' => 'googleapis.com']));
+ $httpHandler = function (RequestInterface $request, array $options = []) use ($apiEndpoint, $errorInfo) {
+ return Create::rejectionFor(
+ RequestException::create(
+ new Request('POST', $apiEndpoint),
+ new Response(
+ 404,
+ [],
+ json_encode([[
+ 'error' => [
+ 'status' => 'NOT_FOUND',
+ 'message' => 'Ruh-roh.',
+ 'details' => [$errorInfo]
+ ]
+ ]])
+ )
+ )
+ );
+ };
+
+ $this->expectException(ApiException::class);
+ $this->expectExceptionCode(5);
+ $this->expectExceptionMessage('Ruh-roh');
+
+ $this->getTransport($httpHandler, $apiEndpoint)
+ ->startServerStreamingCall($this->call, []);
+ }
+
+ /**
+ * @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/resources/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),
+ ],
+ ];
+ }
+
+ public function testClientCertSourceOptionValid()
+ {
+ $mockClientCertSource = function () {
+ return 'MOCK_CERT_SOURCE';
+ };
+ $transport = RestTransport::build(
+ 'address.com:123',
+ __DIR__ . '/../testdata/resources/test_service_rest_client_config.php',
+ ['clientCertSource' => $mockClientCertSource]
+ );
+
+ $reflectionClass = new \ReflectionClass($transport);
+ $reflectionProp = $reflectionClass->getProperty('clientCertSource');
+ $actualClientCertSource = $reflectionProp->getValue($transport);
+
+ $this->assertEquals($mockClientCertSource, $actualClientCertSource);
+ }
+
+ public function testClientCertSourceOptionInvalid()
+ {
+ $mockClientCertSource = 'foo';
+
+ $this->expectException(TypeError::class);
+ $this->expectExceptionMessageMatches('/must be.+callable/i');
+
+ RestTransport::build(
+ 'address.com:123',
+ __DIR__ . '/../testdata/resources/test_service_rest_client_config.php',
+ ['clientCertSource' => $mockClientCertSource]
+ );
+ }
+
+ /**
+ * @dataProvider buildInvalidData
+ */
+ public function testBuildInvalid($apiEndpoint, $restConfigPath, $args)
+ {
+ $this->expectException(ValidationException::class);
+
+ RestTransport::build($apiEndpoint, $restConfigPath, $args);
+ }
+
+ public function buildInvalidData()
+ {
+ $restConfigPath = __DIR__ . '/../testdata/resources/test_service_rest_client_config.php';
+ return [
+ [
+ 'addresswithtoo:many:segments',
+ $restConfigPath,
+ [],
+ ],
+ [
+ 'address.com',
+ 'badpath',
+ [],
+ ],
+ ];
+ }
+
+ public function testNonJsonResponseException()
+ {
+ $httpHandler = function (RequestInterface $request, array $options = []) {
+ return Create::rejectionFor(
+ RequestException::create(
+ new Request('POST', 'http://www.example.com'),
+ new Response(
+ 404,
+ [],
+ 'This is an HTML response'
+ )
+ )
+ );
+ };
+
+ $this->expectException(ApiException::class);
+ $this->expectExceptionCode(5);
+ $this->expectExceptionMessage('This is an HTML response<\/body><\/html>');
+
+ $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 Create::promiseFor(new Response(200, [], '{}'));
+ };
+
+ $this->getTransport($httpHandler)
+ ->startUnaryCall($this->call, $options)
+ ->wait();
+ }
+
+ public function testNonArrayHeadersThrowsException()
+ {
+ $options = [
+ 'headers' => 'not-an-array',
+ ];
+
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('The "headers" option must be an array');
+
+ $this->getTransport()
+ ->startUnaryCall($this->call, $options);
+ }
+
+ public function testNonArrayAuthorizationHeaderThrowsException()
+ {
+ $credentialsWrapper = $this->prophesize(CredentialsWrapper::class);
+ $credentialsWrapper->getAuthorizationHeaderCallback(null)
+ ->shouldBeCalledOnce()
+ ->willReturn(function () {
+ return '';
+ });
+
+ $options = [
+ 'credentialsWrapper' => $credentialsWrapper->reveal(),
+ ];
+
+ $this->expectException(UnexpectedValueException::class);
+ $this->expectExceptionMessage('Expected array response from authorization header callback');
+
+ $this->getTransport()
+ ->startUnaryCall($this->call, $options);
+ }
+}
diff --git a/Gax/tests/Unit/UriTraitTest.php b/Gax/tests/Unit/UriTraitTest.php
new file mode 100644
index 000000000000..3b494c99b7b0
--- /dev/null
+++ b/Gax/tests/Unit/UriTraitTest.php
@@ -0,0 +1,67 @@
+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']]
+ ];
+ }
+}
diff --git a/Gax/tests/Unit/ValidationTraitTest.php b/Gax/tests/Unit/ValidationTraitTest.php
new file mode 100644
index 000000000000..869316a3db99
--- /dev/null
+++ b/Gax/tests/Unit/ValidationTraitTest.php
@@ -0,0 +1,83 @@
+stub = new class () {
+ use ValidationTrait;
+ };
+ }
+
+ public function testValidateMissingRequiredKey()
+ {
+ $input = [
+ 'foo' => 1,
+ 'bar' => 2
+ ];
+
+ $this->expectException(ValidationException::class);
+ $this->expectExceptionMessage('Missing required argument');
+
+ $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);
+ }
+
+ public function testValidateNotNullWithNullRequiredKey()
+ {
+ $input = [
+ 'foo' => 1,
+ 'bar' => null
+ ];
+
+ $this->expectException(ValidationException::class);
+ $this->expectExceptionMessage('Missing required argument');
+ $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);
+ }
+}
diff --git a/Gax/tests/Unit/VersionTest.php b/Gax/tests/Unit/VersionTest.php
new file mode 100644
index 000000000000..f68c7304f61c
--- /dev/null
+++ b/Gax/tests/Unit/VersionTest.php
@@ -0,0 +1,45 @@
+assertStringMatchesFormat('%d.%d.%d', $actualVersion);
+ }
+}
diff --git a/Gax/tests/Unit/testdata/creds/json-key-file.json b/Gax/tests/Unit/testdata/creds/json-key-file.json
new file mode 100644
index 000000000000..f8ddc215b1ab
--- /dev/null
+++ b/Gax/tests/Unit/testdata/creds/json-key-file.json
@@ -0,0 +1,7 @@
+{
+ "type": "authorized_user",
+ "client_id": "example@example.com",
+ "client_secret": "example",
+ "refresh_token": "abc",
+ "project_id": "example_project"
+}
\ No newline at end of file
diff --git a/Gax/tests/Unit/testdata/creds/mtls/.secureConnect/context_aware_metadata.json b/Gax/tests/Unit/testdata/creds/mtls/.secureConnect/context_aware_metadata.json
new file mode 100644
index 000000000000..43e3b48ea125
--- /dev/null
+++ b/Gax/tests/Unit/testdata/creds/mtls/.secureConnect/context_aware_metadata.json
@@ -0,0 +1 @@
+{"cert_provider_command":["echo","foo"]}
\ No newline at end of file
diff --git a/Gax/tests/Unit/testdata/generated/MyMessage.php b/Gax/tests/Unit/testdata/generated/MyMessage.php
new file mode 100644
index 000000000000..80550bae14d0
--- /dev/null
+++ b/Gax/tests/Unit/testdata/generated/MyMessage.php
@@ -0,0 +1,27 @@
+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);
+ }
+
+}
diff --git a/Gax/tests/Unit/testdata/generated/metadata/Example.php b/Gax/tests/Unit/testdata/generated/metadata/Example.php
new file mode 100644
index 000000000000..d958e2b528a2
--- /dev/null
+++ b/Gax/tests/Unit/testdata/generated/metadata/Example.php
@@ -0,0 +1,28 @@
+internalAddGeneratedFile(hex2bin(
+ "0a85010a0d6578616d706c652e70726f746f1219676f6f676c652e617069" .
+ "636f72652e74657374732e756e6974220b0a094d794d6573736167654244" .
+ "ca0219476f6f676c655c417069436f72655c54657374735c556e6974e202" .
+ "254750424d657461646174615c476f6f676c655c417069436f72655c5465" .
+ "7374735c556e6974620670726f746f33"
+ ));
+
+ static::$is_initialized = true;
+ }
+}
+
diff --git a/Gax/tests/Unit/testdata/mocks/CustomOperation/CancelOperationRequest.php b/Gax/tests/Unit/testdata/mocks/CustomOperation/CancelOperationRequest.php
new file mode 100644
index 000000000000..f0b168fae397
--- /dev/null
+++ b/Gax/tests/Unit/testdata/mocks/CustomOperation/CancelOperationRequest.php
@@ -0,0 +1,20 @@
+name = $name;
+ $request->arg2 = $arg2;
+ $request->arg3 = $arg3;
+
+ return $request;
+ }
+}
diff --git a/Gax/tests/Unit/testdata/mocks/CustomOperation/Client/NewSurfaceCustomOperationClient.php b/Gax/tests/Unit/testdata/mocks/CustomOperation/Client/NewSurfaceCustomOperationClient.php
new file mode 100644
index 000000000000..8128fc8408d4
--- /dev/null
+++ b/Gax/tests/Unit/testdata/mocks/CustomOperation/Client/NewSurfaceCustomOperationClient.php
@@ -0,0 +1,23 @@
+name = $name;
+ $request->arg2 = $arg2;
+ $request->arg3 = $arg3;
+
+ return $request;
+ }
+}
diff --git a/Gax/tests/Unit/testdata/mocks/CustomOperation/GetOperationRequest.php b/Gax/tests/Unit/testdata/mocks/CustomOperation/GetOperationRequest.php
new file mode 100644
index 000000000000..7e2783ccb015
--- /dev/null
+++ b/Gax/tests/Unit/testdata/mocks/CustomOperation/GetOperationRequest.php
@@ -0,0 +1,20 @@
+name = $name;
+ $request->arg2 = $arg2;
+ $request->arg3 = $arg3;
+
+ return $request;
+ }
+}
diff --git a/Gax/tests/Unit/testdata/mocks/DeprecatedTestInterceptor.php b/Gax/tests/Unit/testdata/mocks/DeprecatedTestInterceptor.php
new file mode 100644
index 000000000000..cc625ae4477f
--- /dev/null
+++ b/Gax/tests/Unit/testdata/mocks/DeprecatedTestInterceptor.php
@@ -0,0 +1,32 @@
+ true,
+ 'interfaces' => [
+ 'test.interface.v1.api' => [
+ 'MethodWithNumericEnumsQueryParam' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1/fixedurl',
+ 'queryParams' => [
+ 'name',
+ 'number'
+ ]
+ ]
+ ],
+ ],
+];
diff --git a/Gax/tests/Unit/testdata/resources/test_service_client_config.json b/Gax/tests/Unit/testdata/resources/test_service_client_config.json
new file mode 100644
index 000000000000..012e0d1d367d
--- /dev/null
+++ b/Gax/tests/Unit/testdata/resources/test_service_client_config.json
@@ -0,0 +1,39 @@
+{
+ "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,
+ "max_retry_delay_millis": 1000,
+ "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"
+ },
+ "TimeoutOnlyMethod": {
+ "timeout_millis": 40000
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gax/tests/Unit/testdata/resources/test_service_descriptor_config.php b/Gax/tests/Unit/testdata/resources/test_service_descriptor_config.php
new file mode 100644
index 000000000000..9b89ba7c4a1a
--- /dev/null
+++ b/Gax/tests/Unit/testdata/resources/test_service_descriptor_config.php
@@ -0,0 +1,26 @@
+ [
+ 'test.interface.v1.api' => [
+ 'PageStreamingMethod' => [
+ 'pageStreaming' => [
+ 'requestPageTokenGetMethod' => 'getPageToken',
+ 'requestPageTokenSetMethod' => 'setPageToken',
+ 'requestPageSizeGetMethod' => 'getPageSize',
+ 'requestPageSizeSetMethod' => 'setPageSize',
+ 'responsePageTokenGetMethod' => 'getNextPageToken',
+ ],
+ 'autoPopulatedFields' => [
+ 'pageToken' => \Google\Api\FieldInfo\Format::UUID4,
+ ]
+ ],
+ 'templateMap' => [
+ 'project' => 'projects/{project}',
+ 'location' => 'projects/{project}/locations/{location}',
+ 'archive' => 'archives/{archive}',
+ 'book' => 'archives/{archive}/books/{book}',
+ ],
+ ],
+ ],
+];
diff --git a/Gax/tests/Unit/testdata/resources/test_service_grpc_config.json b/Gax/tests/Unit/testdata/resources/test_service_grpc_config.json
new file mode 100644
index 000000000000..0e46c53967eb
--- /dev/null
+++ b/Gax/tests/Unit/testdata/resources/test_service_grpc_config.json
@@ -0,0 +1,17 @@
+{
+ "channelPool": {
+ "maxSize": 10,
+ "maxConcurrentStreamsLowWatermark": 1
+ },
+ "method": [
+ {
+ "name": [
+ "/google.test.v1.Test/TestMethod"
+ ],
+ "affinity": {
+ "command": "BIND",
+ "affinityKey": "key"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Gax/tests/Unit/testdata/resources/test_service_invalid_client_config.json b/Gax/tests/Unit/testdata/resources/test_service_invalid_client_config.json
new file mode 100644
index 000000000000..77e6d8a51f1c
--- /dev/null
+++ b/Gax/tests/Unit/testdata/resources/test_service_invalid_client_config.json
@@ -0,0 +1,35 @@
+{
+ "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"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gax/tests/Unit/testdata/resources/test_service_rest_client_config.php b/Gax/tests/Unit/testdata/resources/test_service_rest_client_config.php
new file mode 100644
index 000000000000..6c19fc1941f5
--- /dev/null
+++ b/Gax/tests/Unit/testdata/resources/test_service_rest_client_config.php
@@ -0,0 +1,164 @@
+ [
+ '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',
+ ],
+ 'MethodWithRequiredQueryParameters' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1/fixedurl',
+ 'queryParams' => [
+ 'name',
+ 'number'
+ ]
+ ],
+ 'MethodWithRequiredNestedQueryParameters' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1/fixedurl',
+ 'queryParams' => [
+ 'nested_message'
+ ]
+ ],
+ 'MethodWithRequiredTimestampQueryParameters' => [
+ 'method' => 'get',
+ 'uriTemplate' => '/v1/fixedurl',
+ 'queryParams' => [
+ 'timestamp_value'
+ ]
+ ]
+ ],
+ ],
+];
diff --git a/Gax/tests/bootstrap.php b/Gax/tests/bootstrap.php
new file mode 100644
index 000000000000..b1110074e105
--- /dev/null
+++ b/Gax/tests/bootstrap.php
@@ -0,0 +1,46 @@
+register(new MessageAwareArrayComparator());
+\SebastianBergmann\Comparator\Factory::getInstance()->register(new ProtobufMessageComparator());
+\SebastianBergmann\Comparator\Factory::getInstance()->register(new ProtobufGPBEmptyComparator());
diff --git a/Spanner/tests/Snippet/NumericTest.php b/Spanner/tests/Snippet/NumericTest.php
index 921d92143080..01883f2232f6 100644
--- a/Spanner/tests/Snippet/NumericTest.php
+++ b/Spanner/tests/Snippet/NumericTest.php
@@ -17,6 +17,7 @@
namespace Google\Cloud\Spanner\Tests\Snippet;
+use Google\Cloud\Core\Testing\GrpcTestTrait;
use Google\Cloud\Core\Testing\Snippet\SnippetTestCase;
use Google\Cloud\Spanner\Numeric;
@@ -25,8 +26,12 @@
*/
class NumericTest extends SnippetTestCase
{
+ use GrpcTestTrait;
+
public function testClass()
{
+ $this->checkAndSkipGrpcTests();
+
$expected = new Numeric('99999999999999999999999999999999999999.999999999');
$snippet = $this->snippetFromClass(Numeric::class);
$res = $snippet->invoke('numeric');
diff --git a/Spanner/tests/Snippet/PgJsonbTest.php b/Spanner/tests/Snippet/PgJsonbTest.php
index b4d388ed5874..74c14837a837 100644
--- a/Spanner/tests/Snippet/PgJsonbTest.php
+++ b/Spanner/tests/Snippet/PgJsonbTest.php
@@ -17,6 +17,7 @@
namespace Google\Cloud\Spanner\Tests\Snippet;
+use Google\Cloud\Core\Testing\GrpcTestTrait;
use Google\Cloud\Core\Testing\Snippet\SnippetTestCase;
use Google\Cloud\Spanner\PgJsonb;
@@ -25,8 +26,12 @@
*/
class PgJsonbTest extends SnippetTestCase
{
+ use GrpcTestTrait;
+
public function testClass()
{
+ $this->checkAndSkipGrpcTests();
+
$expected = new PgJsonb('{}');
$snippet = $this->snippetFromClass(PgJsonb::class);
$res = $snippet->invoke('pgJsonb');
diff --git a/Spanner/tests/Snippet/PgNumericTest.php b/Spanner/tests/Snippet/PgNumericTest.php
index 3184f4d92ee9..8d4a0b96a382 100644
--- a/Spanner/tests/Snippet/PgNumericTest.php
+++ b/Spanner/tests/Snippet/PgNumericTest.php
@@ -17,6 +17,7 @@
namespace Google\Cloud\Spanner\Tests\Snippet;
+use Google\Cloud\Core\Testing\GrpcTestTrait;
use Google\Cloud\Core\Testing\Snippet\SnippetTestCase;
use Google\Cloud\Spanner\PgNumeric;
@@ -25,8 +26,12 @@
*/
class PgNumericTest extends SnippetTestCase
{
+ use GrpcTestTrait;
+
public function testClass()
{
+ $this->checkAndSkipGrpcTests();
+
$expected = new PgNumeric('99999999999999999999999999999999999999.000000999999999');
$snippet = $this->snippetFromClass(PgNumeric::class);
$res = $snippet->invoke('pgNumeric');
diff --git a/Spanner/tests/Snippet/PgOidTest.php b/Spanner/tests/Snippet/PgOidTest.php
index 7cdf215a3ad5..974ce7a567e1 100644
--- a/Spanner/tests/Snippet/PgOidTest.php
+++ b/Spanner/tests/Snippet/PgOidTest.php
@@ -17,6 +17,7 @@
namespace Google\Cloud\Spanner\Tests\Snippet;
+use Google\Cloud\Core\Testing\GrpcTestTrait;
use Google\Cloud\Core\Testing\Snippet\SnippetTestCase;
use Google\Cloud\Spanner\PgOid;
@@ -25,8 +26,12 @@
*/
class PgOidTest extends SnippetTestCase
{
+ use GrpcTestTrait;
+
public function testClass()
{
+ $this->checkAndSkipGrpcTests();
+
$expected = new PgOid('123');
$snippet = $this->snippetFromClass(PgOid::class);
$res = $snippet->invoke('pgOid');
diff --git a/composer.json b/composer.json
index abab0e28f9a2..2a61bd8d6d93 100644
--- a/composer.json
+++ b/composer.json
@@ -60,12 +60,15 @@
"rize/uri-template": "~0.3",
"guzzlehttp/guzzle": "^7.4.5",
"guzzlehttp/psr7": "^2.6",
+ "guzzlehttp/promises": "^2.0",
"monolog/monolog": "^2.9||^3.0",
"psr/http-message": "^1.0|^2.0",
- "ramsey/uuid": "^4.0",
+ "grpc/grpc": "^1.13",
+ "google/auth": "^1.42",
"google/common-protos": "^4.4",
- "google/gax": "^1.40.0",
- "google/auth": "^1.42"
+ "google/protobuf": "^4.31||^5.34",
+ "google/grpc-gcp": "^0.4",
+ "ramsey/uuid": "^4.0"
},
"require-dev": {
"phpunit/phpunit": "^9.6",
@@ -282,6 +285,7 @@
"google/cloud-workflows": "1.3.0",
"google/cloud-workloadmanager": "0.2.0",
"google/common-protos": "4.14.0",
+ "google/gax": "1.42.1",
"google/geo-common-protos": "0.2.4",
"google/grafeas": "1.7.0",
"google/longrunning": "0.7.1",
@@ -313,6 +317,7 @@
},
"autoload": {
"psr-4": {
+ "GPBMetadata\\ApiCore\\": "Gax/metadata/ApiCore",
"GPBMetadata\\Google\\Ads\\Admanager\\": "AdsAdManager/metadata",
"GPBMetadata\\Google\\Ads\\Datamanager\\": "AdsDataManager/metadata",
"GPBMetadata\\Google\\Analytics\\Admin\\": "AnalyticsAdmin/metadata",
@@ -559,6 +564,7 @@
"Google\\Ads\\MarketingPlatform\\Admin\\": "AdsMarketingPlatformAdmin/src",
"Google\\Analytics\\Admin\\": "AnalyticsAdmin/src",
"Google\\Analytics\\Data\\": "AnalyticsData/src",
+ "Google\\ApiCore\\": "Gax/src",
"Google\\ApiCore\\LongRunning\\": "LongRunning/src/ApiCore/LongRunning",
"Google\\Api\\": "CommonProtos/src/Api",
"Google\\Apps\\Card\\": "AppsChat/src/Card",
@@ -796,6 +802,7 @@
},
"autoload-dev": {
"psr-4": {
+ "Google\\ApiCore\\Tests\\": "Gax/tests",
"Google\\Analytics\\Admin\\Tests\\": "AnalyticsAdmin/tests",
"Google\\Analytics\\Data\\Tests\\": "AnalyticsData/tests",
"Google\\Cloud\\AIPlatform\\Tests\\": "AiPlatform/tests",
diff --git a/dev/src/Component.php b/dev/src/Component.php
index d5eaac7b8107..983e67bea25d 100644
--- a/dev/src/Component.php
+++ b/dev/src/Component.php
@@ -254,12 +254,9 @@ private function validateComponentFiles(): void
$this->componentDependencies[] = new Component($componentName);
}
}
- // add gax if it's required
+ // GAX depend on google/common-protos
if (isset($composerJson['require']['google/gax'])) {
- $this->componentDependencies[] = new Component('gax', self::ROOT_DIR . '/dev/vendor/google/gax');
- if (!isset($composerJson['require']['google/common-protos'])) {
- $this->componentDependencies[] = new Component('CommonProtos');
- }
+ $this->componentDependencies[] = new Component('CommonProtos');
}
}
diff --git a/dev/tests/Snippet/ProductNeutralGuides/TroubleshootingTest.php b/dev/tests/Snippet/ProductNeutralGuides/TroubleshootingTest.php
index cc36797ed0bc..e343460de155 100644
--- a/dev/tests/Snippet/ProductNeutralGuides/TroubleshootingTest.php
+++ b/dev/tests/Snippet/ProductNeutralGuides/TroubleshootingTest.php
@@ -55,7 +55,10 @@ public function testLoggingIsEnabledWithEnvironmentVariable()
$logger->debug(Argument::type('string'))->shouldBeCalled();
global $client;
- $client = new TranslationServiceClient(['logger' => $logger->reveal()]);
+ $client = new TranslationServiceClient([
+ 'logger' => $logger->reveal(),
+ 'transport' => 'rest',
+ ]);
try {
putenv('GOOGLE_SDK_PHP_LOGGING=true');
diff --git a/phpcs-ruleset.xml b/phpcs-ruleset.xml
index 4cf31bb8aabe..ef321333c1a9 100644
--- a/phpcs-ruleset.xml
+++ b/phpcs-ruleset.xml
@@ -25,6 +25,8 @@
BigQueryDataExchange/src/Common
Bigtable/tests/Conformance/proxy/src
Core/src/Testing
+ Gax/src/Testing
+ Gax/tests/Unit/GapicClientTraitTest.php
GSuiteAddOns/external
OsLogin/src/Common
LongRunning/src/ApiCore/LongRunning
@@ -37,7 +39,5 @@
tests/Component/TestComposerInstall.php
CommonProtos
Spanner/tests/data/generated
- Translate/src/TranslateClient.php
- Translate/src/Connection/*
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
index 9fd64a0da776..c3b85fee1ceb 100644
--- a/phpstan.neon.dist
+++ b/phpstan.neon.dist
@@ -10,6 +10,8 @@ parameters:
# Ignore Monolog3 for now, as these tests run using Monolog2
- Logging/src/LogMessageProcessor/MonologV3MessageProcessor.php
- Logging/src/LogMessageProcessor/MonologV3MessageProcessor.php
+ # ignore GAX because we implement a stricter phpstan.neon.dist there
+ - Gax
ignoreErrors:
# Protobuf constant classes sometimes contain multiple values for one array key
- identifier: array.duplicateKey
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index cc85a8755277..1b5fca94694e 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -24,6 +24,7 @@
*/tests/Unit
dev/tests/Unit
+ Core