Skip to content

Commit

Permalink
feat: add support for grpc-only gapic clients (#707)
Browse files Browse the repository at this point in the history
  • Loading branch information
Hectorhammett authored Apr 25, 2024
1 parent 46dc31c commit 5783c98
Show file tree
Hide file tree
Showing 32 changed files with 505 additions and 3,288 deletions.
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
10 changes: 5 additions & 5 deletions rules_php_gapic/php_gapic.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def php_gapic_srcjar(
rest_numeric_enums = False,
generate_snippets = True,
# Supported values validated and specified in src/Utils/MigrationMode.php.
migration_mode = "PRE_MIGRATION_SURFACE_ONLY",
migration_mode = "PRE_MIGRATION_SURFACE_ONLY",
generator_binary = Label("//rules_php_gapic:php_gapic_generator_binary"),
**kwargs):
plugin_file_args = {}
Expand All @@ -69,10 +69,10 @@ def php_gapic_srcjar(
# Transport.
if transport == None:
transport = "grpc+rest"
if transport == "grpc":
fail("Error: gRPC-only PHP GAPIC libraries are not yet supported")
if transport != "grpc+rest" and transport != "rest":
fail("Error: Only 'grpc+rest' or 'rest' transports are supported")
if transport != "grpc+rest" and transport != "rest" and transport != "grpc":
fail("Error: Only 'grpc+rest', 'rest' or `grpc` transports are supported")
if transport == "grpc" and migration_mode != "NEW_SURFACE_ONLY":
fail("Error: 'grpc' transport is only supported with 'NEW_SURFACE_ONLY' migration mode")

# Set plugin arguments.
plugin_args = ["metadata"] # Generate the gapic_metadata.json file.
Expand Down
10 changes: 7 additions & 3 deletions src/CodeGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -404,10 +404,14 @@ private static function generateServices(
$code = ResourcesGenerator::generateDescriptorConfig($service, $gapicYamlConfig);
$code = Formatter::format($code);
yield ["src/{$version}resources/{$service->descriptorConfigFilename}", $code];

// Resource: rest_client_config.php
$code = ResourcesGenerator::generateRestConfig($service, $serviceYamlConfig, $numericEnums);
$code = Formatter::format($code);
yield ["src/{$version}resources/{$service->restConfigFilename}", $code];
if ($service->transportType !== Transport::GRPC) {
$code = ResourcesGenerator::generateRestConfig($service, $serviceYamlConfig, $numericEnums);
$code = Formatter::format($code);
yield ["src/{$version}resources/{$service->restConfigFilename}", $code];
}

// Resource: client_config.json
$json = ResourcesGenerator::generateClientConfig($service, $gapicYamlConfig, $grpcServiceConfig);
yield ["src/{$version}resources/{$service->clientConfigFilename}", $json];
Expand Down
91 changes: 60 additions & 31 deletions src/Generation/GapicClientV2Generator.php
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ private function getClientDefaults(): PhpClassMember

// TODO: Consolidate setting all the known array values together.
// We do this here to maintain the existing sensible ordering.
if ($this->serviceDetails->transportType === Transport::GRPC_REST) {
if ($this->serviceDetails->transportType !== Transport::REST) {
$clientDefaultValues['gcpApiConfigPath'] =
AST::concat(AST::__DIR__, "/../resources/{$this->serviceDetails->grpcConfigFilename}");
}
Expand All @@ -444,11 +444,15 @@ private function getClientDefaults(): PhpClassMember
$credentialsConfig['useJwtAccessWithScope'] = false;
}
$clientDefaultValues['credentialsConfig'] = AST::array($credentialsConfig);
$clientDefaultValues['transportConfig'] = AST::array([
'rest' => AST::array([
'restClientConfigPath' => AST::concat(AST::__DIR__, "/../resources/{$this->serviceDetails->restConfigFilename}"),
])
]);

if ($this->serviceDetails->transportType !== Transport::GRPC) {
$clientDefaultValues['transportConfig'] = AST::array([
'rest' => AST::array([
'restClientConfigPath' => AST::concat(AST::__DIR__, "/../resources/{$this->serviceDetails->restConfigFilename}"),
])
]);
}

if ($this->serviceDetails->hasCustomOp) {
$clientDefaultValues['operationsClientClass'] = AST::access(
$this->ctx->type($this->serviceDetails->customOperationServiceClientType),
Expand Down Expand Up @@ -478,15 +482,23 @@ private function defaultTransport()

private function supportedTransports()
{
if ($this->serviceDetails->transportType !== Transport::REST) {
return null;
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']))
));
}

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']))
));
}
return AST::method('supportedTransports')
->withPhpDocText('Implements GapicClientTrait::supportedTransports.')
->withAccess(Access::PRIVATE, Access::STATIC)
->withBody(AST::block(
AST::return(AST::array(['rest']))
));
}

private function construct(): PhpClassMember
Expand All @@ -498,30 +510,30 @@ private function construct(): PhpClassMember
$options = AST::var('options');
$optionsParam = AST::param(ResolvedType::array(), $options, AST::array([]));
$clientOptions = AST::var('clientOptions');
$transportType = $this->serviceDetails->transportType;

// Assumes there are only two transport types.
$isGrpcRest = $this->serviceDetails->transportType === Transport::GRPC_REST;

$restTransportDocText = 'At the moment, supports only `rest`.';
$grpcTransportDocText = 'May be either the string `rest` or `grpc`. Defaults to `grpc` if gRPC support is detected on the system.';
$transportDocText =
PhpDoc::text(
'The transport used for executing network requests. ',
$isGrpcRest ? $grpcTransportDocText : $restTransportDocText,
$this->transportDocText($transportType),
'*Advanced usage*: Additionally, it is possible to pass in an already instantiated',
// TODO(vNext): Don't use a fully-qualified type here.
$ctx->type(Type::fromName(TransportInterface::class), true),
'object. Note that when this object is provided, any settings in $transportConfig, and any $apiEndpoint',
'setting, will be ignored.'
);

$transportConfigSampleValues = [
'grpc' => AST::arrayEllipsis(),
'rest' => AST::arrayEllipsis()
];

$transportConfigSampleValues = [];
if ($isGrpcRest) {
$transportConfigSampleValues['grpc'] = AST::arrayEllipsis();
if (Transport::isGrpcOnly($transportType)) {
unset($transportConfigSampleValues['rest']);
} elseif (Transport::isRestOnly($transportType)) {
unset($transportConfigSampleValues['grpc']);
}
// Set this value here, don't initialize it, so we can maintain alphabetical order
// for the resulting printed doc.
$transportConfigSampleValues['rest'] = AST::arrayEllipsis();

$transportConfigDocText =
PhpDoc::text(
'Configuration options that will be used to construct the transport. Options for',
Expand All @@ -539,22 +551,26 @@ private function construct(): PhpClassMember
'See the',
AST::call(
$ctx->type(
Type::fromName($isGrpcRest ? GrpcTransport::class : RestTransport::class),
Type::fromName(
Transport::isRestOnly($transportType) ?
RestTransport::class :
GrpcTransport::class
),
true
),
AST::method('build')
)(),
$isGrpcRest ? 'and' : '',
$isGrpcRest
? AST::call(
Transport::isGrpcRest($transportType) ? 'and' : '',
Transport::isGrpcRest($transportType) ?
AST::call(
$ctx->type(
Type::fromName(RestTransport::class),
true
),
AST::method('build')
)()
: '',
$isGrpcRest ? 'methods ' : 'method ',
Transport::isGrpcRest($transportType) ? 'methods ' : 'method ',
'for the supported options.'
);
return AST::method('__construct')
Expand Down Expand Up @@ -661,6 +677,19 @@ private function construct(): PhpClassMember
));
}

private function transportDocText(int $transportType): string
{
if (Transport::isRestOnly($transportType)) {
return 'At the moment, supports only `rest`.';
}

if (Transport::isGrpcOnly($transportType)) {
return 'At the moment, supports only `grpc`.';
}

return 'May be either the string `rest` or `grpc`. Defaults to `grpc` if gRPC support is detected on the system.';
}

private function rpcMethod(MethodDetails $method): PhpClassMember
{
$request = AST::var('request');
Expand Down
23 changes: 22 additions & 1 deletion src/Utils/Transport.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class Transport
// supported in the future (e.g. gRPC only).
public const GRPC_REST = 1;
public const REST = 2;
public const GRPC = 3;

/**
* Returns true if the given transport string indicates that grpc+rest transports
Expand All @@ -38,9 +39,29 @@ 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");
}

public static function isRestOnly(int $transport): bool
{
return Transport::compareTransports(Transport::REST, $transport);
}

public static function isGrpcOnly(int $transport): bool
{
return Transport::compareTransports(Transport::GRPC, $transport);
}

public static function isGrpcRest(int $transport): bool
{
return Transport::compareTransports(Transport::GRPC_REST, $transport);
}

private static function compareTransports(int $transportA, int $transportB): bool
{
return $transportA === $transportB;
}
}
3 changes: 2 additions & 1 deletion tests/Integration/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,8 @@ php_gapic_library(
gapic_yaml = "apis/redis/v1/redis_gapic.yaml",
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",
migration_mode = "NEW_SURFACE_ONLY",
transport = "grpc",
deps = [
":redis_php_grpc",
":redis_php_proto",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
// [START redis_v1_generated_CloudRedis_CreateInstance_sync]
use Google\ApiCore\ApiException;
use Google\ApiCore\OperationResponse;
use Google\Cloud\Redis\V1\CloudRedisClient;
use Google\Cloud\Redis\V1\Client\CloudRedisClient;
use Google\Cloud\Redis\V1\CreateInstanceRequest;
use Google\Cloud\Redis\V1\Instance;
use Google\Cloud\Redis\V1\Instance\Tier;
use Google\Rpc\Status;
Expand Down Expand Up @@ -80,16 +81,20 @@ function create_instance_sample(
// Create a client.
$cloudRedisClient = new CloudRedisClient();

// Prepare any non-scalar elements to be passed along with the request.
// Prepare the request message.
$instance = (new Instance())
->setName($instanceName)
->setTier($instanceTier)
->setMemorySizeGb($instanceMemorySizeGb);
$request = (new CreateInstanceRequest())
->setParent($formattedParent)
->setInstanceId($instanceId)
->setInstance($instance);

// Call the API and handle any network failures.
try {
/** @var OperationResponse $response */
$response = $cloudRedisClient->createInstance($formattedParent, $instanceId, $instance);
$response = $cloudRedisClient->createInstance($request);
$response->pollUntilComplete();

if ($response->operationSucceeded()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
// [START redis_v1_generated_CloudRedis_DeleteInstance_sync]
use Google\ApiCore\ApiException;
use Google\ApiCore\OperationResponse;
use Google\Cloud\Redis\V1\CloudRedisClient;
use Google\Cloud\Redis\V1\Client\CloudRedisClient;
use Google\Cloud\Redis\V1\DeleteInstanceRequest;
use Google\Rpc\Status;

/**
Expand All @@ -42,10 +43,14 @@ function delete_instance_sample(string $formattedName): void
// Create a client.
$cloudRedisClient = new CloudRedisClient();

// Prepare the request message.
$request = (new DeleteInstanceRequest())
->setName($formattedName);

// Call the API and handle any network failures.
try {
/** @var OperationResponse $response */
$response = $cloudRedisClient->deleteInstance($formattedName);
$response = $cloudRedisClient->deleteInstance($request);
$response->pollUntilComplete();

if ($response->operationSucceeded()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
// [START redis_v1_generated_CloudRedis_ExportInstance_sync]
use Google\ApiCore\ApiException;
use Google\ApiCore\OperationResponse;
use Google\Cloud\Redis\V1\CloudRedisClient;
use Google\Cloud\Redis\V1\Client\CloudRedisClient;
use Google\Cloud\Redis\V1\ExportInstanceRequest;
use Google\Cloud\Redis\V1\Instance;
use Google\Cloud\Redis\V1\OutputConfig;
use Google\Rpc\Status;
Expand All @@ -47,13 +48,16 @@ function export_instance_sample(string $name): void
// Create a client.
$cloudRedisClient = new CloudRedisClient();

// Prepare any non-scalar elements to be passed along with the request.
// Prepare the request message.
$outputConfig = new OutputConfig();
$request = (new ExportInstanceRequest())
->setName($name)
->setOutputConfig($outputConfig);

// Call the API and handle any network failures.
try {
/** @var OperationResponse $response */
$response = $cloudRedisClient->exportInstance($name, $outputConfig);
$response = $cloudRedisClient->exportInstance($request);
$response->pollUntilComplete();

if ($response->operationSucceeded()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
// [START redis_v1_generated_CloudRedis_FailoverInstance_sync]
use Google\ApiCore\ApiException;
use Google\ApiCore\OperationResponse;
use Google\Cloud\Redis\V1\CloudRedisClient;
use Google\Cloud\Redis\V1\Client\CloudRedisClient;
use Google\Cloud\Redis\V1\FailoverInstanceRequest;
use Google\Cloud\Redis\V1\Instance;
use Google\Rpc\Status;

Expand All @@ -43,10 +44,14 @@ function failover_instance_sample(string $formattedName): void
// Create a client.
$cloudRedisClient = new CloudRedisClient();

// Prepare the request message.
$request = (new FailoverInstanceRequest())
->setName($formattedName);

// Call the API and handle any network failures.
try {
/** @var OperationResponse $response */
$response = $cloudRedisClient->failoverInstance($formattedName);
$response = $cloudRedisClient->failoverInstance($request);
$response->pollUntilComplete();

if ($response->operationSucceeded()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@

// [START redis_v1_generated_CloudRedis_GetInstance_sync]
use Google\ApiCore\ApiException;
use Google\Cloud\Redis\V1\CloudRedisClient;
use Google\Cloud\Redis\V1\Client\CloudRedisClient;
use Google\Cloud\Redis\V1\GetInstanceRequest;
use Google\Cloud\Redis\V1\Instance;

/**
Expand All @@ -40,10 +41,14 @@ function get_instance_sample(string $formattedName): void
// Create a client.
$cloudRedisClient = new CloudRedisClient();

// Prepare the request message.
$request = (new GetInstanceRequest())
->setName($formattedName);

// Call the API and handle any network failures.
try {
/** @var Instance $response */
$response = $cloudRedisClient->getInstance($formattedName);
$response = $cloudRedisClient->getInstance($request);
printf('Response data: %s' . PHP_EOL, $response->serializeToJsonString());
} catch (ApiException $ex) {
printf('Call failed with message: %s' . PHP_EOL, $ex->getMessage());
Expand Down
Loading

0 comments on commit 5783c98

Please sign in to comment.