From 0bc0aad0a56a211f13563a9dfa3278acc83db7e4 Mon Sep 17 00:00:00 2001 From: Blake Li Date: Tue, 21 Dec 2021 10:44:03 -0500 Subject: [PATCH 1/9] Add support for parsing routing rules annotation. Test out params extractor in composer. #869 --- repositories.bzl | 4 +- .../grpc/GrpcServiceStubClassComposer.java | 52 +++++++ .../api/generator/gapic/model/Method.java | 5 + .../generator/gapic/model/RoutingHeaders.java | 43 ++++++ .../generator/gapic/protoparser/BUILD.bazel | 1 + .../generator/gapic/protoparser/Parser.java | 5 + .../gapic/protoparser/RoutingRuleParser.java | 130 ++++++++++++++++++ .../grpc/goldens/GrpcTestingStub.golden | 1 + .../generator/gapic/protoparser/BUILD.bazel | 2 + .../protoparser/RoutingRuleParserTest.java | 53 +++++++ .../api/generator/gapic/testdata/BUILD.bazel | 1 + .../generator/gapic/testdata/testing.proto | 7 + 12 files changed, 302 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/google/api/generator/gapic/model/RoutingHeaders.java create mode 100644 src/main/java/com/google/api/generator/gapic/protoparser/RoutingRuleParser.java create mode 100644 src/test/java/com/google/api/generator/gapic/protoparser/RoutingRuleParserTest.java diff --git a/repositories.bzl b/repositories.bzl index d067d4e689..7ad4029164 100644 --- a/repositories.bzl +++ b/repositories.bzl @@ -59,9 +59,9 @@ def gapic_generator_java_repositories(): _maybe( http_archive, name = "com_google_googleapis", - strip_prefix = "googleapis-ba30d8097582039ac4cc4e21b4e4baa426423075", + strip_prefix = "googleapis-987192dfddeb79d3262b9f9f7dbf092827f931ac", urls = [ - "https://github.com/googleapis/googleapis/archive/ba30d8097582039ac4cc4e21b4e4baa426423075.zip", + "https://github.com/googleapis/googleapis/archive/987192dfddeb79d3262b9f9f7dbf092827f931ac.zip", ], ) diff --git a/src/main/java/com/google/api/generator/gapic/composer/grpc/GrpcServiceStubClassComposer.java b/src/main/java/com/google/api/generator/gapic/composer/grpc/GrpcServiceStubClassComposer.java index c82de5552c..49c0aa0c87 100644 --- a/src/main/java/com/google/api/generator/gapic/composer/grpc/GrpcServiceStubClassComposer.java +++ b/src/main/java/com/google/api/generator/gapic/composer/grpc/GrpcServiceStubClassComposer.java @@ -16,6 +16,7 @@ import com.google.api.gax.grpc.GrpcCallSettings; import com.google.api.gax.grpc.GrpcStubCallableFactory; +import com.google.api.gax.rpc.RequestParamsExtractor; import com.google.api.generator.engine.ast.AssignmentExpr; import com.google.api.generator.engine.ast.ConcreteReference; import com.google.api.generator.engine.ast.EnumRefExpr; @@ -23,6 +24,7 @@ import com.google.api.generator.engine.ast.ExprStatement; import com.google.api.generator.engine.ast.LambdaExpr; import com.google.api.generator.engine.ast.MethodInvocationExpr; +import com.google.api.generator.engine.ast.NewObjectExpr; import com.google.api.generator.engine.ast.ScopeNode; import com.google.api.generator.engine.ast.Statement; import com.google.api.generator.engine.ast.StringObjectValue; @@ -35,6 +37,7 @@ import com.google.api.generator.gapic.model.HttpBindings.HttpBinding; import com.google.api.generator.gapic.model.Message; import com.google.api.generator.gapic.model.Method; +import com.google.api.generator.gapic.model.RoutingHeaders.RoutingHeader; import com.google.api.generator.gapic.model.Service; import com.google.api.generator.gapic.utils.JavaStyle; import com.google.common.base.Preconditions; @@ -209,10 +212,19 @@ protected Expr createTransportSettingsInitExpr( .build(); if (method.hasHttpBindings()) { + NewObjectExpr newExtractorExpr = + NewObjectExpr.builder() + .setType( + TypeNode.withReference( + ConcreteReference.withClazz(ExplicitRoutingHeaderExtractor.class))) + .setArguments() + .build(); callSettingsBuilderExpr = MethodInvocationExpr.builder() .setExprReferenceExpr(callSettingsBuilderExpr) .setMethodName("setParamsExtractor") + // set custom extractor + // .setArguments(newExtractorExpr) .setArguments(createRequestParamsExtractorClassInstance(method)) .build(); } @@ -302,6 +314,8 @@ private LambdaExpr createRequestParamsExtractorClassInstance(Method method) { .setArguments(requestBuilderExpr) .build(); + // TODO: completely remove this part if routing headers is not null? + // Are these params used for anything else other than implicit dynamic routing? Expr paramsPutExpr = MethodInvocationExpr.builder() .setExprReferenceExpr(paramsVarExpr) @@ -313,6 +327,35 @@ private LambdaExpr createRequestParamsExtractorClassInstance(Method method) { bodyExprs.add(paramsPutExpr); } + for (RoutingHeader routingHeader : method.routingHeaders().routingHeadersSet()) { + MethodInvocationExpr.Builder requestFieldGetterExprBuilder = + MethodInvocationExpr.builder().setExprReferenceExpr(requestVarExpr); + // TODO: support nested field + String currFieldName = routingHeader.field(); + String bindingFieldMethodName = + String.format("get%s", JavaStyle.toUpperCamelCase(currFieldName)); + requestFieldGetterExprBuilder = + requestFieldGetterExprBuilder.setMethodName(bindingFieldMethodName); + + MethodInvocationExpr requestBuilderExpr = requestFieldGetterExprBuilder.build(); + Expr valueOfExpr = + MethodInvocationExpr.builder() + .setStaticReferenceType(TypeNode.STRING) + .setMethodName("valueOf") + .setArguments(requestBuilderExpr) + .build(); + + Expr paramsPutExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(paramsVarExpr) + .setMethodName("put") + .setArguments( + ValueExpr.withValue(StringObjectValue.withValue(routingHeader.name())), + valueOfExpr) + .build(); + bodyExprs.add(paramsPutExpr); + } + TypeNode returnType = TypeNode.withReference( ConcreteReference.builder() @@ -335,4 +378,13 @@ private LambdaExpr createRequestParamsExtractorClassInstance(Method method) { .setReturnExpr(returnExpr) .build(); } + + class ExplicitRoutingHeaderExtractor implements RequestParamsExtractor { + + @Override + public Map extract(Object request) { + // no way to extract the field value since request type is dynamic + return null; + } + } } diff --git a/src/main/java/com/google/api/generator/gapic/model/Method.java b/src/main/java/com/google/api/generator/gapic/model/Method.java index 286bd8c84e..c2961994b5 100644 --- a/src/main/java/com/google/api/generator/gapic/model/Method.java +++ b/src/main/java/com/google/api/generator/gapic/model/Method.java @@ -62,6 +62,9 @@ public boolean isPaged() { @Nullable public abstract HttpBindings httpBindings(); + @Nullable + public abstract RoutingHeaders routingHeaders(); + // Example from Expand in echo.proto: Thet TypeNodes that map to // [["content", "error"], ["content", "error", "info"]]. public abstract ImmutableList> methodSignatures(); @@ -140,6 +143,8 @@ public abstract static class Builder { public abstract Builder setOperationPollingMethod(boolean operationPollingMethod); + public abstract Builder setRoutingHeaders(RoutingHeaders routingHeaders); + public abstract Method build(); } } diff --git a/src/main/java/com/google/api/generator/gapic/model/RoutingHeaders.java b/src/main/java/com/google/api/generator/gapic/model/RoutingHeaders.java new file mode 100644 index 0000000000..ee84b56cd6 --- /dev/null +++ b/src/main/java/com/google/api/generator/gapic/model/RoutingHeaders.java @@ -0,0 +1,43 @@ +// 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 +// +// http://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. + +package com.google.api.generator.gapic.model; + +import com.google.auto.value.AutoValue; +import java.util.List; + +@AutoValue +public abstract class RoutingHeaders { + + public abstract List routingHeadersSet(); + + // TODO: change to a builder and expose only add method + public static RoutingHeaders create(List routingHeaderList) { + return new AutoValue_RoutingHeaders(routingHeaderList); + } + + @AutoValue + public abstract static class RoutingHeader { + + public abstract String field(); + + public abstract String name(); + + public abstract String pattern(); + + public static RoutingHeaders.RoutingHeader create(String field, String name, String pattern) { + return new AutoValue_RoutingHeaders_RoutingHeader(field, name, pattern); + } + } +} diff --git a/src/main/java/com/google/api/generator/gapic/protoparser/BUILD.bazel b/src/main/java/com/google/api/generator/gapic/protoparser/BUILD.bazel index 904b7fbf2b..aad6c7f80c 100644 --- a/src/main/java/com/google/api/generator/gapic/protoparser/BUILD.bazel +++ b/src/main/java/com/google/api/generator/gapic/protoparser/BUILD.bazel @@ -27,6 +27,7 @@ java_library( "//src/main/java/com/google/api/generator/gapic/model", "//src/main/java/com/google/api/generator/gapic/utils", "@com_google_api_api_common//jar", + "@com_google_api_grpc_proto_google_common_protos", "@com_google_code_findbugs_jsr305//jar", "@com_google_code_gson//jar", "@com_google_googleapis//google/api:api_java_proto", diff --git a/src/main/java/com/google/api/generator/gapic/protoparser/Parser.java b/src/main/java/com/google/api/generator/gapic/protoparser/Parser.java index bbfcbbce05..a4aa2be50b 100644 --- a/src/main/java/com/google/api/generator/gapic/protoparser/Parser.java +++ b/src/main/java/com/google/api/generator/gapic/protoparser/Parser.java @@ -34,6 +34,7 @@ import com.google.api.generator.gapic.model.OperationResponse; import com.google.api.generator.gapic.model.ResourceName; import com.google.api.generator.gapic.model.ResourceReference; +import com.google.api.generator.gapic.model.RoutingHeaders; import com.google.api.generator.gapic.model.Service; import com.google.api.generator.gapic.model.SourceCodeInfoLocation; import com.google.api.generator.gapic.model.Transport; @@ -709,6 +710,9 @@ static List parseMethods( .getOptions() .getExtension(ExtendedOperationsProto.operationPollingMethod) : false; + // may not need to pass in messageTypes? + RoutingHeaders routingHeaders = + RoutingRuleParser.parse(protoMethod, inputMessage, messageTypes); methods.add( methodBuilder .setName(protoMethod.getName()) @@ -726,6 +730,7 @@ static List parseMethods( resourceNames, outputArgResourceNames)) .setHttpBindings(httpBindings) + .setRoutingHeaders(routingHeaders) .setIsBatching(isBatching) .setPageSizeFieldName(parsePageSizeFieldName(protoMethod, messageTypes, transport)) .setIsDeprecated(isDeprecated) diff --git a/src/main/java/com/google/api/generator/gapic/protoparser/RoutingRuleParser.java b/src/main/java/com/google/api/generator/gapic/protoparser/RoutingRuleParser.java new file mode 100644 index 0000000000..8386b8dce2 --- /dev/null +++ b/src/main/java/com/google/api/generator/gapic/protoparser/RoutingRuleParser.java @@ -0,0 +1,130 @@ +// 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 +// +// http://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. + +package com.google.api.generator.gapic.protoparser; + +import com.google.api.RoutingParameter; +import com.google.api.RoutingProto; +import com.google.api.RoutingRule; +import com.google.api.generator.gapic.model.Field; +import com.google.api.generator.gapic.model.Message; +import com.google.api.generator.gapic.model.RoutingHeaders; +import com.google.api.generator.gapic.model.RoutingHeaders.RoutingHeader; +import com.google.api.pathtemplate.PathTemplate; +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableSortedSet; +import com.google.protobuf.DescriptorProtos.MethodOptions; +import com.google.protobuf.Descriptors.MethodDescriptor; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +public class RoutingRuleParser { + + private static final String ASTERISK = "**"; + + public static RoutingHeaders parse( + MethodDescriptor protoMethod, Message inputMessage, Map messageTypes) { + MethodOptions methodOptions = protoMethod.getOptions(); + + if (!methodOptions.hasExtension(RoutingProto.routing)) { + return RoutingHeaders.create(Collections.emptyList()); + } + + RoutingRule routingRule = methodOptions.getExtension(RoutingProto.routing); + + return parseHttpRuleHelper(routingRule, Optional.of(inputMessage), messageTypes); + } + + private static RoutingHeaders parseHttpRuleHelper( + RoutingRule routingRule, + Optional inputMessageOpt, + Map messageTypes) { + + // one field may map to multiple headers, Example 6 + // multiple fields may map to one header as well, Example 8, last wins + // one combination of field/name may have multiple patterns, the one matches win, Example 3c. + List routingHeaderSet = new ArrayList<>(); + for (RoutingParameter routingParameter : routingRule.getRoutingParametersList()) { + String pathTemplate = routingParameter.getPathTemplate(); + String field = routingParameter.getField(); + // TODO: Validate if field exist in Message or nested Messages + // If failed, stop or ignore? stop? + checkHttpFieldIsValid(field, inputMessageOpt.get(), false); + + // TODO: Validate the pattern, if specified and not **, the pattern must contain one and + // only one named segment + // If failed, stop or ignore? stop? + Set params = getPatternBindings(pathTemplate).build(); + String name = field; + // set name to field if empty, Example 1 + if (!params.isEmpty()) { + name = params.iterator().next(); + } + + // set path to ** if empty, Example 1 + if (pathTemplate.isEmpty()) { + pathTemplate = ASTERISK; + } + + RoutingHeader routingHeader = RoutingHeader.create(field, name, pathTemplate); + routingHeaderSet.add(routingHeader); + } + + return RoutingHeaders.create(routingHeaderSet); + } + + private static ImmutableSortedSet.Builder getPatternBindings(String pattern) { + ImmutableSortedSet.Builder bindings = ImmutableSortedSet.naturalOrder(); + if (pattern.isEmpty()) { + return bindings; + } + + PathTemplate template = PathTemplate.create(pattern); + // Filter out any unbound variable like "$0, $1, etc. + bindings.addAll( + template.vars().stream().filter(s -> !s.contains("$")).collect(Collectors.toSet())); + return bindings; + } + + // TODO:Move to Message.java, also need to handle nested fields. The field has to be of type + // String + private static void checkHttpFieldIsValid(String binding, Message inputMessage, boolean isBody) { + Preconditions.checkState( + !Strings.isNullOrEmpty(binding), + String.format("Null or empty binding for " + inputMessage.name())); + Preconditions.checkState( + inputMessage.fieldMap().containsKey(binding), + String.format( + "Expected message %s to contain field %s but none found", + inputMessage.name(), binding)); + Field field = inputMessage.fieldMap().get(binding); + boolean fieldCondition = !field.isRepeated(); + if (!isBody) { + fieldCondition &= field.type().isProtoPrimitiveType() || field.isEnum(); + } + String messageFormat = + "Expected a non-repeated " + + (isBody ? "" : "primitive ") + + "type for field %s in message %s but got type %s"; + Preconditions.checkState( + fieldCondition, + String.format(messageFormat, field.name(), inputMessage.name(), field.type())); + } +} diff --git a/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcTestingStub.golden b/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcTestingStub.golden index 7312b79d37..b2eee7cc0b 100644 --- a/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcTestingStub.golden +++ b/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcTestingStub.golden @@ -222,6 +222,7 @@ public class GrpcTestingStub extends TestingStub { request -> { ImmutableMap.Builder params = ImmutableMap.builder(); params.put("name", String.valueOf(request.getName())); + params.put("rename", String.valueOf(request.getName())); return params.build(); }) .build(); diff --git a/src/test/java/com/google/api/generator/gapic/protoparser/BUILD.bazel b/src/test/java/com/google/api/generator/gapic/protoparser/BUILD.bazel index f15a3d1a6e..24eaaf09ad 100644 --- a/src/test/java/com/google/api/generator/gapic/protoparser/BUILD.bazel +++ b/src/test/java/com/google/api/generator/gapic/protoparser/BUILD.bazel @@ -16,6 +16,7 @@ TESTS = [ "ServiceYamlParserTest", "SourceCodeInfoParserTest", "TypeParserTest", + "RoutingRuleParserTest" ] filegroup( @@ -45,6 +46,7 @@ filegroup( "//src/test/java/com/google/api/generator/gapic/testdata:showcase_java_proto", "//src/test/java/com/google/api/generator/gapic/testdata:testgapic_java_proto", "@com_google_api_api_common//jar", + "@com_google_api_grpc_proto_google_common_protos", "@com_google_googleapis//google/api:api_java_proto", "@com_google_googleapis//google/rpc:rpc_java_proto", "@com_google_protobuf//:protobuf_java", diff --git a/src/test/java/com/google/api/generator/gapic/protoparser/RoutingRuleParserTest.java b/src/test/java/com/google/api/generator/gapic/protoparser/RoutingRuleParserTest.java new file mode 100644 index 0000000000..e3039f5e33 --- /dev/null +++ b/src/test/java/com/google/api/generator/gapic/protoparser/RoutingRuleParserTest.java @@ -0,0 +1,53 @@ +// 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 +// +// http://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. + +package com.google.api.generator.gapic.protoparser; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; + +import com.google.api.generator.gapic.model.Message; +import com.google.api.generator.gapic.model.RoutingHeaders; +import com.google.protobuf.Descriptors.FileDescriptor; +import com.google.protobuf.Descriptors.MethodDescriptor; +import com.google.protobuf.Descriptors.ServiceDescriptor; +import com.google.showcase.v1beta1.TestingOuterClass; +import java.util.Map; +import java.util.stream.Collectors; +import org.junit.Test; + +public class RoutingRuleParserTest { + + // Do we have to test against a real proto file + // Maybe manually create a MethodDescriptor and test against it? more flexibility and precise. + // testing.proto is being used by other tests as well + @Test + public void parseRoutingRuleAnnotation() { + FileDescriptor testingFileDescriptor = TestingOuterClass.getDescriptor(); + ServiceDescriptor testingService = testingFileDescriptor.getServices().get(0); + assertEquals("Testing", testingService.getName()); + + Map messages = Parser.parseMessages(testingFileDescriptor); + + // GetTest method. + MethodDescriptor rpcMethod = testingService.getMethods().get(5); + Message inputMessage = messages.get("com.google.showcase.v1beta1.GetTestRequest"); + RoutingHeaders routingHeaders = RoutingRuleParser.parse(rpcMethod, inputMessage, messages); + assertThat( + routingHeaders.routingHeadersSet().stream() + .map(routingHeader -> routingHeader.field() + " -> " + routingHeader.name()) + .collect(Collectors.toList())) + .containsExactly("name -> rename"); + } +} diff --git a/src/test/java/com/google/api/generator/gapic/testdata/BUILD.bazel b/src/test/java/com/google/api/generator/gapic/testdata/BUILD.bazel index 3991672985..9bc783cdaa 100644 --- a/src/test/java/com/google/api/generator/gapic/testdata/BUILD.bazel +++ b/src/test/java/com/google/api/generator/gapic/testdata/BUILD.bazel @@ -54,6 +54,7 @@ proto_library( ], deps = [ "@com_google_googleapis//google/api:annotations_proto", + "@com_google_googleapis//google/api:routing_proto", "@com_google_googleapis//google/api:client_proto", "@com_google_googleapis//google/api:field_behavior_proto", "@com_google_googleapis//google/api:resource_proto", diff --git a/src/test/java/com/google/api/generator/gapic/testdata/testing.proto b/src/test/java/com/google/api/generator/gapic/testdata/testing.proto index 490e1d17cc..ef4cc2cc1c 100644 --- a/src/test/java/com/google/api/generator/gapic/testdata/testing.proto +++ b/src/test/java/com/google/api/generator/gapic/testdata/testing.proto @@ -15,6 +15,7 @@ syntax = "proto3"; import "google/api/annotations.proto"; +import "google/api/routing.proto"; import "google/api/client.proto"; import "google/api/resource.proto"; import "google/protobuf/empty.proto"; @@ -78,6 +79,12 @@ service Testing { option (google.api.http) = { get: "/v1beta1/{name=tests/*}" }; + option (google.api.routing) = { + routing_parameters { + field: "name" + path_template: "/v1beta1/{rename=tests/*}" + } + }; option (google.api.method_signature) = "name"; } From 83d1abd600ddfee5471ef3aa78529fba9f21b28a Mon Sep 17 00:00:00 2001 From: Blake Li Date: Tue, 4 Jan 2022 00:17:20 -0700 Subject: [PATCH 2/9] #869. A basic end-to-end solution. - Add unit tests for RoutingRuleParser --- .../grpc/GrpcServiceStubClassComposer.java | 273 +++++++++++++----- .../api/generator/gapic/model/Message.java | 35 +++ .../generator/gapic/model/RoutingHeaders.java | 23 +- .../gapic/protoparser/RoutingRuleParser.java | 82 ++---- .../grpc/goldens/GrpcTestingStub.golden | 32 +- .../api/generator/gapic/model/BUILD.bazel | 1 + .../generator/gapic/model/MessageTest.java | 108 +++++++ .../protoparser/RoutingRuleParserTest.java | 78 +++-- .../api/generator/gapic/testdata/BUILD.bazel | 1 + .../routing_rule_parser_testing.proto | 130 +++++++++ .../generator/gapic/testdata/testing.proto | 5 + 11 files changed, 600 insertions(+), 168 deletions(-) create mode 100644 src/test/java/com/google/api/generator/gapic/model/MessageTest.java create mode 100644 src/test/java/com/google/api/generator/gapic/testdata/routing_rule_parser_testing.proto diff --git a/src/main/java/com/google/api/generator/gapic/composer/grpc/GrpcServiceStubClassComposer.java b/src/main/java/com/google/api/generator/gapic/composer/grpc/GrpcServiceStubClassComposer.java index 49c0aa0c87..9605e6bf32 100644 --- a/src/main/java/com/google/api/generator/gapic/composer/grpc/GrpcServiceStubClassComposer.java +++ b/src/main/java/com/google/api/generator/gapic/composer/grpc/GrpcServiceStubClassComposer.java @@ -16,15 +16,16 @@ import com.google.api.gax.grpc.GrpcCallSettings; import com.google.api.gax.grpc.GrpcStubCallableFactory; -import com.google.api.gax.rpc.RequestParamsExtractor; import com.google.api.generator.engine.ast.AssignmentExpr; import com.google.api.generator.engine.ast.ConcreteReference; import com.google.api.generator.engine.ast.EnumRefExpr; import com.google.api.generator.engine.ast.Expr; import com.google.api.generator.engine.ast.ExprStatement; +import com.google.api.generator.engine.ast.IfStatement; import com.google.api.generator.engine.ast.LambdaExpr; +import com.google.api.generator.engine.ast.MethodDefinition; import com.google.api.generator.engine.ast.MethodInvocationExpr; -import com.google.api.generator.engine.ast.NewObjectExpr; +import com.google.api.generator.engine.ast.RelationalOperationExpr; import com.google.api.generator.engine.ast.ScopeNode; import com.google.api.generator.engine.ast.Statement; import com.google.api.generator.engine.ast.StringObjectValue; @@ -34,13 +35,16 @@ import com.google.api.generator.engine.ast.VariableExpr; import com.google.api.generator.gapic.composer.common.AbstractTransportServiceStubClassComposer; import com.google.api.generator.gapic.composer.store.TypeStore; +import com.google.api.generator.gapic.model.GapicContext; import com.google.api.generator.gapic.model.HttpBindings.HttpBinding; import com.google.api.generator.gapic.model.Message; import com.google.api.generator.gapic.model.Method; import com.google.api.generator.gapic.model.RoutingHeaders.RoutingHeader; import com.google.api.generator.gapic.model.Service; import com.google.api.generator.gapic.utils.JavaStyle; +import com.google.api.pathtemplate.PathTemplate; import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.longrunning.stub.GrpcOperationsStub; import io.grpc.MethodDescriptor; @@ -56,6 +60,7 @@ import java.util.stream.Collectors; public class GrpcServiceStubClassComposer extends AbstractTransportServiceStubClassComposer { + private static final GrpcServiceStubClassComposer INSTANCE = new GrpcServiceStubClassComposer(); // Legacy support for the original reroute_to_grpc_interface option in gapic.yaml. These two APIs @@ -212,19 +217,10 @@ protected Expr createTransportSettingsInitExpr( .build(); if (method.hasHttpBindings()) { - NewObjectExpr newExtractorExpr = - NewObjectExpr.builder() - .setType( - TypeNode.withReference( - ConcreteReference.withClazz(ExplicitRoutingHeaderExtractor.class))) - .setArguments() - .build(); callSettingsBuilderExpr = MethodInvocationExpr.builder() .setExprReferenceExpr(callSettingsBuilderExpr) .setMethodName("setParamsExtractor") - // set custom extractor - // .setArguments(newExtractorExpr) .setArguments(createRequestParamsExtractorClassInstance(method)) .build(); } @@ -241,6 +237,161 @@ protected Expr createTransportSettingsInitExpr( .build(); } + @Override + protected List createClassMethods( + GapicContext context, + Service service, + TypeStore typeStore, + Map classMemberVarExprs, + Map callableClassMemberVarExprs, + Map protoMethodNameToDescriptorVarExprs) { + List classMethods = + super.createClassMethods( + context, + service, + typeStore, + classMemberVarExprs, + callableClassMemberVarExprs, + protoMethodNameToDescriptorVarExprs); + // TODO: need a way to check do we need to create this method or not, or make it an inner method + classMethods.add(createAddParamsMethod()); + return classMethods; + } + + private MethodDefinition createAddParamsMethod() { + TypeNode paramsVarType = + TypeNode.withReference( + ConcreteReference.builder() + .setClazz(ImmutableMap.Builder.class) + .setGenerics(TypeNode.STRING.reference(), TypeNode.STRING.reference()) + .build()); + VariableExpr paramsVarExpr = + VariableExpr.builder() + .setVariable(Variable.builder().setName("params").setType(paramsVarType).build()) + .setIsDecl(true) + .build(); + VariableExpr fieldValueExpr = + VariableExpr.builder() + .setVariable(Variable.builder().setName("fieldValue").setType(TypeNode.STRING).build()) + .setIsDecl(true) + .build(); + VariableExpr keyExpr = + VariableExpr.builder() + .setVariable(Variable.builder().setName("headerKey").setType(TypeNode.STRING).build()) + .setIsDecl(true) + .build(); + VariableExpr patternExpr = + VariableExpr.builder() + .setVariable(Variable.builder().setName("pattern").setType(TypeNode.STRING).build()) + .setIsDecl(true) + .build(); + List methodBody = new ArrayList<>(); + TypeNode pathTemplateType = + TypeNode.withReference(ConcreteReference.builder().setClazz(PathTemplate.class).build()); + VariableExpr pathTemplateVar = + VariableExpr.builder() + .setVariable( + Variable.builder().setName("pathTemplate").setType(pathTemplateType).build()) + .setIsDecl(true) + .build(); + VariableExpr patternExprNonDecl = + VariableExpr.builder() + .setVariable(Variable.builder().setName("pattern").setType(TypeNode.STRING).build()) + .build(); + Expr pathTemplateExpr = + AssignmentExpr.builder() + .setVariableExpr(pathTemplateVar) + .setValueExpr( + MethodInvocationExpr.builder() + .setStaticReferenceType(pathTemplateType) + .setMethodName("create") + .setArguments(patternExprNonDecl) + .setReturnType(pathTemplateType) + .build()) + .build(); + ExprStatement exprStatement1 = ExprStatement.withExpr(pathTemplateExpr); + methodBody.add(exprStatement1); + + VariableExpr pathTemplateVarNonDecl = + VariableExpr.builder() + .setVariable( + Variable.builder().setName("pathTemplate").setType(pathTemplateType).build()) + .build(); + TypeNode matchedValuesType = + TypeNode.withReference( + ConcreteReference.builder() + .setClazz(Map.class) + .setGenerics(TypeNode.STRING.reference(), TypeNode.STRING.reference()) + .build()); + VariableExpr matchedValuesVar = + VariableExpr.builder() + .setVariable( + Variable.builder().setName("matchedValues").setType(matchedValuesType).build()) + .setIsDecl(true) + .build(); + VariableExpr fieldValueExprNonDecl = + VariableExpr.builder() + .setVariable(Variable.builder().setName("fieldValue").setType(pathTemplateType).build()) + .build(); + Expr matchedValuesExpr = + AssignmentExpr.builder() + .setVariableExpr(matchedValuesVar) + .setValueExpr( + MethodInvocationExpr.builder() + .setExprReferenceExpr(pathTemplateVarNonDecl) + .setMethodName("match") + .setArguments(fieldValueExprNonDecl) + .setReturnType(matchedValuesType) + .build()) + .build(); + ExprStatement exprStatement2 = ExprStatement.withExpr(matchedValuesExpr); + methodBody.add(exprStatement2); + + VariableExpr matchedValuesVarNonDecl = + VariableExpr.builder() + .setVariable( + Variable.builder().setName("matchedValues").setType(matchedValuesType).build()) + .build(); + Expr checkMatchedValuesNull = + RelationalOperationExpr.notEqualToWithExprs( + matchedValuesVarNonDecl, ValueExpr.createNullExpr()); + VariableExpr paramsVarExprNonDecl = + VariableExpr.builder() + .setVariable(Variable.builder().setName("params").setType(paramsVarType).build()) + .build(); + VariableExpr keyExprNonDecl = + VariableExpr.builder() + .setVariable(Variable.builder().setName("headerKey").setType(TypeNode.STRING).build()) + .build(); + Expr getMatchedValueExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(matchedValuesVarNonDecl) + .setMethodName("get") + .setArguments(keyExprNonDecl) + .build(); + Expr putParamsExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(paramsVarExprNonDecl) + .setMethodName("put") + .setArguments(keyExprNonDecl, getMatchedValueExpr) + .build(); + ExprStatement putParamsStatement = ExprStatement.withExpr(putParamsExpr); + IfStatement ifStatement = + IfStatement.builder() + .setConditionExpr(checkMatchedValuesNull) + .setBody(ImmutableList.of(putParamsStatement)) + .build(); + methodBody.add(ifStatement); + + return MethodDefinition.builder() + .setScope(ScopeNode.PROTECTED) + .setReturnType(TypeNode.VOID) + .setName("addParams") + .setBody(methodBody) + .setArguments(paramsVarExpr, fieldValueExpr, keyExpr, patternExpr) + .build(); + } + @Override protected String getProtoRpcFullMethodName(Service protoService, Method protoMethod) { if (protoMethod.isMixin()) { @@ -290,23 +441,9 @@ private LambdaExpr createRequestParamsExtractorClassInstance(Method method) { for (HttpBinding httpBindingFieldBinding : method.httpBindings().pathParameters()) { // Handle foo.bar cases by descending into the subfields. - MethodInvocationExpr.Builder requestFieldGetterExprBuilder = - MethodInvocationExpr.builder().setExprReferenceExpr(requestVarExpr); - String[] descendantFields = httpBindingFieldBinding.name().split("\\."); - for (int i = 0; i < descendantFields.length; i++) { - String currFieldName = descendantFields[i]; - String bindingFieldMethodName = - String.format("get%s", JavaStyle.toUpperCamelCase(currFieldName)); - requestFieldGetterExprBuilder = - requestFieldGetterExprBuilder.setMethodName(bindingFieldMethodName); - if (i < descendantFields.length - 1) { - requestFieldGetterExprBuilder = - MethodInvocationExpr.builder() - .setExprReferenceExpr(requestFieldGetterExprBuilder.build()); - } - } + MethodInvocationExpr requestBuilderExpr = + createRequestFieldGetterExpr(requestVarExpr, httpBindingFieldBinding.name()); - MethodInvocationExpr requestBuilderExpr = requestFieldGetterExprBuilder.build(); Expr valueOfExpr = MethodInvocationExpr.builder() .setStaticReferenceType(TypeNode.STRING) @@ -314,46 +451,39 @@ private LambdaExpr createRequestParamsExtractorClassInstance(Method method) { .setArguments(requestBuilderExpr) .build(); - // TODO: completely remove this part if routing headers is not null? + // Comment out for now. TODO: completely remove this part if routing headers is not null? // Are these params used for anything else other than implicit dynamic routing? - Expr paramsPutExpr = - MethodInvocationExpr.builder() - .setExprReferenceExpr(paramsVarExpr) - .setMethodName("put") - .setArguments( - ValueExpr.withValue(StringObjectValue.withValue(httpBindingFieldBinding.name())), - valueOfExpr) - .build(); - bodyExprs.add(paramsPutExpr); + // Expr paramsPutExpr = + // MethodInvocationExpr.builder() + // .setExprReferenceExpr(paramsVarExpr) + // .setMethodName("put") + // .setArguments( + // + // ValueExpr.withValue(StringObjectValue.withValue(httpBindingFieldBinding.name())), + // valueOfExpr) + // .build(); + // bodyExprs.add(paramsPutExpr); } - for (RoutingHeader routingHeader : method.routingHeaders().routingHeadersSet()) { - MethodInvocationExpr.Builder requestFieldGetterExprBuilder = - MethodInvocationExpr.builder().setExprReferenceExpr(requestVarExpr); - // TODO: support nested field - String currFieldName = routingHeader.field(); - String bindingFieldMethodName = - String.format("get%s", JavaStyle.toUpperCamelCase(currFieldName)); - requestFieldGetterExprBuilder = - requestFieldGetterExprBuilder.setMethodName(bindingFieldMethodName); - - MethodInvocationExpr requestBuilderExpr = requestFieldGetterExprBuilder.build(); - Expr valueOfExpr = - MethodInvocationExpr.builder() - .setStaticReferenceType(TypeNode.STRING) - .setMethodName("valueOf") - .setArguments(requestBuilderExpr) - .build(); + for (RoutingHeader routingHeader : method.routingHeaders().routingHeadersList()) { + MethodInvocationExpr requestFieldGetterExpr = + createRequestFieldGetterExpr(requestVarExpr, routingHeader.field()); - Expr paramsPutExpr = + Expr routingHeaderKeyExpr = + ValueExpr.withValue(StringObjectValue.withValue(routingHeader.name())); + Expr routingHeaderPatternExpr = + ValueExpr.withValue(StringObjectValue.withValue(routingHeader.pattern())); + MethodInvocationExpr addParamsMethodExpr = MethodInvocationExpr.builder() - .setExprReferenceExpr(paramsVarExpr) - .setMethodName("put") + .setMethodName("addParams") .setArguments( - ValueExpr.withValue(StringObjectValue.withValue(routingHeader.name())), - valueOfExpr) + paramsVarExpr, + requestFieldGetterExpr, + routingHeaderKeyExpr, + routingHeaderPatternExpr) .build(); - bodyExprs.add(paramsPutExpr); + + bodyExprs.add(addParamsMethodExpr); } TypeNode returnType = @@ -379,12 +509,23 @@ private LambdaExpr createRequestParamsExtractorClassInstance(Method method) { .build(); } - class ExplicitRoutingHeaderExtractor implements RequestParamsExtractor { - - @Override - public Map extract(Object request) { - // no way to extract the field value since request type is dynamic - return null; + private MethodInvocationExpr createRequestFieldGetterExpr( + VariableExpr requestVarExpr, String fieldName) { + MethodInvocationExpr.Builder requestFieldGetterExprBuilder = + MethodInvocationExpr.builder().setExprReferenceExpr(requestVarExpr); + String[] descendantFields = fieldName.split("\\."); + for (int i = 0; i < descendantFields.length; i++) { + String currFieldName = descendantFields[i]; + String bindingFieldMethodName = + String.format("get%s", JavaStyle.toUpperCamelCase(currFieldName)); + requestFieldGetterExprBuilder = + requestFieldGetterExprBuilder.setMethodName(bindingFieldMethodName); + if (i < descendantFields.length - 1) { + requestFieldGetterExprBuilder = + MethodInvocationExpr.builder() + .setExprReferenceExpr(requestFieldGetterExprBuilder.build()); + } } + return requestFieldGetterExprBuilder.build(); } } diff --git a/src/main/java/com/google/api/generator/gapic/model/Message.java b/src/main/java/com/google/api/generator/gapic/model/Message.java index 91a5388e37..d7eb5a1a2c 100644 --- a/src/main/java/com/google/api/generator/gapic/model/Message.java +++ b/src/main/java/com/google/api/generator/gapic/model/Message.java @@ -18,6 +18,8 @@ import com.google.api.generator.engine.ast.Reference; import com.google.api.generator.engine.ast.TypeNode; import com.google.auto.value.AutoValue; +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import com.google.common.collect.ImmutableList; @@ -32,6 +34,13 @@ @AutoValue public abstract class Message { + + static final String EMPTY_FIELD_ERROR_MESSAGE = "Null or empty field name found for message %s"; + static final String FIELD_DOES_NOT_EXIST_ERROR_MESSAGE = + "Expected message %s to contain field %s but none found"; + static final String MESSAGE_NOT_FOUND_ERROR_MESSAGE = + "No containing message found for field %s with type %s"; + public abstract String name(); // The fully-qualified proto name, which differs from the Java fully-qualified name. @@ -80,6 +89,32 @@ public boolean hasResource() { return resource() != null; } + public void validateField(String fieldName, Map messageTypes) { + String[] subFields = fieldName.split("\\."); + Message nestedMessage = this; + for (int i = 0; i < subFields.length; i++) { + String subFieldName = subFields[i]; + if (i < subFields.length - 1) { + Field field = nestedMessage.fieldMap().get(subFieldName); + nestedMessage = messageTypes.get(field.type().reference().fullName()); + Preconditions.checkNotNull( + nestedMessage, + String.format( + MESSAGE_NOT_FOUND_ERROR_MESSAGE, + field.name(), + field.type().reference().simpleName())); + } else { + Preconditions.checkState( + !Strings.isNullOrEmpty(subFieldName), + String.format(EMPTY_FIELD_ERROR_MESSAGE, nestedMessage.name())); + Preconditions.checkState( + nestedMessage.fieldMap().containsKey(subFieldName), + String.format(FIELD_DOES_NOT_EXIST_ERROR_MESSAGE, nestedMessage.name(), subFieldName)); + // TODO: Add type check for String only? + } + } + } + /** Returns the first list repeated field in a message, unwrapped from its list type. */ @Nullable public Field findAndUnwrapPaginatedRepeatedField() { diff --git a/src/main/java/com/google/api/generator/gapic/model/RoutingHeaders.java b/src/main/java/com/google/api/generator/gapic/model/RoutingHeaders.java index ee84b56cd6..c4c260970c 100644 --- a/src/main/java/com/google/api/generator/gapic/model/RoutingHeaders.java +++ b/src/main/java/com/google/api/generator/gapic/model/RoutingHeaders.java @@ -15,16 +15,15 @@ package com.google.api.generator.gapic.model; import com.google.auto.value.AutoValue; -import java.util.List; +import com.google.common.collect.ImmutableList; @AutoValue public abstract class RoutingHeaders { - public abstract List routingHeadersSet(); + public abstract ImmutableList routingHeadersList(); - // TODO: change to a builder and expose only add method - public static RoutingHeaders create(List routingHeaderList) { - return new AutoValue_RoutingHeaders(routingHeaderList); + public static Builder builder() { + return new AutoValue_RoutingHeaders.Builder().setRoutingHeadersList(ImmutableList.of()); } @AutoValue @@ -40,4 +39,18 @@ public static RoutingHeaders.RoutingHeader create(String field, String name, Str return new AutoValue_RoutingHeaders_RoutingHeader(field, name, pattern); } } + + @AutoValue.Builder + public abstract static class Builder { + abstract ImmutableList.Builder routingHeadersListBuilder(); + + public final Builder addRoutingHeader(RoutingHeader routingHeader) { + routingHeadersListBuilder().add(routingHeader); + return this; + } + + public abstract Builder setRoutingHeadersList(ImmutableList routingHeadersList); + + public abstract RoutingHeaders build(); + } } diff --git a/src/main/java/com/google/api/generator/gapic/protoparser/RoutingRuleParser.java b/src/main/java/com/google/api/generator/gapic/protoparser/RoutingRuleParser.java index 8386b8dce2..a96e0203a2 100644 --- a/src/main/java/com/google/api/generator/gapic/protoparser/RoutingRuleParser.java +++ b/src/main/java/com/google/api/generator/gapic/protoparser/RoutingRuleParser.java @@ -17,7 +17,6 @@ import com.google.api.RoutingParameter; import com.google.api.RoutingProto; import com.google.api.RoutingRule; -import com.google.api.generator.gapic.model.Field; import com.google.api.generator.gapic.model.Message; import com.google.api.generator.gapic.model.RoutingHeaders; import com.google.api.generator.gapic.model.RoutingHeaders.RoutingHeader; @@ -27,69 +26,53 @@ import com.google.common.collect.ImmutableSortedSet; import com.google.protobuf.DescriptorProtos.MethodOptions; import com.google.protobuf.Descriptors.MethodDescriptor; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; public class RoutingRuleParser { - private static final String ASTERISK = "**"; + static final String WILDCARD_PATTERN = "{%s=**}"; + static final String PATH_TEMPLATE_WRONG_NUMBER_OF_NAMED_SEGMENT_ERROR_MESSAGE = + "There needs to be one and only one named segment in path template %s"; public static RoutingHeaders parse( MethodDescriptor protoMethod, Message inputMessage, Map messageTypes) { MethodOptions methodOptions = protoMethod.getOptions(); + RoutingHeaders.Builder builder = RoutingHeaders.builder(); if (!methodOptions.hasExtension(RoutingProto.routing)) { - return RoutingHeaders.create(Collections.emptyList()); + return builder.build(); } RoutingRule routingRule = methodOptions.getExtension(RoutingProto.routing); - return parseHttpRuleHelper(routingRule, Optional.of(inputMessage), messageTypes); - } - - private static RoutingHeaders parseHttpRuleHelper( - RoutingRule routingRule, - Optional inputMessageOpt, - Map messageTypes) { - - // one field may map to multiple headers, Example 6 - // multiple fields may map to one header as well, Example 8, last wins - // one combination of field/name may have multiple patterns, the one matches win, Example 3c. - List routingHeaderSet = new ArrayList<>(); for (RoutingParameter routingParameter : routingRule.getRoutingParametersList()) { String pathTemplate = routingParameter.getPathTemplate(); String field = routingParameter.getField(); - // TODO: Validate if field exist in Message or nested Messages - // If failed, stop or ignore? stop? - checkHttpFieldIsValid(field, inputMessageOpt.get(), false); - - // TODO: Validate the pattern, if specified and not **, the pattern must contain one and - // only one named segment - // If failed, stop or ignore? stop? - Set params = getPatternBindings(pathTemplate).build(); - String name = field; - // set name to field if empty, Example 1 - if (!params.isEmpty()) { + // validate if field exist in Message or nested Messages + inputMessage.validateField(field, messageTypes); + String name; + // if specified, the pattern must contain one and only one named segment + if (Strings.isNullOrEmpty(pathTemplate)) { + name = field; + pathTemplate = String.format(WILDCARD_PATTERN, name); + } else { + Set params = getPatternBindings(pathTemplate).build(); + Preconditions.checkArgument( + params.size() == 1, + String.format(PATH_TEMPLATE_WRONG_NUMBER_OF_NAMED_SEGMENT_ERROR_MESSAGE, pathTemplate)); name = params.iterator().next(); } - // set path to ** if empty, Example 1 - if (pathTemplate.isEmpty()) { - pathTemplate = ASTERISK; - } - RoutingHeader routingHeader = RoutingHeader.create(field, name, pathTemplate); - routingHeaderSet.add(routingHeader); + builder.addRoutingHeader(routingHeader); } - return RoutingHeaders.create(routingHeaderSet); + return builder.build(); } + // TODO: duplicate of HttpRuleParser.getPatternBindings, move to a helper class private static ImmutableSortedSet.Builder getPatternBindings(String pattern) { ImmutableSortedSet.Builder bindings = ImmutableSortedSet.naturalOrder(); if (pattern.isEmpty()) { @@ -102,29 +85,4 @@ private static ImmutableSortedSet.Builder getPatternBindings(String patt template.vars().stream().filter(s -> !s.contains("$")).collect(Collectors.toSet())); return bindings; } - - // TODO:Move to Message.java, also need to handle nested fields. The field has to be of type - // String - private static void checkHttpFieldIsValid(String binding, Message inputMessage, boolean isBody) { - Preconditions.checkState( - !Strings.isNullOrEmpty(binding), - String.format("Null or empty binding for " + inputMessage.name())); - Preconditions.checkState( - inputMessage.fieldMap().containsKey(binding), - String.format( - "Expected message %s to contain field %s but none found", - inputMessage.name(), binding)); - Field field = inputMessage.fieldMap().get(binding); - boolean fieldCondition = !field.isRepeated(); - if (!isBody) { - fieldCondition &= field.type().isProtoPrimitiveType() || field.isEnum(); - } - String messageFormat = - "Expected a non-repeated " - + (isBody ? "" : "primitive ") - + "type for field %s in message %s but got type %s"; - Preconditions.checkState( - fieldCondition, - String.format(messageFormat, field.name(), inputMessage.name(), field.type())); - } } diff --git a/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcTestingStub.golden b/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcTestingStub.golden index b2eee7cc0b..a8d8ad52a5 100644 --- a/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcTestingStub.golden +++ b/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcTestingStub.golden @@ -10,6 +10,7 @@ import com.google.api.gax.grpc.GrpcCallSettings; import com.google.api.gax.grpc.GrpcStubCallableFactory; import com.google.api.gax.rpc.ClientContext; import com.google.api.gax.rpc.UnaryCallable; +import com.google.api.pathtemplate.PathTemplate; import com.google.common.collect.ImmutableMap; import com.google.longrunning.stub.GrpcOperationsStub; import com.google.protobuf.Empty; @@ -187,7 +188,6 @@ public class GrpcTestingStub extends TestingStub { .setParamsExtractor( request -> { ImmutableMap.Builder params = ImmutableMap.builder(); - params.put("name", String.valueOf(request.getName())); return params.build(); }) .build(); @@ -201,7 +201,6 @@ public class GrpcTestingStub extends TestingStub { .setParamsExtractor( request -> { ImmutableMap.Builder params = ImmutableMap.builder(); - params.put("name", String.valueOf(request.getName())); return params.build(); }) .build(); @@ -211,7 +210,6 @@ public class GrpcTestingStub extends TestingStub { .setParamsExtractor( request -> { ImmutableMap.Builder params = ImmutableMap.builder(); - params.put("name", String.valueOf(request.getName())); return params.build(); }) .build(); @@ -221,8 +219,12 @@ public class GrpcTestingStub extends TestingStub { .setParamsExtractor( request -> { ImmutableMap.Builder params = ImmutableMap.builder(); - params.put("name", String.valueOf(request.getName())); - params.put("rename", String.valueOf(request.getName())); + addParams(params, request.getName(), "rename", "/v1beta1/{rename=tests/*}"); + addParams( + params, + request.getTableName(), + "routing_id", + "/v1beta1/{routing_id=tests/*}"); return params.build(); }) .build(); @@ -232,7 +234,6 @@ public class GrpcTestingStub extends TestingStub { .setParamsExtractor( request -> { ImmutableMap.Builder params = ImmutableMap.builder(); - params.put("parent", String.valueOf(request.getParent())); return params.build(); }) .build(); @@ -242,7 +243,6 @@ public class GrpcTestingStub extends TestingStub { .setParamsExtractor( request -> { ImmutableMap.Builder params = ImmutableMap.builder(); - params.put("name", String.valueOf(request.getName())); return params.build(); }) .build(); @@ -252,12 +252,6 @@ public class GrpcTestingStub extends TestingStub { .setParamsExtractor( request -> { ImmutableMap.Builder params = ImmutableMap.builder(); - params.put("answer", String.valueOf(request.getAnswer())); - params.put("foo", String.valueOf(request.getFoo())); - params.put("name", String.valueOf(request.getName())); - params.put( - "test_to_verify.name", String.valueOf(request.getTestToVerify().getName())); - params.put("type", String.valueOf(request.getType())); return params.build(); }) .build(); @@ -394,4 +388,16 @@ public class GrpcTestingStub extends TestingStub { public boolean awaitTermination(long duration, TimeUnit unit) throws InterruptedException { return backgroundResources.awaitTermination(duration, unit); } + + protected void addParams( + ImmutableMap.Builder params, + String fieldValue, + String headerKey, + String pattern) { + PathTemplate pathTemplate = PathTemplate.create(pattern); + Map matchedValues = pathTemplate.match(fieldValue); + if (matchedValues != null) { + params.put(headerKey, matchedValues.get(headerKey)); + } + } } diff --git a/src/test/java/com/google/api/generator/gapic/model/BUILD.bazel b/src/test/java/com/google/api/generator/gapic/model/BUILD.bazel index f2697b2efa..5bcebf9047 100644 --- a/src/test/java/com/google/api/generator/gapic/model/BUILD.bazel +++ b/src/test/java/com/google/api/generator/gapic/model/BUILD.bazel @@ -6,6 +6,7 @@ TESTS = [ "GapicServiceConfigTest", "MethodArgumentTest", "MethodTest", + "MessageTest", ] filegroup( diff --git a/src/test/java/com/google/api/generator/gapic/model/MessageTest.java b/src/test/java/com/google/api/generator/gapic/model/MessageTest.java new file mode 100644 index 0000000000..fe5c54085a --- /dev/null +++ b/src/test/java/com/google/api/generator/gapic/model/MessageTest.java @@ -0,0 +1,108 @@ +// Copyright 2021 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 +// +// http://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. + +package com.google.api.generator.gapic.model; + +import static com.google.api.generator.gapic.model.Message.EMPTY_FIELD_ERROR_MESSAGE; +import static com.google.api.generator.gapic.model.Message.FIELD_DOES_NOT_EXIST_ERROR_MESSAGE; +import static com.google.api.generator.gapic.model.Message.MESSAGE_NOT_FOUND_ERROR_MESSAGE; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import com.google.api.generator.engine.ast.TypeNode; +import com.google.api.generator.engine.ast.VaporReference; +import com.google.common.collect.ImmutableMap; +import java.util.Map; +import org.junit.Test; + +public class MessageTest { + + public static final String MESSAGE_NAME = "TestMessage"; + public static final Message.Builder TEST_MESSAGE_BUILDER = + Message.builder() + .setName(MESSAGE_NAME) + .setFullProtoName("com.google.test.TestMessage") + .setType(TypeNode.OBJECT); + + @Test + public void shouldThrowExceptionIfFieldNameIsEmpty() { + Message message = TEST_MESSAGE_BUILDER.build(); + IllegalStateException illegalStateException = + assertThrows( + IllegalStateException.class, () -> message.validateField("", ImmutableMap.of())); + assertThat(illegalStateException.getMessage()) + .isEqualTo(String.format(EMPTY_FIELD_ERROR_MESSAGE, MESSAGE_NAME)); + } + + @Test + public void shouldThrowExceptionIfFieldDoesNotExist() { + Message message = TEST_MESSAGE_BUILDER.build(); + String fieldName = "doesNotExist"; + IllegalStateException illegalStateException = + assertThrows( + IllegalStateException.class, () -> message.validateField(fieldName, ImmutableMap.of())); + assertThat(illegalStateException.getMessage()) + .isEqualTo(String.format(FIELD_DOES_NOT_EXIST_ERROR_MESSAGE, MESSAGE_NAME, fieldName)); + } + + @Test + public void shouldThrowExceptionIfMessageDoesNotExist() { + String subFieldName = "table"; + String fieldTypeName = "doesNotMatter"; + Field subField = + Field.builder() + .setName(subFieldName) + .setType( + TypeNode.withReference( + VaporReference.builder() + .setPakkage("com.google") + .setName(fieldTypeName) + .build())) + .build(); + Message message = + TEST_MESSAGE_BUILDER.setFieldMap(ImmutableMap.of(subFieldName, subField)).build(); + String fieldName = subFieldName + "." + "size"; + NullPointerException illegalStateException = + assertThrows( + NullPointerException.class, () -> message.validateField(fieldName, ImmutableMap.of())); + assertThat(illegalStateException.getMessage()) + .isEqualTo(String.format(MESSAGE_NOT_FOUND_ERROR_MESSAGE, subFieldName, fieldTypeName)); + } + + @Test + public void shouldNotThrowExceptionIfFieldExist() { + String subFieldName = "table"; + String fieldTypeName = "TableFieldType"; + VaporReference fieldType = + VaporReference.builder().setPakkage("com.google").setName(fieldTypeName).build(); + Field subField = + Field.builder().setName(subFieldName).setType(TypeNode.withReference(fieldType)).build(); + String subFieldName2 = "size"; + String fieldName = subFieldName + "." + subFieldName2; + Message subMessage = + Message.builder() + .setName(fieldTypeName) + .setFullProtoName("com.google." + fieldTypeName) + .setType(TypeNode.OBJECT) + .setFieldMap( + ImmutableMap.of( + subFieldName2, + Field.builder().setType(TypeNode.STRING).setName(subFieldName2).build())) + .build(); + Map messageTypes = ImmutableMap.of(fieldType.fullName(), subMessage); + Message message = + TEST_MESSAGE_BUILDER.setFieldMap(ImmutableMap.of(subFieldName, subField)).build(); + message.validateField(fieldName, messageTypes); + } +} diff --git a/src/test/java/com/google/api/generator/gapic/protoparser/RoutingRuleParserTest.java b/src/test/java/com/google/api/generator/gapic/protoparser/RoutingRuleParserTest.java index e3039f5e33..c3465d921b 100644 --- a/src/test/java/com/google/api/generator/gapic/protoparser/RoutingRuleParserTest.java +++ b/src/test/java/com/google/api/generator/gapic/protoparser/RoutingRuleParserTest.java @@ -14,40 +14,74 @@ package com.google.api.generator.gapic.protoparser; +import static com.google.api.generator.gapic.protoparser.RoutingRuleParser.PATH_TEMPLATE_WRONG_NUMBER_OF_NAMED_SEGMENT_ERROR_MESSAGE; +import static com.google.api.generator.gapic.protoparser.RoutingRuleParser.WILDCARD_PATTERN; import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; import com.google.api.generator.gapic.model.Message; import com.google.api.generator.gapic.model.RoutingHeaders; +import com.google.api.generator.gapic.model.RoutingHeaders.RoutingHeader; import com.google.protobuf.Descriptors.FileDescriptor; import com.google.protobuf.Descriptors.MethodDescriptor; import com.google.protobuf.Descriptors.ServiceDescriptor; -import com.google.showcase.v1beta1.TestingOuterClass; +import com.google.showcase.v1beta1.RoutingRuleParserTestingOuterClass; import java.util.Map; -import java.util.stream.Collectors; import org.junit.Test; public class RoutingRuleParserTest { - // Do we have to test against a real proto file - // Maybe manually create a MethodDescriptor and test against it? more flexibility and precise. - // testing.proto is being used by other tests as well + private static final FileDescriptor TESTING_FILE_DESCRIPTOR = + RoutingRuleParserTestingOuterClass.getDescriptor(); + private static final Map MESSAGES = + Parser.parseMessages(TESTING_FILE_DESCRIPTOR); + private static final ServiceDescriptor TESTING_SERVICE = + TESTING_FILE_DESCRIPTOR.getServices().get(0); + + @Test + public void shouldReturnEmptyRoutingHeadersIfMethodHasNoRoutingRules() { + MethodDescriptor rpcMethod = TESTING_SERVICE.getMethods().get(0); + Message inputMessage = MESSAGES.get("com." + rpcMethod.getInputType().getFullName()); + RoutingHeaders routingHeaders = RoutingRuleParser.parse(rpcMethod, inputMessage, MESSAGES); + assertThat(routingHeaders.routingHeadersList()).isEmpty(); + } + + @Test + public void shouldSetPathTemplateToWildcardIfNotDefined() { + MethodDescriptor rpcMethod = TESTING_SERVICE.getMethods().get(1); + Message inputMessage = MESSAGES.get("com." + rpcMethod.getInputType().getFullName()); + RoutingHeaders routingHeaders = RoutingRuleParser.parse(rpcMethod, inputMessage, MESSAGES); + RoutingHeader routingHeader = + RoutingHeader.create("name", "name", String.format(WILDCARD_PATTERN, "name")); + assertThat(routingHeaders.routingHeadersList()).containsExactly(routingHeader); + } + + @Test + public void shouldThrowExceptionIfPathTemplateHasZeroNamedSegment() { + MethodDescriptor rpcMethod = TESTING_SERVICE.getMethods().get(2); + Message inputMessage = MESSAGES.get("com." + rpcMethod.getInputType().getFullName()); + IllegalArgumentException illegalArgumentException = + assertThrows( + IllegalArgumentException.class, + () -> RoutingRuleParser.parse(rpcMethod, inputMessage, MESSAGES)); + assertThat(illegalArgumentException.getMessage()) + .isEqualTo( + String.format( + PATH_TEMPLATE_WRONG_NUMBER_OF_NAMED_SEGMENT_ERROR_MESSAGE, "/v1beta1/tests/*")); + } + @Test - public void parseRoutingRuleAnnotation() { - FileDescriptor testingFileDescriptor = TestingOuterClass.getDescriptor(); - ServiceDescriptor testingService = testingFileDescriptor.getServices().get(0); - assertEquals("Testing", testingService.getName()); - - Map messages = Parser.parseMessages(testingFileDescriptor); - - // GetTest method. - MethodDescriptor rpcMethod = testingService.getMethods().get(5); - Message inputMessage = messages.get("com.google.showcase.v1beta1.GetTestRequest"); - RoutingHeaders routingHeaders = RoutingRuleParser.parse(rpcMethod, inputMessage, messages); - assertThat( - routingHeaders.routingHeadersSet().stream() - .map(routingHeader -> routingHeader.field() + " -> " + routingHeader.name()) - .collect(Collectors.toList())) - .containsExactly("name -> rename"); + public void shouldThrowExceptionIfPathTemplateHasMoreThanOneNamedSegment() { + MethodDescriptor rpcMethod = TESTING_SERVICE.getMethods().get(3); + Message inputMessage = MESSAGES.get("com." + rpcMethod.getInputType().getFullName()); + IllegalArgumentException illegalArgumentException = + assertThrows( + IllegalArgumentException.class, + () -> RoutingRuleParser.parse(rpcMethod, inputMessage, MESSAGES)); + assertThat(illegalArgumentException.getMessage()) + .isEqualTo( + String.format( + PATH_TEMPLATE_WRONG_NUMBER_OF_NAMED_SEGMENT_ERROR_MESSAGE, + "/v1beta1/{name=tests/*}/{second_name=*}")); } } diff --git a/src/test/java/com/google/api/generator/gapic/testdata/BUILD.bazel b/src/test/java/com/google/api/generator/gapic/testdata/BUILD.bazel index 9bc783cdaa..faea4cf584 100644 --- a/src/test/java/com/google/api/generator/gapic/testdata/BUILD.bazel +++ b/src/test/java/com/google/api/generator/gapic/testdata/BUILD.bazel @@ -51,6 +51,7 @@ proto_library( "identity.proto", "testing.proto", "messaging.proto", + "routing_rule_parser_testing.proto", ], deps = [ "@com_google_googleapis//google/api:annotations_proto", diff --git a/src/test/java/com/google/api/generator/gapic/testdata/routing_rule_parser_testing.proto b/src/test/java/com/google/api/generator/gapic/testdata/routing_rule_parser_testing.proto new file mode 100644 index 0000000000..031ea45641 --- /dev/null +++ b/src/test/java/com/google/api/generator/gapic/testdata/routing_rule_parser_testing.proto @@ -0,0 +1,130 @@ +// Copyright 2018 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. + +syntax = "proto3"; + +import "google/api/routing.proto"; +import "google/protobuf/empty.proto"; + +package google.showcase.v1beta1; + +option go_package = "github.com/googleapis/gapic-showcase/server/genproto"; +option java_package = "com.google.showcase.v1beta1"; +option java_multiple_files = true; + +// This service is meant to only run for unit testing RoutingRuleParser +service RoutingRuleParserTesting { + + // Test case for no routing rule found + rpc NoRoutingRuleTest(NoRoutingRuleTestRequest) returns (google.protobuf.Empty) { + + } + + // Test case for empty path template + rpc EmptyPathTemplateTest(EmptyPathTemplateTestRequest) returns (google.protobuf.Empty) { + option (google.api.routing) = { + routing_parameters { + field: "name" + } + }; + } + + // Test case for path template has zero named segment + rpc ZeroSegmentPathTemplateTest(ZeroSegmentPathTemplateTestRequest) returns (google.protobuf.Empty) { + option (google.api.routing) = { + routing_parameters { + field: "name" + path_template: "/v1beta1/tests/*" + } + }; + } + + // Test case for path template has more than one named segment + rpc MultipleSegmentsPathTemplateTest(MultipleSegmentsPathTemplateTestRequest) returns (google.protobuf.Empty) { + option (google.api.routing) = { + routing_parameters { + field: "name" + path_template: "/v1beta1/{name=tests/*}/{second_name=*}" + } + }; + } + + // Test case for happy path + rpc HappyPathTest(HappyPathTestRequest) returns (google.protobuf.Empty) { + option (google.api.routing) = { + routing_parameters { + field: "name" + path_template: "/v1beta1/{rename=tests/*}" + } + }; + } + + // Test case for happy path with multiple routing parameters + rpc MultipleRoutingParamsHappyPathTest(MultipleRoutingParamsHappyPathTestReqest) returns (google.protobuf.Empty) { + option (google.api.routing) = { + routing_parameters { + field: "name" + path_template: "/v1beta1/{rename=tests/*}" + } + routing_parameters { + field: "routing_id" + path_template: "/v1beta1/{id=projects/*}/tables/*" + } + }; + } + + // Test case for happy path with nested fields + rpc NestedFieldsHappyPathTest(NestedFieldsHappyPathTestRequest) returns (google.protobuf.Empty) { + option (google.api.routing) = { + routing_parameters { + field: "account.name" + path_template: "/v1beta1/{rename=tests/*}" + } + }; + } + +} + +message NoRoutingRuleTestRequest { +} + +message EmptyPathTemplateTestRequest { + string name = 1; +} + +message ZeroSegmentPathTemplateTestRequest { + string name = 1; +} + +message MultipleSegmentsPathTemplateTestRequest { + string name = 1; +} + +message HappyPathTestRequest { + string name = 1; +} + +message MultipleRoutingParamsHappyPathTestReqest { + string name = 1; + string routing_id = 2; +} + +message NestedFieldsHappyPathTestRequest { + Account account = 1; +} + +message Account { + string name = 1; +} + diff --git a/src/test/java/com/google/api/generator/gapic/testdata/testing.proto b/src/test/java/com/google/api/generator/gapic/testdata/testing.proto index ef4cc2cc1c..8ef4fd2448 100644 --- a/src/test/java/com/google/api/generator/gapic/testdata/testing.proto +++ b/src/test/java/com/google/api/generator/gapic/testdata/testing.proto @@ -84,6 +84,10 @@ service Testing { field: "name" path_template: "/v1beta1/{rename=tests/*}" } + routing_parameters { + field: "table_name" + path_template: "/v1beta1/{routing_id=tests/*}" + } }; option (google.api.method_signature) = "name"; } @@ -277,6 +281,7 @@ message GetTestRequest { // The session to be retrieved. string name = 1 [(google.api.resource_reference).type = "showcase.googleapis.com/Test"]; + string table_name = 2; } message Test { From f375d04571513e99fab77a3507f34507e098a2c7 Mon Sep 17 00:00:00 2001 From: Blake Li Date: Tue, 4 Jan 2022 15:40:15 -0700 Subject: [PATCH 3/9] #869. Add/refactor unit tests for RoutingRuleParserTest and MessageTest. --- .../generator/gapic/model/MessageTest.java | 4 +- .../protoparser/RoutingRuleParserTest.java | 62 +++++++++++++------ .../routing_rule_parser_testing.proto | 14 +++++ 3 files changed, 59 insertions(+), 21 deletions(-) diff --git a/src/test/java/com/google/api/generator/gapic/model/MessageTest.java b/src/test/java/com/google/api/generator/gapic/model/MessageTest.java index fe5c54085a..cb733c7edc 100644 --- a/src/test/java/com/google/api/generator/gapic/model/MessageTest.java +++ b/src/test/java/com/google/api/generator/gapic/model/MessageTest.java @@ -73,10 +73,10 @@ public void shouldThrowExceptionIfMessageDoesNotExist() { Message message = TEST_MESSAGE_BUILDER.setFieldMap(ImmutableMap.of(subFieldName, subField)).build(); String fieldName = subFieldName + "." + "size"; - NullPointerException illegalStateException = + NullPointerException nullPointerException = assertThrows( NullPointerException.class, () -> message.validateField(fieldName, ImmutableMap.of())); - assertThat(illegalStateException.getMessage()) + assertThat(nullPointerException.getMessage()) .isEqualTo(String.format(MESSAGE_NOT_FOUND_ERROR_MESSAGE, subFieldName, fieldTypeName)); } diff --git a/src/test/java/com/google/api/generator/gapic/protoparser/RoutingRuleParserTest.java b/src/test/java/com/google/api/generator/gapic/protoparser/RoutingRuleParserTest.java index c3465d921b..3363270813 100644 --- a/src/test/java/com/google/api/generator/gapic/protoparser/RoutingRuleParserTest.java +++ b/src/test/java/com/google/api/generator/gapic/protoparser/RoutingRuleParserTest.java @@ -40,30 +40,22 @@ public class RoutingRuleParserTest { @Test public void shouldReturnEmptyRoutingHeadersIfMethodHasNoRoutingRules() { - MethodDescriptor rpcMethod = TESTING_SERVICE.getMethods().get(0); - Message inputMessage = MESSAGES.get("com." + rpcMethod.getInputType().getFullName()); - RoutingHeaders routingHeaders = RoutingRuleParser.parse(rpcMethod, inputMessage, MESSAGES); - assertThat(routingHeaders.routingHeadersList()).isEmpty(); + RoutingHeaders actual = getRoutingHeaders(0); + assertThat(actual.routingHeadersList()).isEmpty(); } @Test public void shouldSetPathTemplateToWildcardIfNotDefined() { - MethodDescriptor rpcMethod = TESTING_SERVICE.getMethods().get(1); - Message inputMessage = MESSAGES.get("com." + rpcMethod.getInputType().getFullName()); - RoutingHeaders routingHeaders = RoutingRuleParser.parse(rpcMethod, inputMessage, MESSAGES); - RoutingHeader routingHeader = + RoutingHeaders actual = getRoutingHeaders(1); + RoutingHeader expected = RoutingHeader.create("name", "name", String.format(WILDCARD_PATTERN, "name")); - assertThat(routingHeaders.routingHeadersList()).containsExactly(routingHeader); + assertThat(actual.routingHeadersList()).containsExactly(expected); } @Test public void shouldThrowExceptionIfPathTemplateHasZeroNamedSegment() { - MethodDescriptor rpcMethod = TESTING_SERVICE.getMethods().get(2); - Message inputMessage = MESSAGES.get("com." + rpcMethod.getInputType().getFullName()); IllegalArgumentException illegalArgumentException = - assertThrows( - IllegalArgumentException.class, - () -> RoutingRuleParser.parse(rpcMethod, inputMessage, MESSAGES)); + assertThrows(IllegalArgumentException.class, () -> getRoutingHeaders(2)); assertThat(illegalArgumentException.getMessage()) .isEqualTo( String.format( @@ -72,16 +64,48 @@ public void shouldThrowExceptionIfPathTemplateHasZeroNamedSegment() { @Test public void shouldThrowExceptionIfPathTemplateHasMoreThanOneNamedSegment() { - MethodDescriptor rpcMethod = TESTING_SERVICE.getMethods().get(3); - Message inputMessage = MESSAGES.get("com." + rpcMethod.getInputType().getFullName()); IllegalArgumentException illegalArgumentException = - assertThrows( - IllegalArgumentException.class, - () -> RoutingRuleParser.parse(rpcMethod, inputMessage, MESSAGES)); + assertThrows(IllegalArgumentException.class, () -> getRoutingHeaders(3)); assertThat(illegalArgumentException.getMessage()) .isEqualTo( String.format( PATH_TEMPLATE_WRONG_NUMBER_OF_NAMED_SEGMENT_ERROR_MESSAGE, "/v1beta1/{name=tests/*}/{second_name=*}")); } + + @Test + public void shouldParseRoutingRulesWithOneParameter() { + RoutingHeaders actual = getRoutingHeaders(4); + RoutingHeader expected = RoutingHeader.create("name", "rename", "/v1beta1/{rename=tests/*}"); + assertThat(actual.routingHeadersList()).containsExactly(expected); + } + + @Test + public void shouldParseRoutingRulesWithMultipleParameter() { + RoutingHeaders actual = getRoutingHeaders(5); + RoutingHeader expectedHeader1 = + RoutingHeader.create("name", "rename", "/v1beta1/{rename=tests/*}"); + RoutingHeader expectedHeader2 = + RoutingHeader.create("routing_id", "id", "/v1beta1/{id=projects/*}/tables/*"); + assertThat(actual.routingHeadersList()).containsExactly(expectedHeader1, expectedHeader2); + } + + @Test + public void shouldParseRoutingRulesWithNestedFields() { + RoutingHeaders actual = getRoutingHeaders(6); + RoutingHeader expectedHeader1 = + RoutingHeader.create("account.name", "rename", "/v1beta1/{rename=tests/*}"); + assertThat(actual.routingHeadersList()).containsExactly(expectedHeader1); + } + + @Test + public void shouldThrowExceptionIfFieldValidationFailed() { + assertThrows(IllegalStateException.class, () -> getRoutingHeaders(7)); + } + + private RoutingHeaders getRoutingHeaders(int testingIndex) { + MethodDescriptor rpcMethod = TESTING_SERVICE.getMethods().get(testingIndex); + Message inputMessage = MESSAGES.get("com." + rpcMethod.getInputType().getFullName()); + return RoutingRuleParser.parse(rpcMethod, inputMessage, MESSAGES); + } } diff --git a/src/test/java/com/google/api/generator/gapic/testdata/routing_rule_parser_testing.proto b/src/test/java/com/google/api/generator/gapic/testdata/routing_rule_parser_testing.proto index 031ea45641..8cb3f17511 100644 --- a/src/test/java/com/google/api/generator/gapic/testdata/routing_rule_parser_testing.proto +++ b/src/test/java/com/google/api/generator/gapic/testdata/routing_rule_parser_testing.proto @@ -94,6 +94,16 @@ service RoutingRuleParserTesting { }; } + // Test case for field validation. We already have extensive unit tests for field validation, so only testing one simple case. + rpc FieldDoesNotExistTest(FieldDoesNotExistTestRequest) returns (google.protobuf.Empty) { + option (google.api.routing) = { + routing_parameters { + field: "does_not_exist" + path_template: "/v1beta1/{rename=tests/*}" + } + }; + } + } message NoRoutingRuleTestRequest { @@ -124,6 +134,10 @@ message NestedFieldsHappyPathTestRequest { Account account = 1; } +message FieldDoesNotExistTestRequest { + string name = 1; +} + message Account { string name = 1; } From 14c92f83ef0a4fa3eafbada2fbda2dae06c69ecc Mon Sep 17 00:00:00 2001 From: Blake Li Date: Wed, 5 Jan 2022 21:45:00 -0700 Subject: [PATCH 4/9] #869. Move the logic of match-and-extract to GAX. Create path templates on code generation time not runtime. --- ...ractTransportServiceStubClassComposer.java | 19 +- .../grpc/GrpcServiceStubClassComposer.java | 183 +++++++++++------- .../HttpJsonServiceStubClassComposer.java | 5 +- .../api/generator/gapic/model/Method.java | 4 + .../grpc/goldens/GrpcTestingStub.golden | 38 ++-- .../routing_rule_parser_testing.proto | 5 +- .../generator/gapic/testdata/testing.proto | 8 +- 7 files changed, 158 insertions(+), 104 deletions(-) diff --git a/src/main/java/com/google/api/generator/gapic/composer/common/AbstractTransportServiceStubClassComposer.java b/src/main/java/com/google/api/generator/gapic/composer/common/AbstractTransportServiceStubClassComposer.java index 1b4827ef70..df41692e61 100644 --- a/src/main/java/com/google/api/generator/gapic/composer/common/AbstractTransportServiceStubClassComposer.java +++ b/src/main/java/com/google/api/generator/gapic/composer/common/AbstractTransportServiceStubClassComposer.java @@ -51,7 +51,6 @@ import com.google.api.generator.engine.ast.VariableExpr; import com.google.api.generator.gapic.composer.comment.StubCommentComposer; import com.google.api.generator.gapic.composer.store.TypeStore; -import com.google.api.generator.gapic.composer.utils.ClassNames; import com.google.api.generator.gapic.composer.utils.PackageChecker; import com.google.api.generator.gapic.model.GapicClass; import com.google.api.generator.gapic.model.GapicClass.Kind; @@ -204,7 +203,6 @@ public GapicClass generate(GapicContext context, Service service) { .setName(className) .setExtendsType( typeStore.get(getTransportContext().classNames().getServiceStubClassName(service))) - .setStatements(classStatements) .setMethods( createClassMethods( context, @@ -212,7 +210,8 @@ public GapicClass generate(GapicContext context, Service service) { typeStore, classMemberVarExprs, callableClassMemberVarExprs, - protoMethodNameToDescriptorVarExprs)) + protoMethodNameToDescriptorVarExprs, classStatements)) + .setStatements(classStatements) .build(); return GapicClass.create(kind, classDef); } @@ -249,7 +248,8 @@ protected List createOperationsStubGetterMethod( } protected abstract Expr createTransportSettingsInitExpr( - Method method, VariableExpr transportSettingsVarExpr, VariableExpr methodDescriptorVarExpr); + Method method, VariableExpr transportSettingsVarExpr, VariableExpr methodDescriptorVarExpr, + List classStatements); protected List createGetMethodDescriptorsMethod( Service service, @@ -430,7 +430,8 @@ protected List createClassMethods( TypeStore typeStore, Map classMemberVarExprs, Map callableClassMemberVarExprs, - Map protoMethodNameToDescriptorVarExprs) { + Map protoMethodNameToDescriptorVarExprs, + List classStatements) { List javaMethods = new ArrayList<>(); javaMethods.addAll(createStaticCreatorMethods(service, typeStore, "newBuilder")); javaMethods.addAll( @@ -440,7 +441,8 @@ protected List createClassMethods( typeStore, classMemberVarExprs, callableClassMemberVarExprs, - protoMethodNameToDescriptorVarExprs)); + protoMethodNameToDescriptorVarExprs, + classStatements)); javaMethods.addAll( createGetMethodDescriptorsMethod(service, typeStore, protoMethodNameToDescriptorVarExprs)); javaMethods.addAll( @@ -541,7 +543,8 @@ protected List createConstructorMethods( TypeStore typeStore, Map classMemberVarExprs, Map callableClassMemberVarExprs, - Map protoMethodNameToDescriptorVarExprs) { + Map protoMethodNameToDescriptorVarExprs, + List classStatements) { TypeNode stubSettingsType = typeStore.get(getTransportContext().classNames().getServiceStubSettingsClassName(service)); VariableExpr settingsVarExpr = @@ -666,7 +669,7 @@ protected List createConstructorMethods( m, javaStyleMethodNameToTransportSettingsVarExprs.get( JavaStyle.toLowerCamelCase(m.name())), - protoMethodNameToDescriptorVarExprs.get(m.name()))) + protoMethodNameToDescriptorVarExprs.get(m.name()), classStatements)) .collect(Collectors.toList())); secondCtorStatements.addAll( secondCtorExprs.stream().map(ExprStatement::withExpr).collect(Collectors.toList())); diff --git a/src/main/java/com/google/api/generator/gapic/composer/grpc/GrpcServiceStubClassComposer.java b/src/main/java/com/google/api/generator/gapic/composer/grpc/GrpcServiceStubClassComposer.java index 9605e6bf32..b8c511562a 100644 --- a/src/main/java/com/google/api/generator/gapic/composer/grpc/GrpcServiceStubClassComposer.java +++ b/src/main/java/com/google/api/generator/gapic/composer/grpc/GrpcServiceStubClassComposer.java @@ -16,6 +16,7 @@ import com.google.api.gax.grpc.GrpcCallSettings; import com.google.api.gax.grpc.GrpcStubCallableFactory; +import com.google.api.gax.rpc.internal.RoutingHeaderHelper; import com.google.api.generator.engine.ast.AssignmentExpr; import com.google.api.generator.engine.ast.ConcreteReference; import com.google.api.generator.engine.ast.EnumRefExpr; @@ -35,7 +36,6 @@ import com.google.api.generator.engine.ast.VariableExpr; import com.google.api.generator.gapic.composer.common.AbstractTransportServiceStubClassComposer; import com.google.api.generator.gapic.composer.store.TypeStore; -import com.google.api.generator.gapic.model.GapicContext; import com.google.api.generator.gapic.model.HttpBindings.HttpBinding; import com.google.api.generator.gapic.model.Message; import com.google.api.generator.gapic.model.Method; @@ -43,7 +43,6 @@ import com.google.api.generator.gapic.model.Service; import com.google.api.generator.gapic.utils.JavaStyle; import com.google.api.pathtemplate.PathTemplate; -import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.longrunning.stub.GrpcOperationsStub; @@ -202,7 +201,10 @@ protected EnumRefExpr getMethodDescriptorMethodTypeExpr(Method protoMethod) { @Override protected Expr createTransportSettingsInitExpr( - Method method, VariableExpr transportSettingsVarExpr, VariableExpr methodDescriptorVarExpr) { + Method method, + VariableExpr transportSettingsVarExpr, + VariableExpr methodDescriptorVarExpr, + List classStatements) { MethodInvocationExpr callSettingsBuilderExpr = MethodInvocationExpr.builder() .setStaticReferenceType(getTransportContext().transportCallSettingsType()) @@ -216,12 +218,12 @@ protected Expr createTransportSettingsInitExpr( .setArguments(Arrays.asList(methodDescriptorVarExpr)) .build(); - if (method.hasHttpBindings()) { + if (method.hasHttpBindings() || method.hasRoutingHeaders()) { callSettingsBuilderExpr = MethodInvocationExpr.builder() .setExprReferenceExpr(callSettingsBuilderExpr) .setMethodName("setParamsExtractor") - .setArguments(createRequestParamsExtractorClassInstance(method)) + .setArguments(createRequestParamsExtractorClassInstance(method, classStatements)) .build(); } @@ -237,27 +239,7 @@ protected Expr createTransportSettingsInitExpr( .build(); } - @Override - protected List createClassMethods( - GapicContext context, - Service service, - TypeStore typeStore, - Map classMemberVarExprs, - Map callableClassMemberVarExprs, - Map protoMethodNameToDescriptorVarExprs) { - List classMethods = - super.createClassMethods( - context, - service, - typeStore, - classMemberVarExprs, - callableClassMemberVarExprs, - protoMethodNameToDescriptorVarExprs); - // TODO: need a way to check do we need to create this method or not, or make it an inner method - classMethods.add(createAddParamsMethod()); - return classMethods; - } - + // TODO: remove this method once we finalized the approach, keep it as a reference for now. private MethodDefinition createAddParamsMethod() { TypeNode paramsVarType = TypeNode.withReference( @@ -408,9 +390,8 @@ protected String getProtoRpcFullMethodName(Service protoService, Method protoMet return String.format("google.iam.v1.IAMPolicy/%s", protoMethod.name()); } - private LambdaExpr createRequestParamsExtractorClassInstance(Method method) { - Preconditions.checkState( - method.hasHttpBindings(), String.format("Method %s has no HTTP binding", method.name())); + private LambdaExpr createRequestParamsExtractorClassInstance( + Method method, List classStatements) { TypeNode paramsVarType = TypeNode.withReference( @@ -439,52 +420,10 @@ private LambdaExpr createRequestParamsExtractorClassInstance(Method method) { VariableExpr.withVariable( Variable.builder().setType(method.inputType()).setName("request").build()); - for (HttpBinding httpBindingFieldBinding : method.httpBindings().pathParameters()) { - // Handle foo.bar cases by descending into the subfields. - MethodInvocationExpr requestBuilderExpr = - createRequestFieldGetterExpr(requestVarExpr, httpBindingFieldBinding.name()); - - Expr valueOfExpr = - MethodInvocationExpr.builder() - .setStaticReferenceType(TypeNode.STRING) - .setMethodName("valueOf") - .setArguments(requestBuilderExpr) - .build(); - - // Comment out for now. TODO: completely remove this part if routing headers is not null? - // Are these params used for anything else other than implicit dynamic routing? - // Expr paramsPutExpr = - // MethodInvocationExpr.builder() - // .setExprReferenceExpr(paramsVarExpr) - // .setMethodName("put") - // .setArguments( - // - // ValueExpr.withValue(StringObjectValue.withValue(httpBindingFieldBinding.name())), - // valueOfExpr) - // .build(); - // bodyExprs.add(paramsPutExpr); - } - - for (RoutingHeader routingHeader : method.routingHeaders().routingHeadersList()) { - MethodInvocationExpr requestFieldGetterExpr = - createRequestFieldGetterExpr(requestVarExpr, routingHeader.field()); + addRequestParamsForHttpBindings(method, paramsVarExpr, bodyExprs, requestVarExpr); - Expr routingHeaderKeyExpr = - ValueExpr.withValue(StringObjectValue.withValue(routingHeader.name())); - Expr routingHeaderPatternExpr = - ValueExpr.withValue(StringObjectValue.withValue(routingHeader.pattern())); - MethodInvocationExpr addParamsMethodExpr = - MethodInvocationExpr.builder() - .setMethodName("addParams") - .setArguments( - paramsVarExpr, - requestFieldGetterExpr, - routingHeaderKeyExpr, - routingHeaderPatternExpr) - .build(); - - bodyExprs.add(addParamsMethodExpr); - } + addRequestParamsForRoutingHeders( + method, classStatements, paramsVarExpr, bodyExprs, requestVarExpr); TypeNode returnType = TypeNode.withReference( @@ -509,11 +448,107 @@ private LambdaExpr createRequestParamsExtractorClassInstance(Method method) { .build(); } + private void addRequestParamsForRoutingHeders( + Method method, + List classStatements, + VariableExpr paramsVarExpr, + List bodyExprs, + VariableExpr requestVarExpr) { + ImmutableList routingHeaders = method.routingHeaders().routingHeadersList(); + for (int i = 0; i < routingHeaders.size(); i++) { + RoutingHeader routingHeader = routingHeaders.get(i); + MethodInvocationExpr requestFieldGetterExpr = + createRequestFieldGetterExpr(requestVarExpr, routingHeader.field()); + Expr routingHeaderKeyExpr = + ValueExpr.withValue(StringObjectValue.withValue(routingHeader.name())); + // TODO: Create proper snake style upper case name + String pathTemplateName = + String.format("PATH_TEMPLATE_%s_%s", method.name().toUpperCase(), i); + TypeNode pathTemplateType = + TypeNode.withReference(ConcreteReference.withClazz(PathTemplate.class)); + Variable pathTemplateVar = + Variable.builder().setType(pathTemplateType).setName(pathTemplateName).build(); + Expr routingHeaderPatternExpr = VariableExpr.withVariable(pathTemplateVar); + VariableExpr pathTemplateVarExpr = + VariableExpr.builder() + .setVariable(pathTemplateVar) + .setIsDecl(true) + .setIsStatic(true) + .setIsFinal(true) + .setScope(ScopeNode.PRIVATE) + .build(); + ValueExpr valueExpr = + ValueExpr.withValue(StringObjectValue.withValue(routingHeader.pattern())); + Expr pathTemplateExpr = + AssignmentExpr.builder() + .setVariableExpr(pathTemplateVarExpr) + .setValueExpr( + MethodInvocationExpr.builder() + .setStaticReferenceType(pathTemplateType) + .setMethodName("create") + .setArguments(valueExpr) + .setReturnType(pathTemplateType) + .build()) + .build(); + Statement pathTemplateClassVar = ExprStatement.withExpr(pathTemplateExpr); + classStatements.add(pathTemplateClassVar); + MethodInvocationExpr addParamsMethodExpr = + MethodInvocationExpr.builder() + .setStaticReferenceType( + TypeNode.withReference(ConcreteReference.withClazz(RoutingHeaderHelper.class))) + .setMethodName("addParams") + .setArguments( + paramsVarExpr, + requestFieldGetterExpr, + routingHeaderKeyExpr, + routingHeaderPatternExpr) + .build(); + + bodyExprs.add(addParamsMethodExpr); + } + } + + private void addRequestParamsForHttpBindings( + Method method, + VariableExpr paramsVarExpr, + List bodyExprs, + VariableExpr requestVarExpr) { + // TODO: Are these params used for anything else other than implicit dynamic routing? + // Can we skip this method if routing headers are configured? + if (!method.routingHeaders().routingHeadersList().isEmpty()) { + return; + } + for (HttpBinding httpBindingFieldBinding : method.httpBindings().pathParameters()) { + // Handle foo.bar cases by descending into the subfields. + MethodInvocationExpr requestBuilderExpr = + createRequestFieldGetterExpr(requestVarExpr, httpBindingFieldBinding.name()); + + Expr valueOfExpr = + MethodInvocationExpr.builder() + .setStaticReferenceType(TypeNode.STRING) + .setMethodName("valueOf") + .setArguments(requestBuilderExpr) + .build(); + + Expr paramsPutExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(paramsVarExpr) + .setMethodName("put") + .setArguments( + ValueExpr.withValue(StringObjectValue.withValue(httpBindingFieldBinding.name())), + valueOfExpr) + .build(); + bodyExprs.add(paramsPutExpr); + } + } + private MethodInvocationExpr createRequestFieldGetterExpr( VariableExpr requestVarExpr, String fieldName) { MethodInvocationExpr.Builder requestFieldGetterExprBuilder = MethodInvocationExpr.builder().setExprReferenceExpr(requestVarExpr); String[] descendantFields = fieldName.split("\\."); + // Handle foo.bar cases by descending into the subfields. + // e.g. foo.bar -> request.getFoo().getBar() for (int i = 0; i < descendantFields.length; i++) { String currFieldName = descendantFields[i]; String bindingFieldMethodName = diff --git a/src/main/java/com/google/api/generator/gapic/composer/rest/HttpJsonServiceStubClassComposer.java b/src/main/java/com/google/api/generator/gapic/composer/rest/HttpJsonServiceStubClassComposer.java index 7643406830..f22b917d82 100644 --- a/src/main/java/com/google/api/generator/gapic/composer/rest/HttpJsonServiceStubClassComposer.java +++ b/src/main/java/com/google/api/generator/gapic/composer/rest/HttpJsonServiceStubClassComposer.java @@ -197,7 +197,10 @@ protected List createOperationsStubGetterMethod( @Override protected Expr createTransportSettingsInitExpr( - Method method, VariableExpr transportSettingsVarExpr, VariableExpr methodDescriptorVarExpr) { + Method method, + VariableExpr transportSettingsVarExpr, + VariableExpr methodDescriptorVarExpr, + List classStatements) { MethodInvocationExpr callSettingsBuilderExpr = MethodInvocationExpr.builder() .setStaticReferenceType( diff --git a/src/main/java/com/google/api/generator/gapic/model/Method.java b/src/main/java/com/google/api/generator/gapic/model/Method.java index c2961994b5..f29f0f0c66 100644 --- a/src/main/java/com/google/api/generator/gapic/model/Method.java +++ b/src/main/java/com/google/api/generator/gapic/model/Method.java @@ -83,6 +83,10 @@ public boolean hasHttpBindings() { return httpBindings() != null && !httpBindings().pathParameters().isEmpty(); } + public boolean hasRoutingHeaders() { + return routingHeaders() != null && !routingHeaders().routingHeadersList().isEmpty(); + } + public boolean isMixin() { return mixedInApiName() != null; } diff --git a/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcTestingStub.golden b/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcTestingStub.golden index a8d8ad52a5..635a0bafdb 100644 --- a/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcTestingStub.golden +++ b/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcTestingStub.golden @@ -10,6 +10,7 @@ import com.google.api.gax.grpc.GrpcCallSettings; import com.google.api.gax.grpc.GrpcStubCallableFactory; import com.google.api.gax.rpc.ClientContext; import com.google.api.gax.rpc.UnaryCallable; +import com.google.api.gax.rpc.internal.RoutingHeaderHelper; import com.google.api.pathtemplate.PathTemplate; import com.google.common.collect.ImmutableMap; import com.google.longrunning.stub.GrpcOperationsStub; @@ -143,6 +144,11 @@ public class GrpcTestingStub extends TestingStub { private final GrpcOperationsStub operationsStub; private final GrpcStubCallableFactory callableFactory; + private static final PathTemplate PATH_TEMPLATE_GETTEST_0 = + PathTemplate.create("/v1beta1/{rename=tests/*}"); + private static final PathTemplate PATH_TEMPLATE_GETTEST_1 = + PathTemplate.create("/v1beta1/{routing_id=tests/*}"); + public static final GrpcTestingStub create(TestingStubSettings settings) throws IOException { return new GrpcTestingStub(settings, ClientContext.create(settings)); } @@ -188,6 +194,7 @@ public class GrpcTestingStub extends TestingStub { .setParamsExtractor( request -> { ImmutableMap.Builder params = ImmutableMap.builder(); + params.put("name", String.valueOf(request.getName())); return params.build(); }) .build(); @@ -201,6 +208,7 @@ public class GrpcTestingStub extends TestingStub { .setParamsExtractor( request -> { ImmutableMap.Builder params = ImmutableMap.builder(); + params.put("name", String.valueOf(request.getName())); return params.build(); }) .build(); @@ -210,6 +218,7 @@ public class GrpcTestingStub extends TestingStub { .setParamsExtractor( request -> { ImmutableMap.Builder params = ImmutableMap.builder(); + params.put("name", String.valueOf(request.getName())); return params.build(); }) .build(); @@ -219,12 +228,13 @@ public class GrpcTestingStub extends TestingStub { .setParamsExtractor( request -> { ImmutableMap.Builder params = ImmutableMap.builder(); - addParams(params, request.getName(), "rename", "/v1beta1/{rename=tests/*}"); - addParams( + RoutingHeaderHelper.addParams( + params, request.getName(), "rename", PATH_TEMPLATE_GETTEST_0); + RoutingHeaderHelper.addParams( params, - request.getTableName(), + request.getRouting().getName(), "routing_id", - "/v1beta1/{routing_id=tests/*}"); + PATH_TEMPLATE_GETTEST_1); return params.build(); }) .build(); @@ -234,6 +244,7 @@ public class GrpcTestingStub extends TestingStub { .setParamsExtractor( request -> { ImmutableMap.Builder params = ImmutableMap.builder(); + params.put("parent", String.valueOf(request.getParent())); return params.build(); }) .build(); @@ -243,6 +254,7 @@ public class GrpcTestingStub extends TestingStub { .setParamsExtractor( request -> { ImmutableMap.Builder params = ImmutableMap.builder(); + params.put("name", String.valueOf(request.getName())); return params.build(); }) .build(); @@ -252,6 +264,12 @@ public class GrpcTestingStub extends TestingStub { .setParamsExtractor( request -> { ImmutableMap.Builder params = ImmutableMap.builder(); + params.put("answer", String.valueOf(request.getAnswer())); + params.put("foo", String.valueOf(request.getFoo())); + params.put("name", String.valueOf(request.getName())); + params.put( + "test_to_verify.name", String.valueOf(request.getTestToVerify().getName())); + params.put("type", String.valueOf(request.getType())); return params.build(); }) .build(); @@ -388,16 +406,4 @@ public class GrpcTestingStub extends TestingStub { public boolean awaitTermination(long duration, TimeUnit unit) throws InterruptedException { return backgroundResources.awaitTermination(duration, unit); } - - protected void addParams( - ImmutableMap.Builder params, - String fieldValue, - String headerKey, - String pattern) { - PathTemplate pathTemplate = PathTemplate.create(pattern); - Map matchedValues = pathTemplate.match(fieldValue); - if (matchedValues != null) { - params.put(headerKey, matchedValues.get(headerKey)); - } - } } diff --git a/src/test/java/com/google/api/generator/gapic/testdata/routing_rule_parser_testing.proto b/src/test/java/com/google/api/generator/gapic/testdata/routing_rule_parser_testing.proto index 8cb3f17511..e6b4e32521 100644 --- a/src/test/java/com/google/api/generator/gapic/testdata/routing_rule_parser_testing.proto +++ b/src/test/java/com/google/api/generator/gapic/testdata/routing_rule_parser_testing.proto @@ -1,4 +1,4 @@ -// Copyright 2018 Google LLC +// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -19,11 +19,10 @@ import "google/protobuf/empty.proto"; package google.showcase.v1beta1; -option go_package = "github.com/googleapis/gapic-showcase/server/genproto"; option java_package = "com.google.showcase.v1beta1"; option java_multiple_files = true; -// This service is meant to only run for unit testing RoutingRuleParser +// This service is only meant for unit testing RoutingRuleParser service RoutingRuleParserTesting { // Test case for no routing rule found diff --git a/src/test/java/com/google/api/generator/gapic/testdata/testing.proto b/src/test/java/com/google/api/generator/gapic/testdata/testing.proto index 8ef4fd2448..a7d15261a4 100644 --- a/src/test/java/com/google/api/generator/gapic/testdata/testing.proto +++ b/src/test/java/com/google/api/generator/gapic/testdata/testing.proto @@ -85,7 +85,7 @@ service Testing { path_template: "/v1beta1/{rename=tests/*}" } routing_parameters { - field: "table_name" + field: "routing.name" path_template: "/v1beta1/{routing_id=tests/*}" } }; @@ -281,7 +281,11 @@ message GetTestRequest { // The session to be retrieved. string name = 1 [(google.api.resource_reference).type = "showcase.googleapis.com/Test"]; - string table_name = 2; + Routing routing = 2; +} + +message Routing { + string name = 1; } message Test { From 7b3503eb40fab0922a5001bb4e0845a55f29e89f Mon Sep 17 00:00:00 2001 From: Blake Li Date: Thu, 13 Jan 2022 11:01:38 -0500 Subject: [PATCH 5/9] #869. Use the new builder to populate routing header params in generated code. --- .../grpc/GrpcServiceStubClassComposer.java | 274 +++++------------- .../api/generator/gapic/model/Message.java | 17 +- .../generator/gapic/model/RoutingHeaders.java | 4 +- .../gapic/protoparser/RoutingRuleParser.java | 12 +- .../grpc/goldens/GrpcTestingStub.golden | 16 +- .../generator/gapic/model/MessageTest.java | 22 +- .../generator/gapic/protoparser/BUILD.bazel | 1 - .../protoparser/RoutingRuleParserTest.java | 28 +- 8 files changed, 120 insertions(+), 254 deletions(-) diff --git a/src/main/java/com/google/api/generator/gapic/composer/grpc/GrpcServiceStubClassComposer.java b/src/main/java/com/google/api/generator/gapic/composer/grpc/GrpcServiceStubClassComposer.java index b8c511562a..77e646d33e 100644 --- a/src/main/java/com/google/api/generator/gapic/composer/grpc/GrpcServiceStubClassComposer.java +++ b/src/main/java/com/google/api/generator/gapic/composer/grpc/GrpcServiceStubClassComposer.java @@ -16,17 +16,15 @@ import com.google.api.gax.grpc.GrpcCallSettings; import com.google.api.gax.grpc.GrpcStubCallableFactory; -import com.google.api.gax.rpc.internal.RoutingHeaderHelper; +import com.google.api.gax.grpc.RoutingHeaderParamsBuilder; import com.google.api.generator.engine.ast.AssignmentExpr; import com.google.api.generator.engine.ast.ConcreteReference; import com.google.api.generator.engine.ast.EnumRefExpr; import com.google.api.generator.engine.ast.Expr; import com.google.api.generator.engine.ast.ExprStatement; -import com.google.api.generator.engine.ast.IfStatement; import com.google.api.generator.engine.ast.LambdaExpr; -import com.google.api.generator.engine.ast.MethodDefinition; import com.google.api.generator.engine.ast.MethodInvocationExpr; -import com.google.api.generator.engine.ast.RelationalOperationExpr; +import com.google.api.generator.engine.ast.NewObjectExpr; import com.google.api.generator.engine.ast.ScopeNode; import com.google.api.generator.engine.ast.Statement; import com.google.api.generator.engine.ast.StringObjectValue; @@ -239,141 +237,6 @@ protected Expr createTransportSettingsInitExpr( .build(); } - // TODO: remove this method once we finalized the approach, keep it as a reference for now. - private MethodDefinition createAddParamsMethod() { - TypeNode paramsVarType = - TypeNode.withReference( - ConcreteReference.builder() - .setClazz(ImmutableMap.Builder.class) - .setGenerics(TypeNode.STRING.reference(), TypeNode.STRING.reference()) - .build()); - VariableExpr paramsVarExpr = - VariableExpr.builder() - .setVariable(Variable.builder().setName("params").setType(paramsVarType).build()) - .setIsDecl(true) - .build(); - VariableExpr fieldValueExpr = - VariableExpr.builder() - .setVariable(Variable.builder().setName("fieldValue").setType(TypeNode.STRING).build()) - .setIsDecl(true) - .build(); - VariableExpr keyExpr = - VariableExpr.builder() - .setVariable(Variable.builder().setName("headerKey").setType(TypeNode.STRING).build()) - .setIsDecl(true) - .build(); - VariableExpr patternExpr = - VariableExpr.builder() - .setVariable(Variable.builder().setName("pattern").setType(TypeNode.STRING).build()) - .setIsDecl(true) - .build(); - List methodBody = new ArrayList<>(); - TypeNode pathTemplateType = - TypeNode.withReference(ConcreteReference.builder().setClazz(PathTemplate.class).build()); - VariableExpr pathTemplateVar = - VariableExpr.builder() - .setVariable( - Variable.builder().setName("pathTemplate").setType(pathTemplateType).build()) - .setIsDecl(true) - .build(); - VariableExpr patternExprNonDecl = - VariableExpr.builder() - .setVariable(Variable.builder().setName("pattern").setType(TypeNode.STRING).build()) - .build(); - Expr pathTemplateExpr = - AssignmentExpr.builder() - .setVariableExpr(pathTemplateVar) - .setValueExpr( - MethodInvocationExpr.builder() - .setStaticReferenceType(pathTemplateType) - .setMethodName("create") - .setArguments(patternExprNonDecl) - .setReturnType(pathTemplateType) - .build()) - .build(); - ExprStatement exprStatement1 = ExprStatement.withExpr(pathTemplateExpr); - methodBody.add(exprStatement1); - - VariableExpr pathTemplateVarNonDecl = - VariableExpr.builder() - .setVariable( - Variable.builder().setName("pathTemplate").setType(pathTemplateType).build()) - .build(); - TypeNode matchedValuesType = - TypeNode.withReference( - ConcreteReference.builder() - .setClazz(Map.class) - .setGenerics(TypeNode.STRING.reference(), TypeNode.STRING.reference()) - .build()); - VariableExpr matchedValuesVar = - VariableExpr.builder() - .setVariable( - Variable.builder().setName("matchedValues").setType(matchedValuesType).build()) - .setIsDecl(true) - .build(); - VariableExpr fieldValueExprNonDecl = - VariableExpr.builder() - .setVariable(Variable.builder().setName("fieldValue").setType(pathTemplateType).build()) - .build(); - Expr matchedValuesExpr = - AssignmentExpr.builder() - .setVariableExpr(matchedValuesVar) - .setValueExpr( - MethodInvocationExpr.builder() - .setExprReferenceExpr(pathTemplateVarNonDecl) - .setMethodName("match") - .setArguments(fieldValueExprNonDecl) - .setReturnType(matchedValuesType) - .build()) - .build(); - ExprStatement exprStatement2 = ExprStatement.withExpr(matchedValuesExpr); - methodBody.add(exprStatement2); - - VariableExpr matchedValuesVarNonDecl = - VariableExpr.builder() - .setVariable( - Variable.builder().setName("matchedValues").setType(matchedValuesType).build()) - .build(); - Expr checkMatchedValuesNull = - RelationalOperationExpr.notEqualToWithExprs( - matchedValuesVarNonDecl, ValueExpr.createNullExpr()); - VariableExpr paramsVarExprNonDecl = - VariableExpr.builder() - .setVariable(Variable.builder().setName("params").setType(paramsVarType).build()) - .build(); - VariableExpr keyExprNonDecl = - VariableExpr.builder() - .setVariable(Variable.builder().setName("headerKey").setType(TypeNode.STRING).build()) - .build(); - Expr getMatchedValueExpr = - MethodInvocationExpr.builder() - .setExprReferenceExpr(matchedValuesVarNonDecl) - .setMethodName("get") - .setArguments(keyExprNonDecl) - .build(); - Expr putParamsExpr = - MethodInvocationExpr.builder() - .setExprReferenceExpr(paramsVarExprNonDecl) - .setMethodName("put") - .setArguments(keyExprNonDecl, getMatchedValueExpr) - .build(); - ExprStatement putParamsStatement = ExprStatement.withExpr(putParamsExpr); - IfStatement ifStatement = - IfStatement.builder() - .setConditionExpr(checkMatchedValuesNull) - .setBody(ImmutableList.of(putParamsStatement)) - .build(); - methodBody.add(ifStatement); - - return MethodDefinition.builder() - .setScope(ScopeNode.PROTECTED) - .setReturnType(TypeNode.VOID) - .setName("addParams") - .setBody(methodBody) - .setArguments(paramsVarExpr, fieldValueExpr, keyExpr, patternExpr) - .build(); - } - @Override protected String getProtoRpcFullMethodName(Service protoService, Method protoMethod) { if (protoMethod.isMixin()) { @@ -392,51 +255,26 @@ protected String getProtoRpcFullMethodName(Service protoService, Method protoMet private LambdaExpr createRequestParamsExtractorClassInstance( Method method, List classStatements) { - - TypeNode paramsVarType = - TypeNode.withReference( - ConcreteReference.builder() - .setClazz(ImmutableMap.Builder.class) - .setGenerics(TypeNode.STRING.reference(), TypeNode.STRING.reference()) - .build()); - VariableExpr paramsVarExpr = - VariableExpr.withVariable( - Variable.builder().setName("params").setType(paramsVarType).build()); - - Expr paramsAssignExpr = - AssignmentExpr.builder() - .setVariableExpr(paramsVarExpr.toBuilder().setIsDecl(true).build()) - .setValueExpr( - MethodInvocationExpr.builder() - .setStaticReferenceType(FIXED_TYPESTORE.get("ImmutableMap")) - .setMethodName("builder") - .setReturnType(paramsVarType) - .build()) - .build(); List bodyExprs = new ArrayList<>(); - bodyExprs.add(paramsAssignExpr); - VariableExpr requestVarExpr = VariableExpr.withVariable( Variable.builder().setType(method.inputType()).setName("request").build()); - - addRequestParamsForHttpBindings(method, paramsVarExpr, bodyExprs, requestVarExpr); - - addRequestParamsForRoutingHeders( - method, classStatements, paramsVarExpr, bodyExprs, requestVarExpr); - TypeNode returnType = TypeNode.withReference( ConcreteReference.builder() .setClazz(Map.class) .setGenerics(TypeNode.STRING.reference(), TypeNode.STRING.reference()) .build()); - Expr returnExpr = - MethodInvocationExpr.builder() - .setExprReferenceExpr(paramsVarExpr) - .setMethodName("build") - .setReturnType(returnType) - .build(); + Expr returnExpr; + // If the google.api.routing annotation is present(even with empty routing parameters), + // the implicit routing headers specified in the google.api.http annotation should not be sent + if (method.routingHeaders() == null) { + returnExpr = addRequestParamsForHttpBindings(method, bodyExprs, requestVarExpr, returnType); + } else { + returnExpr = + addRequestParamsForRoutingHeaders( + method, classStatements, bodyExprs, requestVarExpr, returnType); + } // Overrides extract(). // https://github.com/googleapis/gax-java/blob/8d45d186e36ae97b789a6f89d80ae5213a773b65/gax/src/main/java/com/google/api/gax/rpc/RequestParamsExtractor.java#L55 @@ -448,17 +286,38 @@ private LambdaExpr createRequestParamsExtractorClassInstance( .build(); } - private void addRequestParamsForRoutingHeders( + private Expr addRequestParamsForRoutingHeaders( Method method, List classStatements, - VariableExpr paramsVarExpr, List bodyExprs, - VariableExpr requestVarExpr) { + VariableExpr requestVarExpr, + TypeNode returnType) { + TypeNode routingHeadersBuilderType = + TypeNode.withReference( + ConcreteReference.builder().setClazz(RoutingHeaderParamsBuilder.class).build()); + VariableExpr routingHeadersBuilderVarExpr = + VariableExpr.builder() + .setVariable( + Variable.builder().setName("builder").setType(routingHeadersBuilderType).build()) + .setIsDecl(true) + .build(); + Expr newBuilderExpr = NewObjectExpr.builder().setType(routingHeadersBuilderType).build(); + Expr newRoutingHeadersAssignExpr = + AssignmentExpr.builder() + .setVariableExpr(routingHeadersBuilderVarExpr) + .setValueExpr(newBuilderExpr) + .build(); + bodyExprs.add(newRoutingHeadersAssignExpr); ImmutableList routingHeaders = method.routingHeaders().routingHeadersList(); + VariableExpr routingHeadersBuilderVarNonDeclExpr = + VariableExpr.builder() + .setVariable( + Variable.builder().setName("builder").setType(routingHeadersBuilderType).build()) + .build(); for (int i = 0; i < routingHeaders.size(); i++) { RoutingHeader routingHeader = routingHeaders.get(i); MethodInvocationExpr requestFieldGetterExpr = - createRequestFieldGetterExpr(requestVarExpr, routingHeader.field()); + createRequestFieldGetterExpr(requestVarExpr, routingHeader.fieldName()); Expr routingHeaderKeyExpr = ValueExpr.withValue(StringObjectValue.withValue(routingHeader.name())); // TODO: Create proper snake style upper case name @@ -494,35 +353,48 @@ private void addRequestParamsForRoutingHeders( classStatements.add(pathTemplateClassVar); MethodInvocationExpr addParamsMethodExpr = MethodInvocationExpr.builder() - .setStaticReferenceType( - TypeNode.withReference(ConcreteReference.withClazz(RoutingHeaderHelper.class))) - .setMethodName("addParams") - .setArguments( - paramsVarExpr, - requestFieldGetterExpr, - routingHeaderKeyExpr, - routingHeaderPatternExpr) + .setExprReferenceExpr(routingHeadersBuilderVarNonDeclExpr) + .setMethodName("add") + .setArguments(requestFieldGetterExpr, routingHeaderKeyExpr, routingHeaderPatternExpr) .build(); bodyExprs.add(addParamsMethodExpr); } + + return MethodInvocationExpr.builder() + .setExprReferenceExpr(routingHeadersBuilderVarNonDeclExpr) + .setMethodName("build") + .setReturnType(returnType) + .build(); } - private void addRequestParamsForHttpBindings( - Method method, - VariableExpr paramsVarExpr, - List bodyExprs, - VariableExpr requestVarExpr) { - // TODO: Are these params used for anything else other than implicit dynamic routing? - // Can we skip this method if routing headers are configured? - if (!method.routingHeaders().routingHeadersList().isEmpty()) { - return; - } + private Expr addRequestParamsForHttpBindings( + Method method, List bodyExprs, VariableExpr requestVarExpr, TypeNode returnType) { + TypeNode paramsVarType = + TypeNode.withReference( + ConcreteReference.builder() + .setClazz(ImmutableMap.Builder.class) + .setGenerics(TypeNode.STRING.reference(), TypeNode.STRING.reference()) + .build()); + VariableExpr paramsVarExpr = + VariableExpr.withVariable( + Variable.builder().setName("params").setType(paramsVarType).build()); + + Expr paramsAssignExpr = + AssignmentExpr.builder() + .setVariableExpr(paramsVarExpr.toBuilder().setIsDecl(true).build()) + .setValueExpr( + MethodInvocationExpr.builder() + .setStaticReferenceType(FIXED_TYPESTORE.get("ImmutableMap")) + .setMethodName("builder") + .setReturnType(paramsVarType) + .build()) + .build(); + bodyExprs.add(paramsAssignExpr); + for (HttpBinding httpBindingFieldBinding : method.httpBindings().pathParameters()) { - // Handle foo.bar cases by descending into the subfields. MethodInvocationExpr requestBuilderExpr = createRequestFieldGetterExpr(requestVarExpr, httpBindingFieldBinding.name()); - Expr valueOfExpr = MethodInvocationExpr.builder() .setStaticReferenceType(TypeNode.STRING) @@ -540,6 +412,12 @@ private void addRequestParamsForHttpBindings( .build(); bodyExprs.add(paramsPutExpr); } + + return MethodInvocationExpr.builder() + .setExprReferenceExpr(paramsVarExpr) + .setMethodName("build") + .setReturnType(returnType) + .build(); } private MethodInvocationExpr createRequestFieldGetterExpr( diff --git a/src/main/java/com/google/api/generator/gapic/model/Message.java b/src/main/java/com/google/api/generator/gapic/model/Message.java index d7eb5a1a2c..5d6c8f6c6b 100644 --- a/src/main/java/com/google/api/generator/gapic/model/Message.java +++ b/src/main/java/com/google/api/generator/gapic/model/Message.java @@ -35,12 +35,6 @@ @AutoValue public abstract class Message { - static final String EMPTY_FIELD_ERROR_MESSAGE = "Null or empty field name found for message %s"; - static final String FIELD_DOES_NOT_EXIST_ERROR_MESSAGE = - "Expected message %s to contain field %s but none found"; - static final String MESSAGE_NOT_FOUND_ERROR_MESSAGE = - "No containing message found for field %s with type %s"; - public abstract String name(); // The fully-qualified proto name, which differs from the Java fully-qualified name. @@ -100,16 +94,17 @@ public void validateField(String fieldName, Map messageTypes) { Preconditions.checkNotNull( nestedMessage, String.format( - MESSAGE_NOT_FOUND_ERROR_MESSAGE, - field.name(), - field.type().reference().simpleName())); + "No containing message found for field %s with type %s", + field.name(), field.type().reference().simpleName())); } else { Preconditions.checkState( !Strings.isNullOrEmpty(subFieldName), - String.format(EMPTY_FIELD_ERROR_MESSAGE, nestedMessage.name())); + String.format("Null or empty field name found for message %s", nestedMessage.name())); Preconditions.checkState( nestedMessage.fieldMap().containsKey(subFieldName), - String.format(FIELD_DOES_NOT_EXIST_ERROR_MESSAGE, nestedMessage.name(), subFieldName)); + String.format( + "Expected message %s to contain field %s but none found", + nestedMessage.name(), subFieldName)); // TODO: Add type check for String only? } } diff --git a/src/main/java/com/google/api/generator/gapic/model/RoutingHeaders.java b/src/main/java/com/google/api/generator/gapic/model/RoutingHeaders.java index c4c260970c..8eb9d30dfb 100644 --- a/src/main/java/com/google/api/generator/gapic/model/RoutingHeaders.java +++ b/src/main/java/com/google/api/generator/gapic/model/RoutingHeaders.java @@ -1,4 +1,4 @@ -// Copyright 2020 Google LLC +// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -29,7 +29,7 @@ public static Builder builder() { @AutoValue public abstract static class RoutingHeader { - public abstract String field(); + public abstract String fieldName(); public abstract String name(); diff --git a/src/main/java/com/google/api/generator/gapic/protoparser/RoutingRuleParser.java b/src/main/java/com/google/api/generator/gapic/protoparser/RoutingRuleParser.java index a96e0203a2..3ac54f51a1 100644 --- a/src/main/java/com/google/api/generator/gapic/protoparser/RoutingRuleParser.java +++ b/src/main/java/com/google/api/generator/gapic/protoparser/RoutingRuleParser.java @@ -32,17 +32,13 @@ public class RoutingRuleParser { - static final String WILDCARD_PATTERN = "{%s=**}"; - static final String PATH_TEMPLATE_WRONG_NUMBER_OF_NAMED_SEGMENT_ERROR_MESSAGE = - "There needs to be one and only one named segment in path template %s"; - public static RoutingHeaders parse( MethodDescriptor protoMethod, Message inputMessage, Map messageTypes) { MethodOptions methodOptions = protoMethod.getOptions(); RoutingHeaders.Builder builder = RoutingHeaders.builder(); if (!methodOptions.hasExtension(RoutingProto.routing)) { - return builder.build(); + return null; } RoutingRule routingRule = methodOptions.getExtension(RoutingProto.routing); @@ -56,12 +52,14 @@ public static RoutingHeaders parse( // if specified, the pattern must contain one and only one named segment if (Strings.isNullOrEmpty(pathTemplate)) { name = field; - pathTemplate = String.format(WILDCARD_PATTERN, name); + pathTemplate = String.format("{%s=**}", name); } else { Set params = getPatternBindings(pathTemplate).build(); Preconditions.checkArgument( params.size() == 1, - String.format(PATH_TEMPLATE_WRONG_NUMBER_OF_NAMED_SEGMENT_ERROR_MESSAGE, pathTemplate)); + String.format( + "There needs to be one and only one named segment in path template %s", + pathTemplate)); name = params.iterator().next(); } diff --git a/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcTestingStub.golden b/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcTestingStub.golden index 635a0bafdb..bee1a086e3 100644 --- a/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcTestingStub.golden +++ b/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcTestingStub.golden @@ -8,9 +8,9 @@ import com.google.api.gax.core.BackgroundResource; import com.google.api.gax.core.BackgroundResourceAggregation; import com.google.api.gax.grpc.GrpcCallSettings; import com.google.api.gax.grpc.GrpcStubCallableFactory; +import com.google.api.gax.grpc.RoutingHeaderParamsBuilder; import com.google.api.gax.rpc.ClientContext; import com.google.api.gax.rpc.UnaryCallable; -import com.google.api.gax.rpc.internal.RoutingHeaderHelper; import com.google.api.pathtemplate.PathTemplate; import com.google.common.collect.ImmutableMap; import com.google.longrunning.stub.GrpcOperationsStub; @@ -227,15 +227,11 @@ public class GrpcTestingStub extends TestingStub { .setMethodDescriptor(getTestMethodDescriptor) .setParamsExtractor( request -> { - ImmutableMap.Builder params = ImmutableMap.builder(); - RoutingHeaderHelper.addParams( - params, request.getName(), "rename", PATH_TEMPLATE_GETTEST_0); - RoutingHeaderHelper.addParams( - params, - request.getRouting().getName(), - "routing_id", - PATH_TEMPLATE_GETTEST_1); - return params.build(); + RoutingHeaderParamsBuilder builder = new RoutingHeaderParamsBuilder(); + builder.add(request.getName(), "rename", PATH_TEMPLATE_GETTEST_0); + builder.add( + request.getRouting().getName(), "routing_id", PATH_TEMPLATE_GETTEST_1); + return builder.build(); }) .build(); GrpcCallSettings listTestsTransportSettings = diff --git a/src/test/java/com/google/api/generator/gapic/model/MessageTest.java b/src/test/java/com/google/api/generator/gapic/model/MessageTest.java index cb733c7edc..d3fedb51e2 100644 --- a/src/test/java/com/google/api/generator/gapic/model/MessageTest.java +++ b/src/test/java/com/google/api/generator/gapic/model/MessageTest.java @@ -14,9 +14,6 @@ package com.google.api.generator.gapic.model; -import static com.google.api.generator.gapic.model.Message.EMPTY_FIELD_ERROR_MESSAGE; -import static com.google.api.generator.gapic.model.Message.FIELD_DOES_NOT_EXIST_ERROR_MESSAGE; -import static com.google.api.generator.gapic.model.Message.MESSAGE_NOT_FOUND_ERROR_MESSAGE; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; @@ -36,28 +33,30 @@ public class MessageTest { .setType(TypeNode.OBJECT); @Test - public void shouldThrowExceptionIfFieldNameIsEmpty() { + public void validateField_shouldThrowExceptionIfFieldNameIsEmpty() { Message message = TEST_MESSAGE_BUILDER.build(); IllegalStateException illegalStateException = assertThrows( IllegalStateException.class, () -> message.validateField("", ImmutableMap.of())); assertThat(illegalStateException.getMessage()) - .isEqualTo(String.format(EMPTY_FIELD_ERROR_MESSAGE, MESSAGE_NAME)); + .isEqualTo(String.format("Null or empty field name found for message %s", MESSAGE_NAME)); } @Test - public void shouldThrowExceptionIfFieldDoesNotExist() { + public void validateField_shouldThrowExceptionIfFieldDoesNotExist() { Message message = TEST_MESSAGE_BUILDER.build(); String fieldName = "doesNotExist"; IllegalStateException illegalStateException = assertThrows( IllegalStateException.class, () -> message.validateField(fieldName, ImmutableMap.of())); assertThat(illegalStateException.getMessage()) - .isEqualTo(String.format(FIELD_DOES_NOT_EXIST_ERROR_MESSAGE, MESSAGE_NAME, fieldName)); + .isEqualTo( + String.format( + "Expected message %s to contain field %s but none found", MESSAGE_NAME, fieldName)); } @Test - public void shouldThrowExceptionIfMessageDoesNotExist() { + public void validateField_shouldThrowExceptionIfMessageDoesNotExist() { String subFieldName = "table"; String fieldTypeName = "doesNotMatter"; Field subField = @@ -77,11 +76,14 @@ public void shouldThrowExceptionIfMessageDoesNotExist() { assertThrows( NullPointerException.class, () -> message.validateField(fieldName, ImmutableMap.of())); assertThat(nullPointerException.getMessage()) - .isEqualTo(String.format(MESSAGE_NOT_FOUND_ERROR_MESSAGE, subFieldName, fieldTypeName)); + .isEqualTo( + String.format( + "No containing message found for field %s with type %s", + subFieldName, fieldTypeName)); } @Test - public void shouldNotThrowExceptionIfFieldExist() { + public void validateField_shouldNotThrowExceptionIfFieldExist() { String subFieldName = "table"; String fieldTypeName = "TableFieldType"; VaporReference fieldType = diff --git a/src/test/java/com/google/api/generator/gapic/protoparser/BUILD.bazel b/src/test/java/com/google/api/generator/gapic/protoparser/BUILD.bazel index 24eaaf09ad..74f88ede27 100644 --- a/src/test/java/com/google/api/generator/gapic/protoparser/BUILD.bazel +++ b/src/test/java/com/google/api/generator/gapic/protoparser/BUILD.bazel @@ -46,7 +46,6 @@ filegroup( "//src/test/java/com/google/api/generator/gapic/testdata:showcase_java_proto", "//src/test/java/com/google/api/generator/gapic/testdata:testgapic_java_proto", "@com_google_api_api_common//jar", - "@com_google_api_grpc_proto_google_common_protos", "@com_google_googleapis//google/api:api_java_proto", "@com_google_googleapis//google/rpc:rpc_java_proto", "@com_google_protobuf//:protobuf_java", diff --git a/src/test/java/com/google/api/generator/gapic/protoparser/RoutingRuleParserTest.java b/src/test/java/com/google/api/generator/gapic/protoparser/RoutingRuleParserTest.java index 3363270813..25684bfce7 100644 --- a/src/test/java/com/google/api/generator/gapic/protoparser/RoutingRuleParserTest.java +++ b/src/test/java/com/google/api/generator/gapic/protoparser/RoutingRuleParserTest.java @@ -14,8 +14,6 @@ package com.google.api.generator.gapic.protoparser; -import static com.google.api.generator.gapic.protoparser.RoutingRuleParser.PATH_TEMPLATE_WRONG_NUMBER_OF_NAMED_SEGMENT_ERROR_MESSAGE; -import static com.google.api.generator.gapic.protoparser.RoutingRuleParser.WILDCARD_PATTERN; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; @@ -39,49 +37,49 @@ public class RoutingRuleParserTest { TESTING_FILE_DESCRIPTOR.getServices().get(0); @Test - public void shouldReturnEmptyRoutingHeadersIfMethodHasNoRoutingRules() { + public void parse_shouldReturnNullRoutingHeadersIfMethodHasNoRoutingRules() { RoutingHeaders actual = getRoutingHeaders(0); - assertThat(actual.routingHeadersList()).isEmpty(); + assertThat(actual).isNull(); } @Test - public void shouldSetPathTemplateToWildcardIfNotDefined() { + public void parse_shouldSetPathTemplateToWildcardIfNotDefined() { RoutingHeaders actual = getRoutingHeaders(1); - RoutingHeader expected = - RoutingHeader.create("name", "name", String.format(WILDCARD_PATTERN, "name")); + RoutingHeader expected = RoutingHeader.create("name", "name", String.format("{%s=**}", "name")); assertThat(actual.routingHeadersList()).containsExactly(expected); } @Test - public void shouldThrowExceptionIfPathTemplateHasZeroNamedSegment() { + public void parse_shouldThrowExceptionIfPathTemplateHasZeroNamedSegment() { IllegalArgumentException illegalArgumentException = assertThrows(IllegalArgumentException.class, () -> getRoutingHeaders(2)); assertThat(illegalArgumentException.getMessage()) .isEqualTo( String.format( - PATH_TEMPLATE_WRONG_NUMBER_OF_NAMED_SEGMENT_ERROR_MESSAGE, "/v1beta1/tests/*")); + "There needs to be one and only one named segment in path template %s", + "/v1beta1/tests/*")); } @Test - public void shouldThrowExceptionIfPathTemplateHasMoreThanOneNamedSegment() { + public void parse_shouldThrowExceptionIfPathTemplateHasMoreThanOneNamedSegment() { IllegalArgumentException illegalArgumentException = assertThrows(IllegalArgumentException.class, () -> getRoutingHeaders(3)); assertThat(illegalArgumentException.getMessage()) .isEqualTo( String.format( - PATH_TEMPLATE_WRONG_NUMBER_OF_NAMED_SEGMENT_ERROR_MESSAGE, + "There needs to be one and only one named segment in path template %s", "/v1beta1/{name=tests/*}/{second_name=*}")); } @Test - public void shouldParseRoutingRulesWithOneParameter() { + public void parse_shouldParseRoutingRulesWithOneParameter() { RoutingHeaders actual = getRoutingHeaders(4); RoutingHeader expected = RoutingHeader.create("name", "rename", "/v1beta1/{rename=tests/*}"); assertThat(actual.routingHeadersList()).containsExactly(expected); } @Test - public void shouldParseRoutingRulesWithMultipleParameter() { + public void parse_shouldParseRoutingRulesWithMultipleParameter() { RoutingHeaders actual = getRoutingHeaders(5); RoutingHeader expectedHeader1 = RoutingHeader.create("name", "rename", "/v1beta1/{rename=tests/*}"); @@ -91,7 +89,7 @@ public void shouldParseRoutingRulesWithMultipleParameter() { } @Test - public void shouldParseRoutingRulesWithNestedFields() { + public void parse_shouldParseRoutingRulesWithNestedFields() { RoutingHeaders actual = getRoutingHeaders(6); RoutingHeader expectedHeader1 = RoutingHeader.create("account.name", "rename", "/v1beta1/{rename=tests/*}"); @@ -99,7 +97,7 @@ public void shouldParseRoutingRulesWithNestedFields() { } @Test - public void shouldThrowExceptionIfFieldValidationFailed() { + public void parse_shouldThrowExceptionIfFieldValidationFailed() { assertThrows(IllegalStateException.class, () -> getRoutingHeaders(7)); } From b824c945b1d34448a6f54a33b6b570d97d1e8510 Mon Sep 17 00:00:00 2001 From: Blake Li Date: Tue, 18 Jan 2022 13:45:22 -0500 Subject: [PATCH 6/9] #869. Add null checks for field values before using them to construct routing header params map. - Add test cases for all the examples provided in routing.proto. Move all testing protos for explicit routing headers to its own package and bazel rule. - Switch to inline String format for error messages. --- .../grpc/GrpcServiceStubClassComposer.java | 78 ++- .../api/generator/gapic/model/Message.java | 30 +- .../generator/gapic/model/RoutingHeaders.java | 6 + .../gapic/composer/common/BUILD.bazel | 1 + .../composer/common/TestProtoLoader.java | 26 + .../generator/gapic/composer/grpc/BUILD.bazel | 1 + .../GrpcServiceStubClassComposerTest.java | 15 + .../goldens/GrpcRoutingHeadersStub.golden | 652 ++++++++++++++++++ .../grpc/goldens/GrpcTestingStub.golden | 13 +- .../api/generator/gapic/model/BUILD.bazel | 1 + .../generator/gapic/model/MessageTest.java | 6 +- .../gapic/model/RoutingHeadersTest.java | 31 + .../generator/gapic/protoparser/BUILD.bazel | 1 + .../protoparser/RoutingRuleParserTest.java | 4 +- .../api/generator/gapic/testdata/BUILD.bazel | 20 +- ...licit_dynamic_routing_header_testing.proto | 283 ++++++++ .../routing_rule_parser_testing.proto | 4 +- 17 files changed, 1135 insertions(+), 37 deletions(-) create mode 100644 src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcRoutingHeadersStub.golden create mode 100644 src/test/java/com/google/api/generator/gapic/model/RoutingHeadersTest.java create mode 100644 src/test/java/com/google/api/generator/gapic/testdata/explicit_dynamic_routing_header_testing.proto diff --git a/src/main/java/com/google/api/generator/gapic/composer/grpc/GrpcServiceStubClassComposer.java b/src/main/java/com/google/api/generator/gapic/composer/grpc/GrpcServiceStubClassComposer.java index 77e646d33e..517112073f 100644 --- a/src/main/java/com/google/api/generator/gapic/composer/grpc/GrpcServiceStubClassComposer.java +++ b/src/main/java/com/google/api/generator/gapic/composer/grpc/GrpcServiceStubClassComposer.java @@ -22,9 +22,12 @@ import com.google.api.generator.engine.ast.EnumRefExpr; import com.google.api.generator.engine.ast.Expr; import com.google.api.generator.engine.ast.ExprStatement; +import com.google.api.generator.engine.ast.IfStatement; import com.google.api.generator.engine.ast.LambdaExpr; +import com.google.api.generator.engine.ast.LogicalOperationExpr; import com.google.api.generator.engine.ast.MethodInvocationExpr; import com.google.api.generator.engine.ast.NewObjectExpr; +import com.google.api.generator.engine.ast.RelationalOperationExpr; import com.google.api.generator.engine.ast.ScopeNode; import com.google.api.generator.engine.ast.Statement; import com.google.api.generator.engine.ast.StringObjectValue; @@ -54,7 +57,6 @@ import java.util.Set; import java.util.function.BiFunction; import java.util.function.Function; -import java.util.stream.Collectors; public class GrpcServiceStubClassComposer extends AbstractTransportServiceStubClassComposer { @@ -255,7 +257,7 @@ protected String getProtoRpcFullMethodName(Service protoService, Method protoMet private LambdaExpr createRequestParamsExtractorClassInstance( Method method, List classStatements) { - List bodyExprs = new ArrayList<>(); + List bodyStatements = new ArrayList<>(); VariableExpr requestVarExpr = VariableExpr.withVariable( Variable.builder().setType(method.inputType()).setName("request").build()); @@ -269,19 +271,19 @@ private LambdaExpr createRequestParamsExtractorClassInstance( // If the google.api.routing annotation is present(even with empty routing parameters), // the implicit routing headers specified in the google.api.http annotation should not be sent if (method.routingHeaders() == null) { - returnExpr = addRequestParamsForHttpBindings(method, bodyExprs, requestVarExpr, returnType); + returnExpr = + addRequestParamsForHttpBindings(method, bodyStatements, requestVarExpr, returnType); } else { returnExpr = addRequestParamsForRoutingHeaders( - method, classStatements, bodyExprs, requestVarExpr, returnType); + method, classStatements, bodyStatements, requestVarExpr, returnType); } // Overrides extract(). // https://github.com/googleapis/gax-java/blob/8d45d186e36ae97b789a6f89d80ae5213a773b65/gax/src/main/java/com/google/api/gax/rpc/RequestParamsExtractor.java#L55 return LambdaExpr.builder() .setArguments(requestVarExpr.toBuilder().setIsDecl(true).build()) - .setBody( - bodyExprs.stream().map(e -> ExprStatement.withExpr(e)).collect(Collectors.toList())) + .setBody(bodyStatements) .setReturnExpr(returnExpr) .build(); } @@ -289,7 +291,7 @@ private LambdaExpr createRequestParamsExtractorClassInstance( private Expr addRequestParamsForRoutingHeaders( Method method, List classStatements, - List bodyExprs, + List bodyStatements, VariableExpr requestVarExpr, TypeNode returnType) { TypeNode routingHeadersBuilderType = @@ -307,8 +309,8 @@ private Expr addRequestParamsForRoutingHeaders( .setVariableExpr(routingHeadersBuilderVarExpr) .setValueExpr(newBuilderExpr) .build(); - bodyExprs.add(newRoutingHeadersAssignExpr); - ImmutableList routingHeaders = method.routingHeaders().routingHeadersList(); + bodyStatements.add(ExprStatement.withExpr(newRoutingHeadersAssignExpr)); + List routingHeaders = method.routingHeaders().routingHeadersList(); VariableExpr routingHeadersBuilderVarNonDeclExpr = VariableExpr.builder() .setVariable( @@ -351,14 +353,27 @@ private Expr addRequestParamsForRoutingHeaders( .build(); Statement pathTemplateClassVar = ExprStatement.withExpr(pathTemplateExpr); classStatements.add(pathTemplateClassVar); + Expr valueOfExpr = + MethodInvocationExpr.builder() + .setStaticReferenceType(TypeNode.STRING) + .setMethodName("valueOf") + .setArguments(requestFieldGetterExpr) + .build(); MethodInvocationExpr addParamsMethodExpr = MethodInvocationExpr.builder() .setExprReferenceExpr(routingHeadersBuilderVarNonDeclExpr) .setMethodName("add") - .setArguments(requestFieldGetterExpr, routingHeaderKeyExpr, routingHeaderPatternExpr) + .setArguments(valueOfExpr, routingHeaderKeyExpr, routingHeaderPatternExpr) .build(); - bodyExprs.add(addParamsMethodExpr); + IfStatement ifStatement = + IfStatement.builder() + .setConditionExpr( + fieldValuesNotNullConditionExpr( + requestVarExpr, routingHeader.getDescendantFieldNames())) + .setBody(ImmutableList.of(ExprStatement.withExpr(addParamsMethodExpr))) + .build(); + bodyStatements.add(ifStatement); } return MethodInvocationExpr.builder() @@ -368,8 +383,43 @@ private Expr addRequestParamsForRoutingHeaders( .build(); } + private Expr fieldValuesNotNullConditionExpr( + VariableExpr requestVarExpr, List fieldNames) { + MethodInvocationExpr.Builder requestFieldGetterExprBuilder = + MethodInvocationExpr.builder().setExprReferenceExpr(requestVarExpr); + Expr fieldValuesNotNullExpr = null; + for (int i = 0; i < fieldNames.size(); i++) { + String currFieldName = fieldNames.get(i); + String bindingFieldMethodName = + String.format("get%s", JavaStyle.toUpperCamelCase(currFieldName)); + requestFieldGetterExprBuilder = + requestFieldGetterExprBuilder.setMethodName(bindingFieldMethodName); + // set return type of each method invocation to String just to pass the validation for + // RelationalOperationExpr that both side of relational operation needs to be a valid equality + // type + MethodInvocationExpr requestGetterExpr = + requestFieldGetterExprBuilder.setReturnType(TypeNode.STRING).build(); + Expr currentValueNotNullExpr = + RelationalOperationExpr.notEqualToWithExprs( + requestGetterExpr, ValueExpr.createNullExpr()); + if (fieldValuesNotNullExpr == null) { + fieldValuesNotNullExpr = currentValueNotNullExpr; + } else { + fieldValuesNotNullExpr = + LogicalOperationExpr.logicalAndWithExprs( + fieldValuesNotNullExpr, currentValueNotNullExpr); + } + requestFieldGetterExprBuilder = + MethodInvocationExpr.builder().setExprReferenceExpr(requestGetterExpr); + } + return fieldValuesNotNullExpr; + } + private Expr addRequestParamsForHttpBindings( - Method method, List bodyExprs, VariableExpr requestVarExpr, TypeNode returnType) { + Method method, + List bodyStatements, + VariableExpr requestVarExpr, + TypeNode returnType) { TypeNode paramsVarType = TypeNode.withReference( ConcreteReference.builder() @@ -390,7 +440,7 @@ private Expr addRequestParamsForHttpBindings( .setReturnType(paramsVarType) .build()) .build(); - bodyExprs.add(paramsAssignExpr); + bodyStatements.add(ExprStatement.withExpr(paramsAssignExpr)); for (HttpBinding httpBindingFieldBinding : method.httpBindings().pathParameters()) { MethodInvocationExpr requestBuilderExpr = @@ -410,7 +460,7 @@ private Expr addRequestParamsForHttpBindings( ValueExpr.withValue(StringObjectValue.withValue(httpBindingFieldBinding.name())), valueOfExpr) .build(); - bodyExprs.add(paramsPutExpr); + bodyStatements.add(ExprStatement.withExpr(paramsPutExpr)); } return MethodInvocationExpr.builder() diff --git a/src/main/java/com/google/api/generator/gapic/model/Message.java b/src/main/java/com/google/api/generator/gapic/model/Message.java index 5d6c8f6c6b..98972a9a5d 100644 --- a/src/main/java/com/google/api/generator/gapic/model/Message.java +++ b/src/main/java/com/google/api/generator/gapic/model/Message.java @@ -19,6 +19,7 @@ import com.google.api.generator.engine.ast.TypeNode; import com.google.auto.value.AutoValue; import com.google.common.base.Preconditions; +import com.google.common.base.Splitter; import com.google.common.base.Strings; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; @@ -84,12 +85,20 @@ public boolean hasResource() { } public void validateField(String fieldName, Map messageTypes) { - String[] subFields = fieldName.split("\\."); + List subFields = Splitter.on(".").splitToList(fieldName); Message nestedMessage = this; - for (int i = 0; i < subFields.length; i++) { - String subFieldName = subFields[i]; - if (i < subFields.length - 1) { - Field field = nestedMessage.fieldMap().get(subFieldName); + for (int i = 0; i < subFields.size(); i++) { + String subFieldName = subFields.get(i); + Preconditions.checkState( + !Strings.isNullOrEmpty(subFieldName), + String.format("Null or empty field name found for message %s", nestedMessage.name())); + Field field = nestedMessage.fieldMap().get(subFieldName); + Preconditions.checkNotNull( + field, + String.format( + "Expected message %s to contain field %s but none found", + nestedMessage.name(), subFieldName)); + if (i < subFields.size() - 1) { nestedMessage = messageTypes.get(field.type().reference().fullName()); Preconditions.checkNotNull( nestedMessage, @@ -97,15 +106,12 @@ public void validateField(String fieldName, Map messageTypes) { "No containing message found for field %s with type %s", field.name(), field.type().reference().simpleName())); } else { + // TODO: Type check for String or primitive? Preconditions.checkState( - !Strings.isNullOrEmpty(subFieldName), - String.format("Null or empty field name found for message %s", nestedMessage.name())); - Preconditions.checkState( - nestedMessage.fieldMap().containsKey(subFieldName), + !field.isRepeated() && field.type().isProtoPrimitiveType(), String.format( - "Expected message %s to contain field %s but none found", - nestedMessage.name(), subFieldName)); - // TODO: Add type check for String only? + "The type of field %s must be primitive and not repeated.", + field.name())); } } } diff --git a/src/main/java/com/google/api/generator/gapic/model/RoutingHeaders.java b/src/main/java/com/google/api/generator/gapic/model/RoutingHeaders.java index 8eb9d30dfb..82a6354ea1 100644 --- a/src/main/java/com/google/api/generator/gapic/model/RoutingHeaders.java +++ b/src/main/java/com/google/api/generator/gapic/model/RoutingHeaders.java @@ -15,7 +15,9 @@ package com.google.api.generator.gapic.model; import com.google.auto.value.AutoValue; +import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; +import java.util.List; @AutoValue public abstract class RoutingHeaders { @@ -38,6 +40,10 @@ public abstract static class RoutingHeader { public static RoutingHeaders.RoutingHeader create(String field, String name, String pattern) { return new AutoValue_RoutingHeaders_RoutingHeader(field, name, pattern); } + + public List getDescendantFieldNames() { + return Splitter.on(".").splitToList(fieldName()); + } } @AutoValue.Builder diff --git a/src/test/java/com/google/api/generator/gapic/composer/common/BUILD.bazel b/src/test/java/com/google/api/generator/gapic/composer/common/BUILD.bazel index 28a4bfc8b4..981b78f52e 100644 --- a/src/test/java/com/google/api/generator/gapic/composer/common/BUILD.bazel +++ b/src/test/java/com/google/api/generator/gapic/composer/common/BUILD.bazel @@ -29,6 +29,7 @@ TEST_DEPS = [ "//src/test/java/com/google/api/generator/gapic/testdata:deprecated_service_java_proto", "//src/test/java/com/google/api/generator/gapic/testdata:bookshop_java_proto", "//src/test/java/com/google/api/generator/gapic/testdata:showcase_java_proto", + "//src/test/java/com/google/api/generator/gapic/testdata:explicit_dynamic_routing_headers_testing_java_proto", "//src/test/java/com/google/api/generator/gapic/testdata:testgapic_java_proto", "@com_google_api_api_common//jar", "@com_google_api_gax_java//gax", diff --git a/src/test/java/com/google/api/generator/gapic/composer/common/TestProtoLoader.java b/src/test/java/com/google/api/generator/gapic/composer/common/TestProtoLoader.java index ecc8be4615..b7d32007eb 100644 --- a/src/test/java/com/google/api/generator/gapic/composer/common/TestProtoLoader.java +++ b/src/test/java/com/google/api/generator/gapic/composer/common/TestProtoLoader.java @@ -28,6 +28,7 @@ import com.google.api.generator.gapic.protoparser.Parser; import com.google.api.generator.gapic.protoparser.ServiceConfigParser; import com.google.bookshop.v1beta1.BookshopProto; +import com.google.explicit.dynamic.routing.header.ExplicitDynamicRoutingHeaderTestingOuterClass; import com.google.logging.v2.LogEntryProto; import com.google.logging.v2.LoggingConfigProto; import com.google.logging.v2.LoggingMetricsProto; @@ -218,6 +219,31 @@ public GapicContext parseShowcaseTesting() { .build(); } + public GapicContext parseExplicitDynamicRoutingHeaderTesting() { + FileDescriptor testingFileDescriptor = ExplicitDynamicRoutingHeaderTestingOuterClass.getDescriptor(); + ServiceDescriptor testingService = testingFileDescriptor.getServices().get(0); + assertEquals(testingService.getName(), "ExplicitDynamicRoutingHeaderTesting"); + + Map messageTypes = Parser.parseMessages(testingFileDescriptor); + Map resourceNames = Parser.parseResourceNames(testingFileDescriptor); + Set outputResourceNames = new HashSet<>(); + List services = + Parser.parseService( + testingFileDescriptor, + messageTypes, + resourceNames, + Optional.empty(), + outputResourceNames); + + return GapicContext.builder() + .setMessages(messageTypes) + .setResourceNames(resourceNames) + .setServices(services) + .setHelperResourceNames(outputResourceNames) + .setTransport(transport) + .build(); + } + public GapicContext parsePubSubPublisher() { FileDescriptor serviceFileDescriptor = PubsubProto.getDescriptor(); FileDescriptor commonResourcesFileDescriptor = CommonResources.getDescriptor(); diff --git a/src/test/java/com/google/api/generator/gapic/composer/grpc/BUILD.bazel b/src/test/java/com/google/api/generator/gapic/composer/grpc/BUILD.bazel index 825635a75e..89462e153b 100644 --- a/src/test/java/com/google/api/generator/gapic/composer/grpc/BUILD.bazel +++ b/src/test/java/com/google/api/generator/gapic/composer/grpc/BUILD.bazel @@ -36,6 +36,7 @@ TEST_DEPS = [ "//src/main/java/com/google/api/generator/gapic/protoparser", "//src/main/java/com/google/api/generator/gapic/composer/defaultvalue", "//src/test/java/com/google/api/generator/gapic/testdata:showcase_java_proto", + "//src/test/java/com/google/api/generator/gapic/testdata:explicit_dynamic_routing_headers_testing_java_proto", "@com_google_api_api_common//jar", "@com_google_api_gax_java//gax", "@com_google_api_api_common", diff --git a/src/test/java/com/google/api/generator/gapic/composer/grpc/GrpcServiceStubClassComposerTest.java b/src/test/java/com/google/api/generator/gapic/composer/grpc/GrpcServiceStubClassComposerTest.java index cf07432966..e26d8696e8 100644 --- a/src/test/java/com/google/api/generator/gapic/composer/grpc/GrpcServiceStubClassComposerTest.java +++ b/src/test/java/com/google/api/generator/gapic/composer/grpc/GrpcServiceStubClassComposerTest.java @@ -65,6 +65,21 @@ public void generateGrpcServiceStubClass_httpBindings() { Assert.assertCodeEquals(goldenFilePath, visitor.write()); } + @Test + public void generateGrpcServiceStubClass_routingHeaders() { + GapicContext context = + GrpcTestProtoLoader.instance().parseExplicitDynamicRoutingHeaderTesting(); + Service service = context.services().get(0); + GapicClass clazz = GrpcServiceStubClassComposer.instance().generate(context, service); + + JavaWriterVisitor visitor = new JavaWriterVisitor(); + clazz.classDefinition().accept(visitor); + Utils.saveCodegenToFile(this.getClass(), "GrpcRoutingHeadersStub.golden", visitor.write()); + Path goldenFilePath = + Paths.get(Utils.getGoldenDir(this.getClass()), "GrpcRoutingHeadersStub.golden"); + Assert.assertCodeEquals(goldenFilePath, visitor.write()); + } + @Test public void generateGrpcServiceStubClass_httpBindingsWithSubMessageFields() { GapicContext context = GrpcTestProtoLoader.instance().parsePubSubPublisher(); diff --git a/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcRoutingHeadersStub.golden b/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcRoutingHeadersStub.golden new file mode 100644 index 0000000000..399456316c --- /dev/null +++ b/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcRoutingHeadersStub.golden @@ -0,0 +1,652 @@ +package com.google.explicit.dynamic.routing.header.stub; + +import com.google.api.gax.core.BackgroundResource; +import com.google.api.gax.core.BackgroundResourceAggregation; +import com.google.api.gax.grpc.GrpcCallSettings; +import com.google.api.gax.grpc.GrpcStubCallableFactory; +import com.google.api.gax.grpc.RoutingHeaderParamsBuilder; +import com.google.api.gax.rpc.ClientContext; +import com.google.api.gax.rpc.UnaryCallable; +import com.google.api.pathtemplate.PathTemplate; +import com.google.common.collect.ImmutableMap; +import com.google.explicit.dynamic.routing.header.Request; +import com.google.longrunning.stub.GrpcOperationsStub; +import com.google.protobuf.Empty; +import io.grpc.MethodDescriptor; +import io.grpc.protobuf.ProtoUtils; +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import javax.annotation.Generated; + +// AUTO-GENERATED DOCUMENTATION AND CLASS. +/** + * gRPC stub implementation for the ExplicitDynamicRoutingHeaderTesting service API. + * + *

This class is for advanced usage and reflects the underlying API directly. + */ +@Generated("by gapic-generator-java") +public class GrpcExplicitDynamicRoutingHeaderTestingStub + extends ExplicitDynamicRoutingHeaderTestingStub { + private static final MethodDescriptor example1TestMethodDescriptor = + MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName( + "google.explicit.dynamic.routing.header.ExplicitDynamicRoutingHeaderTesting/Example1Test") + .setRequestMarshaller(ProtoUtils.marshaller(Request.getDefaultInstance())) + .setResponseMarshaller(ProtoUtils.marshaller(Empty.getDefaultInstance())) + .build(); + + private static final MethodDescriptor example2TestMethodDescriptor = + MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName( + "google.explicit.dynamic.routing.header.ExplicitDynamicRoutingHeaderTesting/Example2Test") + .setRequestMarshaller(ProtoUtils.marshaller(Request.getDefaultInstance())) + .setResponseMarshaller(ProtoUtils.marshaller(Empty.getDefaultInstance())) + .build(); + + private static final MethodDescriptor example3TestMethodDescriptor = + MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName( + "google.explicit.dynamic.routing.header.ExplicitDynamicRoutingHeaderTesting/Example3Test") + .setRequestMarshaller(ProtoUtils.marshaller(Request.getDefaultInstance())) + .setResponseMarshaller(ProtoUtils.marshaller(Empty.getDefaultInstance())) + .build(); + + private static final MethodDescriptor example3CTestMethodDescriptor = + MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName( + "google.explicit.dynamic.routing.header.ExplicitDynamicRoutingHeaderTesting/Example3CTest") + .setRequestMarshaller(ProtoUtils.marshaller(Request.getDefaultInstance())) + .setResponseMarshaller(ProtoUtils.marshaller(Empty.getDefaultInstance())) + .build(); + + private static final MethodDescriptor example4TestMethodDescriptor = + MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName( + "google.explicit.dynamic.routing.header.ExplicitDynamicRoutingHeaderTesting/Example4Test") + .setRequestMarshaller(ProtoUtils.marshaller(Request.getDefaultInstance())) + .setResponseMarshaller(ProtoUtils.marshaller(Empty.getDefaultInstance())) + .build(); + + private static final MethodDescriptor example5TestMethodDescriptor = + MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName( + "google.explicit.dynamic.routing.header.ExplicitDynamicRoutingHeaderTesting/Example5Test") + .setRequestMarshaller(ProtoUtils.marshaller(Request.getDefaultInstance())) + .setResponseMarshaller(ProtoUtils.marshaller(Empty.getDefaultInstance())) + .build(); + + private static final MethodDescriptor example6TestMethodDescriptor = + MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName( + "google.explicit.dynamic.routing.header.ExplicitDynamicRoutingHeaderTesting/Example6Test") + .setRequestMarshaller(ProtoUtils.marshaller(Request.getDefaultInstance())) + .setResponseMarshaller(ProtoUtils.marshaller(Empty.getDefaultInstance())) + .build(); + + private static final MethodDescriptor example7TestMethodDescriptor = + MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName( + "google.explicit.dynamic.routing.header.ExplicitDynamicRoutingHeaderTesting/Example7Test") + .setRequestMarshaller(ProtoUtils.marshaller(Request.getDefaultInstance())) + .setResponseMarshaller(ProtoUtils.marshaller(Empty.getDefaultInstance())) + .build(); + + private static final MethodDescriptor example8TestMethodDescriptor = + MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName( + "google.explicit.dynamic.routing.header.ExplicitDynamicRoutingHeaderTesting/Example8Test") + .setRequestMarshaller(ProtoUtils.marshaller(Request.getDefaultInstance())) + .setResponseMarshaller(ProtoUtils.marshaller(Empty.getDefaultInstance())) + .build(); + + private static final MethodDescriptor example9TestMethodDescriptor = + MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName( + "google.explicit.dynamic.routing.header.ExplicitDynamicRoutingHeaderTesting/Example9Test") + .setRequestMarshaller(ProtoUtils.marshaller(Request.getDefaultInstance())) + .setResponseMarshaller(ProtoUtils.marshaller(Empty.getDefaultInstance())) + .build(); + + private static final MethodDescriptor backwardsCompatible1TestMethodDescriptor = + MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName( + "google.explicit.dynamic.routing.header.ExplicitDynamicRoutingHeaderTesting/BackwardsCompatible1Test") + .setRequestMarshaller(ProtoUtils.marshaller(Request.getDefaultInstance())) + .setResponseMarshaller(ProtoUtils.marshaller(Empty.getDefaultInstance())) + .build(); + + private static final MethodDescriptor backwardsCompatible2TestMethodDescriptor = + MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName( + "google.explicit.dynamic.routing.header.ExplicitDynamicRoutingHeaderTesting/BackwardsCompatible2Test") + .setRequestMarshaller(ProtoUtils.marshaller(Request.getDefaultInstance())) + .setResponseMarshaller(ProtoUtils.marshaller(Empty.getDefaultInstance())) + .build(); + + private static final MethodDescriptor backwardsCompatible3TestMethodDescriptor = + MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName( + "google.explicit.dynamic.routing.header.ExplicitDynamicRoutingHeaderTesting/BackwardsCompatible3Test") + .setRequestMarshaller(ProtoUtils.marshaller(Request.getDefaultInstance())) + .setResponseMarshaller(ProtoUtils.marshaller(Empty.getDefaultInstance())) + .build(); + + private final UnaryCallable example1TestCallable; + private final UnaryCallable example2TestCallable; + private final UnaryCallable example3TestCallable; + private final UnaryCallable example3CTestCallable; + private final UnaryCallable example4TestCallable; + private final UnaryCallable example5TestCallable; + private final UnaryCallable example6TestCallable; + private final UnaryCallable example7TestCallable; + private final UnaryCallable example8TestCallable; + private final UnaryCallable example9TestCallable; + private final UnaryCallable backwardsCompatible1TestCallable; + private final UnaryCallable backwardsCompatible2TestCallable; + private final UnaryCallable backwardsCompatible3TestCallable; + + private final BackgroundResource backgroundResources; + private final GrpcOperationsStub operationsStub; + private final GrpcStubCallableFactory callableFactory; + + private static final PathTemplate PATH_TEMPLATE_EXAMPLE1TEST_0 = + PathTemplate.create("{app_profile_id=**}"); + private static final PathTemplate PATH_TEMPLATE_EXAMPLE2TEST_0 = + PathTemplate.create("{routing_id=**}"); + private static final PathTemplate PATH_TEMPLATE_EXAMPLE3TEST_0 = + PathTemplate.create("{table_name=projects/*/instances/*/**}"); + private static final PathTemplate PATH_TEMPLATE_EXAMPLE3CTEST_0 = + PathTemplate.create("{table_name=regions/*/zones/*/**}"); + private static final PathTemplate PATH_TEMPLATE_EXAMPLE3CTEST_1 = + PathTemplate.create("{table_name=projects/*/instances/*/**}"); + private static final PathTemplate PATH_TEMPLATE_EXAMPLE4TEST_0 = + PathTemplate.create("{routing_id=projects/*}/**"); + private static final PathTemplate PATH_TEMPLATE_EXAMPLE5TEST_0 = + PathTemplate.create("{routing_id=projects/*}/**"); + private static final PathTemplate PATH_TEMPLATE_EXAMPLE5TEST_1 = + PathTemplate.create("{routing_id=projects/*/instances/*}/**"); + private static final PathTemplate PATH_TEMPLATE_EXAMPLE6TEST_0 = + PathTemplate.create("{project_id=projects/*}/instances/*/**"); + private static final PathTemplate PATH_TEMPLATE_EXAMPLE6TEST_1 = + PathTemplate.create("projects/*/{instance_id=instances/*}/**"); + private static final PathTemplate PATH_TEMPLATE_EXAMPLE7TEST_0 = + PathTemplate.create("{project_id=projects/*}/**"); + private static final PathTemplate PATH_TEMPLATE_EXAMPLE7TEST_1 = + PathTemplate.create("{routing_id=**}"); + private static final PathTemplate PATH_TEMPLATE_EXAMPLE8TEST_0 = + PathTemplate.create("{routing_id=projects/*}/**"); + private static final PathTemplate PATH_TEMPLATE_EXAMPLE8TEST_1 = + PathTemplate.create("{routing_id=regions/*}/**"); + private static final PathTemplate PATH_TEMPLATE_EXAMPLE8TEST_2 = + PathTemplate.create("{routing_id=**}"); + private static final PathTemplate PATH_TEMPLATE_EXAMPLE9TEST_0 = + PathTemplate.create("projects/*/{table_location=instances/*}/tables/*"); + private static final PathTemplate PATH_TEMPLATE_EXAMPLE9TEST_1 = + PathTemplate.create("{table_location=regions/*/zones/*}/tables/*"); + private static final PathTemplate PATH_TEMPLATE_EXAMPLE9TEST_2 = + PathTemplate.create("{routing_id=projects/*}/**"); + private static final PathTemplate PATH_TEMPLATE_EXAMPLE9TEST_3 = + PathTemplate.create("{routing_id=**}"); + private static final PathTemplate PATH_TEMPLATE_EXAMPLE9TEST_4 = + PathTemplate.create("profiles/{routing_id=*}"); + private static final PathTemplate PATH_TEMPLATE_BACKWARDSCOMPATIBLE1TEST_0 = + PathTemplate.create("{routing_id=projects/*}/**"); + + public static final GrpcExplicitDynamicRoutingHeaderTestingStub create( + ExplicitDynamicRoutingHeaderTestingStubSettings settings) throws IOException { + return new GrpcExplicitDynamicRoutingHeaderTestingStub( + settings, ClientContext.create(settings)); + } + + public static final GrpcExplicitDynamicRoutingHeaderTestingStub create( + ClientContext clientContext) throws IOException { + return new GrpcExplicitDynamicRoutingHeaderTestingStub( + ExplicitDynamicRoutingHeaderTestingStubSettings.newBuilder().build(), clientContext); + } + + public static final GrpcExplicitDynamicRoutingHeaderTestingStub create( + ClientContext clientContext, GrpcStubCallableFactory callableFactory) throws IOException { + return new GrpcExplicitDynamicRoutingHeaderTestingStub( + ExplicitDynamicRoutingHeaderTestingStubSettings.newBuilder().build(), + clientContext, + callableFactory); + } + + /** + * Constructs an instance of GrpcExplicitDynamicRoutingHeaderTestingStub, 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 GrpcExplicitDynamicRoutingHeaderTestingStub( + ExplicitDynamicRoutingHeaderTestingStubSettings settings, ClientContext clientContext) + throws IOException { + this(settings, clientContext, new GrpcExplicitDynamicRoutingHeaderTestingCallableFactory()); + } + + /** + * Constructs an instance of GrpcExplicitDynamicRoutingHeaderTestingStub, 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 GrpcExplicitDynamicRoutingHeaderTestingStub( + ExplicitDynamicRoutingHeaderTestingStubSettings settings, + ClientContext clientContext, + GrpcStubCallableFactory callableFactory) + throws IOException { + this.callableFactory = callableFactory; + this.operationsStub = GrpcOperationsStub.create(clientContext, callableFactory); + + GrpcCallSettings example1TestTransportSettings = + GrpcCallSettings.newBuilder() + .setMethodDescriptor(example1TestMethodDescriptor) + .setParamsExtractor( + request -> { + RoutingHeaderParamsBuilder builder = new RoutingHeaderParamsBuilder(); + if (request.getAppProfileId() != null) { + builder.add( + String.valueOf(request.getAppProfileId()), + "app_profile_id", + PATH_TEMPLATE_EXAMPLE1TEST_0); + } + return builder.build(); + }) + .build(); + GrpcCallSettings example2TestTransportSettings = + GrpcCallSettings.newBuilder() + .setMethodDescriptor(example2TestMethodDescriptor) + .setParamsExtractor( + request -> { + RoutingHeaderParamsBuilder builder = new RoutingHeaderParamsBuilder(); + if (request.getAppProfileId() != null) { + builder.add( + String.valueOf(request.getAppProfileId()), + "routing_id", + PATH_TEMPLATE_EXAMPLE2TEST_0); + } + return builder.build(); + }) + .build(); + GrpcCallSettings example3TestTransportSettings = + GrpcCallSettings.newBuilder() + .setMethodDescriptor(example3TestMethodDescriptor) + .setParamsExtractor( + request -> { + RoutingHeaderParamsBuilder builder = new RoutingHeaderParamsBuilder(); + if (request.getTableName() != null) { + builder.add( + String.valueOf(request.getTableName()), + "table_name", + PATH_TEMPLATE_EXAMPLE3TEST_0); + } + return builder.build(); + }) + .build(); + GrpcCallSettings example3CTestTransportSettings = + GrpcCallSettings.newBuilder() + .setMethodDescriptor(example3CTestMethodDescriptor) + .setParamsExtractor( + request -> { + RoutingHeaderParamsBuilder builder = new RoutingHeaderParamsBuilder(); + if (request.getTableName() != null) { + builder.add( + String.valueOf(request.getTableName()), + "table_name", + PATH_TEMPLATE_EXAMPLE3CTEST_0); + } + if (request.getTableName() != null) { + builder.add( + String.valueOf(request.getTableName()), + "table_name", + PATH_TEMPLATE_EXAMPLE3CTEST_1); + } + return builder.build(); + }) + .build(); + GrpcCallSettings example4TestTransportSettings = + GrpcCallSettings.newBuilder() + .setMethodDescriptor(example4TestMethodDescriptor) + .setParamsExtractor( + request -> { + RoutingHeaderParamsBuilder builder = new RoutingHeaderParamsBuilder(); + if (request.getTableName() != null) { + builder.add( + String.valueOf(request.getTableName()), + "routing_id", + PATH_TEMPLATE_EXAMPLE4TEST_0); + } + return builder.build(); + }) + .build(); + GrpcCallSettings example5TestTransportSettings = + GrpcCallSettings.newBuilder() + .setMethodDescriptor(example5TestMethodDescriptor) + .setParamsExtractor( + request -> { + RoutingHeaderParamsBuilder builder = new RoutingHeaderParamsBuilder(); + if (request.getTableName() != null) { + builder.add( + String.valueOf(request.getTableName()), + "routing_id", + PATH_TEMPLATE_EXAMPLE5TEST_0); + } + if (request.getTableName() != null) { + builder.add( + String.valueOf(request.getTableName()), + "routing_id", + PATH_TEMPLATE_EXAMPLE5TEST_1); + } + return builder.build(); + }) + .build(); + GrpcCallSettings example6TestTransportSettings = + GrpcCallSettings.newBuilder() + .setMethodDescriptor(example6TestMethodDescriptor) + .setParamsExtractor( + request -> { + RoutingHeaderParamsBuilder builder = new RoutingHeaderParamsBuilder(); + if (request.getTableName() != null) { + builder.add( + String.valueOf(request.getTableName()), + "project_id", + PATH_TEMPLATE_EXAMPLE6TEST_0); + } + if (request.getTableName() != null) { + builder.add( + String.valueOf(request.getTableName()), + "instance_id", + PATH_TEMPLATE_EXAMPLE6TEST_1); + } + return builder.build(); + }) + .build(); + GrpcCallSettings example7TestTransportSettings = + GrpcCallSettings.newBuilder() + .setMethodDescriptor(example7TestMethodDescriptor) + .setParamsExtractor( + request -> { + RoutingHeaderParamsBuilder builder = new RoutingHeaderParamsBuilder(); + if (request.getTableName() != null) { + builder.add( + String.valueOf(request.getTableName()), + "project_id", + PATH_TEMPLATE_EXAMPLE7TEST_0); + } + if (request.getAppProfileId() != null) { + builder.add( + String.valueOf(request.getAppProfileId()), + "routing_id", + PATH_TEMPLATE_EXAMPLE7TEST_1); + } + return builder.build(); + }) + .build(); + GrpcCallSettings example8TestTransportSettings = + GrpcCallSettings.newBuilder() + .setMethodDescriptor(example8TestMethodDescriptor) + .setParamsExtractor( + request -> { + RoutingHeaderParamsBuilder builder = new RoutingHeaderParamsBuilder(); + if (request.getTableName() != null) { + builder.add( + String.valueOf(request.getTableName()), + "routing_id", + PATH_TEMPLATE_EXAMPLE8TEST_0); + } + if (request.getTableName() != null) { + builder.add( + String.valueOf(request.getTableName()), + "routing_id", + PATH_TEMPLATE_EXAMPLE8TEST_1); + } + if (request.getAppProfileId() != null) { + builder.add( + String.valueOf(request.getAppProfileId()), + "routing_id", + PATH_TEMPLATE_EXAMPLE8TEST_2); + } + return builder.build(); + }) + .build(); + GrpcCallSettings example9TestTransportSettings = + GrpcCallSettings.newBuilder() + .setMethodDescriptor(example9TestMethodDescriptor) + .setParamsExtractor( + request -> { + RoutingHeaderParamsBuilder builder = new RoutingHeaderParamsBuilder(); + if (request.getTableName() != null) { + builder.add( + String.valueOf(request.getTableName()), + "table_location", + PATH_TEMPLATE_EXAMPLE9TEST_0); + } + if (request.getTableName() != null) { + builder.add( + String.valueOf(request.getTableName()), + "table_location", + PATH_TEMPLATE_EXAMPLE9TEST_1); + } + if (request.getTableName() != null) { + builder.add( + String.valueOf(request.getTableName()), + "routing_id", + PATH_TEMPLATE_EXAMPLE9TEST_2); + } + if (request.getAppProfileId() != null) { + builder.add( + String.valueOf(request.getAppProfileId()), + "routing_id", + PATH_TEMPLATE_EXAMPLE9TEST_3); + } + if (request.getAppProfileId() != null) { + builder.add( + String.valueOf(request.getAppProfileId()), + "routing_id", + PATH_TEMPLATE_EXAMPLE9TEST_4); + } + return builder.build(); + }) + .build(); + GrpcCallSettings backwardsCompatible1TestTransportSettings = + GrpcCallSettings.newBuilder() + .setMethodDescriptor(backwardsCompatible1TestMethodDescriptor) + .setParamsExtractor( + request -> { + RoutingHeaderParamsBuilder builder = new RoutingHeaderParamsBuilder(); + if (request.getTableName() != null) { + builder.add( + String.valueOf(request.getTableName()), + "routing_id", + PATH_TEMPLATE_BACKWARDSCOMPATIBLE1TEST_0); + } + return builder.build(); + }) + .build(); + GrpcCallSettings backwardsCompatible2TestTransportSettings = + GrpcCallSettings.newBuilder() + .setMethodDescriptor(backwardsCompatible2TestMethodDescriptor) + .setParamsExtractor( + request -> { + RoutingHeaderParamsBuilder builder = new RoutingHeaderParamsBuilder(); + return builder.build(); + }) + .build(); + GrpcCallSettings backwardsCompatible3TestTransportSettings = + GrpcCallSettings.newBuilder() + .setMethodDescriptor(backwardsCompatible3TestMethodDescriptor) + .setParamsExtractor( + request -> { + ImmutableMap.Builder params = ImmutableMap.builder(); + params.put("table_name", String.valueOf(request.getTableName())); + return params.build(); + }) + .build(); + + this.example1TestCallable = + callableFactory.createUnaryCallable( + example1TestTransportSettings, settings.example1TestSettings(), clientContext); + this.example2TestCallable = + callableFactory.createUnaryCallable( + example2TestTransportSettings, settings.example2TestSettings(), clientContext); + this.example3TestCallable = + callableFactory.createUnaryCallable( + example3TestTransportSettings, settings.example3TestSettings(), clientContext); + this.example3CTestCallable = + callableFactory.createUnaryCallable( + example3CTestTransportSettings, settings.example3CTestSettings(), clientContext); + this.example4TestCallable = + callableFactory.createUnaryCallable( + example4TestTransportSettings, settings.example4TestSettings(), clientContext); + this.example5TestCallable = + callableFactory.createUnaryCallable( + example5TestTransportSettings, settings.example5TestSettings(), clientContext); + this.example6TestCallable = + callableFactory.createUnaryCallable( + example6TestTransportSettings, settings.example6TestSettings(), clientContext); + this.example7TestCallable = + callableFactory.createUnaryCallable( + example7TestTransportSettings, settings.example7TestSettings(), clientContext); + this.example8TestCallable = + callableFactory.createUnaryCallable( + example8TestTransportSettings, settings.example8TestSettings(), clientContext); + this.example9TestCallable = + callableFactory.createUnaryCallable( + example9TestTransportSettings, settings.example9TestSettings(), clientContext); + this.backwardsCompatible1TestCallable = + callableFactory.createUnaryCallable( + backwardsCompatible1TestTransportSettings, + settings.backwardsCompatible1TestSettings(), + clientContext); + this.backwardsCompatible2TestCallable = + callableFactory.createUnaryCallable( + backwardsCompatible2TestTransportSettings, + settings.backwardsCompatible2TestSettings(), + clientContext); + this.backwardsCompatible3TestCallable = + callableFactory.createUnaryCallable( + backwardsCompatible3TestTransportSettings, + settings.backwardsCompatible3TestSettings(), + clientContext); + + this.backgroundResources = + new BackgroundResourceAggregation(clientContext.getBackgroundResources()); + } + + public GrpcOperationsStub getOperationsStub() { + return operationsStub; + } + + @Override + public UnaryCallable example1TestCallable() { + return example1TestCallable; + } + + @Override + public UnaryCallable example2TestCallable() { + return example2TestCallable; + } + + @Override + public UnaryCallable example3TestCallable() { + return example3TestCallable; + } + + @Override + public UnaryCallable example3CTestCallable() { + return example3CTestCallable; + } + + @Override + public UnaryCallable example4TestCallable() { + return example4TestCallable; + } + + @Override + public UnaryCallable example5TestCallable() { + return example5TestCallable; + } + + @Override + public UnaryCallable example6TestCallable() { + return example6TestCallable; + } + + @Override + public UnaryCallable example7TestCallable() { + return example7TestCallable; + } + + @Override + public UnaryCallable example8TestCallable() { + return example8TestCallable; + } + + @Override + public UnaryCallable example9TestCallable() { + return example9TestCallable; + } + + @Override + public UnaryCallable backwardsCompatible1TestCallable() { + return backwardsCompatible1TestCallable; + } + + @Override + public UnaryCallable backwardsCompatible2TestCallable() { + return backwardsCompatible2TestCallable; + } + + @Override + public UnaryCallable backwardsCompatible3TestCallable() { + return backwardsCompatible3TestCallable; + } + + @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/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcTestingStub.golden b/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcTestingStub.golden index bee1a086e3..73146650b8 100644 --- a/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcTestingStub.golden +++ b/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcTestingStub.golden @@ -228,9 +228,16 @@ public class GrpcTestingStub extends TestingStub { .setParamsExtractor( request -> { RoutingHeaderParamsBuilder builder = new RoutingHeaderParamsBuilder(); - builder.add(request.getName(), "rename", PATH_TEMPLATE_GETTEST_0); - builder.add( - request.getRouting().getName(), "routing_id", PATH_TEMPLATE_GETTEST_1); + if (request.getName() != null) { + builder.add( + String.valueOf(request.getName()), "rename", PATH_TEMPLATE_GETTEST_0); + } + if (request.getRouting() != null && request.getRouting().getName() != null) { + builder.add( + String.valueOf(request.getRouting().getName()), + "routing_id", + PATH_TEMPLATE_GETTEST_1); + } return builder.build(); }) .build(); diff --git a/src/test/java/com/google/api/generator/gapic/model/BUILD.bazel b/src/test/java/com/google/api/generator/gapic/model/BUILD.bazel index 5bcebf9047..7c1ad5181f 100644 --- a/src/test/java/com/google/api/generator/gapic/model/BUILD.bazel +++ b/src/test/java/com/google/api/generator/gapic/model/BUILD.bazel @@ -7,6 +7,7 @@ TESTS = [ "MethodArgumentTest", "MethodTest", "MessageTest", + "RoutingHeadersTest", ] filegroup( diff --git a/src/test/java/com/google/api/generator/gapic/model/MessageTest.java b/src/test/java/com/google/api/generator/gapic/model/MessageTest.java index d3fedb51e2..76f19c2b87 100644 --- a/src/test/java/com/google/api/generator/gapic/model/MessageTest.java +++ b/src/test/java/com/google/api/generator/gapic/model/MessageTest.java @@ -46,10 +46,10 @@ public void validateField_shouldThrowExceptionIfFieldNameIsEmpty() { public void validateField_shouldThrowExceptionIfFieldDoesNotExist() { Message message = TEST_MESSAGE_BUILDER.build(); String fieldName = "doesNotExist"; - IllegalStateException illegalStateException = + NullPointerException nullPointerException = assertThrows( - IllegalStateException.class, () -> message.validateField(fieldName, ImmutableMap.of())); - assertThat(illegalStateException.getMessage()) + NullPointerException.class, () -> message.validateField(fieldName, ImmutableMap.of())); + assertThat(nullPointerException.getMessage()) .isEqualTo( String.format( "Expected message %s to contain field %s but none found", MESSAGE_NAME, fieldName)); diff --git a/src/test/java/com/google/api/generator/gapic/model/RoutingHeadersTest.java b/src/test/java/com/google/api/generator/gapic/model/RoutingHeadersTest.java new file mode 100644 index 0000000000..3ae092f456 --- /dev/null +++ b/src/test/java/com/google/api/generator/gapic/model/RoutingHeadersTest.java @@ -0,0 +1,31 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://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. + +package com.google.api.generator.gapic.model; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.api.generator.gapic.model.RoutingHeaders.RoutingHeader; +import java.util.List; +import org.junit.Test; + +public class RoutingHeadersTest { + + @Test + public void getDescendantFieldNames_shouldSplitFieldNameByDot() { + RoutingHeader routingHeader = RoutingHeader.create("table.name", "name", "/abc/dec"); + List descendantFieldNames = routingHeader.getDescendantFieldNames(); + assertThat(descendantFieldNames).containsExactly("table", "name"); + } +} diff --git a/src/test/java/com/google/api/generator/gapic/protoparser/BUILD.bazel b/src/test/java/com/google/api/generator/gapic/protoparser/BUILD.bazel index 74f88ede27..09fa41b0ef 100644 --- a/src/test/java/com/google/api/generator/gapic/protoparser/BUILD.bazel +++ b/src/test/java/com/google/api/generator/gapic/protoparser/BUILD.bazel @@ -44,6 +44,7 @@ filegroup( "//src/main/java/com/google/api/generator/gapic/utils", "//src/test/java/com/google/api/generator/gapic/testdata:bookshop_java_proto", "//src/test/java/com/google/api/generator/gapic/testdata:showcase_java_proto", + "//src/test/java/com/google/api/generator/gapic/testdata:explicit_dynamic_routing_headers_testing_java_proto", "//src/test/java/com/google/api/generator/gapic/testdata:testgapic_java_proto", "@com_google_api_api_common//jar", "@com_google_googleapis//google/api:api_java_proto", diff --git a/src/test/java/com/google/api/generator/gapic/protoparser/RoutingRuleParserTest.java b/src/test/java/com/google/api/generator/gapic/protoparser/RoutingRuleParserTest.java index 25684bfce7..25d82232bb 100644 --- a/src/test/java/com/google/api/generator/gapic/protoparser/RoutingRuleParserTest.java +++ b/src/test/java/com/google/api/generator/gapic/protoparser/RoutingRuleParserTest.java @@ -20,10 +20,10 @@ import com.google.api.generator.gapic.model.Message; import com.google.api.generator.gapic.model.RoutingHeaders; import com.google.api.generator.gapic.model.RoutingHeaders.RoutingHeader; +import com.google.explicit.dynamic.routing.header.RoutingRuleParserTestingOuterClass; import com.google.protobuf.Descriptors.FileDescriptor; import com.google.protobuf.Descriptors.MethodDescriptor; import com.google.protobuf.Descriptors.ServiceDescriptor; -import com.google.showcase.v1beta1.RoutingRuleParserTestingOuterClass; import java.util.Map; import org.junit.Test; @@ -98,7 +98,7 @@ public void parse_shouldParseRoutingRulesWithNestedFields() { @Test public void parse_shouldThrowExceptionIfFieldValidationFailed() { - assertThrows(IllegalStateException.class, () -> getRoutingHeaders(7)); + assertThrows(Exception.class, () -> getRoutingHeaders(7)); } private RoutingHeaders getRoutingHeaders(int testingIndex) { diff --git a/src/test/java/com/google/api/generator/gapic/testdata/BUILD.bazel b/src/test/java/com/google/api/generator/gapic/testdata/BUILD.bazel index faea4cf584..25516cdb84 100644 --- a/src/test/java/com/google/api/generator/gapic/testdata/BUILD.bazel +++ b/src/test/java/com/google/api/generator/gapic/testdata/BUILD.bazel @@ -51,7 +51,6 @@ proto_library( "identity.proto", "testing.proto", "messaging.proto", - "routing_rule_parser_testing.proto", ], deps = [ "@com_google_googleapis//google/api:annotations_proto", @@ -70,6 +69,20 @@ proto_library( ], ) +proto_library( + name = "explicit_dynamic_routing_headers_testing_proto", + srcs = [ + "routing_rule_parser_testing.proto", + "explicit_dynamic_routing_header_testing.proto", + ], + deps = [ + "@com_google_googleapis//google/api:annotations_proto", + "@com_google_googleapis//google/api:routing_proto", + "@com_google_googleapis//google/api:client_proto", + "@com_google_protobuf//:empty_proto", + ], +) + proto_library( name = "deprecated_service_proto", srcs = [ @@ -129,3 +142,8 @@ java_proto_library( name = "testgapic_java_proto", deps = [":testgapic_proto"], ) + +java_proto_library( + name = "explicit_dynamic_routing_headers_testing_java_proto", + deps = [":explicit_dynamic_routing_headers_testing_proto"], +) diff --git a/src/test/java/com/google/api/generator/gapic/testdata/explicit_dynamic_routing_header_testing.proto b/src/test/java/com/google/api/generator/gapic/testdata/explicit_dynamic_routing_header_testing.proto new file mode 100644 index 0000000000..f2add97a27 --- /dev/null +++ b/src/test/java/com/google/api/generator/gapic/testdata/explicit_dynamic_routing_header_testing.proto @@ -0,0 +1,283 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +import "google/api/annotations.proto"; +import "google/api/routing.proto"; +import "google/api/client.proto"; +import "google/protobuf/empty.proto"; + +package google.explicit.dynamic.routing.header; + +option java_package = "com.google.explicit.dynamic.routing.header"; +option java_multiple_files = true; + +// This service is meant for testing all scenarios related to +// explicit dynamic routing headers feature, including but not limited to all examples in routing.proto +// All test cases in this proto assumes that the configured routing annotation is well formatted and passes all validation +service ExplicitDynamicRoutingHeaderTesting { + + option (google.api.default_host) = "localhost:7469"; + + //Example 1 + // Extracting a field from the request to put into the routing header + // unchanged, with the key equal to the field name. + rpc Example1Test(Request) returns (google.protobuf.Empty) { + option (google.api.routing) = { + //take the `app_profile_id` + routing_parameters { + field: "app_profile_id" + } + }; + } + // Example 2 + // Extracting a field from the request to put into the routing header + // unchanged, with the key different from the field name. + rpc Example2Test(Request) returns (google.protobuf.Empty) { + option (google.api.routing) = { + // Take the `app_profile_id`, but name it `routing_id` in the header. + routing_parameters { + field: "app_profile_id" + path_template: "{routing_id=**}" + } + }; + } + + // Example 3 + // + // Extracting a field from the request to put into the routing + // header, while matching a path template syntax on the field's value. + rpc Example3Test(Request) returns (google.protobuf.Empty) { + option (google.api.routing) = { + routing_parameters { + field: "table_name" + path_template: "{table_name=projects/*/instances/*/**}" + } + }; + } + + // Example 3c + // + //Multiple alternative conflictingly named path templates are + // specified. The one that matches is used to construct the header. + rpc Example3CTest(Request) returns (google.protobuf.Empty) { + option (google.api.routing) = { + routing_parameters { + field: "table_name" + path_template: "{table_name=regions/*/zones/*/**}" + } + routing_parameters { + field: "table_name" + path_template: "{table_name=projects/*/instances/*/**}" + } + }; + } + + // Example 4 + // + // Extracting a single routing header key-value pair by matching a + // template syntax on (a part of) a single request field. + rpc Example4Test(Request) returns (google.protobuf.Empty) { + option (google.api.routing) = { + routing_parameters { + field: "table_name" + path_template: "{routing_id=projects/*}/**" + } + }; + } + + // Example 5 + // + // Extracting a single routing header key-value pair by matching + // several conflictingly named path templates on (parts of) a single request + // field. The last template to match "wins" the conflict. + rpc Example5Test(Request) returns (google.protobuf.Empty) { + option (google.api.routing) = { + // If the `table_name` does not have instances information, + // take just the project id for routing. + // Otherwise take project + instance. + routing_parameters { + field: "table_name" + path_template: "{routing_id=projects/*}/**" + } + routing_parameters { + field: "table_name" + path_template: "{routing_id=projects/*/instances/*}/**" + } + }; + } + + // Example 6 + // + // Extracting multiple routing header key-value pairs by matching + // several non-conflicting path templates on (parts of) a single request field. + rpc Example6Test(Request) returns (google.protobuf.Empty) { + option (google.api.routing) = { + // The routing code needs two keys instead of one composite + // but works only for the tables with the "project-instance" name + // syntax. + + routing_parameters { + field: "table_name" + path_template: "{project_id=projects/*}/instances/*/**" + } + routing_parameters { + field: "table_name" + path_template: "projects/*/{instance_id=instances/*}/**" + } + }; + } + + // Example 7 + // + // Extracting multiple routing header key-value pairs by matching + // several path templates on multiple request fields. + rpc Example7Test(Request) returns (google.protobuf.Empty) { + option (google.api.routing) = { + // The routing needs both `project_id` and `routing_id` + // (from the `app_profile_id` field) for routing. + + routing_parameters { + field: "table_name" + path_template: "{project_id=projects/*}/**" + } + routing_parameters { + field: "app_profile_id" + path_template: "{routing_id=**}" + } + }; + } + + // Example 8 + // + // Extracting a single routing header key-value pair by matching + // several conflictingly named path templates on several request fields. The + // last template to match "wins" the conflict. + rpc Example8Test(Request) returns (google.protobuf.Empty) { + option (google.api.routing) = { + // The `routing_id` can be a project id or a region id depending on + // the table name format, but only if the `app_profile_id` is not set. + // If `app_profile_id` is set it should be used instead. + + routing_parameters { + field: "table_name" + path_template: "{routing_id=projects/*}/**" + } + routing_parameters { + field: "table_name" + path_template: "{routing_id=regions/*}/**" + } + routing_parameters { + field: "app_profile_id" + path_template: "{routing_id=**}" + } + }; + } + + // Example 9 + // + // Bringing it all together. + rpc Example9Test(Request) returns (google.protobuf.Empty) { + option (google.api.routing) = { + // For routing both `table_location` and a `routing_id` are needed. + // + // table_location can be either an instance id or a region+zone id. + // + // For `routing_id`, take the value of `app_profile_id` + // - If it's in the format `profiles/`, send + // just the `` part. + // - If it's any other literal, send it as is. + // If the `app_profile_id` is empty, and the `table_name` starts with + // the project_id, send that instead. + + routing_parameters { + field: "table_name" + path_template: "projects/*/{table_location=instances/*}/tables/*" + } + routing_parameters { + field: "table_name" + path_template: "{table_location=regions/*/zones/*}/tables/*" + } + routing_parameters { + field: "table_name" + path_template: "{routing_id=projects/*}/**" + } + routing_parameters { + field: "app_profile_id" + path_template: "{routing_id=**}" + } + routing_parameters { + field: "app_profile_id" + path_template: "profiles/{routing_id=*}" + } + }; + } + + //We should ignore http annotation if both http and routing header annotations are configured, + rpc BackwardsCompatible1Test(Request) returns (google.protobuf.Empty) { + option (google.api.http) = { + get: "/v1beta1/{table_name=tests/*}" + }; + + option (google.api.routing) = { + routing_parameters { + field: "table_name" + path_template: "{routing_id=projects/*}/**" + } + }; + } + + //We should ignore http annotation even routing header annotation has no routing parameters + rpc BackwardsCompatible2Test(Request) returns (google.protobuf.Empty) { + option (google.api.http) = { + get: "/v1beta1/{table_name=tests/*}" + }; + + option (google.api.routing) = { + + }; + } + + //Http annotation should still work for implicit routing headers as before if routing annotation is not configured. + rpc BackwardsCompatible3Test(Request) returns (google.protobuf.Empty) { + option (google.api.http) = { + get: "/v1beta1/{table_name=tests/*}" + }; + } +} + + + +// Example message: +// +// { +// table_name: projects/proj_foo/instances/instance_bar/table/table_baz, +// app_profile_id: profiles/prof_qux +// } +message Request { + // The name of the Table + // Values can be of the following formats: + // - `projects//tables/` + // - `projects//instances//tables/
` + // - `region//zones//tables/
` + string table_name = 1; + + // This value specifies routing for replication. + // It can be in the following formats: + // - `profiles/` + // - a legacy `profile_id` that can be any string + string app_profile_id = 2; +} + diff --git a/src/test/java/com/google/api/generator/gapic/testdata/routing_rule_parser_testing.proto b/src/test/java/com/google/api/generator/gapic/testdata/routing_rule_parser_testing.proto index e6b4e32521..a18de524eb 100644 --- a/src/test/java/com/google/api/generator/gapic/testdata/routing_rule_parser_testing.proto +++ b/src/test/java/com/google/api/generator/gapic/testdata/routing_rule_parser_testing.proto @@ -17,9 +17,9 @@ syntax = "proto3"; import "google/api/routing.proto"; import "google/protobuf/empty.proto"; -package google.showcase.v1beta1; +package google.explicit.dynamic.routing.header; -option java_package = "com.google.showcase.v1beta1"; +option java_package = "com.google.explicit.dynamic.routing.header"; option java_multiple_files = true; // This service is only meant for unit testing RoutingRuleParser From d1fe9e138a4958735cffcb398797e5af764b15bc Mon Sep 17 00:00:00 2001 From: Blake Li Date: Wed, 26 Jan 2022 21:21:31 -0500 Subject: [PATCH 7/9] #869. Add field type validation for String type. Add more unit tests for edge cases. Resolve review comments and some small refactoring. --- .../grpc/GrpcServiceStubClassComposer.java | 263 ++++++++--------- .../api/generator/gapic/model/Message.java | 17 +- .../api/generator/gapic/model/Method.java | 12 +- ...ingHeaders.java => RoutingHeaderRule.java} | 30 +- .../gapic/protoparser/HttpRuleParser.java | 19 +- .../generator/gapic/protoparser/Parser.java | 7 +- .../gapic/protoparser/PatternParser.java | 34 +++ .../gapic/protoparser/RoutingRuleParser.java | 56 ++-- .../goldens/GrpcRoutingHeadersStub.golden | 270 +++++++----------- .../grpc/goldens/GrpcTestingStub.golden | 19 +- .../api/generator/gapic/model/BUILD.bazel | 2 +- .../generator/gapic/model/MessageTest.java | 117 +++++--- .../api/generator/gapic/model/MethodTest.java | 83 ++++++ ...sTest.java => RoutingHeaderParamTest.java} | 9 +- .../generator/gapic/protoparser/BUILD.bazel | 3 +- .../gapic/protoparser/PatternParserTest.java | 40 +++ .../protoparser/RoutingRuleParserTest.java | 51 ++-- ...licit_dynamic_routing_header_testing.proto | 21 ++ 18 files changed, 599 insertions(+), 454 deletions(-) rename src/main/java/com/google/api/generator/gapic/model/{RoutingHeaders.java => RoutingHeaderRule.java} (50%) create mode 100644 src/main/java/com/google/api/generator/gapic/protoparser/PatternParser.java rename src/test/java/com/google/api/generator/gapic/model/{RoutingHeadersTest.java => RoutingHeaderParamTest.java} (73%) create mode 100644 src/test/java/com/google/api/generator/gapic/protoparser/PatternParserTest.java diff --git a/src/main/java/com/google/api/generator/gapic/composer/grpc/GrpcServiceStubClassComposer.java b/src/main/java/com/google/api/generator/gapic/composer/grpc/GrpcServiceStubClassComposer.java index 517112073f..a33056dad4 100644 --- a/src/main/java/com/google/api/generator/gapic/composer/grpc/GrpcServiceStubClassComposer.java +++ b/src/main/java/com/google/api/generator/gapic/composer/grpc/GrpcServiceStubClassComposer.java @@ -16,7 +16,7 @@ import com.google.api.gax.grpc.GrpcCallSettings; import com.google.api.gax.grpc.GrpcStubCallableFactory; -import com.google.api.gax.grpc.RoutingHeaderParamsBuilder; +import com.google.api.gax.rpc.RequestParamsBuilder; import com.google.api.generator.engine.ast.AssignmentExpr; import com.google.api.generator.engine.ast.ConcreteReference; import com.google.api.generator.engine.ast.EnumRefExpr; @@ -26,7 +26,6 @@ import com.google.api.generator.engine.ast.LambdaExpr; import com.google.api.generator.engine.ast.LogicalOperationExpr; import com.google.api.generator.engine.ast.MethodInvocationExpr; -import com.google.api.generator.engine.ast.NewObjectExpr; import com.google.api.generator.engine.ast.RelationalOperationExpr; import com.google.api.generator.engine.ast.ScopeNode; import com.google.api.generator.engine.ast.Statement; @@ -40,10 +39,11 @@ import com.google.api.generator.gapic.model.HttpBindings.HttpBinding; import com.google.api.generator.gapic.model.Message; import com.google.api.generator.gapic.model.Method; -import com.google.api.generator.gapic.model.RoutingHeaders.RoutingHeader; +import com.google.api.generator.gapic.model.RoutingHeaderRule.RoutingHeaderParam; import com.google.api.generator.gapic.model.Service; import com.google.api.generator.gapic.utils.JavaStyle; import com.google.api.pathtemplate.PathTemplate; +import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.longrunning.stub.GrpcOperationsStub; @@ -218,7 +218,7 @@ protected Expr createTransportSettingsInitExpr( .setArguments(Arrays.asList(methodDescriptorVarExpr)) .build(); - if (method.hasHttpBindings() || method.hasRoutingHeaders()) { + if (method.shouldSetParamsExtractor()) { callSettingsBuilderExpr = MethodInvocationExpr.builder() .setExprReferenceExpr(callSettingsBuilderExpr) @@ -267,16 +267,16 @@ private LambdaExpr createRequestParamsExtractorClassInstance( .setClazz(Map.class) .setGenerics(TypeNode.STRING.reference(), TypeNode.STRING.reference()) .build()); - Expr returnExpr; + MethodInvocationExpr.Builder returnExpr = + MethodInvocationExpr.builder().setReturnType(returnType); // If the google.api.routing annotation is present(even with empty routing parameters), // the implicit routing headers specified in the google.api.http annotation should not be sent - if (method.routingHeaders() == null) { - returnExpr = - addRequestParamsForHttpBindings(method, bodyStatements, requestVarExpr, returnType); + if (method.routingHeaderRule() == null) { + createRequestParamsExtractorBodyForHttpBindings( + method, requestVarExpr, bodyStatements, returnExpr); } else { - returnExpr = - addRequestParamsForRoutingHeaders( - method, classStatements, bodyStatements, requestVarExpr, returnType); + createRequestParamsExtractorBodyForRoutingHeaders( + method, requestVarExpr, classStatements, bodyStatements, returnExpr); } // Overrides extract(). @@ -284,103 +284,161 @@ private LambdaExpr createRequestParamsExtractorClassInstance( return LambdaExpr.builder() .setArguments(requestVarExpr.toBuilder().setIsDecl(true).build()) .setBody(bodyStatements) - .setReturnExpr(returnExpr) + .setReturnExpr(returnExpr.build()) .build(); } - private Expr addRequestParamsForRoutingHeaders( + private void createRequestParamsExtractorBodyForHttpBindings( Method method, - List classStatements, + VariableExpr requestVarExpr, List bodyStatements, + MethodInvocationExpr.Builder returnExprBuilder) { + TypeNode paramsVarType = + TypeNode.withReference( + ConcreteReference.builder() + .setClazz(ImmutableMap.Builder.class) + .setGenerics(TypeNode.STRING.reference(), TypeNode.STRING.reference()) + .build()); + VariableExpr paramsVarExpr = + VariableExpr.withVariable( + Variable.builder().setName("params").setType(paramsVarType).build()); + + Expr paramsAssignExpr = + AssignmentExpr.builder() + .setVariableExpr(paramsVarExpr.toBuilder().setIsDecl(true).build()) + .setValueExpr( + MethodInvocationExpr.builder() + .setStaticReferenceType(FIXED_TYPESTORE.get("ImmutableMap")) + .setMethodName("builder") + .setReturnType(paramsVarType) + .build()) + .build(); + bodyStatements.add(ExprStatement.withExpr(paramsAssignExpr)); + + for (HttpBinding httpBindingFieldBinding : method.httpBindings().pathParameters()) { + MethodInvocationExpr requestBuilderExpr = + createRequestFieldGetterExpr(requestVarExpr, httpBindingFieldBinding.name()); + Expr valueOfExpr = + MethodInvocationExpr.builder() + .setStaticReferenceType(TypeNode.STRING) + .setMethodName("valueOf") + .setArguments(requestBuilderExpr) + .build(); + + Expr paramsPutExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(paramsVarExpr) + .setMethodName("put") + .setArguments( + ValueExpr.withValue(StringObjectValue.withValue(httpBindingFieldBinding.name())), + valueOfExpr) + .build(); + bodyStatements.add(ExprStatement.withExpr(paramsPutExpr)); + } + + returnExprBuilder.setExprReferenceExpr(paramsVarExpr).setMethodName("build"); + } + + private void createRequestParamsExtractorBodyForRoutingHeaders( + Method method, VariableExpr requestVarExpr, - TypeNode returnType) { + List classStatements, + List bodyStatements, + MethodInvocationExpr.Builder returnExprBuilder) { TypeNode routingHeadersBuilderType = TypeNode.withReference( - ConcreteReference.builder().setClazz(RoutingHeaderParamsBuilder.class).build()); + ConcreteReference.builder().setClazz(RequestParamsBuilder.class).build()); VariableExpr routingHeadersBuilderVarExpr = VariableExpr.builder() .setVariable( Variable.builder().setName("builder").setType(routingHeadersBuilderType).build()) .setIsDecl(true) .build(); - Expr newBuilderExpr = NewObjectExpr.builder().setType(routingHeadersBuilderType).build(); - Expr newRoutingHeadersAssignExpr = + MethodInvocationExpr routingHeaderBuilderInvokeExpr = + MethodInvocationExpr.builder() + .setStaticReferenceType(routingHeadersBuilderType) + .setMethodName("create") + .setReturnType(routingHeadersBuilderType) + .build(); + Expr routingHeadersBuilderInitExpr = AssignmentExpr.builder() .setVariableExpr(routingHeadersBuilderVarExpr) - .setValueExpr(newBuilderExpr) + .setValueExpr(routingHeaderBuilderInvokeExpr) .build(); - bodyStatements.add(ExprStatement.withExpr(newRoutingHeadersAssignExpr)); - List routingHeaders = method.routingHeaders().routingHeadersList(); + bodyStatements.add(ExprStatement.withExpr(routingHeadersBuilderInitExpr)); + List routingHeaderParams = method.routingHeaderRule().routingHeaderParams(); VariableExpr routingHeadersBuilderVarNonDeclExpr = VariableExpr.builder() .setVariable( Variable.builder().setName("builder").setType(routingHeadersBuilderType).build()) .build(); - for (int i = 0; i < routingHeaders.size(); i++) { - RoutingHeader routingHeader = routingHeaders.get(i); + for (int i = 0; i < routingHeaderParams.size(); i++) { + RoutingHeaderParam routingHeaderParam = routingHeaderParams.get(i); MethodInvocationExpr requestFieldGetterExpr = - createRequestFieldGetterExpr(requestVarExpr, routingHeader.fieldName()); + createRequestFieldGetterExpr(requestVarExpr, routingHeaderParam.fieldName()); Expr routingHeaderKeyExpr = - ValueExpr.withValue(StringObjectValue.withValue(routingHeader.name())); - // TODO: Create proper snake style upper case name + ValueExpr.withValue(StringObjectValue.withValue(routingHeaderParam.key())); String pathTemplateName = - String.format("PATH_TEMPLATE_%s_%s", method.name().toUpperCase(), i); + String.format("%s_%s_PATH_TEMPLATE", JavaStyle.toUpperSnakeCase(method.name()), i); TypeNode pathTemplateType = TypeNode.withReference(ConcreteReference.withClazz(PathTemplate.class)); Variable pathTemplateVar = Variable.builder().setType(pathTemplateType).setName(pathTemplateName).build(); Expr routingHeaderPatternExpr = VariableExpr.withVariable(pathTemplateVar); - VariableExpr pathTemplateVarExpr = - VariableExpr.builder() - .setVariable(pathTemplateVar) - .setIsDecl(true) - .setIsStatic(true) - .setIsFinal(true) - .setScope(ScopeNode.PRIVATE) - .build(); - ValueExpr valueExpr = - ValueExpr.withValue(StringObjectValue.withValue(routingHeader.pattern())); - Expr pathTemplateExpr = - AssignmentExpr.builder() - .setVariableExpr(pathTemplateVarExpr) - .setValueExpr( - MethodInvocationExpr.builder() - .setStaticReferenceType(pathTemplateType) - .setMethodName("create") - .setArguments(valueExpr) - .setReturnType(pathTemplateType) - .build()) - .build(); - Statement pathTemplateClassVar = ExprStatement.withExpr(pathTemplateExpr); + Statement pathTemplateClassVar = + createPathTemplateClassStatement(routingHeaderParam, pathTemplateType, pathTemplateVar); classStatements.add(pathTemplateClassVar); - Expr valueOfExpr = - MethodInvocationExpr.builder() - .setStaticReferenceType(TypeNode.STRING) - .setMethodName("valueOf") - .setArguments(requestFieldGetterExpr) - .build(); - MethodInvocationExpr addParamsMethodExpr = + MethodInvocationExpr addParamMethodExpr = MethodInvocationExpr.builder() .setExprReferenceExpr(routingHeadersBuilderVarNonDeclExpr) .setMethodName("add") - .setArguments(valueOfExpr, routingHeaderKeyExpr, routingHeaderPatternExpr) + .setArguments(requestFieldGetterExpr, routingHeaderKeyExpr, routingHeaderPatternExpr) .build(); - IfStatement ifStatement = - IfStatement.builder() - .setConditionExpr( - fieldValuesNotNullConditionExpr( - requestVarExpr, routingHeader.getDescendantFieldNames())) - .setBody(ImmutableList.of(ExprStatement.withExpr(addParamsMethodExpr))) - .build(); - bodyStatements.add(ifStatement); + ExprStatement addParamStatement = ExprStatement.withExpr(addParamMethodExpr); + // No need to add null check if there is no nested fields + if (routingHeaderParam.getDescendantFieldNames().size() == 1) { + bodyStatements.add(addParamStatement); + } else { + IfStatement ifStatement = + IfStatement.builder() + .setConditionExpr( + fieldValuesNotNullConditionExpr( + requestVarExpr, routingHeaderParam.getDescendantFieldNames())) + .setBody(ImmutableList.of(addParamStatement)) + .build(); + bodyStatements.add(ifStatement); + } } - - return MethodInvocationExpr.builder() + returnExprBuilder .setExprReferenceExpr(routingHeadersBuilderVarNonDeclExpr) - .setMethodName("build") - .setReturnType(returnType) - .build(); + .setMethodName("build"); + } + + private Statement createPathTemplateClassStatement( + RoutingHeaderParam routingHeaderParam, TypeNode pathTemplateType, Variable pathTemplateVar) { + VariableExpr pathTemplateVarExpr = + VariableExpr.builder() + .setVariable(pathTemplateVar) + .setIsDecl(true) + .setIsStatic(true) + .setIsFinal(true) + .setScope(ScopeNode.PRIVATE) + .build(); + ValueExpr valueExpr = + ValueExpr.withValue(StringObjectValue.withValue(routingHeaderParam.pattern())); + Expr pathTemplateExpr = + AssignmentExpr.builder() + .setVariableExpr(pathTemplateVarExpr) + .setValueExpr( + MethodInvocationExpr.builder() + .setStaticReferenceType(pathTemplateType) + .setMethodName("create") + .setArguments(valueExpr) + .setReturnType(pathTemplateType) + .build()) + .build(); + return ExprStatement.withExpr(pathTemplateExpr); } private Expr fieldValuesNotNullConditionExpr( @@ -388,7 +446,7 @@ private Expr fieldValuesNotNullConditionExpr( MethodInvocationExpr.Builder requestFieldGetterExprBuilder = MethodInvocationExpr.builder().setExprReferenceExpr(requestVarExpr); Expr fieldValuesNotNullExpr = null; - for (int i = 0; i < fieldNames.size(); i++) { + for (int i = 0; i < fieldNames.size() - 1; i++) { String currFieldName = fieldNames.get(i); String bindingFieldMethodName = String.format("get%s", JavaStyle.toUpperCamelCase(currFieldName)); @@ -415,75 +473,20 @@ private Expr fieldValuesNotNullConditionExpr( return fieldValuesNotNullExpr; } - private Expr addRequestParamsForHttpBindings( - Method method, - List bodyStatements, - VariableExpr requestVarExpr, - TypeNode returnType) { - TypeNode paramsVarType = - TypeNode.withReference( - ConcreteReference.builder() - .setClazz(ImmutableMap.Builder.class) - .setGenerics(TypeNode.STRING.reference(), TypeNode.STRING.reference()) - .build()); - VariableExpr paramsVarExpr = - VariableExpr.withVariable( - Variable.builder().setName("params").setType(paramsVarType).build()); - - Expr paramsAssignExpr = - AssignmentExpr.builder() - .setVariableExpr(paramsVarExpr.toBuilder().setIsDecl(true).build()) - .setValueExpr( - MethodInvocationExpr.builder() - .setStaticReferenceType(FIXED_TYPESTORE.get("ImmutableMap")) - .setMethodName("builder") - .setReturnType(paramsVarType) - .build()) - .build(); - bodyStatements.add(ExprStatement.withExpr(paramsAssignExpr)); - - for (HttpBinding httpBindingFieldBinding : method.httpBindings().pathParameters()) { - MethodInvocationExpr requestBuilderExpr = - createRequestFieldGetterExpr(requestVarExpr, httpBindingFieldBinding.name()); - Expr valueOfExpr = - MethodInvocationExpr.builder() - .setStaticReferenceType(TypeNode.STRING) - .setMethodName("valueOf") - .setArguments(requestBuilderExpr) - .build(); - - Expr paramsPutExpr = - MethodInvocationExpr.builder() - .setExprReferenceExpr(paramsVarExpr) - .setMethodName("put") - .setArguments( - ValueExpr.withValue(StringObjectValue.withValue(httpBindingFieldBinding.name())), - valueOfExpr) - .build(); - bodyStatements.add(ExprStatement.withExpr(paramsPutExpr)); - } - - return MethodInvocationExpr.builder() - .setExprReferenceExpr(paramsVarExpr) - .setMethodName("build") - .setReturnType(returnType) - .build(); - } - private MethodInvocationExpr createRequestFieldGetterExpr( VariableExpr requestVarExpr, String fieldName) { MethodInvocationExpr.Builder requestFieldGetterExprBuilder = MethodInvocationExpr.builder().setExprReferenceExpr(requestVarExpr); - String[] descendantFields = fieldName.split("\\."); + List descendantFields = Splitter.on(".").splitToList(fieldName); // Handle foo.bar cases by descending into the subfields. // e.g. foo.bar -> request.getFoo().getBar() - for (int i = 0; i < descendantFields.length; i++) { - String currFieldName = descendantFields[i]; + for (int i = 0; i < descendantFields.size(); i++) { + String currFieldName = descendantFields.get(i); String bindingFieldMethodName = String.format("get%s", JavaStyle.toUpperCamelCase(currFieldName)); requestFieldGetterExprBuilder = requestFieldGetterExprBuilder.setMethodName(bindingFieldMethodName); - if (i < descendantFields.length - 1) { + if (i < descendantFields.size() - 1) { requestFieldGetterExprBuilder = MethodInvocationExpr.builder() .setExprReferenceExpr(requestFieldGetterExprBuilder.build()); diff --git a/src/main/java/com/google/api/generator/gapic/model/Message.java b/src/main/java/com/google/api/generator/gapic/model/Message.java index 98972a9a5d..ecfdd484df 100644 --- a/src/main/java/com/google/api/generator/gapic/model/Message.java +++ b/src/main/java/com/google/api/generator/gapic/model/Message.java @@ -84,7 +84,15 @@ public boolean hasResource() { return resource() != null; } - public void validateField(String fieldName, Map messageTypes) { + /** + * Validates if the field or fields exist in the message and the type of the leaf level field. + * + * @param fieldName The field name. For nested field, concatenate each field name with dot. For + * example: abc.def.ghi + * @param messageTypes All messages configured in a rpc service. + * @param type {@link TypeNode} The expected type for the leaf level field + */ + public void validateField(String fieldName, Map messageTypes, TypeNode type) { List subFields = Splitter.on(".").splitToList(fieldName); Message nestedMessage = this; for (int i = 0; i < subFields.size(); i++) { @@ -106,12 +114,9 @@ public void validateField(String fieldName, Map messageTypes) { "No containing message found for field %s with type %s", field.name(), field.type().reference().simpleName())); } else { - // TODO: Type check for String or primitive? Preconditions.checkState( - !field.isRepeated() && field.type().isProtoPrimitiveType(), - String.format( - "The type of field %s must be primitive and not repeated.", - field.name())); + !field.isRepeated() && field.type().equals(type), + String.format("The type of field %s must be String and not repeated.", field.name())); } } } diff --git a/src/main/java/com/google/api/generator/gapic/model/Method.java b/src/main/java/com/google/api/generator/gapic/model/Method.java index f29f0f0c66..dc2452f2d5 100644 --- a/src/main/java/com/google/api/generator/gapic/model/Method.java +++ b/src/main/java/com/google/api/generator/gapic/model/Method.java @@ -63,7 +63,7 @@ public boolean isPaged() { public abstract HttpBindings httpBindings(); @Nullable - public abstract RoutingHeaders routingHeaders(); + public abstract RoutingHeaderRule routingHeaderRule(); // Example from Expand in echo.proto: Thet TypeNodes that map to // [["content", "error"], ["content", "error", "info"]]. @@ -83,8 +83,12 @@ public boolean hasHttpBindings() { return httpBindings() != null && !httpBindings().pathParameters().isEmpty(); } - public boolean hasRoutingHeaders() { - return routingHeaders() != null && !routingHeaders().routingHeadersList().isEmpty(); + public boolean hasRoutingHeaderParams() { + return routingHeaderRule() != null && !routingHeaderRule().routingHeaderParams().isEmpty(); + } + + public boolean shouldSetParamsExtractor() { + return (hasHttpBindings() && routingHeaderRule() == null) || hasRoutingHeaderParams(); } public boolean isMixin() { @@ -147,7 +151,7 @@ public abstract static class Builder { public abstract Builder setOperationPollingMethod(boolean operationPollingMethod); - public abstract Builder setRoutingHeaders(RoutingHeaders routingHeaders); + public abstract Builder setRoutingHeaderRule(RoutingHeaderRule routingHeaderRule); public abstract Method build(); } diff --git a/src/main/java/com/google/api/generator/gapic/model/RoutingHeaders.java b/src/main/java/com/google/api/generator/gapic/model/RoutingHeaderRule.java similarity index 50% rename from src/main/java/com/google/api/generator/gapic/model/RoutingHeaders.java rename to src/main/java/com/google/api/generator/gapic/model/RoutingHeaderRule.java index 82a6354ea1..ff6cf4de28 100644 --- a/src/main/java/com/google/api/generator/gapic/model/RoutingHeaders.java +++ b/src/main/java/com/google/api/generator/gapic/model/RoutingHeaderRule.java @@ -19,26 +19,31 @@ import com.google.common.collect.ImmutableList; import java.util.List; +/** + * This model represents routing rules configured in rpc services. It will be used for generating + * the logic to match-and-extract field values from request, the extracted values will be + * concatenated to a request header that is used for routing purposes. + */ @AutoValue -public abstract class RoutingHeaders { +public abstract class RoutingHeaderRule { - public abstract ImmutableList routingHeadersList(); + public abstract ImmutableList routingHeaderParams(); public static Builder builder() { - return new AutoValue_RoutingHeaders.Builder().setRoutingHeadersList(ImmutableList.of()); + return new AutoValue_RoutingHeaderRule.Builder().setRoutingHeaderParams(ImmutableList.of()); } @AutoValue - public abstract static class RoutingHeader { + public abstract static class RoutingHeaderParam { public abstract String fieldName(); - public abstract String name(); + public abstract String key(); public abstract String pattern(); - public static RoutingHeaders.RoutingHeader create(String field, String name, String pattern) { - return new AutoValue_RoutingHeaders_RoutingHeader(field, name, pattern); + public static RoutingHeaderParam create(String field, String key, String pattern) { + return new AutoValue_RoutingHeaderRule_RoutingHeaderParam(field, key, pattern); } public List getDescendantFieldNames() { @@ -48,15 +53,16 @@ public List getDescendantFieldNames() { @AutoValue.Builder public abstract static class Builder { - abstract ImmutableList.Builder routingHeadersListBuilder(); + abstract ImmutableList.Builder routingHeaderParamsBuilder(); - public final Builder addRoutingHeader(RoutingHeader routingHeader) { - routingHeadersListBuilder().add(routingHeader); + public final Builder addParam(RoutingHeaderParam routingHeaderParam) { + routingHeaderParamsBuilder().add(routingHeaderParam); return this; } - public abstract Builder setRoutingHeadersList(ImmutableList routingHeadersList); + public abstract Builder setRoutingHeaderParams( + ImmutableList routingHeaderParams); - public abstract RoutingHeaders build(); + public abstract RoutingHeaderRule build(); } } diff --git a/src/main/java/com/google/api/generator/gapic/protoparser/HttpRuleParser.java b/src/main/java/com/google/api/generator/gapic/protoparser/HttpRuleParser.java index 5a37a38074..db8f74292a 100644 --- a/src/main/java/com/google/api/generator/gapic/protoparser/HttpRuleParser.java +++ b/src/main/java/com/google/api/generator/gapic/protoparser/HttpRuleParser.java @@ -36,7 +36,6 @@ import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Collectors; public class HttpRuleParser { private static final String ASTERISK = "*"; @@ -68,11 +67,12 @@ private static HttpBindings parseHttpRuleHelper( HttpRule httpRule, Optional inputMessageOpt, Map messageTypes) { // Get pattern. String pattern = getHttpVerbPattern(httpRule); - ImmutableSet.Builder bindingsBuilder = getPatternBindings(pattern); + ImmutableSet.Builder bindingsBuilder = ImmutableSet.builder(); + bindingsBuilder.addAll(PatternParser.getPattenBindings(pattern)); if (httpRule.getAdditionalBindingsCount() > 0) { for (HttpRule additionalRule : httpRule.getAdditionalBindingsList()) { // TODO: save additional bindings path in HttpRuleBindings - bindingsBuilder.addAll(getPatternBindings(getHttpVerbPattern(additionalRule)).build()); + bindingsBuilder.addAll(PatternParser.getPattenBindings(getHttpVerbPattern(additionalRule))); } } @@ -177,19 +177,6 @@ private static String getHttpVerbPattern(HttpRule httpRule) { } } - private static ImmutableSortedSet.Builder getPatternBindings(String pattern) { - ImmutableSortedSet.Builder bindings = ImmutableSortedSet.naturalOrder(); - if (pattern.isEmpty()) { - return bindings; - } - - PathTemplate template = PathTemplate.create(pattern); - // Filter out any unbound variable like "$0, $1, etc. - bindings.addAll( - template.vars().stream().filter(s -> !s.contains("$")).collect(Collectors.toSet())); - return bindings; - } - private static void checkHttpFieldIsValid(String binding, Message inputMessage, boolean isBody) { Preconditions.checkState( !Strings.isNullOrEmpty(binding), diff --git a/src/main/java/com/google/api/generator/gapic/protoparser/Parser.java b/src/main/java/com/google/api/generator/gapic/protoparser/Parser.java index a4aa2be50b..df45446229 100644 --- a/src/main/java/com/google/api/generator/gapic/protoparser/Parser.java +++ b/src/main/java/com/google/api/generator/gapic/protoparser/Parser.java @@ -34,7 +34,7 @@ import com.google.api.generator.gapic.model.OperationResponse; import com.google.api.generator.gapic.model.ResourceName; import com.google.api.generator.gapic.model.ResourceReference; -import com.google.api.generator.gapic.model.RoutingHeaders; +import com.google.api.generator.gapic.model.RoutingHeaderRule; import com.google.api.generator.gapic.model.Service; import com.google.api.generator.gapic.model.SourceCodeInfoLocation; import com.google.api.generator.gapic.model.Transport; @@ -710,8 +710,7 @@ static List parseMethods( .getOptions() .getExtension(ExtendedOperationsProto.operationPollingMethod) : false; - // may not need to pass in messageTypes? - RoutingHeaders routingHeaders = + RoutingHeaderRule routingHeaderRule = RoutingRuleParser.parse(protoMethod, inputMessage, messageTypes); methods.add( methodBuilder @@ -730,7 +729,7 @@ static List parseMethods( resourceNames, outputArgResourceNames)) .setHttpBindings(httpBindings) - .setRoutingHeaders(routingHeaders) + .setRoutingHeaderRule(routingHeaderRule) .setIsBatching(isBatching) .setPageSizeFieldName(parsePageSizeFieldName(protoMethod, messageTypes, transport)) .setIsDeprecated(isDeprecated) diff --git a/src/main/java/com/google/api/generator/gapic/protoparser/PatternParser.java b/src/main/java/com/google/api/generator/gapic/protoparser/PatternParser.java new file mode 100644 index 0000000000..47ab9e5411 --- /dev/null +++ b/src/main/java/com/google/api/generator/gapic/protoparser/PatternParser.java @@ -0,0 +1,34 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://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. + +package com.google.api.generator.gapic.protoparser; + +import com.google.api.pathtemplate.PathTemplate; +import com.google.common.collect.ImmutableSortedSet; +import java.util.Set; + +public class PatternParser { + + public static Set getPattenBindings(String pattern) { + ImmutableSortedSet.Builder bindings = ImmutableSortedSet.naturalOrder(); + if (pattern.isEmpty()) { + return bindings.build(); + } + + PathTemplate template = PathTemplate.create(pattern); + // Filter out any unbound variable like "$0, $1, etc. + template.vars().stream().filter(s -> !s.contains("$")).forEach(bindings::add); + return bindings.build(); + } +} diff --git a/src/main/java/com/google/api/generator/gapic/protoparser/RoutingRuleParser.java b/src/main/java/com/google/api/generator/gapic/protoparser/RoutingRuleParser.java index 3ac54f51a1..23f8ed0741 100644 --- a/src/main/java/com/google/api/generator/gapic/protoparser/RoutingRuleParser.java +++ b/src/main/java/com/google/api/generator/gapic/protoparser/RoutingRuleParser.java @@ -17,70 +17,50 @@ import com.google.api.RoutingParameter; import com.google.api.RoutingProto; import com.google.api.RoutingRule; +import com.google.api.generator.engine.ast.TypeNode; import com.google.api.generator.gapic.model.Message; -import com.google.api.generator.gapic.model.RoutingHeaders; -import com.google.api.generator.gapic.model.RoutingHeaders.RoutingHeader; -import com.google.api.pathtemplate.PathTemplate; +import com.google.api.generator.gapic.model.RoutingHeaderRule; +import com.google.api.generator.gapic.model.RoutingHeaderRule.RoutingHeaderParam; import com.google.common.base.Preconditions; import com.google.common.base.Strings; -import com.google.common.collect.ImmutableSortedSet; import com.google.protobuf.DescriptorProtos.MethodOptions; import com.google.protobuf.Descriptors.MethodDescriptor; import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; public class RoutingRuleParser { - public static RoutingHeaders parse( + public static RoutingHeaderRule parse( MethodDescriptor protoMethod, Message inputMessage, Map messageTypes) { MethodOptions methodOptions = protoMethod.getOptions(); - - RoutingHeaders.Builder builder = RoutingHeaders.builder(); if (!methodOptions.hasExtension(RoutingProto.routing)) { return null; } + RoutingHeaderRule.Builder routingHeaderRuleBuilder = RoutingHeaderRule.builder(); RoutingRule routingRule = methodOptions.getExtension(RoutingProto.routing); - for (RoutingParameter routingParameter : routingRule.getRoutingParametersList()) { String pathTemplate = routingParameter.getPathTemplate(); - String field = routingParameter.getField(); - // validate if field exist in Message or nested Messages - inputMessage.validateField(field, messageTypes); - String name; - // if specified, the pattern must contain one and only one named segment + String fieldName = routingParameter.getField(); + // validate if field exist in Message or nested Messages and the type of leaf level field + inputMessage.validateField(fieldName, messageTypes, TypeNode.STRING); + String key; if (Strings.isNullOrEmpty(pathTemplate)) { - name = field; - pathTemplate = String.format("{%s=**}", name); + key = fieldName; + pathTemplate = String.format("{%s=**}", key); } else { - Set params = getPatternBindings(pathTemplate).build(); + Set namedSegments = PatternParser.getPattenBindings(pathTemplate); Preconditions.checkArgument( - params.size() == 1, + namedSegments.size() == 1, String.format( "There needs to be one and only one named segment in path template %s", pathTemplate)); - name = params.iterator().next(); + key = namedSegments.iterator().next(); } - - RoutingHeader routingHeader = RoutingHeader.create(field, name, pathTemplate); - builder.addRoutingHeader(routingHeader); - } - - return builder.build(); - } - - // TODO: duplicate of HttpRuleParser.getPatternBindings, move to a helper class - private static ImmutableSortedSet.Builder getPatternBindings(String pattern) { - ImmutableSortedSet.Builder bindings = ImmutableSortedSet.naturalOrder(); - if (pattern.isEmpty()) { - return bindings; + RoutingHeaderParam routingHeaderParam = + RoutingHeaderParam.create(fieldName, key, pathTemplate); + routingHeaderRuleBuilder.addParam(routingHeaderParam); } - - PathTemplate template = PathTemplate.create(pattern); - // Filter out any unbound variable like "$0, $1, etc. - bindings.addAll( - template.vars().stream().filter(s -> !s.contains("$")).collect(Collectors.toSet())); - return bindings; + return routingHeaderRuleBuilder.build(); } } diff --git a/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcRoutingHeadersStub.golden b/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcRoutingHeadersStub.golden index 399456316c..84489dd164 100644 --- a/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcRoutingHeadersStub.golden +++ b/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcRoutingHeadersStub.golden @@ -4,12 +4,13 @@ import com.google.api.gax.core.BackgroundResource; import com.google.api.gax.core.BackgroundResourceAggregation; import com.google.api.gax.grpc.GrpcCallSettings; import com.google.api.gax.grpc.GrpcStubCallableFactory; -import com.google.api.gax.grpc.RoutingHeaderParamsBuilder; import com.google.api.gax.rpc.ClientContext; +import com.google.api.gax.rpc.RequestParamsBuilder; import com.google.api.gax.rpc.UnaryCallable; import com.google.api.pathtemplate.PathTemplate; import com.google.common.collect.ImmutableMap; import com.google.explicit.dynamic.routing.header.Request; +import com.google.explicit.dynamic.routing.header.RequestWithNestedField; import com.google.longrunning.stub.GrpcOperationsStub; import com.google.protobuf.Empty; import io.grpc.MethodDescriptor; @@ -145,6 +146,17 @@ public class GrpcExplicitDynamicRoutingHeaderTestingStub .setResponseMarshaller(ProtoUtils.marshaller(Empty.getDefaultInstance())) .build(); + private static final MethodDescriptor + nestedFieldTestMethodDescriptor = + MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName( + "google.explicit.dynamic.routing.header.ExplicitDynamicRoutingHeaderTesting/NestedFieldTest") + .setRequestMarshaller( + ProtoUtils.marshaller(RequestWithNestedField.getDefaultInstance())) + .setResponseMarshaller(ProtoUtils.marshaller(Empty.getDefaultInstance())) + .build(); + private final UnaryCallable example1TestCallable; private final UnaryCallable example2TestCallable; private final UnaryCallable example3TestCallable; @@ -158,52 +170,55 @@ public class GrpcExplicitDynamicRoutingHeaderTestingStub private final UnaryCallable backwardsCompatible1TestCallable; private final UnaryCallable backwardsCompatible2TestCallable; private final UnaryCallable backwardsCompatible3TestCallable; + private final UnaryCallable nestedFieldTestCallable; private final BackgroundResource backgroundResources; private final GrpcOperationsStub operationsStub; private final GrpcStubCallableFactory callableFactory; - private static final PathTemplate PATH_TEMPLATE_EXAMPLE1TEST_0 = + private static final PathTemplate EXAMPLE1_TEST_0_PATH_TEMPLATE = PathTemplate.create("{app_profile_id=**}"); - private static final PathTemplate PATH_TEMPLATE_EXAMPLE2TEST_0 = + private static final PathTemplate EXAMPLE2_TEST_0_PATH_TEMPLATE = PathTemplate.create("{routing_id=**}"); - private static final PathTemplate PATH_TEMPLATE_EXAMPLE3TEST_0 = + private static final PathTemplate EXAMPLE3_TEST_0_PATH_TEMPLATE = PathTemplate.create("{table_name=projects/*/instances/*/**}"); - private static final PathTemplate PATH_TEMPLATE_EXAMPLE3CTEST_0 = + private static final PathTemplate EXAMPLE3_C_TEST_0_PATH_TEMPLATE = PathTemplate.create("{table_name=regions/*/zones/*/**}"); - private static final PathTemplate PATH_TEMPLATE_EXAMPLE3CTEST_1 = + private static final PathTemplate EXAMPLE3_C_TEST_1_PATH_TEMPLATE = PathTemplate.create("{table_name=projects/*/instances/*/**}"); - private static final PathTemplate PATH_TEMPLATE_EXAMPLE4TEST_0 = + private static final PathTemplate EXAMPLE4_TEST_0_PATH_TEMPLATE = PathTemplate.create("{routing_id=projects/*}/**"); - private static final PathTemplate PATH_TEMPLATE_EXAMPLE5TEST_0 = + private static final PathTemplate EXAMPLE5_TEST_0_PATH_TEMPLATE = PathTemplate.create("{routing_id=projects/*}/**"); - private static final PathTemplate PATH_TEMPLATE_EXAMPLE5TEST_1 = + private static final PathTemplate EXAMPLE5_TEST_1_PATH_TEMPLATE = PathTemplate.create("{routing_id=projects/*/instances/*}/**"); - private static final PathTemplate PATH_TEMPLATE_EXAMPLE6TEST_0 = + private static final PathTemplate EXAMPLE6_TEST_0_PATH_TEMPLATE = PathTemplate.create("{project_id=projects/*}/instances/*/**"); - private static final PathTemplate PATH_TEMPLATE_EXAMPLE6TEST_1 = + private static final PathTemplate EXAMPLE6_TEST_1_PATH_TEMPLATE = PathTemplate.create("projects/*/{instance_id=instances/*}/**"); - private static final PathTemplate PATH_TEMPLATE_EXAMPLE7TEST_0 = + private static final PathTemplate EXAMPLE7_TEST_0_PATH_TEMPLATE = PathTemplate.create("{project_id=projects/*}/**"); - private static final PathTemplate PATH_TEMPLATE_EXAMPLE7TEST_1 = + private static final PathTemplate EXAMPLE7_TEST_1_PATH_TEMPLATE = PathTemplate.create("{routing_id=**}"); - private static final PathTemplate PATH_TEMPLATE_EXAMPLE8TEST_0 = + private static final PathTemplate EXAMPLE8_TEST_0_PATH_TEMPLATE = PathTemplate.create("{routing_id=projects/*}/**"); - private static final PathTemplate PATH_TEMPLATE_EXAMPLE8TEST_1 = + private static final PathTemplate EXAMPLE8_TEST_1_PATH_TEMPLATE = PathTemplate.create("{routing_id=regions/*}/**"); - private static final PathTemplate PATH_TEMPLATE_EXAMPLE8TEST_2 = + private static final PathTemplate EXAMPLE8_TEST_2_PATH_TEMPLATE = PathTemplate.create("{routing_id=**}"); - private static final PathTemplate PATH_TEMPLATE_EXAMPLE9TEST_0 = + private static final PathTemplate EXAMPLE9_TEST_0_PATH_TEMPLATE = PathTemplate.create("projects/*/{table_location=instances/*}/tables/*"); - private static final PathTemplate PATH_TEMPLATE_EXAMPLE9TEST_1 = + private static final PathTemplate EXAMPLE9_TEST_1_PATH_TEMPLATE = PathTemplate.create("{table_location=regions/*/zones/*}/tables/*"); - private static final PathTemplate PATH_TEMPLATE_EXAMPLE9TEST_2 = + private static final PathTemplate EXAMPLE9_TEST_2_PATH_TEMPLATE = PathTemplate.create("{routing_id=projects/*}/**"); - private static final PathTemplate PATH_TEMPLATE_EXAMPLE9TEST_3 = + private static final PathTemplate EXAMPLE9_TEST_3_PATH_TEMPLATE = PathTemplate.create("{routing_id=**}"); - private static final PathTemplate PATH_TEMPLATE_EXAMPLE9TEST_4 = + private static final PathTemplate EXAMPLE9_TEST_4_PATH_TEMPLATE = PathTemplate.create("profiles/{routing_id=*}"); - private static final PathTemplate PATH_TEMPLATE_BACKWARDSCOMPATIBLE1TEST_0 = + private static final PathTemplate BACKWARDS_COMPATIBLE1_TEST_0_PATH_TEMPLATE = + PathTemplate.create("{routing_id=projects/*}/**"); + private static final PathTemplate NESTED_FIELD_TEST_0_PATH_TEMPLATE = PathTemplate.create("{routing_id=projects/*}/**"); public static final GrpcExplicitDynamicRoutingHeaderTestingStub create( @@ -255,13 +270,9 @@ public class GrpcExplicitDynamicRoutingHeaderTestingStub .setMethodDescriptor(example1TestMethodDescriptor) .setParamsExtractor( request -> { - RoutingHeaderParamsBuilder builder = new RoutingHeaderParamsBuilder(); - if (request.getAppProfileId() != null) { - builder.add( - String.valueOf(request.getAppProfileId()), - "app_profile_id", - PATH_TEMPLATE_EXAMPLE1TEST_0); - } + RequestParamsBuilder builder = RequestParamsBuilder.create(); + builder.add( + request.getAppProfileId(), "app_profile_id", EXAMPLE1_TEST_0_PATH_TEMPLATE); return builder.build(); }) .build(); @@ -270,13 +281,9 @@ public class GrpcExplicitDynamicRoutingHeaderTestingStub .setMethodDescriptor(example2TestMethodDescriptor) .setParamsExtractor( request -> { - RoutingHeaderParamsBuilder builder = new RoutingHeaderParamsBuilder(); - if (request.getAppProfileId() != null) { - builder.add( - String.valueOf(request.getAppProfileId()), - "routing_id", - PATH_TEMPLATE_EXAMPLE2TEST_0); - } + RequestParamsBuilder builder = RequestParamsBuilder.create(); + builder.add( + request.getAppProfileId(), "routing_id", EXAMPLE2_TEST_0_PATH_TEMPLATE); return builder.build(); }) .build(); @@ -285,13 +292,8 @@ public class GrpcExplicitDynamicRoutingHeaderTestingStub .setMethodDescriptor(example3TestMethodDescriptor) .setParamsExtractor( request -> { - RoutingHeaderParamsBuilder builder = new RoutingHeaderParamsBuilder(); - if (request.getTableName() != null) { - builder.add( - String.valueOf(request.getTableName()), - "table_name", - PATH_TEMPLATE_EXAMPLE3TEST_0); - } + RequestParamsBuilder builder = RequestParamsBuilder.create(); + builder.add(request.getTableName(), "table_name", EXAMPLE3_TEST_0_PATH_TEMPLATE); return builder.build(); }) .build(); @@ -300,19 +302,11 @@ public class GrpcExplicitDynamicRoutingHeaderTestingStub .setMethodDescriptor(example3CTestMethodDescriptor) .setParamsExtractor( request -> { - RoutingHeaderParamsBuilder builder = new RoutingHeaderParamsBuilder(); - if (request.getTableName() != null) { - builder.add( - String.valueOf(request.getTableName()), - "table_name", - PATH_TEMPLATE_EXAMPLE3CTEST_0); - } - if (request.getTableName() != null) { - builder.add( - String.valueOf(request.getTableName()), - "table_name", - PATH_TEMPLATE_EXAMPLE3CTEST_1); - } + RequestParamsBuilder builder = RequestParamsBuilder.create(); + builder.add( + request.getTableName(), "table_name", EXAMPLE3_C_TEST_0_PATH_TEMPLATE); + builder.add( + request.getTableName(), "table_name", EXAMPLE3_C_TEST_1_PATH_TEMPLATE); return builder.build(); }) .build(); @@ -321,13 +315,8 @@ public class GrpcExplicitDynamicRoutingHeaderTestingStub .setMethodDescriptor(example4TestMethodDescriptor) .setParamsExtractor( request -> { - RoutingHeaderParamsBuilder builder = new RoutingHeaderParamsBuilder(); - if (request.getTableName() != null) { - builder.add( - String.valueOf(request.getTableName()), - "routing_id", - PATH_TEMPLATE_EXAMPLE4TEST_0); - } + RequestParamsBuilder builder = RequestParamsBuilder.create(); + builder.add(request.getTableName(), "routing_id", EXAMPLE4_TEST_0_PATH_TEMPLATE); return builder.build(); }) .build(); @@ -336,19 +325,9 @@ public class GrpcExplicitDynamicRoutingHeaderTestingStub .setMethodDescriptor(example5TestMethodDescriptor) .setParamsExtractor( request -> { - RoutingHeaderParamsBuilder builder = new RoutingHeaderParamsBuilder(); - if (request.getTableName() != null) { - builder.add( - String.valueOf(request.getTableName()), - "routing_id", - PATH_TEMPLATE_EXAMPLE5TEST_0); - } - if (request.getTableName() != null) { - builder.add( - String.valueOf(request.getTableName()), - "routing_id", - PATH_TEMPLATE_EXAMPLE5TEST_1); - } + RequestParamsBuilder builder = RequestParamsBuilder.create(); + builder.add(request.getTableName(), "routing_id", EXAMPLE5_TEST_0_PATH_TEMPLATE); + builder.add(request.getTableName(), "routing_id", EXAMPLE5_TEST_1_PATH_TEMPLATE); return builder.build(); }) .build(); @@ -357,19 +336,9 @@ public class GrpcExplicitDynamicRoutingHeaderTestingStub .setMethodDescriptor(example6TestMethodDescriptor) .setParamsExtractor( request -> { - RoutingHeaderParamsBuilder builder = new RoutingHeaderParamsBuilder(); - if (request.getTableName() != null) { - builder.add( - String.valueOf(request.getTableName()), - "project_id", - PATH_TEMPLATE_EXAMPLE6TEST_0); - } - if (request.getTableName() != null) { - builder.add( - String.valueOf(request.getTableName()), - "instance_id", - PATH_TEMPLATE_EXAMPLE6TEST_1); - } + RequestParamsBuilder builder = RequestParamsBuilder.create(); + builder.add(request.getTableName(), "project_id", EXAMPLE6_TEST_0_PATH_TEMPLATE); + builder.add(request.getTableName(), "instance_id", EXAMPLE6_TEST_1_PATH_TEMPLATE); return builder.build(); }) .build(); @@ -378,19 +347,10 @@ public class GrpcExplicitDynamicRoutingHeaderTestingStub .setMethodDescriptor(example7TestMethodDescriptor) .setParamsExtractor( request -> { - RoutingHeaderParamsBuilder builder = new RoutingHeaderParamsBuilder(); - if (request.getTableName() != null) { - builder.add( - String.valueOf(request.getTableName()), - "project_id", - PATH_TEMPLATE_EXAMPLE7TEST_0); - } - if (request.getAppProfileId() != null) { - builder.add( - String.valueOf(request.getAppProfileId()), - "routing_id", - PATH_TEMPLATE_EXAMPLE7TEST_1); - } + RequestParamsBuilder builder = RequestParamsBuilder.create(); + builder.add(request.getTableName(), "project_id", EXAMPLE7_TEST_0_PATH_TEMPLATE); + builder.add( + request.getAppProfileId(), "routing_id", EXAMPLE7_TEST_1_PATH_TEMPLATE); return builder.build(); }) .build(); @@ -399,25 +359,11 @@ public class GrpcExplicitDynamicRoutingHeaderTestingStub .setMethodDescriptor(example8TestMethodDescriptor) .setParamsExtractor( request -> { - RoutingHeaderParamsBuilder builder = new RoutingHeaderParamsBuilder(); - if (request.getTableName() != null) { - builder.add( - String.valueOf(request.getTableName()), - "routing_id", - PATH_TEMPLATE_EXAMPLE8TEST_0); - } - if (request.getTableName() != null) { - builder.add( - String.valueOf(request.getTableName()), - "routing_id", - PATH_TEMPLATE_EXAMPLE8TEST_1); - } - if (request.getAppProfileId() != null) { - builder.add( - String.valueOf(request.getAppProfileId()), - "routing_id", - PATH_TEMPLATE_EXAMPLE8TEST_2); - } + RequestParamsBuilder builder = RequestParamsBuilder.create(); + builder.add(request.getTableName(), "routing_id", EXAMPLE8_TEST_0_PATH_TEMPLATE); + builder.add(request.getTableName(), "routing_id", EXAMPLE8_TEST_1_PATH_TEMPLATE); + builder.add( + request.getAppProfileId(), "routing_id", EXAMPLE8_TEST_2_PATH_TEMPLATE); return builder.build(); }) .build(); @@ -426,37 +372,16 @@ public class GrpcExplicitDynamicRoutingHeaderTestingStub .setMethodDescriptor(example9TestMethodDescriptor) .setParamsExtractor( request -> { - RoutingHeaderParamsBuilder builder = new RoutingHeaderParamsBuilder(); - if (request.getTableName() != null) { - builder.add( - String.valueOf(request.getTableName()), - "table_location", - PATH_TEMPLATE_EXAMPLE9TEST_0); - } - if (request.getTableName() != null) { - builder.add( - String.valueOf(request.getTableName()), - "table_location", - PATH_TEMPLATE_EXAMPLE9TEST_1); - } - if (request.getTableName() != null) { - builder.add( - String.valueOf(request.getTableName()), - "routing_id", - PATH_TEMPLATE_EXAMPLE9TEST_2); - } - if (request.getAppProfileId() != null) { - builder.add( - String.valueOf(request.getAppProfileId()), - "routing_id", - PATH_TEMPLATE_EXAMPLE9TEST_3); - } - if (request.getAppProfileId() != null) { - builder.add( - String.valueOf(request.getAppProfileId()), - "routing_id", - PATH_TEMPLATE_EXAMPLE9TEST_4); - } + RequestParamsBuilder builder = RequestParamsBuilder.create(); + builder.add( + request.getTableName(), "table_location", EXAMPLE9_TEST_0_PATH_TEMPLATE); + builder.add( + request.getTableName(), "table_location", EXAMPLE9_TEST_1_PATH_TEMPLATE); + builder.add(request.getTableName(), "routing_id", EXAMPLE9_TEST_2_PATH_TEMPLATE); + builder.add( + request.getAppProfileId(), "routing_id", EXAMPLE9_TEST_3_PATH_TEMPLATE); + builder.add( + request.getAppProfileId(), "routing_id", EXAMPLE9_TEST_4_PATH_TEMPLATE); return builder.build(); }) .build(); @@ -465,24 +390,17 @@ public class GrpcExplicitDynamicRoutingHeaderTestingStub .setMethodDescriptor(backwardsCompatible1TestMethodDescriptor) .setParamsExtractor( request -> { - RoutingHeaderParamsBuilder builder = new RoutingHeaderParamsBuilder(); - if (request.getTableName() != null) { - builder.add( - String.valueOf(request.getTableName()), - "routing_id", - PATH_TEMPLATE_BACKWARDSCOMPATIBLE1TEST_0); - } + RequestParamsBuilder builder = RequestParamsBuilder.create(); + builder.add( + request.getTableName(), + "routing_id", + BACKWARDS_COMPATIBLE1_TEST_0_PATH_TEMPLATE); return builder.build(); }) .build(); GrpcCallSettings backwardsCompatible2TestTransportSettings = GrpcCallSettings.newBuilder() .setMethodDescriptor(backwardsCompatible2TestMethodDescriptor) - .setParamsExtractor( - request -> { - RoutingHeaderParamsBuilder builder = new RoutingHeaderParamsBuilder(); - return builder.build(); - }) .build(); GrpcCallSettings backwardsCompatible3TestTransportSettings = GrpcCallSettings.newBuilder() @@ -494,6 +412,22 @@ public class GrpcExplicitDynamicRoutingHeaderTestingStub return params.build(); }) .build(); + GrpcCallSettings nestedFieldTestTransportSettings = + GrpcCallSettings.newBuilder() + .setMethodDescriptor(nestedFieldTestMethodDescriptor) + .setParamsExtractor( + request -> { + RequestParamsBuilder builder = RequestParamsBuilder.create(); + if (request.getNestedField() != null + && request.getNestedField().getAnotherNestedField() != null) { + builder.add( + request.getNestedField().getAnotherNestedField().getName(), + "routing_id", + NESTED_FIELD_TEST_0_PATH_TEMPLATE); + } + return builder.build(); + }) + .build(); this.example1TestCallable = callableFactory.createUnaryCallable( @@ -540,6 +474,9 @@ public class GrpcExplicitDynamicRoutingHeaderTestingStub backwardsCompatible3TestTransportSettings, settings.backwardsCompatible3TestSettings(), clientContext); + this.nestedFieldTestCallable = + callableFactory.createUnaryCallable( + nestedFieldTestTransportSettings, settings.nestedFieldTestSettings(), clientContext); this.backgroundResources = new BackgroundResourceAggregation(clientContext.getBackgroundResources()); @@ -614,6 +551,11 @@ public class GrpcExplicitDynamicRoutingHeaderTestingStub return backwardsCompatible3TestCallable; } + @Override + public UnaryCallable nestedFieldTestCallable() { + return nestedFieldTestCallable; + } + @Override public final void close() { try { diff --git a/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcTestingStub.golden b/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcTestingStub.golden index 73146650b8..6c0a667291 100644 --- a/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcTestingStub.golden +++ b/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcTestingStub.golden @@ -8,8 +8,8 @@ import com.google.api.gax.core.BackgroundResource; import com.google.api.gax.core.BackgroundResourceAggregation; import com.google.api.gax.grpc.GrpcCallSettings; import com.google.api.gax.grpc.GrpcStubCallableFactory; -import com.google.api.gax.grpc.RoutingHeaderParamsBuilder; import com.google.api.gax.rpc.ClientContext; +import com.google.api.gax.rpc.RequestParamsBuilder; import com.google.api.gax.rpc.UnaryCallable; import com.google.api.pathtemplate.PathTemplate; import com.google.common.collect.ImmutableMap; @@ -144,9 +144,9 @@ public class GrpcTestingStub extends TestingStub { private final GrpcOperationsStub operationsStub; private final GrpcStubCallableFactory callableFactory; - private static final PathTemplate PATH_TEMPLATE_GETTEST_0 = + private static final PathTemplate GET_TEST_0_PATH_TEMPLATE = PathTemplate.create("/v1beta1/{rename=tests/*}"); - private static final PathTemplate PATH_TEMPLATE_GETTEST_1 = + private static final PathTemplate GET_TEST_1_PATH_TEMPLATE = PathTemplate.create("/v1beta1/{routing_id=tests/*}"); public static final GrpcTestingStub create(TestingStubSettings settings) throws IOException { @@ -227,16 +227,11 @@ public class GrpcTestingStub extends TestingStub { .setMethodDescriptor(getTestMethodDescriptor) .setParamsExtractor( request -> { - RoutingHeaderParamsBuilder builder = new RoutingHeaderParamsBuilder(); - if (request.getName() != null) { + RequestParamsBuilder builder = RequestParamsBuilder.create(); + builder.add(request.getName(), "rename", GET_TEST_0_PATH_TEMPLATE); + if (request.getRouting() != null) { builder.add( - String.valueOf(request.getName()), "rename", PATH_TEMPLATE_GETTEST_0); - } - if (request.getRouting() != null && request.getRouting().getName() != null) { - builder.add( - String.valueOf(request.getRouting().getName()), - "routing_id", - PATH_TEMPLATE_GETTEST_1); + request.getRouting().getName(), "routing_id", GET_TEST_1_PATH_TEMPLATE); } return builder.build(); }) diff --git a/src/test/java/com/google/api/generator/gapic/model/BUILD.bazel b/src/test/java/com/google/api/generator/gapic/model/BUILD.bazel index 7c1ad5181f..bc62109ad9 100644 --- a/src/test/java/com/google/api/generator/gapic/model/BUILD.bazel +++ b/src/test/java/com/google/api/generator/gapic/model/BUILD.bazel @@ -7,7 +7,7 @@ TESTS = [ "MethodArgumentTest", "MethodTest", "MessageTest", - "RoutingHeadersTest", + "RoutingHeaderParamTest", ] filegroup( diff --git a/src/test/java/com/google/api/generator/gapic/model/MessageTest.java b/src/test/java/com/google/api/generator/gapic/model/MessageTest.java index 76f19c2b87..469c86c212 100644 --- a/src/test/java/com/google/api/generator/gapic/model/MessageTest.java +++ b/src/test/java/com/google/api/generator/gapic/model/MessageTest.java @@ -25,8 +25,13 @@ public class MessageTest { - public static final String MESSAGE_NAME = "TestMessage"; - public static final Message.Builder TEST_MESSAGE_BUILDER = + private static final String SUB_FIELD_NAME = "table"; + private static final String LEAF_FIELD_NAME = "size"; + private static final String SUB_FIELD_TYPE = "TableFieldType"; + public static final VaporReference FIELD_TYPE = + VaporReference.builder().setPakkage("com.google").setName(SUB_FIELD_TYPE).build(); + private static final String MESSAGE_NAME = "TestMessage"; + private static final Message.Builder testMessageBuilder = Message.builder() .setName(MESSAGE_NAME) .setFullProtoName("com.google.test.TestMessage") @@ -34,22 +39,26 @@ public class MessageTest { @Test public void validateField_shouldThrowExceptionIfFieldNameIsEmpty() { - Message message = TEST_MESSAGE_BUILDER.build(); + Message message = testMessageBuilder.build(); IllegalStateException illegalStateException = assertThrows( - IllegalStateException.class, () -> message.validateField("", ImmutableMap.of())); - assertThat(illegalStateException.getMessage()) + IllegalStateException.class, + () -> message.validateField("", ImmutableMap.of(), TypeNode.STRING)); + assertThat(illegalStateException) + .hasMessageThat() .isEqualTo(String.format("Null or empty field name found for message %s", MESSAGE_NAME)); } @Test public void validateField_shouldThrowExceptionIfFieldDoesNotExist() { - Message message = TEST_MESSAGE_BUILDER.build(); + Message message = testMessageBuilder.build(); String fieldName = "doesNotExist"; NullPointerException nullPointerException = assertThrows( - NullPointerException.class, () -> message.validateField(fieldName, ImmutableMap.of())); - assertThat(nullPointerException.getMessage()) + NullPointerException.class, + () -> message.validateField(fieldName, ImmutableMap.of(), TypeNode.STRING)); + assertThat(nullPointerException) + .hasMessageThat() .isEqualTo( String.format( "Expected message %s to contain field %s but none found", MESSAGE_NAME, fieldName)); @@ -57,54 +66,86 @@ public void validateField_shouldThrowExceptionIfFieldDoesNotExist() { @Test public void validateField_shouldThrowExceptionIfMessageDoesNotExist() { - String subFieldName = "table"; - String fieldTypeName = "doesNotMatter"; Field subField = Field.builder() - .setName(subFieldName) + .setName(SUB_FIELD_NAME) .setType( TypeNode.withReference( VaporReference.builder() .setPakkage("com.google") - .setName(fieldTypeName) + .setName(SUB_FIELD_TYPE) .build())) .build(); Message message = - TEST_MESSAGE_BUILDER.setFieldMap(ImmutableMap.of(subFieldName, subField)).build(); - String fieldName = subFieldName + "." + "size"; + testMessageBuilder.setFieldMap(ImmutableMap.of(SUB_FIELD_NAME, subField)).build(); + String fieldName = SUB_FIELD_NAME + "." + LEAF_FIELD_NAME; NullPointerException nullPointerException = assertThrows( - NullPointerException.class, () -> message.validateField(fieldName, ImmutableMap.of())); - assertThat(nullPointerException.getMessage()) + NullPointerException.class, + () -> message.validateField(fieldName, ImmutableMap.of(), TypeNode.STRING)); + assertThat(nullPointerException) + .hasMessageThat() .isEqualTo( String.format( "No containing message found for field %s with type %s", - subFieldName, fieldTypeName)); + SUB_FIELD_NAME, SUB_FIELD_TYPE)); + } + + @Test + public void validateField_shouldThrowExceptionIfFieldIsRepeated() { + Field leafField = + Field.builder() + .setType(TypeNode.STRING) + .setIsRepeated(true) + .setName(LEAF_FIELD_NAME) + .build(); + testLeafField(leafField); + } + + @Test + public void validateField_shouldThrowExceptionIfFieldIsOfWrongType() { + Field leafField = Field.builder().setType(TypeNode.BOOLEAN).setName(LEAF_FIELD_NAME).build(); + testLeafField(leafField); + } + + private void testLeafField(Field leafField) { + Message subMessage = createSubMessage(leafField); + Map messageTypes = ImmutableMap.of(FIELD_TYPE.fullName(), subMessage); + IllegalStateException illegalStateException = + assertThrows( + IllegalStateException.class, + () -> + createdMessage() + .validateField( + SUB_FIELD_NAME + "." + LEAF_FIELD_NAME, messageTypes, TypeNode.STRING)); + assertThat(illegalStateException) + .hasMessageThat() + .isEqualTo( + String.format( + "The type of field %s must be String and not repeated.", LEAF_FIELD_NAME)); } @Test public void validateField_shouldNotThrowExceptionIfFieldExist() { - String subFieldName = "table"; - String fieldTypeName = "TableFieldType"; - VaporReference fieldType = - VaporReference.builder().setPakkage("com.google").setName(fieldTypeName).build(); + Field leafField = Field.builder().setType(TypeNode.STRING).setName(LEAF_FIELD_NAME).build(); + Message subMessage = createSubMessage(leafField); + Map messageTypes = ImmutableMap.of(FIELD_TYPE.fullName(), subMessage); + createdMessage() + .validateField(SUB_FIELD_NAME + "." + LEAF_FIELD_NAME, messageTypes, TypeNode.STRING); + } + + private Message createdMessage() { Field subField = - Field.builder().setName(subFieldName).setType(TypeNode.withReference(fieldType)).build(); - String subFieldName2 = "size"; - String fieldName = subFieldName + "." + subFieldName2; - Message subMessage = - Message.builder() - .setName(fieldTypeName) - .setFullProtoName("com.google." + fieldTypeName) - .setType(TypeNode.OBJECT) - .setFieldMap( - ImmutableMap.of( - subFieldName2, - Field.builder().setType(TypeNode.STRING).setName(subFieldName2).build())) - .build(); - Map messageTypes = ImmutableMap.of(fieldType.fullName(), subMessage); - Message message = - TEST_MESSAGE_BUILDER.setFieldMap(ImmutableMap.of(subFieldName, subField)).build(); - message.validateField(fieldName, messageTypes); + Field.builder().setName(SUB_FIELD_NAME).setType(TypeNode.withReference(FIELD_TYPE)).build(); + return testMessageBuilder.setFieldMap(ImmutableMap.of(SUB_FIELD_NAME, subField)).build(); + } + + private Message createSubMessage(Field leafField) { + return Message.builder() + .setName(SUB_FIELD_TYPE) + .setFullProtoName("com.google." + SUB_FIELD_TYPE) + .setType(TypeNode.OBJECT) + .setFieldMap(ImmutableMap.of(LEAF_FIELD_NAME, leafField)) + .build(); } } diff --git a/src/test/java/com/google/api/generator/gapic/model/MethodTest.java b/src/test/java/com/google/api/generator/gapic/model/MethodTest.java index 2c245151f4..fd5bba4b49 100644 --- a/src/test/java/com/google/api/generator/gapic/model/MethodTest.java +++ b/src/test/java/com/google/api/generator/gapic/model/MethodTest.java @@ -16,9 +16,29 @@ import static com.google.common.truth.Truth.assertThat; +import com.google.api.generator.engine.ast.TypeNode; +import com.google.api.generator.gapic.model.HttpBindings.HttpBinding; +import com.google.api.generator.gapic.model.HttpBindings.HttpVerb; +import com.google.api.generator.gapic.model.RoutingHeaderRule.RoutingHeaderParam; +import com.google.common.collect.ImmutableSet; import org.junit.Test; public class MethodTest { + + private static final Method METHOD = + Method.builder() + .setName("My method") + .setInputType(TypeNode.STRING) + .setOutputType(TypeNode.STRING) + .build(); + private static final HttpBindings HTTP_BINDINGS = + HttpBindings.builder() + .setPathParameters(ImmutableSet.of(HttpBinding.create("table", true, ""))) + .setPattern("/pattern/test") + .setIsAsteriskBody(false) + .setHttpVerb(HttpVerb.GET) + .build(); + @Test public void toStream() { // Argument order: isClientStreaming, isServerStreaming. @@ -27,4 +47,67 @@ public void toStream() { assertThat(Method.toStream(false, true)).isEqualTo(Method.Stream.SERVER); assertThat(Method.toStream(true, true)).isEqualTo(Method.Stream.BIDI); } + + @Test + public void hasRoutingHeaders_shouldReturnFalseIfRoutingHeadersIsNull() { + assertThat(METHOD.hasRoutingHeaderParams()).isFalse(); + } + + @Test + public void hasRoutingHeaders_shouldReturnFalseIfRoutingHeadersIsEmpty() { + Method method = + METHOD.toBuilder().setRoutingHeaderRule(RoutingHeaderRule.builder().build()).build(); + assertThat(method.hasRoutingHeaderParams()).isFalse(); + } + + @Test + public void hasRoutingHeaders_shouldReturnTrueIfRoutingHeadersIsNotEmpty() { + Method method = + METHOD + .toBuilder() + .setRoutingHeaderRule( + RoutingHeaderRule.builder() + .addParam(RoutingHeaderParam.create("table", "routing_id", "")) + .build()) + .build(); + assertThat(method.hasRoutingHeaderParams()).isTrue(); + } + + @Test + public void shouldSetParamsExtractor_shouldReturnTrueIfHasRoutingHeaders() { + Method method = + METHOD + .toBuilder() + .setRoutingHeaderRule( + RoutingHeaderRule.builder() + .addParam(RoutingHeaderParam.create("table", "routing_id", "")) + .build()) + .build(); + assertThat(method.shouldSetParamsExtractor()).isTrue(); + } + + @Test + public void shouldSetParamsExtractor_shouldReturnTrueIfHasHttpBindingsAndRoutingHeadersIsNull() { + Method method = + METHOD.toBuilder().setHttpBindings(HTTP_BINDINGS).setRoutingHeaderRule(null).build(); + assertThat(method.shouldSetParamsExtractor()).isTrue(); + } + + @Test + public void + shouldSetParamsExtractor_shouldReturnFalseIfHasHttpBindingsAndRoutingHeadersIsEmpty() { + Method method = + METHOD + .toBuilder() + .setHttpBindings(HTTP_BINDINGS) + .setRoutingHeaderRule(RoutingHeaderRule.builder().build()) + .build(); + assertThat(method.shouldSetParamsExtractor()).isFalse(); + } + + @Test + public void shouldSetParamsExtractor_shouldReturnFalseIfHasNoHttpBindingsAndNoRoutingHeaders() { + Method method = METHOD.toBuilder().setHttpBindings(null).setRoutingHeaderRule(null).build(); + assertThat(method.shouldSetParamsExtractor()).isFalse(); + } } diff --git a/src/test/java/com/google/api/generator/gapic/model/RoutingHeadersTest.java b/src/test/java/com/google/api/generator/gapic/model/RoutingHeaderParamTest.java similarity index 73% rename from src/test/java/com/google/api/generator/gapic/model/RoutingHeadersTest.java rename to src/test/java/com/google/api/generator/gapic/model/RoutingHeaderParamTest.java index 3ae092f456..8c2d1d361e 100644 --- a/src/test/java/com/google/api/generator/gapic/model/RoutingHeadersTest.java +++ b/src/test/java/com/google/api/generator/gapic/model/RoutingHeaderParamTest.java @@ -16,16 +16,17 @@ import static com.google.common.truth.Truth.assertThat; -import com.google.api.generator.gapic.model.RoutingHeaders.RoutingHeader; +import com.google.api.generator.gapic.model.RoutingHeaderRule.RoutingHeaderParam; import java.util.List; import org.junit.Test; -public class RoutingHeadersTest { +public class RoutingHeaderParamTest { @Test public void getDescendantFieldNames_shouldSplitFieldNameByDot() { - RoutingHeader routingHeader = RoutingHeader.create("table.name", "name", "/abc/dec"); - List descendantFieldNames = routingHeader.getDescendantFieldNames(); + RoutingHeaderParam routingHeaderParam = + RoutingHeaderParam.create("table.name", "name", "/abc/dec"); + List descendantFieldNames = routingHeaderParam.getDescendantFieldNames(); assertThat(descendantFieldNames).containsExactly("table", "name"); } } diff --git a/src/test/java/com/google/api/generator/gapic/protoparser/BUILD.bazel b/src/test/java/com/google/api/generator/gapic/protoparser/BUILD.bazel index 09fa41b0ef..ead36d5fb0 100644 --- a/src/test/java/com/google/api/generator/gapic/protoparser/BUILD.bazel +++ b/src/test/java/com/google/api/generator/gapic/protoparser/BUILD.bazel @@ -16,7 +16,8 @@ TESTS = [ "ServiceYamlParserTest", "SourceCodeInfoParserTest", "TypeParserTest", - "RoutingRuleParserTest" + "RoutingRuleParserTest", + "PatternParserTest", ] filegroup( diff --git a/src/test/java/com/google/api/generator/gapic/protoparser/PatternParserTest.java b/src/test/java/com/google/api/generator/gapic/protoparser/PatternParserTest.java new file mode 100644 index 0000000000..8415ca2b21 --- /dev/null +++ b/src/test/java/com/google/api/generator/gapic/protoparser/PatternParserTest.java @@ -0,0 +1,40 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://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. + +package com.google.api.generator.gapic.protoparser; + +import static com.google.common.truth.Truth.assertThat; + +import java.util.Set; +import org.junit.Test; + +public class PatternParserTest { + @Test + public void getPattenBindings_shouldReturnEmptySetIfPatternIsEmpty() { + assertThat(PatternParser.getPattenBindings("")).isEmpty(); + } + + @Test + public void getPattenBindings_shouldFilterOutUnboundVariables() { + Set actual = PatternParser.getPattenBindings("{routing_id=projects/*}/**"); + assertThat(actual).hasSize(1); + } + + @Test + public void getPattenBindings_shouldReturnBindingsInNatualOrder() { + Set actual = + PatternParser.getPattenBindings("{routing_id=projects/*}/{name=instance/*}"); + assertThat(actual).containsExactly("name", "routing_id").inOrder(); + } +} diff --git a/src/test/java/com/google/api/generator/gapic/protoparser/RoutingRuleParserTest.java b/src/test/java/com/google/api/generator/gapic/protoparser/RoutingRuleParserTest.java index 25d82232bb..0908d79080 100644 --- a/src/test/java/com/google/api/generator/gapic/protoparser/RoutingRuleParserTest.java +++ b/src/test/java/com/google/api/generator/gapic/protoparser/RoutingRuleParserTest.java @@ -18,8 +18,8 @@ import static org.junit.Assert.assertThrows; import com.google.api.generator.gapic.model.Message; -import com.google.api.generator.gapic.model.RoutingHeaders; -import com.google.api.generator.gapic.model.RoutingHeaders.RoutingHeader; +import com.google.api.generator.gapic.model.RoutingHeaderRule; +import com.google.api.generator.gapic.model.RoutingHeaderRule.RoutingHeaderParam; import com.google.explicit.dynamic.routing.header.RoutingRuleParserTestingOuterClass; import com.google.protobuf.Descriptors.FileDescriptor; import com.google.protobuf.Descriptors.MethodDescriptor; @@ -31,22 +31,21 @@ public class RoutingRuleParserTest { private static final FileDescriptor TESTING_FILE_DESCRIPTOR = RoutingRuleParserTestingOuterClass.getDescriptor(); - private static final Map MESSAGES = - Parser.parseMessages(TESTING_FILE_DESCRIPTOR); private static final ServiceDescriptor TESTING_SERVICE = TESTING_FILE_DESCRIPTOR.getServices().get(0); @Test public void parse_shouldReturnNullRoutingHeadersIfMethodHasNoRoutingRules() { - RoutingHeaders actual = getRoutingHeaders(0); + RoutingHeaderRule actual = getRoutingHeaders(0); assertThat(actual).isNull(); } @Test public void parse_shouldSetPathTemplateToWildcardIfNotDefined() { - RoutingHeaders actual = getRoutingHeaders(1); - RoutingHeader expected = RoutingHeader.create("name", "name", String.format("{%s=**}", "name")); - assertThat(actual.routingHeadersList()).containsExactly(expected); + RoutingHeaderRule actual = getRoutingHeaders(1); + RoutingHeaderParam expected = + RoutingHeaderParam.create("name", "name", String.format("{%s=**}", "name")); + assertThat(actual.routingHeaderParams()).containsExactly(expected); } @Test @@ -73,27 +72,30 @@ public void parse_shouldThrowExceptionIfPathTemplateHasMoreThanOneNamedSegment() @Test public void parse_shouldParseRoutingRulesWithOneParameter() { - RoutingHeaders actual = getRoutingHeaders(4); - RoutingHeader expected = RoutingHeader.create("name", "rename", "/v1beta1/{rename=tests/*}"); - assertThat(actual.routingHeadersList()).containsExactly(expected); + RoutingHeaderRule actual = getRoutingHeaders(4); + RoutingHeaderParam expected = + RoutingHeaderParam.create("name", "rename", "/v1beta1/{rename=tests/*}"); + assertThat(actual.routingHeaderParams()).containsExactly(expected); } @Test public void parse_shouldParseRoutingRulesWithMultipleParameter() { - RoutingHeaders actual = getRoutingHeaders(5); - RoutingHeader expectedHeader1 = - RoutingHeader.create("name", "rename", "/v1beta1/{rename=tests/*}"); - RoutingHeader expectedHeader2 = - RoutingHeader.create("routing_id", "id", "/v1beta1/{id=projects/*}/tables/*"); - assertThat(actual.routingHeadersList()).containsExactly(expectedHeader1, expectedHeader2); + RoutingHeaderRule actual = getRoutingHeaders(5); + RoutingHeaderParam expectedHeader1 = + RoutingHeaderParam.create("name", "rename", "/v1beta1/{rename=tests/*}"); + RoutingHeaderParam expectedHeader2 = + RoutingHeaderParam.create("routing_id", "id", "/v1beta1/{id=projects/*}/tables/*"); + assertThat(actual.routingHeaderParams()) + .containsExactly(expectedHeader1, expectedHeader2) + .inOrder(); } @Test public void parse_shouldParseRoutingRulesWithNestedFields() { - RoutingHeaders actual = getRoutingHeaders(6); - RoutingHeader expectedHeader1 = - RoutingHeader.create("account.name", "rename", "/v1beta1/{rename=tests/*}"); - assertThat(actual.routingHeadersList()).containsExactly(expectedHeader1); + RoutingHeaderRule actual = getRoutingHeaders(6); + RoutingHeaderParam expectedHeader1 = + RoutingHeaderParam.create("account.name", "rename", "/v1beta1/{rename=tests/*}"); + assertThat(actual.routingHeaderParams()).containsExactly(expectedHeader1); } @Test @@ -101,9 +103,10 @@ public void parse_shouldThrowExceptionIfFieldValidationFailed() { assertThrows(Exception.class, () -> getRoutingHeaders(7)); } - private RoutingHeaders getRoutingHeaders(int testingIndex) { + private RoutingHeaderRule getRoutingHeaders(int testingIndex) { MethodDescriptor rpcMethod = TESTING_SERVICE.getMethods().get(testingIndex); - Message inputMessage = MESSAGES.get("com." + rpcMethod.getInputType().getFullName()); - return RoutingRuleParser.parse(rpcMethod, inputMessage, MESSAGES); + Map messages = Parser.parseMessages(TESTING_FILE_DESCRIPTOR); + Message inputMessage = messages.get("com." + rpcMethod.getInputType().getFullName()); + return RoutingRuleParser.parse(rpcMethod, inputMessage, messages); } } diff --git a/src/test/java/com/google/api/generator/gapic/testdata/explicit_dynamic_routing_header_testing.proto b/src/test/java/com/google/api/generator/gapic/testdata/explicit_dynamic_routing_header_testing.proto index f2add97a27..cbe9526a7b 100644 --- a/src/test/java/com/google/api/generator/gapic/testdata/explicit_dynamic_routing_header_testing.proto +++ b/src/test/java/com/google/api/generator/gapic/testdata/explicit_dynamic_routing_header_testing.proto @@ -256,6 +256,15 @@ service ExplicitDynamicRoutingHeaderTesting { get: "/v1beta1/{table_name=tests/*}" }; } + + rpc NestedFieldTest(RequestWithNestedField) returns (google.protobuf.Empty) { + option (google.api.routing) = { + routing_parameters { + field: "nested_field.another_nested_field.name" + path_template: "{routing_id=projects/*}/**" + } + }; + } } @@ -281,3 +290,15 @@ message Request { string app_profile_id = 2; } +message RequestWithNestedField { + NestedField nested_field = 1; +} + +message NestedField { + AnotherNestedField another_nested_field = 1; +} + +message AnotherNestedField { + string name = 1; +} + From 165c324df1bbeada73ff99d280a7a0c814235042 Mon Sep 17 00:00:00 2001 From: Blake Li Date: Thu, 27 Jan 2022 15:51:51 -0500 Subject: [PATCH 8/9] #869. Add comment to explain the new PatternParser. --- .../google/api/generator/gapic/protoparser/PatternParser.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/google/api/generator/gapic/protoparser/PatternParser.java b/src/main/java/com/google/api/generator/gapic/protoparser/PatternParser.java index 47ab9e5411..2a374c47af 100644 --- a/src/main/java/com/google/api/generator/gapic/protoparser/PatternParser.java +++ b/src/main/java/com/google/api/generator/gapic/protoparser/PatternParser.java @@ -20,6 +20,8 @@ public class PatternParser { + // This method tries to parse all named segments from pattern and sort in natual order + // e.g. /v1beta1/{table_name=tests/*}/{routing_id=instances/*}/** -> (routing_id, table_name) public static Set getPattenBindings(String pattern) { ImmutableSortedSet.Builder bindings = ImmutableSortedSet.naturalOrder(); if (pattern.isEmpty()) { From ef5e0220ea1ab5ec37cdbc5f1518bcf723ab7310 Mon Sep 17 00:00:00 2001 From: Blake Li Date: Thu, 27 Jan 2022 23:49:45 -0500 Subject: [PATCH 9/9] #869. Bump gax_java to 2.11.0 --- WORKSPACE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WORKSPACE b/WORKSPACE index 767c6560b6..047974cb54 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -33,7 +33,7 @@ jvm_maven_import_external( # which in its turn, prioritizes actual generated clients runtime dependencies # over the generator dependencies. -_gax_java_version = "2.10.0" +_gax_java_version = "2.11.0" http_archive( name = "com_google_api_gax_java",