diff --git a/.github/workflows/ci-maven.yaml b/.github/workflows/ci-maven.yaml index 4c209508dd..e614dea160 100644 --- a/.github/workflows/ci-maven.yaml +++ b/.github/workflows/ci-maven.yaml @@ -106,7 +106,6 @@ jobs: with: java-version: 8 distribution: temurin - cache: maven - run: java -version - name: Run tests in Java 8 with the source compiled in Java 11 for gapic-generator-java shell: bash diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 6b773f3046..85fdd75ab4 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -38,4 +38,4 @@ for gapic-generator-java's Bazel build. ```sh mvn fmt:format - ``` + ``` \ No newline at end of file diff --git a/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractTransportServiceStubClassComposer.java b/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractTransportServiceStubClassComposer.java index d217196a0a..b566ed5a18 100644 --- a/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractTransportServiceStubClassComposer.java +++ b/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractTransportServiceStubClassComposer.java @@ -649,6 +649,7 @@ protected List createConstructorMethods( if (generateOperationsStubLogic(service)) { secondCtorExprs.addAll( createOperationsStubInitExpr( + context, service, thisExpr, operationsStubClassVarExpr, @@ -758,6 +759,7 @@ protected List createConstructorMethods( } protected List createOperationsStubInitExpr( + GapicContext context, Service service, Expr thisExpr, VariableExpr operationsStubClassVarExpr, diff --git a/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/rest/HttpJsonServiceStubClassComposer.java b/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/rest/HttpJsonServiceStubClassComposer.java index 2712dd4638..c1be8dc67b 100644 --- a/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/rest/HttpJsonServiceStubClassComposer.java +++ b/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/rest/HttpJsonServiceStubClassComposer.java @@ -14,6 +14,7 @@ package com.google.api.generator.gapic.composer.rest; +import com.google.api.HttpRule; import com.google.api.core.InternalApi; import com.google.api.gax.httpjson.ApiMethodDescriptor; import com.google.api.gax.httpjson.ApiMethodDescriptor.MethodType; @@ -63,6 +64,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.BiMap; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.protobuf.TypeRegistry; import java.util.ArrayList; import java.util.Arrays; @@ -73,10 +75,10 @@ import java.util.Set; import java.util.function.BiFunction; import java.util.function.Function; +import java.util.function.Predicate; import java.util.stream.Collectors; public class HttpJsonServiceStubClassComposer extends AbstractTransportServiceStubClassComposer { - private static final HttpJsonServiceStubClassComposer INSTANCE = new HttpJsonServiceStubClassComposer(); @@ -89,6 +91,7 @@ public class HttpJsonServiceStubClassComposer extends AbstractTransportServiceSt .setType(FIXED_REST_TYPESTORE.get(TypeRegistry.class.getSimpleName())) .build()) .build(); + private static final String LRO_NAME_PREFIX = "google.longrunning.Operations"; protected HttpJsonServiceStubClassComposer() { super(RestContext.instance()); @@ -109,7 +112,9 @@ private static TypeStore createStaticTypes() { HttpJsonCallSettings.class, HttpJsonOperationSnapshot.class, HttpJsonStubCallableFactory.class, + HttpRule.class, Map.class, + ImmutableMap.class, ProtoMessageRequestFormatter.class, ProtoMessageResponseParser.class, ProtoRestSerializer.class, @@ -1075,6 +1080,7 @@ private List getMethodTypeExpr(Method protoMethod) { @Override protected List createOperationsStubInitExpr( + GapicContext context, Service service, Expr thisExpr, VariableExpr operationsStubClassVarExpr, @@ -1089,6 +1095,47 @@ protected List createOperationsStubInitExpr( arguments.add(TYPE_REGISTRY_VAR_EXPR); } + // If the Service contains custom HttpRules for Operations, we pass a map of the custom rules to + // the Operations Client + Map operationCustomHttpRules = parseOperationsCustomHttpRules(context); + if (operationCustomHttpRules.size() > 0) { + Expr operationCustomHttpBindingsBuilderExpr = + MethodInvocationExpr.builder() + .setStaticReferenceType(FIXED_REST_TYPESTORE.get(ImmutableMap.class.getSimpleName())) + .setMethodName("builder") + .setGenerics( + Arrays.asList( + TypeNode.STRING.reference(), + FIXED_REST_TYPESTORE.get(HttpRule.class.getSimpleName()).reference())) + .build(); + + // Sorting is done to ensure consistent ordering of the entries in the Custom HttpRule Map + for (String selector : + operationCustomHttpRules.keySet().stream().sorted().collect(Collectors.toList())) { + HttpRule httpRule = operationCustomHttpRules.get(selector); + Expr httpRuleBuilderExpr = createHttpRuleExpr(httpRule, true); + + operationCustomHttpBindingsBuilderExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(operationCustomHttpBindingsBuilderExpr) + .setMethodName("put") + .setArguments( + Arrays.asList( + ValueExpr.withValue(StringObjectValue.withValue(selector)), + httpRuleBuilderExpr)) + .build(); + } + + operationCustomHttpBindingsBuilderExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(operationCustomHttpBindingsBuilderExpr) + .setMethodName("build") + .setReturnType(FIXED_REST_TYPESTORE.get(ImmutableMap.class.getSimpleName())) + .build(); + + arguments.add(operationCustomHttpBindingsBuilderExpr); + } + return Collections.singletonList( AssignmentExpr.builder() .setVariableExpr( @@ -1103,6 +1150,73 @@ protected List createOperationsStubInitExpr( .build()); } + /* Build an Expr that creates an HttpRule. Creates a builder and adds the http verb, custom path, and any additional bindings. `additional_bindings` can only be nested one layer deep, so we only check once */ + private Expr createHttpRuleExpr(HttpRule httpRule, boolean checkAdditionalBindings) { + Expr httpRuleBuilderExpr = + MethodInvocationExpr.builder() + .setStaticReferenceType(FIXED_REST_TYPESTORE.get(HttpRule.class.getSimpleName())) + .setMethodName("newBuilder") + .build(); + + httpRuleBuilderExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(httpRuleBuilderExpr) + // toLowerCase as the PatternCase result is all uppercase + .setMethodName(setMethodFormat(httpRule.getPatternCase().toString().toLowerCase())) + .setArguments( + ValueExpr.withValue( + StringObjectValue.withValue(getOperationsURIValueFromHttpRule(httpRule)))) + .setReturnType(FIXED_REST_TYPESTORE.get(HttpRule.class.getSimpleName())) + .build(); + + if (checkAdditionalBindings) { + for (HttpRule additionalBindings : httpRule.getAdditionalBindingsList()) { + httpRuleBuilderExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(httpRuleBuilderExpr) + .setMethodName("addAdditionalBindings") + .setArguments(Arrays.asList(createHttpRuleExpr(additionalBindings, false))) + .build(); + } + } + + httpRuleBuilderExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(httpRuleBuilderExpr) + .setMethodName("build") + .setReturnType(FIXED_REST_TYPESTORE.get(HttpRule.class.getSimpleName())) + .build(); + return httpRuleBuilderExpr; + } + + /* Parses the Service Yaml file's for custom HttpRules. Filter the HttpRules for ones that match Operations */ + Map parseOperationsCustomHttpRules(GapicContext context) { + Predicate predicate = x -> x.getSelector().contains(LRO_NAME_PREFIX); + com.google.api.Service service = context.serviceYamlProto(); + if (service == null || service.getHttp() == null) { + return ImmutableMap.of(); + } + return service.getHttp().getRulesList().stream() + .filter(predicate) + .collect(Collectors.toMap(HttpRule::getSelector, x -> x)); + } + + /* This is meant to be used for the OperationsClient Mixin OperationsClient's RPCs are mapped to GET/POST/DELETE and this function only expects those HttpVerbs to be used */ + String getOperationsURIValueFromHttpRule(HttpRule httpRule) { + switch (httpRule.getPatternCase().getNumber()) { + case 2: + return httpRule.getGet(); + case 4: + return httpRule.getPost(); + case 5: + return httpRule.getDelete(); + default: + throw new IllegalArgumentException( + "Operations HttpRule should only contain GET/POST/DELETE. Invalid: " + + httpRule.getSelector()); + } + } + @Override protected List createLongRunningClient(Service service, TypeStore typeStore) { Method pollingMethod = service.operationPollingMethod(); diff --git a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpcrest/GrpcRestTestProtoLoader.java b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpcrest/GrpcRestTestProtoLoader.java index 1e3b057170..c94dfa3d9b 100644 --- a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpcrest/GrpcRestTestProtoLoader.java +++ b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpcrest/GrpcRestTestProtoLoader.java @@ -14,6 +14,7 @@ package com.google.api.generator.gapic.composer.grpcrest; +import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -26,6 +27,7 @@ import com.google.api.generator.gapic.model.Transport; import com.google.api.generator.gapic.protoparser.Parser; import com.google.api.generator.gapic.protoparser.ServiceConfigParser; +import com.google.api.generator.gapic.protoparser.ServiceYamlParser; import com.google.longrunning.OperationsProto; import com.google.protobuf.Descriptors.FileDescriptor; import com.google.protobuf.Descriptors.ServiceDescriptor; @@ -58,6 +60,13 @@ public GapicContext parseShowcaseEcho() { ServiceDescriptor echoServiceDescriptor = echoFileDescriptor.getServices().get(0); assertEquals("Echo", echoServiceDescriptor.getName()); + String serviceYamlFileName = "echo_v1beta1.yaml"; + Path serviceYamlPath = Paths.get(getTestFilesDirectory(), serviceYamlFileName); + Optional serviceYamlOpt = + ServiceYamlParser.parse(serviceYamlPath.toString()); + assertThat(serviceYamlOpt.isPresent()).isTrue(); + com.google.api.Service service = serviceYamlOpt.get(); + Map messageTypes = Parser.parseMessages(echoFileDescriptor); messageTypes.putAll(Parser.parseMessages(OperationsProto.getDescriptor())); messageTypes.putAll(Parser.parseMessages(StructProto.getDescriptor())); @@ -66,7 +75,7 @@ public GapicContext parseShowcaseEcho() { Set outputResourceNames = new HashSet<>(); List services = Parser.parseService( - echoFileDescriptor, messageTypes, resourceNames, Optional.empty(), outputResourceNames); + echoFileDescriptor, messageTypes, resourceNames, serviceYamlOpt, outputResourceNames); String jsonFilename = "showcase_grpc_service_config.json"; Path jsonPath = Paths.get(getTestFilesDirectory(), jsonFilename); @@ -79,6 +88,7 @@ public GapicContext parseShowcaseEcho() { .setResourceNames(resourceNames) .setServices(services) .setServiceConfig(config) + .setServiceYamlProto(service) .setHelperResourceNames(outputResourceNames) .setTransport(getTransport()) .build(); diff --git a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpcrest/goldens/HttpJsonEchoStub.golden b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpcrest/goldens/HttpJsonEchoStub.golden index d027d9ed10..f0323ff116 100644 --- a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpcrest/goldens/HttpJsonEchoStub.golden +++ b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpcrest/goldens/HttpJsonEchoStub.golden @@ -3,6 +3,7 @@ package com.google.showcase.grpcrest.v1beta1.stub; import static com.google.showcase.grpcrest.v1beta1.EchoClient.PagedExpandPagedResponse; import static com.google.showcase.grpcrest.v1beta1.EchoClient.SimplePagedExpandPagedResponse; +import com.google.api.HttpRule; import com.google.api.core.BetaApi; import com.google.api.core.InternalApi; import com.google.api.gax.core.BackgroundResource; @@ -21,6 +22,7 @@ import com.google.api.gax.rpc.ClientContext; import com.google.api.gax.rpc.OperationCallable; import com.google.api.gax.rpc.ServerStreamingCallable; import com.google.api.gax.rpc.UnaryCallable; +import com.google.common.collect.ImmutableMap; import com.google.longrunning.Operation; import com.google.protobuf.TypeRegistry; import com.google.showcase.grpcrest.v1beta1.BlockRequest; @@ -382,7 +384,56 @@ public class HttpJsonEchoStub extends EchoStub { throws IOException { this.callableFactory = callableFactory; this.httpJsonOperationsStub = - HttpJsonOperationsStub.create(clientContext, callableFactory, typeRegistry); + HttpJsonOperationsStub.create( + clientContext, + callableFactory, + typeRegistry, + ImmutableMap.builder() + .put( + "google.longrunning.Operations.CancelOperation", + HttpRule.newBuilder() + .setPost("/v1beta1/{name=operations/**}:cancel") + .addAdditionalBindings( + HttpRule.newBuilder() + .setPost("/v1beta2/{name=operations/**}:cancel") + .build()) + .addAdditionalBindings( + HttpRule.newBuilder() + .setPost("/v1beta3/{name=operations/**}:cancel") + .build()) + .build()) + .put( + "google.longrunning.Operations.DeleteOperation", + HttpRule.newBuilder() + .setDelete("/v1beta1/{name=operations/**}") + .addAdditionalBindings( + HttpRule.newBuilder() + .setDelete("/v1beta2/{name=operations/**}") + .build()) + .addAdditionalBindings( + HttpRule.newBuilder() + .setDelete("/v1beta3/{name=operations/**}") + .build()) + .build()) + .put( + "google.longrunning.Operations.GetOperation", + HttpRule.newBuilder() + .setGet("/v1beta1/{name=operations/**}") + .addAdditionalBindings( + HttpRule.newBuilder().setGet("/v1beta2/{name=operations/**}").build()) + .addAdditionalBindings( + HttpRule.newBuilder().setGet("/v1beta3/{name=operations/**}").build()) + .build()) + .put( + "google.longrunning.Operations.ListOperations", + HttpRule.newBuilder() + .setGet("/v1beta1/operations") + .addAdditionalBindings( + HttpRule.newBuilder().setGet("/v1beta2/operations").build()) + .addAdditionalBindings( + HttpRule.newBuilder().setGet("/v1beta3/operations").build()) + .build()) + .build()); HttpJsonCallSettings echoTransportSettings = HttpJsonCallSettings.newBuilder() diff --git a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/HttpJsonServiceStubClassComposerTest.java b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/HttpJsonServiceStubClassComposerTest.java index 243ef54ce2..9892b251f7 100644 --- a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/HttpJsonServiceStubClassComposerTest.java +++ b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/HttpJsonServiceStubClassComposerTest.java @@ -14,6 +14,11 @@ package com.google.api.generator.gapic.composer.rest; +import static org.junit.Assert.assertThrows; + +import com.google.api.CustomHttpPattern; +import com.google.api.Http; +import com.google.api.HttpRule; import com.google.api.generator.engine.ast.TypeNode; import com.google.api.generator.engine.writer.JavaWriterVisitor; import com.google.api.generator.gapic.model.Field; @@ -23,9 +28,12 @@ import com.google.api.generator.gapic.model.Service; import com.google.api.generator.test.framework.Assert; import com.google.api.generator.test.framework.Utils; +import com.google.common.collect.ImmutableList; import com.google.common.truth.Truth; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.List; +import java.util.Map; import org.junit.Before; import org.junit.Test; @@ -39,10 +47,10 @@ public void setUp() throws Exception { } @Test - public void generateServiceClasses() { + public void generateComplianceServiceClasses() { GapicContext context = RestTestProtoLoader.instance().parseCompliance(); - Service echoProtoService = context.services().get(0); - GapicClass clazz = composer.generate(context, echoProtoService); + Service complianceProtoServices = context.services().get(0); + GapicClass clazz = composer.generate(context, complianceProtoServices); JavaWriterVisitor visitor = new JavaWriterVisitor(); clazz.classDefinition().accept(visitor); @@ -52,6 +60,19 @@ public void generateServiceClasses() { Assert.assertCodeEquals(goldenFilePath, visitor.write()); } + @Test + public void generateEchoServiceClasses() { + GapicContext context = RestTestProtoLoader.instance().parseEcho(); + Service echoProtoService = context.services().get(0); + GapicClass clazz = composer.generate(context, echoProtoService); + + JavaWriterVisitor visitor = new JavaWriterVisitor(); + clazz.classDefinition().accept(visitor); + Utils.saveCodegenToFile(this.getClass(), "HttpJsonEchoStub.golden", visitor.write()); + Path goldenFilePath = Paths.get(Utils.getGoldenDir(this.getClass()), "HttpJsonEchoStub.golden"); + Assert.assertCodeEquals(goldenFilePath, visitor.write()); + } + @Test public void getBindingFieldMethodName_shouldReturnGetFieldListIfTheFieldIsInLastPositionAndIsRepeated() { @@ -96,4 +117,80 @@ public void getBindingFieldMethodName_shouldReturnGetFieldIfTheFieldIsNotInLastP String actual = composer.getBindingFieldMethodName(httpBinding, 4, 1, "Value"); Truth.assertThat(actual).isEqualTo("getValue"); } + + @Test + public void parseOperationsCustomHttpRules_shouldReturnMapIfContextContainsValidServiceYaml() { + List httpRuleList = + ImmutableList.of( + HttpRule.newBuilder() + .setSelector("google.longrunning.Operations.Get") + .setGet("testGet") + .build(), + HttpRule.newBuilder() + .setSelector("google.longrunning.Operations.Post") + .setPost("testPost") + .build(), + HttpRule.newBuilder() + .setSelector("google.longrunning.Operations.Delete") + .setDelete("testDelete") + .build()); + GapicContext contextServiceYaml = RestTestProtoLoader.instance().parseCompliance(); + contextServiceYaml = + contextServiceYaml + .toBuilder() + .setServiceYamlProto( + com.google.api.Service.newBuilder() + .setHttp(Http.newBuilder().addAllRules(httpRuleList)) + .build()) + .build(); + Map httpRuleMap = composer.parseOperationsCustomHttpRules(contextServiceYaml); + Truth.assertThat(httpRuleMap.isEmpty()).isFalse(); + Truth.assertThat(httpRuleMap.size()).isEqualTo(httpRuleList.size()); + } + + @Test + public void parseOperationsCustomHttpRules_shouldReturnEmptyMapIfContextHasInvalidServiceYaml() { + GapicContext contextNullServiceYaml = RestTestProtoLoader.instance().parseCompliance(); + contextNullServiceYaml = contextNullServiceYaml.toBuilder().setServiceYamlProto(null).build(); + Map httpRuleMapNull = + composer.parseOperationsCustomHttpRules(contextNullServiceYaml); + Truth.assertThat(httpRuleMapNull.isEmpty()).isTrue(); + + GapicContext contextEmptyServiceYaml = RestTestProtoLoader.instance().parseCompliance(); + contextNullServiceYaml = + contextEmptyServiceYaml + .toBuilder() + .setServiceYamlProto(com.google.api.Service.newBuilder().build()) + .build(); + Map httpRuleMapEmpty = + composer.parseOperationsCustomHttpRules(contextNullServiceYaml); + Truth.assertThat(httpRuleMapEmpty.isEmpty()).isTrue(); + } + + @Test + public void testGetOperationsURIValueFromHttpRule() { + HttpRule getHttpRule = HttpRule.newBuilder().setGet("Get").build(); + Truth.assertThat(composer.getOperationsURIValueFromHttpRule(getHttpRule)).isEqualTo("Get"); + HttpRule postHttpRule = HttpRule.newBuilder().setPost("Post").build(); + Truth.assertThat(composer.getOperationsURIValueFromHttpRule(postHttpRule)).isEqualTo("Post"); + HttpRule deleteHttpRule = HttpRule.newBuilder().setDelete("Delete").build(); + Truth.assertThat(composer.getOperationsURIValueFromHttpRule(deleteHttpRule)) + .isEqualTo("Delete"); + + HttpRule patchHttpRule = HttpRule.newBuilder().setPatch("Patch").build(); + assertThrows( + IllegalArgumentException.class, + () -> composer.getOperationsURIValueFromHttpRule(patchHttpRule)); + HttpRule putHttpRule = HttpRule.newBuilder().setPut("Put").build(); + assertThrows( + IllegalArgumentException.class, + () -> composer.getOperationsURIValueFromHttpRule(putHttpRule)); + HttpRule customHttpRule = + HttpRule.newBuilder() + .setCustom(CustomHttpPattern.newBuilder().setPath("Custom").build()) + .build(); + assertThrows( + IllegalArgumentException.class, + () -> composer.getOperationsURIValueFromHttpRule(customHttpRule)); + } } diff --git a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/RestTestProtoLoader.java b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/RestTestProtoLoader.java index 3713242c1f..fe76bb2b16 100644 --- a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/RestTestProtoLoader.java +++ b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/RestTestProtoLoader.java @@ -14,8 +14,7 @@ package com.google.api.generator.gapic.composer.rest; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static com.google.common.truth.Truth.assertThat; import com.google.api.generator.gapic.composer.common.TestProtoLoader; import com.google.api.generator.gapic.model.GapicContext; @@ -26,9 +25,13 @@ import com.google.api.generator.gapic.model.Transport; import com.google.api.generator.gapic.protoparser.Parser; import com.google.api.generator.gapic.protoparser.ServiceConfigParser; +import com.google.api.generator.gapic.protoparser.ServiceYamlParser; +import com.google.longrunning.OperationsProto; import com.google.protobuf.Descriptors.FileDescriptor; import com.google.protobuf.Descriptors.ServiceDescriptor; +import com.google.protobuf.StructProto; import com.google.showcase.v1beta1.ComplianceOuterClass; +import com.google.showcase.v1beta1.EchoOuterClass; import java.nio.file.Path; import java.nio.file.Paths; import java.util.HashSet; @@ -49,21 +52,63 @@ public static RestTestProtoLoader instance() { } public GapicContext parseCompliance() { - FileDescriptor echoFileDescriptor = ComplianceOuterClass.getDescriptor(); + FileDescriptor complianceFileDescriptor = ComplianceOuterClass.getDescriptor(); + ServiceDescriptor complianceServiceDescriptor = complianceFileDescriptor.getServices().get(0); + assertThat(complianceServiceDescriptor.getName()).isEqualTo("Compliance"); + + Map messageTypes = Parser.parseMessages(complianceFileDescriptor); + Map resourceNames = Parser.parseResourceNames(complianceFileDescriptor); + Set outputResourceNames = new HashSet<>(); + List services = + Parser.parseService( + complianceFileDescriptor, + messageTypes, + resourceNames, + Optional.empty(), + outputResourceNames); + + String jsonFilename = "showcase_grpc_service_config.json"; + Path jsonPath = Paths.get(getTestFilesDirectory(), jsonFilename); + Optional configOpt = ServiceConfigParser.parse(jsonPath.toString()); + assertThat(configOpt.isPresent()).isTrue(); + GapicServiceConfig config = configOpt.get(); + + return GapicContext.builder() + .setMessages(messageTypes) + .setResourceNames(resourceNames) + .setServices(services) + .setServiceConfig(config) + .setHelperResourceNames(outputResourceNames) + .setTransport(getTransport()) + .setRestNumericEnumsEnabled(true) + .build(); + } + + public GapicContext parseEcho() { + FileDescriptor echoFileDescriptor = EchoOuterClass.getDescriptor(); ServiceDescriptor echoServiceDescriptor = echoFileDescriptor.getServices().get(0); - assertEquals(echoServiceDescriptor.getName(), "Compliance"); + assertThat(echoServiceDescriptor.getName()).isEqualTo("Echo"); + + String serviceYamlFileName = "echo_v1beta1.yaml"; + Path serviceYamlPath = Paths.get(getTestFilesDirectory(), serviceYamlFileName); + Optional serviceYamlOpt = + ServiceYamlParser.parse(serviceYamlPath.toString()); + assertThat(serviceYamlOpt.isPresent()).isTrue(); + com.google.api.Service service = serviceYamlOpt.get(); Map messageTypes = Parser.parseMessages(echoFileDescriptor); + messageTypes.putAll(Parser.parseMessages(OperationsProto.getDescriptor())); + messageTypes.putAll(Parser.parseMessages(StructProto.getDescriptor())); Map resourceNames = Parser.parseResourceNames(echoFileDescriptor); Set outputResourceNames = new HashSet<>(); List services = Parser.parseService( - echoFileDescriptor, messageTypes, resourceNames, Optional.empty(), outputResourceNames); + echoFileDescriptor, messageTypes, resourceNames, serviceYamlOpt, outputResourceNames); String jsonFilename = "showcase_grpc_service_config.json"; Path jsonPath = Paths.get(getTestFilesDirectory(), jsonFilename); Optional configOpt = ServiceConfigParser.parse(jsonPath.toString()); - assertTrue(configOpt.isPresent()); + assertThat(configOpt.isPresent()).isTrue(); GapicServiceConfig config = configOpt.get(); return GapicContext.builder() @@ -71,6 +116,7 @@ public GapicContext parseCompliance() { .setResourceNames(resourceNames) .setServices(services) .setServiceConfig(config) + .setServiceYamlProto(service) .setHelperResourceNames(outputResourceNames) .setTransport(getTransport()) .setRestNumericEnumsEnabled(true) diff --git a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/goldens/HttpJsonEchoStub.golden b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/goldens/HttpJsonEchoStub.golden new file mode 100644 index 0000000000..18904bdfbe --- /dev/null +++ b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/goldens/HttpJsonEchoStub.golden @@ -0,0 +1,607 @@ +package com.google.showcase.v1beta1.stub; + +import static com.google.showcase.v1beta1.EchoClient.PagedExpandPagedResponse; +import static com.google.showcase.v1beta1.EchoClient.SimplePagedExpandPagedResponse; + +import com.google.api.HttpRule; +import com.google.api.core.BetaApi; +import com.google.api.core.InternalApi; +import com.google.api.gax.core.BackgroundResource; +import com.google.api.gax.core.BackgroundResourceAggregation; +import com.google.api.gax.httpjson.ApiMethodDescriptor; +import com.google.api.gax.httpjson.HttpJsonCallSettings; +import com.google.api.gax.httpjson.HttpJsonOperationSnapshot; +import com.google.api.gax.httpjson.HttpJsonStubCallableFactory; +import com.google.api.gax.httpjson.ProtoMessageRequestFormatter; +import com.google.api.gax.httpjson.ProtoMessageResponseParser; +import com.google.api.gax.httpjson.ProtoRestSerializer; +import com.google.api.gax.httpjson.longrunning.stub.HttpJsonOperationsStub; +import com.google.api.gax.longrunning.OperationSnapshot; +import com.google.api.gax.rpc.BidiStreamingCallable; +import com.google.api.gax.rpc.ClientContext; +import com.google.api.gax.rpc.ClientStreamingCallable; +import com.google.api.gax.rpc.OperationCallable; +import com.google.api.gax.rpc.ServerStreamingCallable; +import com.google.api.gax.rpc.UnaryCallable; +import com.google.common.collect.ImmutableMap; +import com.google.longrunning.Operation; +import com.google.protobuf.TypeRegistry; +import com.google.showcase.v1beta1.BlockRequest; +import com.google.showcase.v1beta1.BlockResponse; +import com.google.showcase.v1beta1.EchoRequest; +import com.google.showcase.v1beta1.EchoResponse; +import com.google.showcase.v1beta1.ExpandRequest; +import com.google.showcase.v1beta1.Object; +import com.google.showcase.v1beta1.PagedExpandRequest; +import com.google.showcase.v1beta1.PagedExpandResponse; +import com.google.showcase.v1beta1.WaitMetadata; +import com.google.showcase.v1beta1.WaitRequest; +import com.google.showcase.v1beta1.WaitResponse; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import javax.annotation.Generated; + +// AUTO-GENERATED DOCUMENTATION AND CLASS. +/** + * REST stub implementation for the Echo service API. + * + *

This class is for advanced usage and reflects the underlying API directly. + */ +@BetaApi +@Generated("by gapic-generator-java") +public class HttpJsonEchoStub extends EchoStub { + private static final TypeRegistry typeRegistry = + TypeRegistry.newBuilder() + .add(WaitResponse.getDescriptor()) + .add(WaitMetadata.getDescriptor()) + .build(); + + private static final ApiMethodDescriptor echoMethodDescriptor = + ApiMethodDescriptor.newBuilder() + .setFullMethodName("google.showcase.v1beta1.Echo/Echo") + .setHttpMethod("POST") + .setType(ApiMethodDescriptor.MethodType.UNARY) + .setRequestFormatter( + ProtoMessageRequestFormatter.newBuilder() + .setPath( + "/v1beta1/echo:echo", + request -> { + Map fields = new HashMap<>(); + ProtoRestSerializer serializer = ProtoRestSerializer.create(); + return fields; + }) + .setQueryParamsExtractor( + request -> { + Map> fields = new HashMap<>(); + ProtoRestSerializer serializer = ProtoRestSerializer.create(); + serializer.putQueryParam(fields, "$alt", "json;enum-encoding=int"); + return fields; + }) + .setRequestBodyExtractor( + request -> + ProtoRestSerializer.create() + .toBody("*", request.toBuilder().build(), true)) + .build()) + .setResponseParser( + ProtoMessageResponseParser.newBuilder() + .setDefaultInstance(EchoResponse.getDefaultInstance()) + .setDefaultTypeRegistry(typeRegistry) + .build()) + .build(); + + private static final ApiMethodDescriptor expandMethodDescriptor = + ApiMethodDescriptor.newBuilder() + .setFullMethodName("google.showcase.v1beta1.Echo/Expand") + .setHttpMethod("POST") + .setType(ApiMethodDescriptor.MethodType.SERVER_STREAMING) + .setRequestFormatter( + ProtoMessageRequestFormatter.newBuilder() + .setPath( + "/v1beta1/echo:expand", + request -> { + Map fields = new HashMap<>(); + ProtoRestSerializer serializer = + ProtoRestSerializer.create(); + return fields; + }) + .setQueryParamsExtractor( + request -> { + Map> fields = new HashMap<>(); + ProtoRestSerializer serializer = + ProtoRestSerializer.create(); + serializer.putQueryParam(fields, "$alt", "json;enum-encoding=int"); + return fields; + }) + .setRequestBodyExtractor( + request -> + ProtoRestSerializer.create() + .toBody("*", request.toBuilder().build(), true)) + .build()) + .setResponseParser( + ProtoMessageResponseParser.newBuilder() + .setDefaultInstance(EchoResponse.getDefaultInstance()) + .setDefaultTypeRegistry(typeRegistry) + .build()) + .build(); + + private static final ApiMethodDescriptor + pagedExpandMethodDescriptor = + ApiMethodDescriptor.newBuilder() + .setFullMethodName("google.showcase.v1beta1.Echo/PagedExpand") + .setHttpMethod("POST") + .setType(ApiMethodDescriptor.MethodType.UNARY) + .setRequestFormatter( + ProtoMessageRequestFormatter.newBuilder() + .setPath( + "/v1beta1/echo:pagedExpand", + request -> { + Map fields = new HashMap<>(); + ProtoRestSerializer serializer = + ProtoRestSerializer.create(); + return fields; + }) + .setQueryParamsExtractor( + request -> { + Map> fields = new HashMap<>(); + ProtoRestSerializer serializer = + ProtoRestSerializer.create(); + serializer.putQueryParam(fields, "$alt", "json;enum-encoding=int"); + return fields; + }) + .setRequestBodyExtractor( + request -> + ProtoRestSerializer.create() + .toBody("*", request.toBuilder().build(), true)) + .build()) + .setResponseParser( + ProtoMessageResponseParser.newBuilder() + .setDefaultInstance(PagedExpandResponse.getDefaultInstance()) + .setDefaultTypeRegistry(typeRegistry) + .build()) + .build(); + + private static final ApiMethodDescriptor + simplePagedExpandMethodDescriptor = + ApiMethodDescriptor.newBuilder() + .setFullMethodName("google.showcase.v1beta1.Echo/SimplePagedExpand") + .setHttpMethod("POST") + .setType(ApiMethodDescriptor.MethodType.UNARY) + .setRequestFormatter( + ProtoMessageRequestFormatter.newBuilder() + .setPath( + "/v1beta1/echo:pagedExpand", + request -> { + Map fields = new HashMap<>(); + ProtoRestSerializer serializer = + ProtoRestSerializer.create(); + return fields; + }) + .setQueryParamsExtractor( + request -> { + Map> fields = new HashMap<>(); + ProtoRestSerializer serializer = + ProtoRestSerializer.create(); + serializer.putQueryParam(fields, "$alt", "json;enum-encoding=int"); + return fields; + }) + .setRequestBodyExtractor( + request -> + ProtoRestSerializer.create() + .toBody("*", request.toBuilder().build(), true)) + .build()) + .setResponseParser( + ProtoMessageResponseParser.newBuilder() + .setDefaultInstance(PagedExpandResponse.getDefaultInstance()) + .setDefaultTypeRegistry(typeRegistry) + .build()) + .build(); + + private static final ApiMethodDescriptor waitMethodDescriptor = + ApiMethodDescriptor.newBuilder() + .setFullMethodName("google.showcase.v1beta1.Echo/Wait") + .setHttpMethod("POST") + .setType(ApiMethodDescriptor.MethodType.UNARY) + .setRequestFormatter( + ProtoMessageRequestFormatter.newBuilder() + .setPath( + "/v1beta1/echo:wait", + request -> { + Map fields = new HashMap<>(); + ProtoRestSerializer serializer = ProtoRestSerializer.create(); + return fields; + }) + .setQueryParamsExtractor( + request -> { + Map> fields = new HashMap<>(); + ProtoRestSerializer serializer = ProtoRestSerializer.create(); + serializer.putQueryParam(fields, "$alt", "json;enum-encoding=int"); + return fields; + }) + .setRequestBodyExtractor( + request -> + ProtoRestSerializer.create() + .toBody("*", request.toBuilder().build(), true)) + .build()) + .setResponseParser( + ProtoMessageResponseParser.newBuilder() + .setDefaultInstance(Operation.getDefaultInstance()) + .setDefaultTypeRegistry(typeRegistry) + .build()) + .setOperationSnapshotFactory( + (WaitRequest request, Operation response) -> + HttpJsonOperationSnapshot.create(response)) + .build(); + + private static final ApiMethodDescriptor blockMethodDescriptor = + ApiMethodDescriptor.newBuilder() + .setFullMethodName("google.showcase.v1beta1.Echo/Block") + .setHttpMethod("POST") + .setType(ApiMethodDescriptor.MethodType.UNARY) + .setRequestFormatter( + ProtoMessageRequestFormatter.newBuilder() + .setPath( + "/v1beta1/echo:block", + request -> { + Map fields = new HashMap<>(); + ProtoRestSerializer serializer = ProtoRestSerializer.create(); + return fields; + }) + .setQueryParamsExtractor( + request -> { + Map> fields = new HashMap<>(); + ProtoRestSerializer serializer = ProtoRestSerializer.create(); + serializer.putQueryParam(fields, "$alt", "json;enum-encoding=int"); + return fields; + }) + .setRequestBodyExtractor( + request -> + ProtoRestSerializer.create() + .toBody("*", request.toBuilder().build(), true)) + .build()) + .setResponseParser( + ProtoMessageResponseParser.newBuilder() + .setDefaultInstance(BlockResponse.getDefaultInstance()) + .setDefaultTypeRegistry(typeRegistry) + .build()) + .build(); + + private static final ApiMethodDescriptor collideNameMethodDescriptor = + ApiMethodDescriptor.newBuilder() + .setFullMethodName("google.showcase.v1beta1.Echo/CollideName") + .setHttpMethod("POST") + .setType(ApiMethodDescriptor.MethodType.UNARY) + .setRequestFormatter( + ProtoMessageRequestFormatter.newBuilder() + .setPath( + "/v1beta1/echo:foo", + request -> { + Map fields = new HashMap<>(); + ProtoRestSerializer serializer = ProtoRestSerializer.create(); + return fields; + }) + .setQueryParamsExtractor( + request -> { + Map> fields = new HashMap<>(); + ProtoRestSerializer serializer = ProtoRestSerializer.create(); + serializer.putQueryParam(fields, "$alt", "json;enum-encoding=int"); + return fields; + }) + .setRequestBodyExtractor( + request -> + ProtoRestSerializer.create() + .toBody("*", request.toBuilder().build(), true)) + .build()) + .setResponseParser( + ProtoMessageResponseParser.newBuilder() + .setDefaultInstance(Object.getDefaultInstance()) + .setDefaultTypeRegistry(typeRegistry) + .build()) + .build(); + + private final UnaryCallable echoCallable; + private final ServerStreamingCallable expandCallable; + private final UnaryCallable pagedExpandCallable; + private final UnaryCallable + pagedExpandPagedCallable; + private final UnaryCallable simplePagedExpandCallable; + private final UnaryCallable + simplePagedExpandPagedCallable; + private final UnaryCallable waitCallable; + private final OperationCallable waitOperationCallable; + private final UnaryCallable blockCallable; + private final UnaryCallable collideNameCallable; + + private final BackgroundResource backgroundResources; + private final HttpJsonOperationsStub httpJsonOperationsStub; + private final HttpJsonStubCallableFactory callableFactory; + + public static final HttpJsonEchoStub create(EchoStubSettings settings) throws IOException { + return new HttpJsonEchoStub(settings, ClientContext.create(settings)); + } + + public static final HttpJsonEchoStub create(ClientContext clientContext) throws IOException { + return new HttpJsonEchoStub(EchoStubSettings.newBuilder().build(), clientContext); + } + + public static final HttpJsonEchoStub create( + ClientContext clientContext, HttpJsonStubCallableFactory callableFactory) throws IOException { + return new HttpJsonEchoStub( + EchoStubSettings.newBuilder().build(), clientContext, callableFactory); + } + + /** + * Constructs an instance of HttpJsonEchoStub, using the given settings. This is protected so that + * it is easy to make a subclass, but otherwise, the static factory methods should be preferred. + */ + protected HttpJsonEchoStub(EchoStubSettings settings, ClientContext clientContext) + throws IOException { + this(settings, clientContext, new HttpJsonEchoCallableFactory()); + } + + /** + * Constructs an instance of HttpJsonEchoStub, using the given settings. This is protected so that + * it is easy to make a subclass, but otherwise, the static factory methods should be preferred. + */ + protected HttpJsonEchoStub( + EchoStubSettings settings, + ClientContext clientContext, + HttpJsonStubCallableFactory callableFactory) + throws IOException { + this.callableFactory = callableFactory; + this.httpJsonOperationsStub = + HttpJsonOperationsStub.create( + clientContext, + callableFactory, + typeRegistry, + ImmutableMap.builder() + .put( + "google.longrunning.Operations.CancelOperation", + HttpRule.newBuilder() + .setPost("/v1beta1/{name=operations/**}:cancel") + .addAdditionalBindings( + HttpRule.newBuilder() + .setPost("/v1beta2/{name=operations/**}:cancel") + .build()) + .addAdditionalBindings( + HttpRule.newBuilder() + .setPost("/v1beta3/{name=operations/**}:cancel") + .build()) + .build()) + .put( + "google.longrunning.Operations.DeleteOperation", + HttpRule.newBuilder() + .setDelete("/v1beta1/{name=operations/**}") + .addAdditionalBindings( + HttpRule.newBuilder() + .setDelete("/v1beta2/{name=operations/**}") + .build()) + .addAdditionalBindings( + HttpRule.newBuilder() + .setDelete("/v1beta3/{name=operations/**}") + .build()) + .build()) + .put( + "google.longrunning.Operations.GetOperation", + HttpRule.newBuilder() + .setGet("/v1beta1/{name=operations/**}") + .addAdditionalBindings( + HttpRule.newBuilder().setGet("/v1beta2/{name=operations/**}").build()) + .addAdditionalBindings( + HttpRule.newBuilder().setGet("/v1beta3/{name=operations/**}").build()) + .build()) + .put( + "google.longrunning.Operations.ListOperations", + HttpRule.newBuilder() + .setGet("/v1beta1/operations") + .addAdditionalBindings( + HttpRule.newBuilder().setGet("/v1beta2/operations").build()) + .addAdditionalBindings( + HttpRule.newBuilder().setGet("/v1beta3/operations").build()) + .build()) + .build()); + + HttpJsonCallSettings echoTransportSettings = + HttpJsonCallSettings.newBuilder() + .setMethodDescriptor(echoMethodDescriptor) + .setTypeRegistry(typeRegistry) + .build(); + HttpJsonCallSettings expandTransportSettings = + HttpJsonCallSettings.newBuilder() + .setMethodDescriptor(expandMethodDescriptor) + .setTypeRegistry(typeRegistry) + .build(); + HttpJsonCallSettings pagedExpandTransportSettings = + HttpJsonCallSettings.newBuilder() + .setMethodDescriptor(pagedExpandMethodDescriptor) + .setTypeRegistry(typeRegistry) + .build(); + HttpJsonCallSettings + simplePagedExpandTransportSettings = + HttpJsonCallSettings.newBuilder() + .setMethodDescriptor(simplePagedExpandMethodDescriptor) + .setTypeRegistry(typeRegistry) + .build(); + HttpJsonCallSettings waitTransportSettings = + HttpJsonCallSettings.newBuilder() + .setMethodDescriptor(waitMethodDescriptor) + .setTypeRegistry(typeRegistry) + .build(); + HttpJsonCallSettings blockTransportSettings = + HttpJsonCallSettings.newBuilder() + .setMethodDescriptor(blockMethodDescriptor) + .setTypeRegistry(typeRegistry) + .build(); + HttpJsonCallSettings collideNameTransportSettings = + HttpJsonCallSettings.newBuilder() + .setMethodDescriptor(collideNameMethodDescriptor) + .setTypeRegistry(typeRegistry) + .build(); + + this.echoCallable = + callableFactory.createUnaryCallable( + echoTransportSettings, settings.echoSettings(), clientContext); + this.expandCallable = + callableFactory.createServerStreamingCallable( + expandTransportSettings, settings.expandSettings(), clientContext); + this.pagedExpandCallable = + callableFactory.createUnaryCallable( + pagedExpandTransportSettings, settings.pagedExpandSettings(), clientContext); + this.pagedExpandPagedCallable = + callableFactory.createPagedCallable( + pagedExpandTransportSettings, settings.pagedExpandSettings(), clientContext); + this.simplePagedExpandCallable = + callableFactory.createUnaryCallable( + simplePagedExpandTransportSettings, + settings.simplePagedExpandSettings(), + clientContext); + this.simplePagedExpandPagedCallable = + callableFactory.createPagedCallable( + simplePagedExpandTransportSettings, + settings.simplePagedExpandSettings(), + clientContext); + this.waitCallable = + callableFactory.createUnaryCallable( + waitTransportSettings, settings.waitSettings(), clientContext); + this.waitOperationCallable = + callableFactory.createOperationCallable( + waitTransportSettings, + settings.waitOperationSettings(), + clientContext, + httpJsonOperationsStub); + this.blockCallable = + callableFactory.createUnaryCallable( + blockTransportSettings, settings.blockSettings(), clientContext); + this.collideNameCallable = + callableFactory.createUnaryCallable( + collideNameTransportSettings, settings.collideNameSettings(), clientContext); + + this.backgroundResources = + new BackgroundResourceAggregation(clientContext.getBackgroundResources()); + } + + @InternalApi + public static List getMethodDescriptors() { + List methodDescriptors = new ArrayList<>(); + methodDescriptors.add(echoMethodDescriptor); + methodDescriptors.add(expandMethodDescriptor); + methodDescriptors.add(pagedExpandMethodDescriptor); + methodDescriptors.add(simplePagedExpandMethodDescriptor); + methodDescriptors.add(waitMethodDescriptor); + methodDescriptors.add(blockMethodDescriptor); + methodDescriptors.add(collideNameMethodDescriptor); + return methodDescriptors; + } + + public HttpJsonOperationsStub getHttpJsonOperationsStub() { + return httpJsonOperationsStub; + } + + @Override + public UnaryCallable echoCallable() { + return echoCallable; + } + + @Override + public ServerStreamingCallable expandCallable() { + return expandCallable; + } + + @Override + public UnaryCallable pagedExpandCallable() { + return pagedExpandCallable; + } + + @Override + public UnaryCallable pagedExpandPagedCallable() { + return pagedExpandPagedCallable; + } + + @Override + public UnaryCallable simplePagedExpandCallable() { + return simplePagedExpandCallable; + } + + @Override + public UnaryCallable + simplePagedExpandPagedCallable() { + return simplePagedExpandPagedCallable; + } + + @Override + public UnaryCallable waitCallable() { + return waitCallable; + } + + @Override + public OperationCallable waitOperationCallable() { + return waitOperationCallable; + } + + @Override + public UnaryCallable blockCallable() { + return blockCallable; + } + + @Override + public UnaryCallable collideNameCallable() { + return collideNameCallable; + } + + @Override + public ClientStreamingCallable collectCallable() { + throw new UnsupportedOperationException( + "Not implemented: collectCallable(). REST transport is not implemented for this method yet."); + } + + @Override + public BidiStreamingCallable chatCallable() { + throw new UnsupportedOperationException( + "Not implemented: chatCallable(). REST transport is not implemented for this method yet."); + } + + @Override + public BidiStreamingCallable chatAgainCallable() { + throw new UnsupportedOperationException( + "Not implemented: chatAgainCallable(). REST transport is not implemented for this method yet."); + } + + @Override + public final void close() { + try { + backgroundResources.close(); + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new IllegalStateException("Failed to close resource", e); + } + } + + @Override + public void shutdown() { + backgroundResources.shutdown(); + } + + @Override + public boolean isShutdown() { + return backgroundResources.isShutdown(); + } + + @Override + public boolean isTerminated() { + return backgroundResources.isTerminated(); + } + + @Override + public void shutdownNow() { + backgroundResources.shutdownNow(); + } + + @Override + public boolean awaitTermination(long duration, TimeUnit unit) throws InterruptedException { + return backgroundResources.awaitTermination(duration, unit); + } +} diff --git a/gapic-generator-java/src/test/resources/echo_v1beta1.yaml b/gapic-generator-java/src/test/resources/echo_v1beta1.yaml new file mode 100644 index 0000000000..321758a4ea --- /dev/null +++ b/gapic-generator-java/src/test/resources/echo_v1beta1.yaml @@ -0,0 +1,97 @@ +# Copyright 2020 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. + +type: google.api.Service +config_version: 3 +name: showcase.googleapis.com +title: Client Libraries Showcase API + +apis: + - name: google.showcase.v1beta1.Compliance + - name: google.showcase.v1beta1.Echo + - name: google.showcase.v1beta1.Identity + - name: google.showcase.v1beta1.Messaging + - name: google.showcase.v1beta1.SequenceService + - name: google.showcase.v1beta1.Testing + # Mix-in services + - name: 'google.cloud.location.Locations' + - name: 'google.iam.v1.IAMPolicy' + - name: 'google.longrunning.Operations' + +documentation: + summary: |- + Showcase represents both a model API and an integration testing surface for + client library generator consumption. + +backend: + rules: + - selector: 'google.cloud.location.Locations.*' + deadline: 60.0 + - selector: 'google.iam.v1.IAMPolicy.*' + deadline: 60.0 + - selector: 'google.longrunning.Operations.*' + deadline: 60.0 + +http: + rules: + - selector: google.cloud.location.Locations.ListLocations + get: '/v1beta1/{name=projects/*}/locations' + - selector: google.cloud.location.Locations.GetLocation + get: '/v1beta1/{name=projects/*/locations/*}' + - selector: google.iam.v1.IAMPolicy.SetIamPolicy + post: '/v1beta1/{resource=users/*}:setIamPolicy' + body: '*' + additional_bindings: + - post: '/v1beta1/{resource=rooms/*}:setIamPolicy' + body: '*' + - post: '/v1beta1/{resource=rooms/*/blurbs/*}:setIamPolicy' + body: '*' + - post: '/v1beta1/{resource=sequences/*}:setIamPolicy' + body: '*' + - selector: google.iam.v1.IAMPolicy.GetIamPolicy + get: '/v1beta1/{resource=users/*}:getIamPolicy' + additional_bindings: + - get: '/v1beta1/{resource=rooms/*}:getIamPolicy' + - get: '/v1beta1/{resource=rooms/*/blurbs/*}:getIamPolicy' + - get: '/v1beta1/{resource=sequences/*}:getIamPolicy' + - selector: google.iam.v1.IAMPolicy.TestIamPermissions + post: '/v1beta1/{resource=users/*}:testIamPermissions' + body: '*' + additional_bindings: + - post: '/v1beta1/{resource=rooms/*}:testIamPermissions' + body: '*' + - post: '/v1beta1/{resource=rooms/*/blurbs/*}:testIamPermissions' + body: '*' + - post: '/v1beta1/{resource=sequences/*}:testIamPermissions' + body: '*' + - selector: google.longrunning.Operations.ListOperations + get: '/v1beta1/operations' + additional_bindings: + - get: '/v1beta2/operations' + - get: '/v1beta3/operations' + - selector: google.longrunning.Operations.GetOperation + get: '/v1beta1/{name=operations/**}' + additional_bindings: + - get: '/v1beta2/{name=operations/**}' + - get: '/v1beta3/{name=operations/**}' + - selector: google.longrunning.Operations.DeleteOperation + delete: '/v1beta1/{name=operations/**}' + additional_bindings: + - delete: '/v1beta2/{name=operations/**}' + - delete: '/v1beta3/{name=operations/**}' + - selector: google.longrunning.Operations.CancelOperation + post: '/v1beta1/{name=operations/**}:cancel' + additional_bindings: + - post: '/v1beta2/{name=operations/**}:cancel' + - post: '/v1beta3/{name=operations/**}:cancel' \ No newline at end of file diff --git a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoMessageRequestFormatter.java b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoMessageRequestFormatter.java index f04a16edbc..fa3480ad23 100644 --- a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoMessageRequestFormatter.java +++ b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoMessageRequestFormatter.java @@ -147,6 +147,12 @@ public Builder setAdditionalPaths(String... rawAdditionalPaths) { return this; } + @InternalApi + public Builder updateRawPath(String rawPath) { + this.rawPath = rawPath; + return this; + } + @InternalApi public Builder updateRawPath(String target, String replacement) { this.rawPath = this.rawPath.replace(target, replacement); diff --git a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/longrunning/stub/HttpJsonOperationsStub.java b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/longrunning/stub/HttpJsonOperationsStub.java index 5759c41726..eb53b71ba6 100644 --- a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/longrunning/stub/HttpJsonOperationsStub.java +++ b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/longrunning/stub/HttpJsonOperationsStub.java @@ -29,6 +29,7 @@ */ package com.google.api.gax.httpjson.longrunning.stub; +import com.google.api.HttpRule; import com.google.api.client.http.HttpMethods; import com.google.api.core.InternalApi; import com.google.api.gax.core.BackgroundResource; @@ -45,6 +46,8 @@ import com.google.api.gax.rpc.ClientContext; import com.google.api.gax.rpc.LongRunningClient; import com.google.api.gax.rpc.UnaryCallable; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; import com.google.longrunning.CancelOperationRequest; import com.google.longrunning.DeleteOperationRequest; import com.google.longrunning.GetOperationRequest; @@ -52,28 +55,31 @@ import com.google.longrunning.ListOperationsResponse; import com.google.longrunning.Operation; import com.google.protobuf.Empty; -import com.google.protobuf.Message; import com.google.protobuf.TypeRegistry; import java.io.IOException; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; -import java.util.regex.Matcher; -import java.util.regex.Pattern; // AUTO-GENERATED DOCUMENTATION AND CLASS. /** * REST stub implementation for the Operations service API. * *

This class is for advanced usage and reflects the underlying API directly. + * + *

Note: This OperationsClient was originally auto-generated by the generator. There has been a + * few handwritten changes since then and has not been regenerated. */ public class HttpJsonOperationsStub extends OperationsStub { - private static final Pattern CLIENT_PACKAGE_VERSION_PATTERN = - Pattern.compile("\\.(?v\\d+[a-zA-Z]*\\d*[a-zA-Z]*\\d*)\\.[\\w.]*stub"); - - private static final ApiMethodDescriptor + private static final String LRO_LIST_OPERATIONS = "google.longrunning.Operations.ListOperations"; + private static final String LRO_GET_OPERATION = "google.longrunning.Operations.GetOperation"; + private static final String LRO_DELETE_OPERATION = + "google.longrunning.Operations.DeleteOperation"; + private static final String LRO_CANCEL_OPERATION = + "google.longrunning.Operations.CancelOperation"; + + private ApiMethodDescriptor listOperationsMethodDescriptor = ApiMethodDescriptor.newBuilder() .setFullMethodName("google.longrunning.Operations/ListOperations") @@ -107,85 +113,82 @@ public class HttpJsonOperationsStub extends OperationsStub { .build()) .build(); - private static final ApiMethodDescriptor - getOperationMethodDescriptor = - ApiMethodDescriptor.newBuilder() - .setFullMethodName("google.longrunning.Operations/GetOperation") - .setHttpMethod(HttpMethods.GET) - .setRequestFormatter( - ProtoMessageRequestFormatter.newBuilder() - .setPath( - "/v1/{name=**/operations/*}", - request -> { - Map fields = new HashMap<>(); - ProtoRestSerializer serializer = - ProtoRestSerializer.create(); - serializer.putPathParam(fields, "name", request.getName()); - return fields; - }) - .setQueryParamsExtractor(request -> new HashMap<>()) - .setRequestBodyExtractor(request -> null) - .build()) - .setResponseParser( - ProtoMessageResponseParser.newBuilder() - .setDefaultInstance(Operation.getDefaultInstance()) - .build()) - .setOperationSnapshotFactory( - (request, response) -> HttpJsonOperationSnapshot.create(response)) - .setPollingRequestFactory( - compoundOperationId -> - GetOperationRequest.newBuilder().setName(compoundOperationId).build()) - .build(); - - private static final ApiMethodDescriptor - deleteOperationMethodDescriptor = - ApiMethodDescriptor.newBuilder() - .setFullMethodName("google.longrunning.Operations/DeleteOperation") - .setHttpMethod(HttpMethods.DELETE) - .setRequestFormatter( - ProtoMessageRequestFormatter.newBuilder() - .setPath( - "/v1/{name=**/operations/*}", - request -> { - Map fields = new HashMap<>(); - ProtoRestSerializer serializer = - ProtoRestSerializer.create(); - serializer.putPathParam(fields, "name", request.getName()); - return fields; - }) - .setQueryParamsExtractor(request -> new HashMap<>()) - .setRequestBodyExtractor(request -> null) - .build()) - .setResponseParser( - ProtoMessageResponseParser.newBuilder() - .setDefaultInstance(Empty.getDefaultInstance()) - .build()) - .build(); - - private static final ApiMethodDescriptor - cancelOperationMethodDescriptor = - ApiMethodDescriptor.newBuilder() - .setFullMethodName("google.longrunning.Operations/CancelOperation") - .setHttpMethod(HttpMethods.POST) - .setRequestFormatter( - ProtoMessageRequestFormatter.newBuilder() - .setPath( - "/v1/{name=**/operations/*}:cancel", - request -> { - Map fields = new HashMap<>(); - ProtoRestSerializer serializer = - ProtoRestSerializer.create(); - serializer.putPathParam(fields, "name", request.getName()); - return fields; - }) - .setQueryParamsExtractor(request -> new HashMap<>()) - .setRequestBodyExtractor(request -> null) - .build()) - .setResponseParser( - ProtoMessageResponseParser.newBuilder() - .setDefaultInstance(Empty.getDefaultInstance()) - .build()) - .build(); + private ApiMethodDescriptor getOperationMethodDescriptor = + ApiMethodDescriptor.newBuilder() + .setFullMethodName("google.longrunning.Operations/GetOperation") + .setHttpMethod(HttpMethods.GET) + .setRequestFormatter( + ProtoMessageRequestFormatter.newBuilder() + .setPath( + "/v1/{name=**/operations/*}", + request -> { + Map fields = new HashMap<>(); + ProtoRestSerializer serializer = + ProtoRestSerializer.create(); + serializer.putPathParam(fields, "name", request.getName()); + return fields; + }) + .setQueryParamsExtractor(request -> new HashMap<>()) + .setRequestBodyExtractor(request -> null) + .build()) + .setResponseParser( + ProtoMessageResponseParser.newBuilder() + .setDefaultInstance(Operation.getDefaultInstance()) + .build()) + .setOperationSnapshotFactory( + (request, response) -> HttpJsonOperationSnapshot.create(response)) + .setPollingRequestFactory( + compoundOperationId -> + GetOperationRequest.newBuilder().setName(compoundOperationId).build()) + .build(); + + private ApiMethodDescriptor deleteOperationMethodDescriptor = + ApiMethodDescriptor.newBuilder() + .setFullMethodName("google.longrunning.Operations/DeleteOperation") + .setHttpMethod(HttpMethods.DELETE) + .setRequestFormatter( + ProtoMessageRequestFormatter.newBuilder() + .setPath( + "/v1/{name=**/operations/*}", + request -> { + Map fields = new HashMap<>(); + ProtoRestSerializer serializer = + ProtoRestSerializer.create(); + serializer.putPathParam(fields, "name", request.getName()); + return fields; + }) + .setQueryParamsExtractor(request -> new HashMap<>()) + .setRequestBodyExtractor(request -> null) + .build()) + .setResponseParser( + ProtoMessageResponseParser.newBuilder() + .setDefaultInstance(Empty.getDefaultInstance()) + .build()) + .build(); + + private ApiMethodDescriptor cancelOperationMethodDescriptor = + ApiMethodDescriptor.newBuilder() + .setFullMethodName("google.longrunning.Operations/CancelOperation") + .setHttpMethod(HttpMethods.POST) + .setRequestFormatter( + ProtoMessageRequestFormatter.newBuilder() + .setPath( + "/v1/{name=**/operations/*}:cancel", + request -> { + Map fields = new HashMap<>(); + ProtoRestSerializer serializer = + ProtoRestSerializer.create(); + serializer.putPathParam(fields, "name", request.getName()); + return fields; + }) + .setQueryParamsExtractor(request -> new HashMap<>()) + .setRequestBodyExtractor(request -> null) + .build()) + .setResponseParser( + ProtoMessageResponseParser.newBuilder() + .setDefaultInstance(Empty.getDefaultInstance()) + .build()) + .build(); private final UnaryCallable listOperationsCallable; private final UnaryCallable @@ -196,7 +199,6 @@ public class HttpJsonOperationsStub extends OperationsStub { private final LongRunningClient longRunningClient; private final BackgroundResource backgroundResources; - private final HttpJsonStubCallableFactory callableFactory; public static final HttpJsonOperationsStub create(OperationsStubSettings settings) throws IOException { @@ -226,6 +228,20 @@ public static final HttpJsonOperationsStub create( OperationsStubSettings.newBuilder().build(), clientContext, callableFactory, typeRegistry); } + public static final HttpJsonOperationsStub create( + ClientContext clientContext, + HttpJsonStubCallableFactory callableFactory, + TypeRegistry typeRegistry, + Map customHttpBindings) + throws IOException { + return new HttpJsonOperationsStub( + OperationsStubSettings.newBuilder().build(), + clientContext, + callableFactory, + typeRegistry, + customHttpBindings); + } + /** * Constructs an instance of HttpJsonOperationsStub, using the given settings. This is protected * so that it is easy to make a subclass, but otherwise, the static factory methods should be @@ -237,7 +253,8 @@ protected HttpJsonOperationsStub(OperationsStubSettings settings, ClientContext settings, clientContext, new HttpJsonOperationsCallableFactory(), - TypeRegistry.getEmptyTypeRegistry()); + TypeRegistry.getEmptyTypeRegistry(), + new HashMap<>()); } /** @@ -251,36 +268,36 @@ protected HttpJsonOperationsStub( HttpJsonStubCallableFactory callableFactory, TypeRegistry typeRegistry) throws IOException { - this.callableFactory = callableFactory; - - Matcher packageMatcher = - CLIENT_PACKAGE_VERSION_PATTERN.matcher(callableFactory.getClass().getPackage().getName()); + this(settings, clientContext, callableFactory, typeRegistry, new HashMap<>()); + } - String apiVersion = packageMatcher.find() ? packageMatcher.group("version") : null; + private HttpJsonOperationsStub( + OperationsStubSettings settings, + ClientContext clientContext, + HttpJsonStubCallableFactory callableFactory, + TypeRegistry typeRegistry, + Map customHttpBindings) { + updateDefaultApiMethodDescriptors(customHttpBindings); HttpJsonCallSettings listOperationsTransportSettings = HttpJsonCallSettings.newBuilder() - .setMethodDescriptor( - getApiVersionedMethodDescriptor(listOperationsMethodDescriptor, apiVersion)) + .setMethodDescriptor(listOperationsMethodDescriptor) .setTypeRegistry(typeRegistry) .build(); HttpJsonCallSettings getOperationTransportSettings = HttpJsonCallSettings.newBuilder() - .setMethodDescriptor( - getApiVersionedMethodDescriptor(getOperationMethodDescriptor, apiVersion)) + .setMethodDescriptor(getOperationMethodDescriptor) .setTypeRegistry(typeRegistry) .build(); HttpJsonCallSettings deleteOperationTransportSettings = HttpJsonCallSettings.newBuilder() - .setMethodDescriptor( - getApiVersionedMethodDescriptor(deleteOperationMethodDescriptor, apiVersion)) + .setMethodDescriptor(deleteOperationMethodDescriptor) .setTypeRegistry(typeRegistry) .build(); HttpJsonCallSettings cancelOperationTransportSettings = HttpJsonCallSettings.newBuilder() - .setMethodDescriptor( - getApiVersionedMethodDescriptor(cancelOperationMethodDescriptor, apiVersion)) + .setMethodDescriptor(cancelOperationMethodDescriptor) .setTypeRegistry(typeRegistry) .build(); @@ -309,32 +326,117 @@ protected HttpJsonOperationsStub( backgroundResources = new BackgroundResourceAggregation(clientContext.getBackgroundResources()); } - private static - ApiMethodDescriptor getApiVersionedMethodDescriptor( - ApiMethodDescriptor methodDescriptor, String apiVersion) { - if (apiVersion == null) { - return methodDescriptor; + /* OperationsClient's RPCs are mapped to GET/POST/DELETE and this function only expects those HttpVerbs to be used */ + private String getValueBasedOnPatternCase(HttpRule httpRule) { + switch (httpRule.getPatternCase().getNumber()) { + case 2: + return httpRule.getGet(); + case 4: + return httpRule.getPost(); + case 5: + return httpRule.getDelete(); + default: + throw new IllegalArgumentException( + "Operations HttpRule should only contain GET/POST/DELETE. Invalid: " + + httpRule.getSelector()); + } + } + + /* This is to allow libraries to customize the Operation MethodDescriptors from the service yaml file */ + private void updateDefaultApiMethodDescriptors( + Map customOperationHttpBindings) { + if (customOperationHttpBindings.containsKey(LRO_LIST_OPERATIONS)) { + listOperationsMethodDescriptor = + listOperationsMethodDescriptor + .toBuilder() + .setRequestFormatter( + ((ProtoMessageRequestFormatter) + listOperationsMethodDescriptor.getRequestFormatter()) + .toBuilder() + .updateRawPath(customOperationHttpBindings.get(LRO_LIST_OPERATIONS).getGet()) + .setAdditionalPaths( + customOperationHttpBindings.get(LRO_LIST_OPERATIONS) + .getAdditionalBindingsList().stream() + .map(this::getValueBasedOnPatternCase) + .toArray(String[]::new)) + .build()) + .build(); + } + + if (customOperationHttpBindings.containsKey(LRO_GET_OPERATION)) { + getOperationMethodDescriptor = + getOperationMethodDescriptor + .toBuilder() + .setRequestFormatter( + ((ProtoMessageRequestFormatter) + getOperationMethodDescriptor.getRequestFormatter()) + .toBuilder() + .updateRawPath(customOperationHttpBindings.get(LRO_GET_OPERATION).getGet()) + .setAdditionalPaths( + customOperationHttpBindings.get(LRO_GET_OPERATION) + .getAdditionalBindingsList().stream() + .map(this::getValueBasedOnPatternCase) + .toArray(String[]::new)) + .build()) + .build(); + } + + if (customOperationHttpBindings.containsKey(LRO_DELETE_OPERATION)) { + deleteOperationMethodDescriptor = + deleteOperationMethodDescriptor + .toBuilder() + .setRequestFormatter( + ((ProtoMessageRequestFormatter) + deleteOperationMethodDescriptor.getRequestFormatter()) + .toBuilder() + .updateRawPath( + customOperationHttpBindings.get(LRO_DELETE_OPERATION).getDelete()) + .setAdditionalPaths( + customOperationHttpBindings.get(LRO_DELETE_OPERATION) + .getAdditionalBindingsList().stream() + .map(this::getValueBasedOnPatternCase) + .toArray(String[]::new)) + .build()) + .build(); } - ApiMethodDescriptor.Builder descriptorBuilder = - methodDescriptor.toBuilder(); - ProtoMessageRequestFormatter requestFormatter = - (ProtoMessageRequestFormatter) descriptorBuilder.getRequestFormatter(); + if (customOperationHttpBindings.containsKey(LRO_CANCEL_OPERATION)) { + cancelOperationMethodDescriptor = + cancelOperationMethodDescriptor + .toBuilder() + .setRequestFormatter( + ((ProtoMessageRequestFormatter) + cancelOperationMethodDescriptor.getRequestFormatter()) + .toBuilder() + .updateRawPath( + customOperationHttpBindings.get(LRO_CANCEL_OPERATION).getPost()) + .setAdditionalPaths( + customOperationHttpBindings.get(LRO_CANCEL_OPERATION) + .getAdditionalBindingsList().stream() + .map(this::getValueBasedOnPatternCase) + .toArray(String[]::new)) + .build()) + .build(); + } + } - return descriptorBuilder - .setRequestFormatter( - requestFormatter.toBuilder().updateRawPath("/v1/", '/' + apiVersion + '/').build()) - .build(); + /* + This function returns the list of method descriptors (custom or default). + This is meant to be called only for tests. + */ + @VisibleForTesting + @InternalApi + public List getAllMethodDescriptors() { + return ImmutableList.of( + listOperationsMethodDescriptor, + getOperationMethodDescriptor, + deleteOperationMethodDescriptor, + cancelOperationMethodDescriptor); } @InternalApi public static List getMethodDescriptors() { - List methodDescriptors = new ArrayList<>(); - methodDescriptors.add(listOperationsMethodDescriptor); - methodDescriptors.add(getOperationMethodDescriptor); - methodDescriptors.add(deleteOperationMethodDescriptor); - methodDescriptors.add(cancelOperationMethodDescriptor); - return methodDescriptors; + return ImmutableList.of(); } @Override diff --git a/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/longrunning/OperationsClientTest.java b/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/longrunning/OperationsClientTest.java index 1240ba742b..c0a3696675 100644 --- a/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/longrunning/OperationsClientTest.java +++ b/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/longrunning/OperationsClientTest.java @@ -32,19 +32,23 @@ import com.google.api.gax.core.NoCredentialsProvider; import com.google.api.gax.httpjson.GaxHttpJsonProperties; import com.google.api.gax.httpjson.longrunning.OperationsClient.ListOperationsPagedResponse; +import com.google.api.gax.httpjson.longrunning.stub.HttpJsonOperationsCallableFactory; import com.google.api.gax.httpjson.longrunning.stub.HttpJsonOperationsStub; import com.google.api.gax.httpjson.testing.MockHttpService; import com.google.api.gax.rpc.ApiClientHeaderProvider; import com.google.api.gax.rpc.ApiException; import com.google.api.gax.rpc.ApiExceptionFactory; +import com.google.api.gax.rpc.ClientContext; import com.google.api.gax.rpc.InvalidArgumentException; import com.google.api.gax.rpc.StatusCode; +import com.google.api.gax.rpc.testing.FakeCallContext; import com.google.api.gax.rpc.testing.FakeStatusCode; import com.google.common.collect.Lists; import com.google.longrunning.ListOperationsResponse; import com.google.longrunning.Operation; import com.google.protobuf.Any; import com.google.protobuf.Empty; +import com.google.protobuf.TypeRegistry; import java.io.IOException; import java.util.Arrays; import java.util.List; @@ -60,9 +64,18 @@ public class OperationsClientTest { @BeforeClass public static void startStaticServer() throws IOException { + HttpJsonOperationsStub httpJsonOperationsStub = + HttpJsonOperationsStub.create( + ClientContext.newBuilder() + .setCredentials(NoCredentialsProvider.create().getCredentials()) + .setDefaultCallContext(FakeCallContext.createDefault()) + .build(), + new HttpJsonOperationsCallableFactory(), + TypeRegistry.newBuilder().build()); mockService = new MockHttpService( - HttpJsonOperationsStub.getMethodDescriptors(), OperationsSettings.getDefaultEndpoint()); + httpJsonOperationsStub.getAllMethodDescriptors(), + OperationsSettings.getDefaultEndpoint()); OperationsSettings settings = OperationsSettings.newBuilder() .setTransportChannelProvider( diff --git a/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/longrunning/stub/HttpJsonOperationsStubTest.java b/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/longrunning/stub/HttpJsonOperationsStubTest.java new file mode 100644 index 0000000000..b695188c04 --- /dev/null +++ b/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/longrunning/stub/HttpJsonOperationsStubTest.java @@ -0,0 +1,180 @@ +/* + * Copyright 2023 Google LLC + * + * 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 LLC 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. + */ +package com.google.api.gax.httpjson.longrunning.stub; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.api.HttpRule; +import com.google.api.gax.core.NoCredentialsProvider; +import com.google.api.gax.httpjson.ApiMethodDescriptor; +import com.google.api.gax.httpjson.ProtoMessageRequestFormatter; +import com.google.api.gax.rpc.ClientContext; +import com.google.api.gax.rpc.testing.FakeCallContext; +import com.google.common.collect.ImmutableMap; +import com.google.longrunning.CancelOperationRequest; +import com.google.longrunning.DeleteOperationRequest; +import com.google.longrunning.GetOperationRequest; +import com.google.longrunning.ListOperationsRequest; +import com.google.protobuf.TypeRegistry; +import java.io.IOException; +import java.util.List; +import org.junit.Test; + +public class HttpJsonOperationsStubTest { + + @Test + public void testMethodDescriptorsURI() throws IOException { + String operationListURI = "testList"; + String operationGetURI = "testGet"; + String operationDeleteURI = "testDelete"; + String operationCancelURI = "testCancel"; + HttpJsonOperationsStub httpJsonOperationsStub = + HttpJsonOperationsStub.create( + ClientContext.newBuilder() + .setCredentials(NoCredentialsProvider.create().getCredentials()) + .setDefaultCallContext(FakeCallContext.createDefault()) + .build(), + new HttpJsonOperationsCallableFactory(), + TypeRegistry.newBuilder().build(), + ImmutableMap.of( + "google.longrunning.Operations.ListOperations", + HttpRule.newBuilder().setGet(operationListURI).build(), + "google.longrunning.Operations.GetOperation", + HttpRule.newBuilder().setGet(operationGetURI).build(), + "google.longrunning.Operations.DeleteOperation", + HttpRule.newBuilder().setDelete(operationDeleteURI).build(), + "google.longrunning.Operations.CancelOperation", + HttpRule.newBuilder().setPost(operationCancelURI).build())); + // The order is: List, Get, Delete, Cancel + List apiMethodDescriptorList = + httpJsonOperationsStub.getAllMethodDescriptors(); + assertThat(apiMethodDescriptorList.get(0).getRequestFormatter().getPathTemplate().toRawString()) + .isEqualTo(operationListURI); + assertThat(apiMethodDescriptorList.get(1).getRequestFormatter().getPathTemplate().toRawString()) + .isEqualTo(operationGetURI); + assertThat(apiMethodDescriptorList.get(2).getRequestFormatter().getPathTemplate().toRawString()) + .isEqualTo(operationDeleteURI); + assertThat(apiMethodDescriptorList.get(3).getRequestFormatter().getPathTemplate().toRawString()) + .isEqualTo(operationCancelURI); + } + + @Test + public void testMethodDescriptorsAdditionalBindings() throws IOException { + // We set a random URI in this OperationsStub, otherwise PathTemplate won't compile + String operationListAdditionalBindingURI = "testList2"; + String operationGetAdditionalBindingURI = "testGet2"; + String operationDeleteAdditionalBindingURI = "testDelete2"; + String operationCancelAdditionalBindingURI = "testCancel2"; + HttpJsonOperationsStub httpJsonOperationsStub = + HttpJsonOperationsStub.create( + ClientContext.newBuilder() + .setCredentials(NoCredentialsProvider.create().getCredentials()) + .setDefaultCallContext(FakeCallContext.createDefault()) + .build(), + new HttpJsonOperationsCallableFactory(), + TypeRegistry.newBuilder().build(), + ImmutableMap.of( + "google.longrunning.Operations.ListOperations", + HttpRule.newBuilder() + .setGet("test") + .addAdditionalBindings( + HttpRule.newBuilder().setGet(operationListAdditionalBindingURI)) + .build(), + "google.longrunning.Operations.GetOperation", + HttpRule.newBuilder() + .setGet("test") + .addAdditionalBindings( + HttpRule.newBuilder().setGet(operationGetAdditionalBindingURI)) + .build(), + "google.longrunning.Operations.DeleteOperation", + HttpRule.newBuilder() + .setDelete("test") + .addAdditionalBindings( + HttpRule.newBuilder().setDelete(operationDeleteAdditionalBindingURI)) + .build(), + "google.longrunning.Operations.CancelOperation", + HttpRule.newBuilder() + .setPost("test") + .addAdditionalBindings( + HttpRule.newBuilder().setPost(operationCancelAdditionalBindingURI)) + .build())); + // The order is: List, Get, Delete, Cancel + List apiMethodDescriptorList = + httpJsonOperationsStub.getAllMethodDescriptors(); + ProtoMessageRequestFormatter + listOperationsRequestProtoMessageRequestFormatter = + (ProtoMessageRequestFormatter) + apiMethodDescriptorList.get(0).getRequestFormatter(); + assertThat(listOperationsRequestProtoMessageRequestFormatter.getAdditionalPathTemplates()) + .hasSize(1); + assertThat( + listOperationsRequestProtoMessageRequestFormatter + .getAdditionalPathTemplates() + .get(0) + .toRawString()) + .isEqualTo(operationListAdditionalBindingURI); + ProtoMessageRequestFormatter + getOperationRequestProtoMessageRequestFormatter = + (ProtoMessageRequestFormatter) + apiMethodDescriptorList.get(1).getRequestFormatter(); + assertThat(getOperationRequestProtoMessageRequestFormatter.getAdditionalPathTemplates()) + .hasSize(1); + assertThat( + getOperationRequestProtoMessageRequestFormatter + .getAdditionalPathTemplates() + .get(0) + .toRawString()) + .isEqualTo(operationGetAdditionalBindingURI); + ProtoMessageRequestFormatter + deleteOperationRequestProtoMessageRequestFormatter = + (ProtoMessageRequestFormatter) + apiMethodDescriptorList.get(2).getRequestFormatter(); + assertThat(deleteOperationRequestProtoMessageRequestFormatter.getAdditionalPathTemplates()) + .hasSize(1); + assertThat( + deleteOperationRequestProtoMessageRequestFormatter + .getAdditionalPathTemplates() + .get(0) + .toRawString()) + .isEqualTo(operationDeleteAdditionalBindingURI); + ProtoMessageRequestFormatter + cancelOperationRequestProtoMessageRequestFormatter = + (ProtoMessageRequestFormatter) + apiMethodDescriptorList.get(3).getRequestFormatter(); + assertThat(cancelOperationRequestProtoMessageRequestFormatter.getAdditionalPathTemplates()) + .hasSize(1); + assertThat( + cancelOperationRequestProtoMessageRequestFormatter + .getAdditionalPathTemplates() + .get(0) + .toRawString()) + .isEqualTo(operationCancelAdditionalBindingURI); + } +} diff --git a/showcase/gapic-showcase/src/main/java/com/google/showcase/v1beta1/stub/HttpJsonEchoStub.java b/showcase/gapic-showcase/src/main/java/com/google/showcase/v1beta1/stub/HttpJsonEchoStub.java index 379794c41d..3623597566 100644 --- a/showcase/gapic-showcase/src/main/java/com/google/showcase/v1beta1/stub/HttpJsonEchoStub.java +++ b/showcase/gapic-showcase/src/main/java/com/google/showcase/v1beta1/stub/HttpJsonEchoStub.java @@ -19,6 +19,7 @@ import static com.google.showcase.v1beta1.EchoClient.PagedExpandLegacyMappedPagedResponse; import static com.google.showcase.v1beta1.EchoClient.PagedExpandPagedResponse; +import com.google.api.HttpRule; import com.google.api.core.BetaApi; import com.google.api.core.InternalApi; import com.google.api.gax.core.BackgroundResource; @@ -38,6 +39,7 @@ import com.google.api.gax.rpc.OperationCallable; import com.google.api.gax.rpc.ServerStreamingCallable; import com.google.api.gax.rpc.UnaryCallable; +import com.google.common.collect.ImmutableMap; import com.google.longrunning.Operation; import com.google.protobuf.TypeRegistry; import com.google.showcase.v1beta1.BlockRequest; @@ -366,7 +368,24 @@ protected HttpJsonEchoStub( throws IOException { this.callableFactory = callableFactory; this.httpJsonOperationsStub = - HttpJsonOperationsStub.create(clientContext, callableFactory, typeRegistry); + HttpJsonOperationsStub.create( + clientContext, + callableFactory, + typeRegistry, + ImmutableMap.builder() + .put( + "google.longrunning.Operations.CancelOperation", + HttpRule.newBuilder().setPost("/v1beta1/{name=operations/**}:cancel").build()) + .put( + "google.longrunning.Operations.DeleteOperation", + HttpRule.newBuilder().setDelete("/v1beta1/{name=operations/**}").build()) + .put( + "google.longrunning.Operations.GetOperation", + HttpRule.newBuilder().setGet("/v1beta1/{name=operations/**}").build()) + .put( + "google.longrunning.Operations.ListOperations", + HttpRule.newBuilder().setGet("/v1beta1/operations").build()) + .build()); HttpJsonCallSettings echoTransportSettings = HttpJsonCallSettings.newBuilder() diff --git a/showcase/gapic-showcase/src/main/java/com/google/showcase/v1beta1/stub/HttpJsonMessagingStub.java b/showcase/gapic-showcase/src/main/java/com/google/showcase/v1beta1/stub/HttpJsonMessagingStub.java index a86209571f..cc6ffddf9b 100644 --- a/showcase/gapic-showcase/src/main/java/com/google/showcase/v1beta1/stub/HttpJsonMessagingStub.java +++ b/showcase/gapic-showcase/src/main/java/com/google/showcase/v1beta1/stub/HttpJsonMessagingStub.java @@ -19,6 +19,7 @@ import static com.google.showcase.v1beta1.MessagingClient.ListBlurbsPagedResponse; import static com.google.showcase.v1beta1.MessagingClient.ListRoomsPagedResponse; +import com.google.api.HttpRule; import com.google.api.core.BetaApi; import com.google.api.core.InternalApi; import com.google.api.gax.core.BackgroundResource; @@ -38,6 +39,7 @@ import com.google.api.gax.rpc.OperationCallable; import com.google.api.gax.rpc.ServerStreamingCallable; import com.google.api.gax.rpc.UnaryCallable; +import com.google.common.collect.ImmutableMap; import com.google.longrunning.Operation; import com.google.protobuf.Empty; import com.google.protobuf.TypeRegistry; @@ -563,7 +565,24 @@ protected HttpJsonMessagingStub( throws IOException { this.callableFactory = callableFactory; this.httpJsonOperationsStub = - HttpJsonOperationsStub.create(clientContext, callableFactory, typeRegistry); + HttpJsonOperationsStub.create( + clientContext, + callableFactory, + typeRegistry, + ImmutableMap.builder() + .put( + "google.longrunning.Operations.CancelOperation", + HttpRule.newBuilder().setPost("/v1beta1/{name=operations/**}:cancel").build()) + .put( + "google.longrunning.Operations.DeleteOperation", + HttpRule.newBuilder().setDelete("/v1beta1/{name=operations/**}").build()) + .put( + "google.longrunning.Operations.GetOperation", + HttpRule.newBuilder().setGet("/v1beta1/{name=operations/**}").build()) + .put( + "google.longrunning.Operations.ListOperations", + HttpRule.newBuilder().setGet("/v1beta1/operations").build()) + .build()); HttpJsonCallSettings createRoomTransportSettings = HttpJsonCallSettings.newBuilder() diff --git a/test/integration/goldens/redis/src/com/google/cloud/redis/v1beta1/stub/HttpJsonCloudRedisStub.java b/test/integration/goldens/redis/src/com/google/cloud/redis/v1beta1/stub/HttpJsonCloudRedisStub.java index aac829bf6f..cf4e271cdc 100644 --- a/test/integration/goldens/redis/src/com/google/cloud/redis/v1beta1/stub/HttpJsonCloudRedisStub.java +++ b/test/integration/goldens/redis/src/com/google/cloud/redis/v1beta1/stub/HttpJsonCloudRedisStub.java @@ -18,6 +18,7 @@ import static com.google.cloud.redis.v1beta1.CloudRedisClient.ListInstancesPagedResponse; +import com.google.api.HttpRule; import com.google.api.core.BetaApi; import com.google.api.core.InternalApi; import com.google.api.gax.core.BackgroundResource; @@ -48,6 +49,7 @@ import com.google.cloud.redis.v1beta1.RescheduleMaintenanceRequest; import com.google.cloud.redis.v1beta1.UpdateInstanceRequest; import com.google.cloud.redis.v1beta1.UpgradeInstanceRequest; +import com.google.common.collect.ImmutableMap; import com.google.longrunning.Operation; import com.google.protobuf.Any; import com.google.protobuf.Empty; @@ -575,7 +577,32 @@ protected HttpJsonCloudRedisStub( throws IOException { this.callableFactory = callableFactory; this.httpJsonOperationsStub = - HttpJsonOperationsStub.create(clientContext, callableFactory, typeRegistry); + HttpJsonOperationsStub.create( + clientContext, + callableFactory, + typeRegistry, + ImmutableMap.builder() + .put( + "google.longrunning.Operations.CancelOperation", + HttpRule.newBuilder() + .setPost("/v1beta1/{name=projects/*/locations/*/operations/*}:cancel") + .build()) + .put( + "google.longrunning.Operations.DeleteOperation", + HttpRule.newBuilder() + .setDelete("/v1beta1/{name=projects/*/locations/*/operations/*}") + .build()) + .put( + "google.longrunning.Operations.GetOperation", + HttpRule.newBuilder() + .setGet("/v1beta1/{name=projects/*/locations/*/operations/*}") + .build()) + .put( + "google.longrunning.Operations.ListOperations", + HttpRule.newBuilder() + .setGet("/v1beta1/{name=projects/*/locations/*}/operations") + .build()) + .build()); HttpJsonCallSettings listInstancesTransportSettings =