Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: autopopulate fields in the request #2353

Merged
merged 42 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
6bbd9cf
feat: autopopulate fields in the request
alicejli Jan 10, 2024
7b4be22
revert showcase golden changes
alicejli Jan 10, 2024
d89d7dd
add AbstractTransportServiceStubClassComposerTest
alicejli Jan 10, 2024
5cb916d
add REST implementation
alicejli Jan 10, 2024
93b4ea0
Merge branch 'main' into addRequestMutator
alicejli Jan 16, 2024
6454a16
add GrpcDirectCallableTest
alicejli Jan 16, 2024
a9523b8
remove unnecessary null check
alicejli Jan 16, 2024
70cfc0f
add field info proto registry and more unit test cases
alicejli Jan 17, 2024
ca3da5d
add HttpJsonDirectCallableTest
alicejli Jan 18, 2024
42d1afa
refactor AbstractTransportServiceSTubClassComposer
alicejli Jan 18, 2024
c0519e8
Merge branch 'main' into addRequestMutator
alicejli Jan 19, 2024
de4ea1f
add more unit tests for Field
alicejli Jan 19, 2024
90c6f5d
feat: refactor request mutator expression
alicejli Jan 22, 2024
5c15e48
Merge branch 'main' into addRequestMutator
alicejli Jan 22, 2024
9cf613e
fix goldens
alicejli Jan 22, 2024
29d4c94
add showcase test for autopopulation
alicejli Jan 23, 2024
4a4fb8b
fix lint
alicejli Jan 23, 2024
268f4d4
change assertion in showcase test
alicejli Jan 23, 2024
cb26b8d
refactor for sonarcloud
alicejli Jan 23, 2024
f8712d4
sonarcloud fixes
alicejli Jan 23, 2024
6f91bd4
sonarcloud
alicejli Jan 23, 2024
3a5518a
sonarcloud fix
alicejli Jan 23, 2024
2c7ea7e
fix sonarcloud
alicejli Jan 23, 2024
06e956c
slight refactoring
alicejli Jan 25, 2024
4a3de4c
revert changes to directCallable and replace with retryable Callable
alicejli Jan 29, 2024
8c8ee78
Merge branch 'main' into addRequestMutator
alicejli Jan 29, 2024
596ec43
overload retrying Callables method
alicejli Jan 29, 2024
d88d6f0
change license header format
alicejli Jan 29, 2024
50adf96
fix license header
alicejli Jan 29, 2024
0ee16c5
fix showcase lint
alicejli Jan 29, 2024
2f807f1
add comment
alicejli Jan 29, 2024
b0fd69f
add showcase comment
alicejli Jan 29, 2024
520b362
add CallableTest and httpjson Retrying test
alicejli Jan 29, 2024
254b50f
fix lint
alicejli Jan 29, 2024
d9d2cb8
add RetryingCallable test and some refactoring
alicejli Jan 29, 2024
a29b81c
refactor GrpcCallableFactory
alicejli Jan 30, 2024
f3d19eb
remove extraneous from HttpJsonDirectCallableTest
alicejli Jan 30, 2024
6c6e7cb
remove FakeHttpJsonChannel
alicejli Jan 30, 2024
806876f
revert changes to tests for extra param
alicejli Jan 30, 2024
83c7a10
refactoring
alicejli Jan 31, 2024
c340e72
Update gapic-generator-java/src/test/java/com/google/api/generator/ga…
alicejli Jan 31, 2024
2d17751
Update gax-java/gax/src/test/java/com/google/api/gax/rpc/RetryingCall…
alicejli Jan 31, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import com.google.api.AnnotationsProto;
import com.google.api.ClientProto;
import com.google.api.FieldBehaviorProto;
import com.google.api.FieldInfoProto;
import com.google.api.ResourceProto;
import com.google.api.RoutingProto;
import com.google.cloud.ExtendedOperationsProto;
Expand All @@ -31,6 +32,7 @@ public static void registerAllExtensions(ExtensionRegistry extensionRegistry) {
ClientProto.registerAllExtensions(extensionRegistry);
ResourceProto.registerAllExtensions(extensionRegistry);
FieldBehaviorProto.registerAllExtensions(extensionRegistry);
FieldInfoProto.registerAllExtensions(extensionRegistry);
ExtendedOperationsProto.registerAllExtensions(extensionRegistry);
RoutingProto.registerAllExtensions(extensionRegistry);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
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.Reference;
import com.google.api.generator.engine.ast.ReferenceConstructorExpr;
import com.google.api.generator.engine.ast.RelationalOperationExpr;
import com.google.api.generator.engine.ast.ScopeNode;
Expand All @@ -58,6 +59,7 @@
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.PackageChecker;
import com.google.api.generator.gapic.model.Field;
import com.google.api.generator.gapic.model.GapicClass;
import com.google.api.generator.gapic.model.GapicClass.Kind;
import com.google.api.generator.gapic.model.GapicContext;
Expand All @@ -70,8 +72,10 @@
import com.google.api.generator.gapic.model.Transport;
import com.google.api.generator.gapic.utils.JavaStyle;
import com.google.api.pathtemplate.PathTemplate;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.longrunning.Operation;
Expand All @@ -84,9 +88,11 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import javax.annotation.Generated;
import javax.annotation.Nullable;
Expand Down Expand Up @@ -136,6 +142,7 @@ private static TypeStore createStaticTypes() {
OperationCallable.class,
OperationSnapshot.class,
RequestParamsExtractor.class,
UUID.class,
ServerStreamingCallable.class,
TimeUnit.class,
TypeRegistry.class,
Expand Down Expand Up @@ -277,7 +284,8 @@ protected Expr createTransportSettingsInitExpr(
Method method,
VariableExpr transportSettingsVarExpr,
VariableExpr methodDescriptorVarExpr,
List<Statement> classStatements) {
List<Statement> classStatements,
ImmutableMap<String, Message> messageTypes) {
MethodInvocationExpr callSettingsBuilderExpr =
MethodInvocationExpr.builder()
.setStaticReferenceType(getTransportContext().transportCallSettingsType())
Expand Down Expand Up @@ -318,6 +326,15 @@ protected Expr createTransportSettingsInitExpr(
.build();
}

if (method.hasAutoPopulatedFields() && shouldGenerateRequestMutator(method, messageTypes)) {
callSettingsBuilderExpr =
MethodInvocationExpr.builder()
.setExprReferenceExpr(callSettingsBuilderExpr)
.setMethodName("setRequestMutator")
.setArguments(createRequestMutatorClassInstance(method, messageTypes))
.build();
}

callSettingsBuilderExpr =
MethodInvocationExpr.builder()
.setExprReferenceExpr(callSettingsBuilderExpr)
Expand Down Expand Up @@ -760,7 +777,8 @@ protected List<MethodDefinition> createConstructorMethods(
javaStyleMethodNameToTransportSettingsVarExprs.get(
JavaStyle.toLowerCamelCase(m.name())),
protoMethodNameToDescriptorVarExprs.get(m.name()),
classStatements))
classStatements,
context.messages()))
.collect(Collectors.toList()));
secondCtorStatements.addAll(
secondCtorExprs.stream().map(ExprStatement::withExpr).collect(Collectors.toList()));
Expand Down Expand Up @@ -1233,12 +1251,200 @@ protected TypeNode getTransportOperationsStubType(Service service) {
return transportOpeationsStubType;
}

protected static LambdaExpr createRequestMutatorClassInstance(
Method method, ImmutableMap<String, Message> messageTypes) {
List<Statement> bodyStatements = new ArrayList<>();
VariableExpr requestVarExpr = createRequestVarExpr(method);

Reference requestBuilderRef =
VaporReference.builder()
.setEnclosingClassNames(method.inputType().reference().name())
.setName("Builder")
.setPakkage(method.inputType().reference().pakkage())
.build();

TypeNode requestBuilderType = TypeNode.withReference(requestBuilderRef);

VariableExpr requestBuilderVarExpr =
VariableExpr.builder()
.setVariable(
Variable.builder().setName("requestBuilder").setType(requestBuilderType).build())
.setIsDecl(true)
.build();

MethodInvocationExpr setRequestBuilderInvocationExpr =
MethodInvocationExpr.builder()
.setExprReferenceExpr(requestVarExpr)
.setMethodName("toBuilder")
.setReturnType(requestBuilderType)
.build();

Expr requestBuilderExpr =
AssignmentExpr.builder()
.setVariableExpr(requestBuilderVarExpr)
.setValueExpr(setRequestBuilderInvocationExpr)
.build();

bodyStatements.add(ExprStatement.withExpr(requestBuilderExpr));

VariableExpr returnBuilderVarExpr =
VariableExpr.builder()
.setVariable(
Variable.builder().setName("requestBuilder").setType(requestBuilderType).build())
.setIsDecl(false)
.build();

MethodInvocationExpr.Builder returnExpr =
MethodInvocationExpr.builder()
.setExprReferenceExpr(returnBuilderVarExpr)
.setMethodName("build");

createRequestMutatorBody(method, messageTypes, bodyStatements, returnBuilderVarExpr);

return LambdaExpr.builder()
.setArguments(requestVarExpr.toBuilder().setIsDecl(true).build())
.setBody(bodyStatements)
.setReturnExpr(returnExpr.build())
.build();
}

@VisibleForTesting
static List<Statement> createRequestMutatorBody(
Method method,
ImmutableMap<String, Message> messageTypes,
List<Statement> bodyStatements,
VariableExpr returnBuilderVarExpr) {

if (!shouldGenerateRequestMutator(method, messageTypes)) {
return bodyStatements;
}
alicejli marked this conversation as resolved.
Show resolved Hide resolved
Message methodRequestMessage = messageTypes.get(method.inputType().reference().fullName());
method.autoPopulatedFields().stream()
lqiu96 marked this conversation as resolved.
Show resolved Hide resolved
// Map each field name to its corresponding Field object, if present
.map(
fieldName ->
methodRequestMessage.fields().stream()
.filter(field -> field.name().equals(fieldName))
.findFirst())
alicejli marked this conversation as resolved.
Show resolved Hide resolved
.filter(Optional::isPresent) // Keep only the existing Fields
.map(Optional::get) // Extract the Field from the Optional
.filter(Field::canBeAutoPopulated) // Filter fields that can be autopopulated
.forEach(
matchedField -> {
// Create statements for each autopopulated Field
bodyStatements.add(
createAutoPopulatedRequestStatement(
method, matchedField.name(), returnBuilderVarExpr));
});
return bodyStatements;
}

@VisibleForTesting
static Statement createAutoPopulatedRequestStatement(
Method method, String fieldName, VariableExpr returnBuilderVarExpr) {

VariableExpr requestVarExpr = createRequestVarExpr(method);

// Expected expression: request.getRequestId()
MethodInvocationExpr getAutoPopulatedFieldInvocationExpr =
MethodInvocationExpr.builder()
.setExprReferenceExpr(requestVarExpr)
.setMethodName(String.format("get%s", JavaStyle.toUpperCamelCase(fieldName)))
.setReturnType(TypeNode.STRING)
.build();

VariableExpr stringsVar =
VariableExpr.withVariable(
Variable.builder()
.setType(TypeNode.withReference(ConcreteReference.withClazz(Strings.class)))
.setName("Strings")
.build());

// Expected expression: Strings.isNullOrEmpty(request.getRequestId())
MethodInvocationExpr isNullOrEmptyFieldInvocationExpr =
MethodInvocationExpr.builder()
.setExprReferenceExpr(stringsVar)
.setMethodName("isNullOrEmpty")
.setReturnType(TypeNode.BOOLEAN)
.setArguments(getAutoPopulatedFieldInvocationExpr)
.build();

// Note: Currently, autopopulation is only for UUID.
VariableExpr uuidVarExpr =
VariableExpr.withVariable(
Variable.builder()
.setType(
TypeNode.withReference(
ConcreteReference.builder().setClazz(UUID.class).build()))
.setName("UUID")
.build());

// Expected expression: UUID.randomUUID()
MethodInvocationExpr autoPopulatedFieldsArgsHelper =
MethodInvocationExpr.builder()
.setExprReferenceExpr(uuidVarExpr)
.setMethodName("randomUUID")
.setReturnType(
TypeNode.withReference(ConcreteReference.builder().setClazz(UUID.class).build()))
.build();

// Expected expression: UUID.randomUUID().toString()
MethodInvocationExpr autoPopulatedFieldsArgsToString =
MethodInvocationExpr.builder()
.setExprReferenceExpr(autoPopulatedFieldsArgsHelper)
.setMethodName("toString")
.setReturnType(TypeNode.STRING)
.build();

// Expected expression: requestBuilder().setField(UUID.randomUUID().toString())
MethodInvocationExpr setAutoPopulatedFieldInvocationExpr =
MethodInvocationExpr.builder()
.setArguments(autoPopulatedFieldsArgsToString)
.setExprReferenceExpr(returnBuilderVarExpr)
.setMethodName(String.format("set%s", JavaStyle.toUpperCamelCase(fieldName)))
.setReturnType(method.inputType())
.build();

return IfStatement.builder()
.setConditionExpr(isNullOrEmptyFieldInvocationExpr)
.setBody(Arrays.asList(ExprStatement.withExpr(setAutoPopulatedFieldInvocationExpr)))
.build();
}

/**
* The Request Mutator should only be generated if the field exists in the Message and is properly
* configured in the Message(see {@link Field#canBeAutoPopulated()})
*/
@VisibleForTesting
static Boolean shouldGenerateRequestMutator(
Method method, ImmutableMap<String, Message> messageTypes) {
if (method.inputType().reference() == null
|| method.inputType().reference().fullName() == null) {
return false;
}
String methodRequestName = method.inputType().reference().fullName();

Message methodRequestMessage = messageTypes.get(methodRequestName);
if (methodRequestMessage == null || methodRequestMessage.fields() == null) {
return false;
}
return method.autoPopulatedFields().stream().anyMatch(shouldAutoPopulate(methodRequestMessage));
}

/**
* The field has to exist in the Message and properly configured in the Message(see {@link
* Field#canBeAutoPopulated()})
*/
private static Predicate<String> shouldAutoPopulate(Message methodRequestMessage) {
return fieldName ->
methodRequestMessage.fields().stream()
.anyMatch(field -> field.name().equals(fieldName) && field.canBeAutoPopulated());
}

protected LambdaExpr createRequestParamsExtractorClassInstance(
Method method, List<Statement> classStatements) {
List<Statement> bodyStatements = new ArrayList<>();
VariableExpr requestVarExpr =
VariableExpr.withVariable(
Variable.builder().setType(method.inputType()).setName("request").build());
VariableExpr requestVarExpr = createRequestVarExpr(method);
TypeNode returnType =
TypeNode.withReference(
ConcreteReference.builder()
Expand Down Expand Up @@ -1499,4 +1705,9 @@ private MethodInvocationExpr createRequestFieldGetterExpr(
}
return requestFieldGetterExprBuilder.build();
}

private static VariableExpr createRequestVarExpr(Method method) {
return VariableExpr.withVariable(
Variable.builder().setType(method.inputType()).setName("request").build());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,17 @@ public boolean hasResourceReference() {
return type().equals(TypeNode.STRING) && resourceReference() != null;
}

// Check that the field format is of UUID, it is not annotated as required, and is of type String.
// Unless
// those three conditions are met, do not autopopulate the field.
// In the future, if additional formats are supported for autopopulation, this will need to be
// refactored to support those formats.
public boolean canBeAutoPopulated() {
return Format.UUID4.equals(fieldInfoFormat())
&& !isRequired()
&& TypeNode.STRING.equals(type());
}

@Override
public boolean equals(Object o) {
if (!(o instanceof Field)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1025,10 +1025,10 @@ private static Field parseField(
if (fieldOptions.hasExtension(FieldInfoProto.fieldInfo)) {
fieldInfoFormat = fieldOptions.getExtension(FieldInfoProto.fieldInfo).getFormat();
}
if (fieldOptions.getExtensionCount(FieldBehaviorProto.fieldBehavior) > 0) {
if (fieldOptions
.getExtension(FieldBehaviorProto.fieldBehavior)
.contains(FieldBehavior.REQUIRED)) ;
if (fieldOptions.getExtensionCount(FieldBehaviorProto.fieldBehavior) > 0
alicejli marked this conversation as resolved.
Show resolved Hide resolved
&& fieldOptions
.getExtension(FieldBehaviorProto.fieldBehavior)
.contains(FieldBehavior.REQUIRED)) {
isRequired = true;
}

Expand Down
Loading
Loading