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 + +![Build Status](https://github.com/googleapis/gax-php/actions/workflows/tests.yml/badge.svg) + +- [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