diff --git a/composer.json b/composer.json index 9b8563c1..d93f2662 100644 --- a/composer.json +++ b/composer.json @@ -16,6 +16,7 @@ "Testing\\BasicBidiStreaming\\": "tests/Unit/ProtoTests/BasicBidiStreaming/out/src", "Testing\\BasicClientStreaming\\": "tests/Unit/ProtoTests/BasicClientStreaming/out/src", "Testing\\BasicDiregapic\\": "tests/Unit/ProtoTests/BasicDiregapic/out/src", + "Testing\\BasicGrpcOnly\\": "tests/Unit/ProtoTests/BasicGrpcOnly/out/src", "Testing\\BasicLro\\": "tests/Unit/ProtoTests/BasicLro/out/src", "Testing\\BasicOneof\\": "tests/Unit/ProtoTests/BasicOneof/out/src", "Testing\\BasicOneofNew\\": "tests/Unit/ProtoTests/BasicOneofNew/out/src", diff --git a/rules_php_gapic/php_gapic.bzl b/rules_php_gapic/php_gapic.bzl index 3c9f379b..3cc82ff2 100644 --- a/rules_php_gapic/php_gapic.bzl +++ b/rules_php_gapic/php_gapic.bzl @@ -70,7 +70,7 @@ def php_gapic_srcjar( if transport == None: transport = "grpc+rest" if transport != "grpc+rest" and transport != "rest" and transport != "grpc": - fail("Error: Only 'grpc+rest' or 'rest' transports are supported") + fail("Error: Only 'grpc+rest', 'rest' or `grpc` transports are supported") # Set plugin arguments. plugin_args = ["metadata"] # Generate the gapic_metadata.json file. diff --git a/src/Generation/GapicClientV2Generator.php b/src/Generation/GapicClientV2Generator.php index 1c2db765..27a7a300 100644 --- a/src/Generation/GapicClientV2Generator.php +++ b/src/Generation/GapicClientV2Generator.php @@ -480,20 +480,20 @@ private function supportedTransports() { if ($this->serviceDetails->transportType === Transport::REST) { return AST::method('supportedTransports') - ->withPhpDocText('Implements ClientOptionsTrait::supportedTransports.') - ->withAccess(Access::PRIVATE, Access::STATIC) - ->withBody(AST::block( - AST::return(AST::array(['rest'])) - )); + ->withPhpDocText('Implements ClientOptionsTrait::supportedTransports.') + ->withAccess(Access::PRIVATE, Access::STATIC) + ->withBody(AST::block( + AST::return(AST::array(['rest'])) + )); } if ($this->serviceDetails->transportType === Transport::GRPC) { return AST::method('supportedTransports') - ->withPhpDocText('Implements ClientOptionsTrait::supportedTransports.') - ->withAccess(Access::PRIVATE, Access::STATIC) - ->withBody(AST::block( - AST::return(AST::array(['grpc', 'grpc-fallback'])) - )); + ->withPhpDocText('Implements ClientOptionsTrait::supportedTransports.') + ->withAccess(Access::PRIVATE, Access::STATIC) + ->withBody(AST::block( + AST::return(AST::array(['grpc', 'grpc-fallback'])) + )); } } diff --git a/src/Utils/Transport.php b/src/Utils/Transport.php index ec6c964d..15efdcee 100644 --- a/src/Utils/Transport.php +++ b/src/Utils/Transport.php @@ -39,7 +39,7 @@ public static function parseTransport(?string $transport): int return static::REST; } if ($transport === "grpc") { - throw new \Exception("gRPC-only PHP clients are not supported at this time"); + return static::GRPC; } throw new \Exception("Transport $transport not supported"); diff --git a/tests/Integration/BUILD.bazel b/tests/Integration/BUILD.bazel index 3e7e86d1..7824b144 100644 --- a/tests/Integration/BUILD.bazel +++ b/tests/Integration/BUILD.bazel @@ -354,6 +354,7 @@ php_gapic_library( grpc_service_config = "@com_google_googleapis//google/cloud/redis/v1:redis_grpc_service_config.json", service_yaml = "@com_google_googleapis//google/cloud/redis/v1:redis_v1.yaml", migration_mode = "MIGRATION_MODE_UNSPECIFIED", + transport = "grpc", deps = [ ":redis_php_grpc", ":redis_php_proto", diff --git a/tests/Integration/goldens/redis/src/V1/Client/CloudRedisClient.php b/tests/Integration/goldens/redis/src/V1/Client/CloudRedisClient.php index 0940e4c7..c98d88f3 100644 --- a/tests/Integration/goldens/redis/src/V1/Client/CloudRedisClient.php +++ b/tests/Integration/goldens/redis/src/V1/Client/CloudRedisClient.php @@ -131,7 +131,6 @@ private static function getClientDefaults() 'apiEndpoint' => self::SERVICE_ADDRESS . ':' . self::DEFAULT_SERVICE_PORT, 'clientConfig' => __DIR__ . '/../resources/cloud_redis_client_config.json', 'descriptorsConfigPath' => __DIR__ . '/../resources/cloud_redis_descriptor_config.php', - 'gcpApiConfigPath' => __DIR__ . '/../resources/cloud_redis_grpc_config.json', 'credentialsConfig' => [ 'defaultScopes' => self::$serviceScopes, ], @@ -143,6 +142,15 @@ private static function getClientDefaults() ]; } + /** Implements ClientOptionsTrait::supportedTransports. */ + private static function supportedTransports() + { + return [ + 'grpc', + 'grpc-fallback', + ]; + } + /** * Return an OperationsClient object with the same endpoint as $this. * @@ -263,9 +271,8 @@ public static function parseName(string $formattedName, string $template = null) * 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 + * The transport used for executing network requests. At the moment, supports only + * `rest`. *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. @@ -274,11 +281,9 @@ public static function parseName(string $formattedName, string $template = null) * 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 + * See the {@see \Google\ApiCore\Transport\RestTransport::build()} method for the * supported options. * @type callable $clientCertSource * A callable which returns the client cert as a string. This can be used to diff --git a/tests/Integration/goldens/redis/src/V1/Gapic/CloudRedisGapicClient.php b/tests/Integration/goldens/redis/src/V1/Gapic/CloudRedisGapicClient.php index d33ccb76..21f664fb 100644 --- a/tests/Integration/goldens/redis/src/V1/Gapic/CloudRedisGapicClient.php +++ b/tests/Integration/goldens/redis/src/V1/Gapic/CloudRedisGapicClient.php @@ -168,7 +168,6 @@ private static function getClientDefaults() 'apiEndpoint' => self::SERVICE_ADDRESS . ':' . self::DEFAULT_SERVICE_PORT, 'clientConfig' => __DIR__ . '/../resources/cloud_redis_client_config.json', 'descriptorsConfigPath' => __DIR__ . '/../resources/cloud_redis_descriptor_config.php', - 'gcpApiConfigPath' => __DIR__ . '/../resources/cloud_redis_grpc_config.json', 'credentialsConfig' => [ 'defaultScopes' => self::$serviceScopes, ], @@ -347,9 +346,8 @@ public function resumeOperation($operationName, $methodName = null) * 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 + * The transport used for executing network requests. At the moment, supports only + * `rest`. *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. @@ -358,11 +356,9 @@ public function resumeOperation($operationName, $methodName = null) * 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 + * See the {@see \Google\ApiCore\Transport\RestTransport::build()} method for the * supported options. * @type callable $clientCertSource * A callable which returns the client cert as a string. This can be used to diff --git a/tests/Unit/ProtoTests/BasicGrpcOnly/basic-grpc-only.proto b/tests/Unit/ProtoTests/BasicGrpcOnly/basic-grpc-only.proto new file mode 100644 index 00000000..cd1a7866 --- /dev/null +++ b/tests/Unit/ProtoTests/BasicGrpcOnly/basic-grpc-only.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +package testing.basicgrpconly; + +// php_namespace option not included; to test generating namespace from proto package. + +import "google/api/annotations.proto"; +import "google/api/client.proto"; +import "google/api/field_behavior.proto"; + +// This is a basicGrpcOnly service. +service BasicGrpcOnly { + option (google.api.default_host) = "basicGrpcOnly.example.com"; +} diff --git a/tests/Unit/ProtoTests/BasicGrpcOnly/basic_grpc_only_service.yaml b/tests/Unit/ProtoTests/BasicGrpcOnly/basic_grpc_only_service.yaml new file mode 100644 index 00000000..eaec119b --- /dev/null +++ b/tests/Unit/ProtoTests/BasicGrpcOnly/basic_grpc_only_service.yaml @@ -0,0 +1,6 @@ +type: google.api.Service +config_version: 3 + +authentication: + rules: + - selector: 'testing.basicGrpcOnly.BasicGrpcOnly.*' \ No newline at end of file diff --git a/tests/Unit/ProtoTests/BasicGrpcOnly/out/src/Client/BasicGrpcOnlyClient.php b/tests/Unit/ProtoTests/BasicGrpcOnly/out/src/Client/BasicGrpcOnlyClient.php new file mode 100644 index 00000000..fb6f5042 --- /dev/null +++ b/tests/Unit/ProtoTests/BasicGrpcOnly/out/src/Client/BasicGrpcOnlyClient.php @@ -0,0 +1,141 @@ + self::SERVICE_NAME, + 'apiEndpoint' => self::SERVICE_ADDRESS . ':' . self::DEFAULT_SERVICE_PORT, + 'clientConfig' => __DIR__ . '/../resources/basic_grpc_only_client_config.json', + 'descriptorsConfigPath' => __DIR__ . '/../resources/basic_grpc_only_descriptor_config.php', + 'credentialsConfig' => [ + 'defaultScopes' => self::$serviceScopes, + ], + 'transportConfig' => [ + 'rest' => [ + 'restClientConfigPath' => __DIR__ . '/../resources/basic_grpc_only_rest_client_config.php', + ], + ], + ]; + } + + /** Implements ClientOptionsTrait::supportedTransports. */ + private static function supportedTransports() + { + return [ + 'grpc', + 'grpc-fallback', + ]; + } + + /** + * 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 'basicGrpcOnly.example.com: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. + * @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. At the moment, supports only + * `rest`. *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 = [ + * 'rest' => [...], + * ]; + * See the {@see \Google\ApiCore\Transport\RestTransport::build()} method 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. + * } + * + * @throws ValidationException + */ + public function __construct(array $options = []) + { + $clientOptions = $this->buildClientOptions($options); + $this->setClientOptions($clientOptions); + } +} diff --git a/tests/Unit/ProtoTests/BasicGrpcOnly/out/src/gapic_metadata.json b/tests/Unit/ProtoTests/BasicGrpcOnly/out/src/gapic_metadata.json new file mode 100644 index 00000000..a0d0e85b --- /dev/null +++ b/tests/Unit/ProtoTests/BasicGrpcOnly/out/src/gapic_metadata.json @@ -0,0 +1,16 @@ +{ + "schema": "1.0", + "comment": "This file maps proto services\/RPCs to the corresponding library clients\/methods", + "language": "php", + "protoPackage": "testing.basicgrpconly", + "libraryPackage": "Testing\\Basicgrpconly", + "services": { + "BasicGrpcOnly": { + "clients": { + "grpc": { + "libraryClient": "BasicGrpcOnlyGapicClient" + } + } + } + } +} \ No newline at end of file diff --git a/tests/Unit/ProtoTests/BasicGrpcOnly/out/src/resources/basic_grpc_only_client_config.json b/tests/Unit/ProtoTests/BasicGrpcOnly/out/src/resources/basic_grpc_only_client_config.json new file mode 100644 index 00000000..474d5a78 --- /dev/null +++ b/tests/Unit/ProtoTests/BasicGrpcOnly/out/src/resources/basic_grpc_only_client_config.json @@ -0,0 +1,25 @@ +{ + "interfaces": { + "testing.basicgrpconly.BasicGrpcOnly": { + "retry_codes": { + "idempotent": [ + "DEADLINE_EXCEEDED", + "UNAVAILABLE" + ], + "non_idempotent": [] + }, + "retry_params": { + "default": { + "initial_retry_delay_millis": 100, + "retry_delay_multiplier": 1.3, + "max_retry_delay_millis": 60000, + "initial_rpc_timeout_millis": 20000, + "rpc_timeout_multiplier": 1.0, + "max_rpc_timeout_millis": 20000, + "total_timeout_millis": 600000 + } + }, + "methods": [] + } + } +} diff --git a/tests/Unit/ProtoTests/BasicGrpcOnly/out/src/resources/basic_grpc_only_descriptor_config.php b/tests/Unit/ProtoTests/BasicGrpcOnly/out/src/resources/basic_grpc_only_descriptor_config.php new file mode 100644 index 00000000..552c3608 --- /dev/null +++ b/tests/Unit/ProtoTests/BasicGrpcOnly/out/src/resources/basic_grpc_only_descriptor_config.php @@ -0,0 +1,27 @@ + [ + 'testing.basicgrpconly.BasicGrpcOnly' => [], + ], +]; diff --git a/tests/Unit/ProtoTests/BasicGrpcOnly/out/src/resources/basic_grpc_only_rest_client_config.php b/tests/Unit/ProtoTests/BasicGrpcOnly/out/src/resources/basic_grpc_only_rest_client_config.php new file mode 100644 index 00000000..b33f5795 --- /dev/null +++ b/tests/Unit/ProtoTests/BasicGrpcOnly/out/src/resources/basic_grpc_only_rest_client_config.php @@ -0,0 +1,26 @@ + [], + 'numericEnums' => true, +]; diff --git a/tests/Unit/ProtoTests/BasicGrpcOnly/out/tests/Unit/Client/BasicGrpcOnlyClientTest.php b/tests/Unit/ProtoTests/BasicGrpcOnly/out/tests/Unit/Client/BasicGrpcOnlyClientTest.php new file mode 100644 index 00000000..9ff0f6d0 --- /dev/null +++ b/tests/Unit/ProtoTests/BasicGrpcOnly/out/tests/Unit/Client/BasicGrpcOnlyClientTest.php @@ -0,0 +1,56 @@ +getMockBuilder(CredentialsWrapper::class)->disableOriginalConstructor()->getMock(); + } + + /** @return BasicGrpcOnlyClient */ + private function createClient(array $options = []) + { + $options += [ + 'credentials' => $this->createCredentials(), + ]; + return new BasicGrpcOnlyClient($options); + } +} diff --git a/tests/Unit/ProtoTests/ClientTest.php b/tests/Unit/ProtoTests/ClientTest.php index 91dac016..d2a54f31 100644 --- a/tests/Unit/ProtoTests/ClientTest.php +++ b/tests/Unit/ProtoTests/ClientTest.php @@ -22,6 +22,7 @@ use Testing\BasicDiregapic\LibraryClient; use Google\ApiCore\InsecureCredentialsWrapper; use Google\ApiCore\ValidationException; +use Testing\BasicGrpcOnly\Client\BasicGrpcOnlyClient; final class ClientTest extends TestCase { @@ -35,4 +36,15 @@ public function testUnsupportedTransportThrowsException() 'credentials' => new InsecureCredentialsWrapper(), ]); } + + public function testGrpcOnlyClientThrowsExceptionForRestTransport() + { + $this->expectException(ValidationException::class); + $this->expectExceptionMessage('Unexpected transport option "rest". Supported transports: grpc, grpc-fallback'); + + $client = new BasicGrpcOnlyClient([ + 'transport' => 'rest', + 'credentials' => new InsecureCredentialsWrapper(), + ]); + } } diff --git a/tests/Unit/ProtoTests/GoldenUpdateMain.php b/tests/Unit/ProtoTests/GoldenUpdateMain.php index 2c2f81c6..518d1fa9 100644 --- a/tests/Unit/ProtoTests/GoldenUpdateMain.php +++ b/tests/Unit/ProtoTests/GoldenUpdateMain.php @@ -98,6 +98,12 @@ class GoldenUpdateMain 16 => [ 'name' => 'BasicAutoPopulation', 'protoPath' => 'BasicAutoPopulation/basic-auto-population.proto' + ], + 17 => [ + 'name' => 'BasicGrpcOnlyClient', + 'protoPath' => 'BasicGrpcOnly/basic-grpc-only.proto', + 'migrationMode' => MigrationMode::NEW_SURFACE_ONLY, + 'transport' => 'grpc' ] ];