diff --git a/src/main/java/com/google/api/generator/gapic/composer/ServiceClientClassComposer.java b/src/main/java/com/google/api/generator/gapic/composer/ServiceClientClassComposer.java index 35dff2c4d7..939381b818 100644 --- a/src/main/java/com/google/api/generator/gapic/composer/ServiceClientClassComposer.java +++ b/src/main/java/com/google/api/generator/gapic/composer/ServiceClientClassComposer.java @@ -565,15 +565,10 @@ private static List createMethodVariants( .setReturnType(methodOutputType) .build(); - Optional methodSampleCode = Optional.empty(); - if (!method.hasLro()) { - // TODO(summerji): Remove the condition check once finished the implementation on lro sample - // code. - methodSampleCode = - Optional.of( - ServiceClientSampleCodeComposer.composeRpcMethodHeaderSampleCode( - method, types.get(clientName), signature, resourceNames, messageTypes)); - } + Optional methodSampleCode = + Optional.of( + ServiceClientSampleCodeComposer.composeRpcMethodHeaderSampleCode( + method, types.get(clientName), signature, resourceNames, messageTypes)); MethodDefinition.Builder methodVariantBuilder = MethodDefinition.builder() .setHeaderCommentStatements( diff --git a/src/main/java/com/google/api/generator/gapic/composer/ServiceClientSampleCodeComposer.java b/src/main/java/com/google/api/generator/gapic/composer/ServiceClientSampleCodeComposer.java index 7211ae8ce6..52b99d62ef 100644 --- a/src/main/java/com/google/api/generator/gapic/composer/ServiceClientSampleCodeComposer.java +++ b/src/main/java/com/google/api/generator/gapic/composer/ServiceClientSampleCodeComposer.java @@ -210,6 +210,10 @@ public static String composeRpcMethodHeaderSampleCode( composeUnaryPagedRpcMethodSampleCode( method, clientType, repeatedResponseType, arguments, resourceNames)); } + if (method.hasLro()) { + return SampleCodeWriter.write( + composeUnaryLroRpcMethodSampleCode(method, clientType, arguments, resourceNames)); + } return SampleCodeWriter.write( composeUnaryRpcMethodSampleCode(method, clientType, arguments, resourceNames)); } @@ -334,6 +338,68 @@ static TryCatchStatement composeUnaryPagedRpcMethodSampleCode( .build(); } + @VisibleForTesting + static TryCatchStatement composeUnaryLroRpcMethodSampleCode( + Method method, + TypeNode clientType, + List arguments, + Map resourceNames) { + VariableExpr clientVarExpr = + VariableExpr.withVariable( + Variable.builder() + .setName(JavaStyle.toLowerCamelCase(clientType.reference().name())) + .setType(clientType) + .build()); + List rpcMethodArgVarExprs = createRpcMethodArgumentVariableExprs(arguments); + List rpcMethodArgDefaultValueExprs = + createRpcMethodArgumentDefaultValueExprs(arguments, resourceNames); + List bodyExprs = + createAssignmentsForVarExprsWithValueExprs( + rpcMethodArgVarExprs, rpcMethodArgDefaultValueExprs); + // Assign response variable with invoking client's lro method. + // e.g. if return void, echoClient.waitAsync(ttl).get(); or, + // e.g. if return other type, WaitResponse response = echoClient.waitAsync(ttl).get(); + Expr invokeLroMethodExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(clientVarExpr) + .setMethodName(String.format("%sAsync", JavaStyle.toLowerCamelCase(method.name()))) + .setArguments( + rpcMethodArgVarExprs.stream().map(e -> (Expr) e).collect(Collectors.toList())) + .build(); + Expr getResponseMethodExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(invokeLroMethodExpr) + .setMethodName("get") + .setReturnType(method.lro().responseType()) + .build(); + boolean returnsVoid = isProtoEmptyType(method.lro().responseType()); + if (returnsVoid) { + bodyExprs.add(getResponseMethodExpr); + } else { + VariableExpr responseVarExpr = + VariableExpr.builder() + .setVariable( + Variable.builder() + .setName("response") + .setType(method.lro().responseType()) + .build()) + .setIsDecl(true) + .build(); + bodyExprs.add( + AssignmentExpr.builder() + .setVariableExpr(responseVarExpr) + .setValueExpr(getResponseMethodExpr) + .build()); + } + + return TryCatchStatement.builder() + .setTryResourceExpr(assignClientVariableWithCreateMethodExpr(clientVarExpr)) + .setTryBody( + bodyExprs.stream().map(e -> ExprStatement.withExpr(e)).collect(Collectors.toList())) + .setIsSampleCode(true) + .build(); + } + // ==================================Helpers===================================================// // Create a list of RPC method arguments' variable expressions. diff --git a/src/test/java/com/google/api/generator/gapic/composer/ServiceClientSampleCodeComposerTest.java b/src/test/java/com/google/api/generator/gapic/composer/ServiceClientSampleCodeComposerTest.java index e902ef0177..b3a923de7c 100644 --- a/src/test/java/com/google/api/generator/gapic/composer/ServiceClientSampleCodeComposerTest.java +++ b/src/test/java/com/google/api/generator/gapic/composer/ServiceClientSampleCodeComposerTest.java @@ -23,6 +23,7 @@ import com.google.api.generator.engine.ast.VaporReference; import com.google.api.generator.gapic.composer.samplecode.SampleCodeWriter; import com.google.api.generator.gapic.model.Field; +import com.google.api.generator.gapic.model.LongrunningOperation; import com.google.api.generator.gapic.model.Message; import com.google.api.generator.gapic.model.Method; import com.google.api.generator.gapic.model.MethodArgument; @@ -40,6 +41,8 @@ public class ServiceClientSampleCodeComposerTest { private static final String SHOWCASE_PACKAGE_NAME = "com.google.showcase.v1beta1"; + private static final String LRO_PACKAGE_NAME = "com.google.longrunning"; + private static final String PROTO_PACKAGE_NAME = "com.google.protobuf"; // =======================================RPC Method Header Sample Code=======================// @Test @@ -128,6 +131,76 @@ public void validComposeRpcMethodHeaderSampleCode_pagedUnaryRpc() { assertEquals(expected, results); } + @Test + public void validComposeRpcMethodHeaderSampleCode_lroUnaryRpc() { + FileDescriptor echoFileDescriptor = EchoOuterClass.getDescriptor(); + Map resourceNames = Parser.parseResourceNames(echoFileDescriptor); + Map messageTypes = Parser.parseMessages(echoFileDescriptor); + TypeNode clientType = + TypeNode.withReference( + VaporReference.builder() + .setName("EchoClient") + .setPakkage(SHOWCASE_PACKAGE_NAME) + .build()); + TypeNode inputType = + TypeNode.withReference( + VaporReference.builder() + .setName("WaitRequest") + .setPakkage(SHOWCASE_PACKAGE_NAME) + .build()); + TypeNode outputType = + TypeNode.withReference( + VaporReference.builder().setName("Operation").setPakkage(LRO_PACKAGE_NAME).build()); + TypeNode responseType = + TypeNode.withReference( + VaporReference.builder() + .setName("WaitResponse") + .setPakkage(SHOWCASE_PACKAGE_NAME) + .build()); + TypeNode metadataType = + TypeNode.withReference( + VaporReference.builder() + .setName("WaitMetadata") + .setPakkage(SHOWCASE_PACKAGE_NAME) + .build()); + LongrunningOperation lro = LongrunningOperation.withTypes(responseType, metadataType); + TypeNode ttlTypeNode = + TypeNode.withReference( + VaporReference.builder().setName("Duration").setPakkage(PROTO_PACKAGE_NAME).build()); + MethodArgument ttl = + MethodArgument.builder() + .setName("ttl") + .setType(ttlTypeNode) + .setField( + Field.builder() + .setName("ttl") + .setType(ttlTypeNode) + .setIsMessage(true) + .setIsContainedInOneof(true) + .build()) + .build(); + List arguments = Arrays.asList(ttl); + Method method = + Method.builder() + .setName("Wait") + .setInputType(inputType) + .setOutputType(outputType) + .setLro(lro) + .setMethodSignatures(Arrays.asList(arguments)) + .build(); + + String results = + ServiceClientSampleCodeComposer.composeRpcMethodHeaderSampleCode( + method, clientType, arguments, resourceNames, messageTypes); + String expected = + LineFormatter.lines( + "try (EchoClient echoClient = EchoClient.create()) {\n", + " Duration ttl = Duration.newBuilder().build();\n", + " WaitResponse response = echoClient.waitAsync(ttl).get();\n", + "}"); + assertEquals(results, expected); + } + @Test public void invalidComposeRpcMethodHeaderSampleCode_noMatchedRepeatedResponseTypeInPagedMethod() { FileDescriptor echoFileDescriptor = EchoOuterClass.getDescriptor(); @@ -770,7 +843,7 @@ public void composeUnaryRpcMethodSampleCode_methodReturnVoid() { .build()); TypeNode outputType = TypeNode.withReference( - VaporReference.builder().setName("Empty").setPakkage("com.google.protobuf").build()); + VaporReference.builder().setName("Empty").setPakkage(PROTO_PACKAGE_NAME).build()); List> methodSignatures = Arrays.asList( Arrays.asList( @@ -921,4 +994,142 @@ public void composeUnaryPagedRpcMethodSampleCode_noMethodArguments() { "}"); assertEquals(results, expected); } + + // ===================================Unary LRO RPC Method Sample Code ======================// + @Test + public void composeUnaryLroRpcMethodSampleCode_lroReturnResponseType() { + FileDescriptor echoFileDescriptor = EchoOuterClass.getDescriptor(); + Map resourceNames = Parser.parseResourceNames(echoFileDescriptor); + TypeNode clientType = + TypeNode.withReference( + VaporReference.builder() + .setName("EchoClient") + .setPakkage(SHOWCASE_PACKAGE_NAME) + .build()); + TypeNode inputType = + TypeNode.withReference( + VaporReference.builder() + .setName("WaitRequest") + .setPakkage(SHOWCASE_PACKAGE_NAME) + .build()); + TypeNode outputType = + TypeNode.withReference( + VaporReference.builder().setName("Operation").setPakkage(LRO_PACKAGE_NAME).build()); + TypeNode responseType = + TypeNode.withReference( + VaporReference.builder() + .setName("WaitResponse") + .setPakkage(SHOWCASE_PACKAGE_NAME) + .build()); + TypeNode metadataType = + TypeNode.withReference( + VaporReference.builder() + .setName("WaitMetadata") + .setPakkage(SHOWCASE_PACKAGE_NAME) + .build()); + LongrunningOperation lro = LongrunningOperation.withTypes(responseType, metadataType); + TypeNode ttlTypeNode = + TypeNode.withReference( + VaporReference.builder().setName("Duration").setPakkage(PROTO_PACKAGE_NAME).build()); + MethodArgument ttl = + MethodArgument.builder() + .setName("ttl") + .setType(ttlTypeNode) + .setField( + Field.builder() + .setName("ttl") + .setType(ttlTypeNode) + .setIsMessage(true) + .setIsContainedInOneof(true) + .build()) + .build(); + List arguments = Arrays.asList(ttl); + Method method = + Method.builder() + .setName("Wait") + .setInputType(inputType) + .setOutputType(outputType) + .setLro(lro) + .setMethodSignatures(Arrays.asList(arguments)) + .build(); + + String results = + SampleCodeWriter.write( + ServiceClientSampleCodeComposer.composeUnaryLroRpcMethodSampleCode( + method, clientType, arguments, resourceNames)); + String expected = + LineFormatter.lines( + "try (EchoClient echoClient = EchoClient.create()) {\n", + " Duration ttl = Duration.newBuilder().build();\n", + " WaitResponse response = echoClient.waitAsync(ttl).get();\n", + "}"); + assertEquals(results, expected); + } + + @Test + public void composeUnaryLroRpcMethodSampleCode_lroReturnVoid() { + FileDescriptor echoFileDescriptor = EchoOuterClass.getDescriptor(); + Map resourceNames = Parser.parseResourceNames(echoFileDescriptor); + TypeNode clientType = + TypeNode.withReference( + VaporReference.builder() + .setName("EchoClient") + .setPakkage(SHOWCASE_PACKAGE_NAME) + .build()); + TypeNode inputType = + TypeNode.withReference( + VaporReference.builder() + .setName("WaitRequest") + .setPakkage(SHOWCASE_PACKAGE_NAME) + .build()); + TypeNode outputType = + TypeNode.withReference( + VaporReference.builder().setName("Operation").setPakkage(LRO_PACKAGE_NAME).build()); + TypeNode responseType = + TypeNode.withReference( + VaporReference.builder().setName("Empty").setPakkage(PROTO_PACKAGE_NAME).build()); + TypeNode metadataType = + TypeNode.withReference( + VaporReference.builder() + .setName("WaitMetadata") + .setPakkage(SHOWCASE_PACKAGE_NAME) + .build()); + LongrunningOperation lro = LongrunningOperation.withTypes(responseType, metadataType); + TypeNode ttlTypeNode = + TypeNode.withReference( + VaporReference.builder().setName("Duration").setPakkage(PROTO_PACKAGE_NAME).build()); + MethodArgument ttl = + MethodArgument.builder() + .setName("ttl") + .setType(ttlTypeNode) + .setField( + Field.builder() + .setName("ttl") + .setType(ttlTypeNode) + .setIsMessage(true) + .setIsContainedInOneof(true) + .build()) + .build(); + List arguments = Arrays.asList(ttl); + Method method = + Method.builder() + .setName("Wait") + .setInputType(inputType) + .setOutputType(outputType) + .setLro(lro) + .setMethodSignatures(Arrays.asList(arguments)) + .build(); + + String results = + SampleCodeWriter.write( + ServiceClientSampleCodeComposer.composeUnaryLroRpcMethodSampleCode( + method, clientType, arguments, resourceNames)); + String expected = + LineFormatter.lines( + "try (EchoClient echoClient = EchoClient.create()) {\n", + " Duration ttl = Duration.newBuilder().build();\n", + " echoClient.waitAsync(ttl).get();\n", + "}"); + assertEquals(results, expected); + } } diff --git a/src/test/java/com/google/api/generator/gapic/composer/goldens/EchoClient.golden b/src/test/java/com/google/api/generator/gapic/composer/goldens/EchoClient.golden index 69a21d3a3d..f8d375314b 100644 --- a/src/test/java/com/google/api/generator/gapic/composer/goldens/EchoClient.golden +++ b/src/test/java/com/google/api/generator/gapic/composer/goldens/EchoClient.golden @@ -403,6 +403,15 @@ public class EchoClient implements BackgroundResource { // AUTO-GENERATED DOCUMENTATION AND METHOD. /** + * Sample code: + * + *
{@code
+   * try (EchoClient echoClient = EchoClient.create()) {
+   *   Duration ttl = Duration.newBuilder().build();
+   *   WaitResponse response = echoClient.waitAsync(ttl).get();
+   * }
+   * }
+ * * @param ttl * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -413,6 +422,15 @@ public class EchoClient implements BackgroundResource { // AUTO-GENERATED DOCUMENTATION AND METHOD. /** + * Sample code: + * + *
{@code
+   * try (EchoClient echoClient = EchoClient.create()) {
+   *   Timestamp endTime = Timestamp.newBuilder().build();
+   *   WaitResponse response = echoClient.waitAsync(endTime).get();
+   * }
+   * }
+ * * @param endTime * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ diff --git a/test/integration/goldens/redis/CloudRedisClient.java b/test/integration/goldens/redis/CloudRedisClient.java index b128a022e0..01c2dc68d9 100644 --- a/test/integration/goldens/redis/CloudRedisClient.java +++ b/test/integration/goldens/redis/CloudRedisClient.java @@ -391,6 +391,17 @@ public final UnaryCallable getInstanceCallable() { *

The returned operation is automatically deleted after a few hours, so there is no need to * call DeleteOperation. * + *

Sample code: + * + *

{@code
+   * try (CloudRedisClient cloudRedisClient = CloudRedisClient.create()) {
+   *   LocationName parent = LocationName.of("[PROJECT]", "[LOCATION]");
+   *   String instanceId = "instanceId902024336";
+   *   Instance instance = Instance.newBuilder().build();
+   *   Instance response = cloudRedisClient.createInstanceAsync(parent, instanceId, instance).get();
+   * }
+   * }
+ * * @param parent Required. The resource name of the instance location using the form: * `projects/{project_id}/locations/{location_id}` where `location_id` refers to a GCP region. * @param instanceId Required. The logical name of the Redis instance in the customer project with @@ -432,6 +443,17 @@ public final OperationFuture createInstanceAsync( *

The returned operation is automatically deleted after a few hours, so there is no need to * call DeleteOperation. * + *

Sample code: + * + *

{@code
+   * try (CloudRedisClient cloudRedisClient = CloudRedisClient.create()) {
+   *   String parent = LocationName.of("[PROJECT]", "[LOCATION]").toString();
+   *   String instanceId = "instanceId902024336";
+   *   Instance instance = Instance.newBuilder().build();
+   *   Instance response = cloudRedisClient.createInstanceAsync(parent, instanceId, instance).get();
+   * }
+   * }
+ * * @param parent Required. The resource name of the instance location using the form: * `projects/{project_id}/locations/{location_id}` where `location_id` refers to a GCP region. * @param instanceId Required. The logical name of the Redis instance in the customer project with @@ -532,6 +554,16 @@ public final UnaryCallable createInstanceCalla * The returned operation is automatically deleted after a few hours, so there is no need to call * DeleteOperation. * + *

Sample code: + * + *

{@code
+   * try (CloudRedisClient cloudRedisClient = CloudRedisClient.create()) {
+   *   FieldMask updateMask = FieldMask.newBuilder().build();
+   *   Instance instance = Instance.newBuilder().build();
+   *   Instance response = cloudRedisClient.updateInstanceAsync(updateMask, instance).get();
+   * }
+   * }
+ * * @param updateMask Required. Mask of fields to update. At least one path must be supplied in * this field. The elements of the repeated paths field may only include these fields from * [Instance][google.cloud.redis.v1.Instance]: @@ -595,6 +627,16 @@ public final UnaryCallable updateInstanceCalla /** * Upgrades Redis instance to the newer Redis version specified in the request. * + *

Sample code: + * + *

{@code
+   * try (CloudRedisClient cloudRedisClient = CloudRedisClient.create()) {
+   *   InstanceName name = InstanceName.of("[PROJECT]", "[LOCATION]", "[INSTANCE]");
+   *   String redisVersion = "redisVersion-1972584739";
+   *   Instance response = cloudRedisClient.upgradeInstanceAsync(name, redisVersion).get();
+   * }
+   * }
+ * * @param name Required. Redis instance resource name using the form: * `projects/{project_id}/locations/{location_id}/instances/{instance_id}` where `location_id` * refers to a GCP region. @@ -615,6 +657,16 @@ public final OperationFuture upgradeInstanceAsync( /** * Upgrades Redis instance to the newer Redis version specified in the request. * + *

Sample code: + * + *

{@code
+   * try (CloudRedisClient cloudRedisClient = CloudRedisClient.create()) {
+   *   String name = InstanceName.of("[PROJECT]", "[LOCATION]", "[INSTANCE]").toString();
+   *   String redisVersion = "redisVersion-1972584739";
+   *   Instance response = cloudRedisClient.upgradeInstanceAsync(name, redisVersion).get();
+   * }
+   * }
+ * * @param name Required. Redis instance resource name using the form: * `projects/{project_id}/locations/{location_id}/instances/{instance_id}` where `location_id` * refers to a GCP region. @@ -671,6 +723,16 @@ public final UnaryCallable upgradeInstanceCal *

The returned operation is automatically deleted after a few hours, so there is no need to * call DeleteOperation. * + *

Sample code: + * + *

{@code
+   * try (CloudRedisClient cloudRedisClient = CloudRedisClient.create()) {
+   *   String name = "name3373707";
+   *   InputConfig inputConfig = InputConfig.newBuilder().build();
+   *   Instance response = cloudRedisClient.importInstanceAsync(name, inputConfig).get();
+   * }
+   * }
+ * * @param name Required. Redis instance resource name using the form: * `projects/{project_id}/locations/{location_id}/instances/{instance_id}` where `location_id` * refers to a GCP region. @@ -744,6 +806,16 @@ public final UnaryCallable importInstanceCalla *

The returned operation is automatically deleted after a few hours, so there is no need to * call DeleteOperation. * + *

Sample code: + * + *

{@code
+   * try (CloudRedisClient cloudRedisClient = CloudRedisClient.create()) {
+   *   String name = "name3373707";
+   *   OutputConfig outputConfig = OutputConfig.newBuilder().build();
+   *   Instance response = cloudRedisClient.exportInstanceAsync(name, outputConfig).get();
+   * }
+   * }
+ * * @param name Required. Redis instance resource name using the form: * `projects/{project_id}/locations/{location_id}/instances/{instance_id}` where `location_id` * refers to a GCP region. @@ -810,6 +882,17 @@ public final UnaryCallable exportInstanceCalla * Initiates a failover of the master node to current replica node for a specific STANDARD tier * Cloud Memorystore for Redis instance. * + *

Sample code: + * + *

{@code
+   * try (CloudRedisClient cloudRedisClient = CloudRedisClient.create()) {
+   *   InstanceName name = InstanceName.of("[PROJECT]", "[LOCATION]", "[INSTANCE]");
+   *   FailoverInstanceRequest.DataProtectionMode dataProtectionMode =
+   *       FailoverInstanceRequest.DataProtectionMode.forNumber(0);
+   *   Instance response = cloudRedisClient.failoverInstanceAsync(name, dataProtectionMode).get();
+   * }
+   * }
+ * * @param name Required. Redis instance resource name using the form: * `projects/{project_id}/locations/{location_id}/instances/{instance_id}` where `location_id` * refers to a GCP region. @@ -832,6 +915,17 @@ public final OperationFuture failoverInstanceAsync( * Initiates a failover of the master node to current replica node for a specific STANDARD tier * Cloud Memorystore for Redis instance. * + *

Sample code: + * + *

{@code
+   * try (CloudRedisClient cloudRedisClient = CloudRedisClient.create()) {
+   *   String name = InstanceName.of("[PROJECT]", "[LOCATION]", "[INSTANCE]").toString();
+   *   FailoverInstanceRequest.DataProtectionMode dataProtectionMode =
+   *       FailoverInstanceRequest.DataProtectionMode.forNumber(0);
+   *   Instance response = cloudRedisClient.failoverInstanceAsync(name, dataProtectionMode).get();
+   * }
+   * }
+ * * @param name Required. Redis instance resource name using the form: * `projects/{project_id}/locations/{location_id}/instances/{instance_id}` where `location_id` * refers to a GCP region. @@ -889,6 +983,15 @@ public final UnaryCallable failoverInstanceC /** * Deletes a specific Redis instance. Instance stops serving and data is deleted. * + *

Sample code: + * + *

{@code
+   * try (CloudRedisClient cloudRedisClient = CloudRedisClient.create()) {
+   *   InstanceName name = InstanceName.of("[PROJECT]", "[LOCATION]", "[INSTANCE]");
+   *   cloudRedisClient.deleteInstanceAsync(name).get();
+   * }
+   * }
+ * * @param name Required. Redis instance resource name using the form: * `projects/{project_id}/locations/{location_id}/instances/{instance_id}` where `location_id` * refers to a GCP region. @@ -904,6 +1007,15 @@ public final OperationFuture deleteInstanceAsync(Insta /** * Deletes a specific Redis instance. Instance stops serving and data is deleted. * + *

Sample code: + * + *

{@code
+   * try (CloudRedisClient cloudRedisClient = CloudRedisClient.create()) {
+   *   String name = InstanceName.of("[PROJECT]", "[LOCATION]", "[INSTANCE]").toString();
+   *   cloudRedisClient.deleteInstanceAsync(name).get();
+   * }
+   * }
+ * * @param name Required. Redis instance resource name using the form: * `projects/{project_id}/locations/{location_id}/instances/{instance_id}` where `location_id` * refers to a GCP region.