Skip to content

Commit

Permalink
Add support for parsing routing rules annotation. Test out params ext…
Browse files Browse the repository at this point in the history
…ractor in composer. #869
  • Loading branch information
blakeli0 committed Dec 21, 2021
1 parent 6af0e18 commit 0bc0aad
Show file tree
Hide file tree
Showing 12 changed files with 302 additions and 2 deletions.
4 changes: 2 additions & 2 deletions repositories.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -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",
],
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@

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.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;
Expand All @@ -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;
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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)
Expand All @@ -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()
Expand All @@ -335,4 +378,13 @@ private LambdaExpr createRequestParamsExtractorClassInstance(Method method) {
.setReturnExpr(returnExpr)
.build();
}

class ExplicitRoutingHeaderExtractor implements RequestParamsExtractor {

@Override
public Map<String, String> extract(Object request) {
// no way to extract the field value since request type is dynamic
return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<List<MethodArgument>> methodSignatures();
Expand Down Expand Up @@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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<RoutingHeader> routingHeadersSet();

// TODO: change to a builder and expose only add method
public static RoutingHeaders create(List<RoutingHeader> 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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -709,6 +710,9 @@ static List<Method> 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())
Expand All @@ -726,6 +730,7 @@ static List<Method> parseMethods(
resourceNames,
outputArgResourceNames))
.setHttpBindings(httpBindings)
.setRoutingHeaders(routingHeaders)
.setIsBatching(isBatching)
.setPageSizeFieldName(parsePageSizeFieldName(protoMethod, messageTypes, transport))
.setIsDeprecated(isDeprecated)
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String, Message> 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<Message> inputMessageOpt,
Map<String, Message> 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<RoutingHeader> 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<String> 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<String> getPatternBindings(String pattern) {
ImmutableSortedSet.Builder<String> 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()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ public class GrpcTestingStub extends TestingStub {
request -> {
ImmutableMap.Builder<String, String> params = ImmutableMap.builder();
params.put("name", String.valueOf(request.getName()));
params.put("rename", String.valueOf(request.getName()));
return params.build();
})
.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ TESTS = [
"ServiceYamlParserTest",
"SourceCodeInfoParserTest",
"TypeParserTest",
"RoutingRuleParserTest"
]

filegroup(
Expand Down Expand Up @@ -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",
Expand Down
Loading

0 comments on commit 0bc0aad

Please sign in to comment.