Skip to content

Commit

Permalink
feat: Dynamic Routing Headers for HttpJson (#1667)
Browse files Browse the repository at this point in the history
* feat: Add RequestParamsExtractor to HttpJsonCallSettings

* feat: Add RequestParamsCallable classes

* feat: Update HttpJsonCallableFactory to use new RequestParamCallables

* feat: Add RequestParam to CallOptions

* feat: Add RequestParam header for HttpJson

* feat: Add initial Dynamic Routing Header showcase test

* feat: Ignore empty string for Request Param

* feat: Regenerate test client code

* feat: Do not generate HttpBinding logic for REST

* feat: Add Dynamic Routing Header showcase test cases

* feat: Move header key value to constant

* feat: Add URLEncoding for header

* feat: Update showcase interceptor name

* feat: Generate HttpJson Dyanmic Routing Header Stub

* feat: Fix Request Params Test

* feat: Update variable names

* feat: Add gRPC Dynamic Routing Header showcase test

* feat: Clean up IT showcase test

* chore: Add comments for RequestBuilderParams

* feat: Generate test client code with Implict Routing Headers

* feat: Regenerate test client code

* fix: Generate httpjson golden test case

* chore: Fix format issues

* chore: Regenerate integration golden tests

* chore: Add more tests for RequestParamsBuilder

* chore: Update docs for showcase test

* chore: Refactor TestClientInitializer to include interceptors

* chore: Add null assertions for showcase test

* chore: Remove String.valueOf check for routing params

* chore: Remove String.valueOf check for routing params

* chore: Regenerate the showcase clients

* chore: Address pr comments

* chore: Update Dynamic Routing Header test cases

* chore: Use PercentEscaper for percent encoding

* chore: Empty commit

* chore: Migrate transport specific code to abstract class

* chore: Address pr comments

* chore: Address pr comments

* chore: Use HttpJsonMetadata for headers

* chore: Revert call options changes

* chore: Set default request header map

* chore: Move encoding logic to RequestUrlParamsEncoder

* chore: Fix lint issues

* chore: Add clirr ignore for RequestUrlParamsEncoder

* chore: Address PR comments

* chore: Address PR comments
  • Loading branch information
lqiu96 committed Jun 6, 2023
1 parent e78b267 commit 003b993
Show file tree
Hide file tree
Showing 64 changed files with 2,704 additions and 1,136 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -16,40 +16,26 @@

import com.google.api.gax.grpc.GrpcCallSettings;
import com.google.api.gax.grpc.GrpcStubCallableFactory;
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;
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.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;
import com.google.api.generator.engine.ast.TypeNode;
import com.google.api.generator.engine.ast.ValueExpr;
import com.google.api.generator.engine.ast.Variable;
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.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.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;
import io.grpc.MethodDescriptor;
import io.grpc.protobuf.ProtoUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
Expand Down Expand Up @@ -200,46 +186,6 @@ protected EnumRefExpr getMethodDescriptorMethodTypeExpr(Method protoMethod) {
.build();
}

@Override
protected Expr createTransportSettingsInitExpr(
Method method,
VariableExpr transportSettingsVarExpr,
VariableExpr methodDescriptorVarExpr,
List<Statement> classStatements) {
MethodInvocationExpr callSettingsBuilderExpr =
MethodInvocationExpr.builder()
.setStaticReferenceType(getTransportContext().transportCallSettingsType())
.setGenerics(transportSettingsVarExpr.type().reference().generics())
.setMethodName("newBuilder")
.build();
callSettingsBuilderExpr =
MethodInvocationExpr.builder()
.setExprReferenceExpr(callSettingsBuilderExpr)
.setMethodName("setMethodDescriptor")
.setArguments(Arrays.asList(methodDescriptorVarExpr))
.build();

if (method.shouldSetParamsExtractor()) {
callSettingsBuilderExpr =
MethodInvocationExpr.builder()
.setExprReferenceExpr(callSettingsBuilderExpr)
.setMethodName("setParamsExtractor")
.setArguments(createRequestParamsExtractorClassInstance(method, classStatements))
.build();
}

callSettingsBuilderExpr =
MethodInvocationExpr.builder()
.setExprReferenceExpr(callSettingsBuilderExpr)
.setMethodName("build")
.setReturnType(transportSettingsVarExpr.type())
.build();
return AssignmentExpr.builder()
.setVariableExpr(transportSettingsVarExpr.toBuilder().setIsDecl(true).build())
.setValueExpr(callSettingsBuilderExpr)
.build();
}

@Override
protected String getProtoRpcFullMethodName(Service protoService, Method protoMethod) {
if (protoMethod.isMixin()) {
Expand All @@ -255,244 +201,4 @@ protected String getProtoRpcFullMethodName(Service protoService, Method protoMet
// long-term solution.
return String.format("google.iam.v1.IAMPolicy/%s", protoMethod.name());
}

private LambdaExpr createRequestParamsExtractorClassInstance(
Method method, List<Statement> classStatements) {
List<Statement> bodyStatements = new ArrayList<>();
VariableExpr requestVarExpr =
VariableExpr.withVariable(
Variable.builder().setType(method.inputType()).setName("request").build());
TypeNode returnType =
TypeNode.withReference(
ConcreteReference.builder()
.setClazz(Map.class)
.setGenerics(TypeNode.STRING.reference(), TypeNode.STRING.reference())
.build());
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.routingHeaderRule() == null) {
createRequestParamsExtractorBodyForHttpBindings(
method, requestVarExpr, bodyStatements, returnExpr);
} else {
createRequestParamsExtractorBodyForRoutingHeaders(
method, requestVarExpr, classStatements, bodyStatements, returnExpr);
}

// 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(bodyStatements)
.setReturnExpr(returnExpr.build())
.build();
}

private void createRequestParamsExtractorBodyForHttpBindings(
Method method,
VariableExpr requestVarExpr,
List<Statement> 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,
List<Statement> classStatements,
List<Statement> bodyStatements,
MethodInvocationExpr.Builder returnExprBuilder) {
TypeNode routingHeadersBuilderType =
TypeNode.withReference(
ConcreteReference.builder().setClazz(RequestParamsBuilder.class).build());
VariableExpr routingHeadersBuilderVarExpr =
VariableExpr.builder()
.setVariable(
Variable.builder().setName("builder").setType(routingHeadersBuilderType).build())
.setIsDecl(true)
.build();
MethodInvocationExpr routingHeaderBuilderInvokeExpr =
MethodInvocationExpr.builder()
.setStaticReferenceType(routingHeadersBuilderType)
.setMethodName("create")
.setReturnType(routingHeadersBuilderType)
.build();
Expr routingHeadersBuilderInitExpr =
AssignmentExpr.builder()
.setVariableExpr(routingHeadersBuilderVarExpr)
.setValueExpr(routingHeaderBuilderInvokeExpr)
.build();
bodyStatements.add(ExprStatement.withExpr(routingHeadersBuilderInitExpr));
List<RoutingHeaderParam> routingHeaderParams = method.routingHeaderRule().routingHeaderParams();
VariableExpr routingHeadersBuilderVarNonDeclExpr =
VariableExpr.builder()
.setVariable(
Variable.builder().setName("builder").setType(routingHeadersBuilderType).build())
.build();
for (int i = 0; i < routingHeaderParams.size(); i++) {
RoutingHeaderParam routingHeaderParam = routingHeaderParams.get(i);
MethodInvocationExpr requestFieldGetterExpr =
createRequestFieldGetterExpr(requestVarExpr, routingHeaderParam.fieldName());
Expr routingHeaderKeyExpr =
ValueExpr.withValue(StringObjectValue.withValue(routingHeaderParam.key()));
String pathTemplateName =
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);
Statement pathTemplateClassVar =
createPathTemplateClassStatement(routingHeaderParam, pathTemplateType, pathTemplateVar);
classStatements.add(pathTemplateClassVar);
MethodInvocationExpr addParamMethodExpr =
MethodInvocationExpr.builder()
.setExprReferenceExpr(routingHeadersBuilderVarNonDeclExpr)
.setMethodName("add")
.setArguments(requestFieldGetterExpr, routingHeaderKeyExpr, routingHeaderPatternExpr)
.build();

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);
}
}
returnExprBuilder
.setExprReferenceExpr(routingHeadersBuilderVarNonDeclExpr)
.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(
VariableExpr requestVarExpr, List<String> fieldNames) {
MethodInvocationExpr.Builder requestFieldGetterExprBuilder =
MethodInvocationExpr.builder().setExprReferenceExpr(requestVarExpr);
Expr fieldValuesNotNullExpr = null;
for (int i = 0; i < fieldNames.size() - 1; 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 MethodInvocationExpr createRequestFieldGetterExpr(
VariableExpr requestVarExpr, String fieldName) {
MethodInvocationExpr.Builder requestFieldGetterExprBuilder =
MethodInvocationExpr.builder().setExprReferenceExpr(requestVarExpr);
List<String> 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.size(); i++) {
String currFieldName = descendantFields.get(i);
String bindingFieldMethodName =
String.format("get%s", JavaStyle.toUpperCamelCase(currFieldName));
requestFieldGetterExprBuilder =
requestFieldGetterExprBuilder.setMethodName(bindingFieldMethodName);
if (i < descendantFields.size() - 1) {
requestFieldGetterExprBuilder =
MethodInvocationExpr.builder()
.setExprReferenceExpr(requestFieldGetterExprBuilder.build());
}
}
return requestFieldGetterExprBuilder.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -210,45 +210,6 @@ protected List<MethodDefinition> createOperationsStubGetterMethod(
return super.createOperationsStubGetterMethod(service, operationsStubVarExpr);
}

@Override
protected Expr createTransportSettingsInitExpr(
Method method,
VariableExpr transportSettingsVarExpr,
VariableExpr methodDescriptorVarExpr,
List<Statement> classStatements) {
MethodInvocationExpr callSettingsBuilderExpr =
MethodInvocationExpr.builder()
.setStaticReferenceType(
FIXED_REST_TYPESTORE.get(HttpJsonCallSettings.class.getSimpleName()))
.setGenerics(transportSettingsVarExpr.type().reference().generics())
.setMethodName("newBuilder")
.build();
callSettingsBuilderExpr =
MethodInvocationExpr.builder()
.setExprReferenceExpr(callSettingsBuilderExpr)
.setMethodName("setMethodDescriptor")
.setArguments(Arrays.asList(methodDescriptorVarExpr))
.build();

callSettingsBuilderExpr =
MethodInvocationExpr.builder()
.setExprReferenceExpr(callSettingsBuilderExpr)
.setMethodName("setTypeRegistry")
.setArguments(Arrays.asList(TYPE_REGISTRY_VAR_EXPR))
.build();

callSettingsBuilderExpr =
MethodInvocationExpr.builder()
.setExprReferenceExpr(callSettingsBuilderExpr)
.setMethodName("build")
.setReturnType(transportSettingsVarExpr.type())
.build();
return AssignmentExpr.builder()
.setVariableExpr(transportSettingsVarExpr.toBuilder().setIsDecl(true).build())
.setValueExpr(callSettingsBuilderExpr)
.build();
}

@Override
protected List<AnnotationNode> createClassAnnotations(Service service) {
List<AnnotationNode> annotations = super.createClassAnnotations(service);
Expand Down
Loading

0 comments on commit 003b993

Please sign in to comment.