Skip to content

Commit

Permalink
feat: new surface custom LROs (#712)
Browse files Browse the repository at this point in the history
  • Loading branch information
bshaffer committed May 30, 2024
1 parent 908d92f commit 0afff0f
Show file tree
Hide file tree
Showing 29 changed files with 1,864 additions and 12 deletions.
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"Testing\\BasicPaginated\\": "tests/Unit/ProtoTests/BasicPaginated/out/src",
"Testing\\BasicServerStreaming\\": "tests/Unit/ProtoTests/BasicServerStreaming/out/src",
"Testing\\CustomLro\\": "tests/Unit/ProtoTests/CustomLro/out/src",
"Testing\\CustomLroNew\\": "tests/Unit/ProtoTests/CustomLroNew/out/src",
"Testing\\Deprecated\\": "tests/Unit/ProtoTests/DeprecatedService/out/src",
"Testing\\DisableSnippets\\": "tests/Unit/ProtoTests/DisableSnippets/out/src",
"Testing\\GrpcServiceConfig\\": "tests/Unit/ProtoTests/GrpcServiceConfig/out/src",
Expand Down
7 changes: 4 additions & 3 deletions src/CodeGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,8 @@ public static function generate(
$result[] = $file;
}

if ($transportType === Transport::REST) {
// GAPIC enums are only needed for legacy DIREGAPICs
if ($transportType === Transport::REST && $migrationMode !== MigrationMode::NEW_SURFACE_ONLY) {
foreach (static::generateEnumConstants(
$byPackage,
$catalog,
Expand Down Expand Up @@ -404,14 +405,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
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
3 changes: 2 additions & 1 deletion src/Generation/GapicClientV2Generator.php
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,8 @@ private function getClientDefaults(): PhpClassMember
]);
}

if ($this->serviceDetails->hasCustomOp) {
if ($this->serviceDetails->hasCustomOp && $this->serviceDetails->migrationMode !== MigrationMode::NEW_SURFACE_ONLY) {
// This is only needed for legacy custom operations clients.
$clientDefaultValues['operationsClientClass'] = AST::access(
$this->ctx->type($this->serviceDetails->customOperationServiceClientType),
AST::CLS
Expand Down
11 changes: 9 additions & 2 deletions src/Generation/ResourcesGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -203,13 +203,20 @@ public static function customOperationDescriptor(ServiceDetails $serviceDetails,
->map(fn ($x) => $x->getter->getName())->toArray()
),
'getOperationMethod' => $method->operationPollingMethod->methodName,
'cancelOperationMethod' => $serviceDetails->hasCustomOpCancel ? 'cancel': AST::NULL,
'deleteOperationMethod' => $serviceDetails->hasCustomOpDelete ? 'delete': AST::NULL,
'cancelOperationMethod' => $serviceDetails->customOpCancel ? 'cancel': AST::NULL,
'deleteOperationMethod' => $serviceDetails->customOpDelete ? 'delete': AST::NULL,
'operationErrorCodeMethod' => $errorCode->getter->getName(),
'operationErrorMessageMethod' => $errorMessage->getter->getName(),
'operationNameMethod' => $name->getter->getName(),
'operationStatusMethod' => $status->getter->getName(),
'operationStatusDoneValue' => $doneValue,
'getOperationRequest' => $method->operationPollingMethod->requestType->getFullname(),
'cancelOperationRequest' => $serviceDetails->customOpCancel
? $serviceDetails->customOpCancel->requestType->getFullname()
: AST::NULL,
'deleteOperationRequest' => $serviceDetails->customOpDelete
? $serviceDetails->customOpDelete->requestType->getFullname()
: AST::NULL,
]);
}

Expand Down
15 changes: 11 additions & 4 deletions src/Generation/ServiceDetails.php
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,9 @@ class ServiceDetails
/** @var Type *Readonly* The Type of the service's operations_service client. */
public Type $customOperationServiceClientType;

public bool $hasCustomOpDelete;
public ?MethodDetails $customOpDelete;

public bool $hasCustomOpCancel;
public ?MethodDetails $customOpCancel;

/** @var bool *Readonly* Whether this service makes use of resources. */
public bool $hasResources;
Expand Down Expand Up @@ -209,9 +209,16 @@ public function __construct(
// but for simplicity we will assume they are all the same and use the first.
$this->customOperationService = $customOperations[0]->operationService;

// Find if there is a "Delete" or "Cancel" method on the operation service.
// If so, assume these are the "delete" and "cancel" methods respectively.
$operationCancel = Vector::new($this->customOperationService->getMethod())
->filter(fn ($x) => $x->getName() === 'Cancel');
$operationDelete = Vector::new($this->customOperationService->getMethod())
->filter(fn ($x) => $x->getName() === 'Delete');

// Determine if the operation service implements the Cancel and/or the Delete RPCs.
$this->hasCustomOpCancel = Vector::new($this->customOperationService->getMethod())->any(fn ($x) => $x->getName() === 'Cancel');
$this->hasCustomOpDelete = Vector::new($this->customOperationService->getMethod())->any(fn ($x) => $x->getName() === 'Delete');
$this->customOpCancel = $operationCancel->count() !== 0 ? MethodDetails::create($this, $operationCancel->firstOrNull()) : null;
$this->customOpDelete = $operationDelete->count() !== 0 ? MethodDetails::create($this, $operationDelete->firstOrNull()) : null;

// Assuming the custom operations service client is in the same namespace as the client to generate.
$cname = $this->customOperationService->getName() . 'Client';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,9 @@ private function getDefaultOperationDescriptor()
'operationNameMethod' => 'getName',
'operationStatusMethod' => 'getStatus',
'operationStatusDoneValue' => \Google\Cloud\Compute\V1\Operation\Status::DONE,
'getOperationRequest' => '\Google\Cloud\Compute\V1\GetRegionOperationRequest',
'cancelOperationRequest' => null,
'deleteOperationRequest' => null,
];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@
'operationNameMethod' => 'getName',
'operationStatusMethod' => 'getStatus',
'operationStatusDoneValue' => \Google\Cloud\Compute\V1\Operation\Status::DONE,
'getOperationRequest' => '\Google\Cloud\Compute\V1\GetRegionOperationRequest',
'cancelOperationRequest' => null,
'deleteOperationRequest' => null,
],
],
'Insert' => [
Expand All @@ -53,6 +56,9 @@
'operationNameMethod' => 'getName',
'operationStatusMethod' => 'getStatus',
'operationStatusDoneValue' => \Google\Cloud\Compute\V1\Operation\Status::DONE,
'getOperationRequest' => '\Google\Cloud\Compute\V1\GetRegionOperationRequest',
'cancelOperationRequest' => null,
'deleteOperationRequest' => null,
],
],
'AggregatedList' => [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,9 @@ private function getDefaultOperationDescriptor()
'operationNameMethod' => 'getName',
'operationStatusMethod' => 'getStatus',
'operationStatusDoneValue' => \Testing\CustomLro\CustomOperationResponse\Status::DONE,
'getOperationRequest' => '\Testing\CustomLro\GetOperationRequest',
'cancelOperationRequest' => '\Testing\CustomLro\CancelOperationRequest',
'deleteOperationRequest' => '\Testing\CustomLro\DeleteOperationRequest',
];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@
'operationNameMethod' => 'getName',
'operationStatusMethod' => 'getStatus',
'operationStatusDoneValue' => \Testing\CustomLro\CustomOperationResponse\Status::DONE,
'getOperationRequest' => '\Testing\CustomLro\GetOperationRequest',
'cancelOperationRequest' => '\Testing\CustomLro\CancelOperationRequest',
'deleteOperationRequest' => '\Testing\CustomLro\DeleteOperationRequest',
],
],
],
Expand Down
106 changes: 106 additions & 0 deletions tests/Unit/ProtoTests/CustomLroNew/custom_lro_new.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
syntax = "proto3";

import "google/api/annotations.proto";
import "google/api/client.proto";
import "google/api/field_behavior.proto";
import "google/cloud/extended_operations.proto";
import "google/protobuf/empty.proto";

package testing.customlronew;

option php_namespace = "Testing\\CustomLroNew";

service CustomLro {
option (google.api.default_host) = "customlro.example.com";
option (google.api.oauth_scopes) = "scope1,scope2";
rpc CreateFoo(CreateFooRequest) returns (CustomOperationResponse) {
option (google.api.http) = {
post: "/foo"
body: "*"
};
option (google.cloud.operation_service) = "CustomLroOperations";
}
}

service CustomLroOperations {
option (google.api.default_host) = "customlro.example.com";
option (google.api.oauth_scopes) = "scope1,scope2";
rpc Get(GetOperationRequest) returns (CustomOperationResponse) {
option (google.api.http) = {
get: "/operation"
};
option (google.cloud.operation_polling_method) = true;
option (google.api.method_signature) = 'operation,project,region,foo';
}

rpc Cancel(CancelOperationRequest) returns (google.protobuf.Empty) {
option (google.api.http) = {
patch: "/operation"
};
}

rpc Delete(DeleteOperationRequest) returns (google.protobuf.Empty) {
option (google.api.http) = {
delete: "/operation"
};
}
}

message CustomOperationResponse {
optional string name = 1 [(google.cloud.operation_field) = NAME];

optional string http_error_message = 2 [(google.cloud.operation_field) = ERROR_MESSAGE];

optional int32 http_error_status_code = 3 [(google.cloud.operation_field) = ERROR_CODE];

enum Status {
// A value indicating that the enum field is not set.
UNDEFINED_STATUS = 0;

DONE = 1;

PENDING = 2;

RUNNING = 3;
}

optional Status status = 4 [(google.cloud.operation_field) = STATUS];
}

message CreateFooRequest {
string foo = 1 [(google.cloud.operation_request_field) = "foo"];

string project = 2 [
(google.cloud.operation_request_field) = "project",
(google.api.field_behavior) = REQUIRED];

string region = 3 [
(google.cloud.operation_request_field) = "region",
(google.api.field_behavior) = REQUIRED];
}

message GetOperationRequest {
// Name of the Operations resource to return.
string operation = 1 [
(google.cloud.operation_response_field) = "name",
(google.api.field_behavior) = REQUIRED];

// Project ID for this request.
string project = 2 [(google.api.field_behavior) = REQUIRED];

// Name of the region for this request.
string region = 3 [(google.api.field_behavior) = REQUIRED];

// The foo from the initial request.
string foo = 4 [(google.api.field_behavior) = REQUIRED];
}

message CancelOperationRequest {
// Name of th Operations resource to cancel.
string operation = 1 [(google.api.field_behavior) = REQUIRED];
}

message DeleteOperationRequest {
// Name of th Operations resource to delete.
string operation = 1 [(google.api.field_behavior) = REQUIRED];
}
28 changes: 28 additions & 0 deletions tests/Unit/ProtoTests/CustomLroNew/custom_lro_new.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
type: google.api.Service
config_version: 1
name: customlro.example.com
title: Custom LRO Example API

# Included protobuf APIs
apis:
- name: testing.customlro.CustomLro
- name: testing.customlro.CustomLroOperations

# Documentation section
documentation:
summary:
A simple Custom LRO Example API.

# Auth section
authentication:
rules:
- selector: '*'
oauth:
canonical_scopes: scope1,
scope2

# Backend section
backend:
rules:
- selector: testing.customlro.CustomLro.CreateFoo
deadline: 1.0
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@

/**
* @param string $operation Name of the Operations resource to return.
* @param string $project Project ID for this request.
* @param string $region Name of the region for this request.
* @param string $foo The foo from the initial request.
*
* @return \Testing\CustomLroNew\GetOperationRequest
*
* @experimental
*/
public static function build(string $operation, string $project, string $region, string $foo): self
{
return (new self())
->setOperation($operation)
->setProject($project)
->setRegion($region)
->setFoo($foo);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php
/*
* Copyright 2022 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/*
* GENERATED CODE WARNING
* This file was automatically generated - do not edit!
*/

require_once __DIR__ . '/../../../vendor/autoload.php';

// [START customlro_generated_CustomLro_CreateFoo_sync]
use Google\ApiCore\ApiException;
use Google\ApiCore\OperationResponse;
use Google\Rpc\Status;
use Testing\CustomLroNew\Client\CustomLroClient;
use Testing\CustomLroNew\CreateFooRequest;

/**
* @param string $project
* @param string $region
*/
function create_foo_sample(string $project, string $region): void
{
// Create a client.
$customLroClient = new CustomLroClient();

// Prepare the request message.
$request = (new CreateFooRequest())
->setProject($project)
->setRegion($region);

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

if ($response->operationSucceeded()) {
printf('Operation completed successfully.' . PHP_EOL);
} else {
/** @var Status $error */
$error = $response->getError();
printf('Operation failed with error data: %s' . PHP_EOL, $error->serializeToJsonString());
}
} catch (ApiException $ex) {
printf('Call failed with message: %s' . PHP_EOL, $ex->getMessage());
}
}

/**
* Helper to execute the sample.
*
* This sample has been automatically generated and should be regarded as a code
* template only. It will require modifications to work:
* - It may require correct/in-range values for request initialization.
* - It may require specifying regional endpoints when creating the service client,
* please see the apiEndpoint client configuration option for more details.
*/
function callSample(): void
{
$project = '[PROJECT]';
$region = '[REGION]';

create_foo_sample($project, $region);
}
// [END customlro_generated_CustomLro_CreateFoo_sync]
Loading

0 comments on commit 0afff0f

Please sign in to comment.