Skip to content

Commit b28235a

Browse files
alicejliblakeli0
andauthored
feat: autopopulate fields in the request (#2353)
* feat: autopopulate fields in the request * revert showcase golden changes * add AbstractTransportServiceStubClassComposerTest * add REST implementation * add GrpcDirectCallableTest * remove unnecessary null check * add field info proto registry and more unit test cases * add HttpJsonDirectCallableTest * refactor AbstractTransportServiceSTubClassComposer * add more unit tests for Field * feat: refactor request mutator expression * fix goldens * add showcase test for autopopulation * fix lint * change assertion in showcase test * refactor for sonarcloud * sonarcloud fixes * sonarcloud * sonarcloud fix * fix sonarcloud * slight refactoring * revert changes to directCallable and replace with retryable Callable * overload retrying Callables method * change license header format * fix license header * fix showcase lint * add comment * add showcase comment * add CallableTest and httpjson Retrying test * fix lint * add RetryingCallable test and some refactoring * refactor GrpcCallableFactory * remove extraneous from HttpJsonDirectCallableTest * remove FakeHttpJsonChannel * revert changes to tests for extra param * refactoring * Update gapic-generator-java/src/test/java/com/google/api/generator/gapic/model/FieldTest.java Co-authored-by: Blake Li <[email protected]> * Update gax-java/gax/src/test/java/com/google/api/gax/rpc/RetryingCallableTest.java Co-authored-by: Blake Li <[email protected]> --------- Co-authored-by: Blake Li <[email protected]>
1 parent 9916540 commit b28235a

File tree

40 files changed

+1928
-68
lines changed

40 files changed

+1928
-68
lines changed

gapic-generator-java/src/main/java/com/google/api/generator/ProtoRegistry.java

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import com.google.api.AnnotationsProto;
1818
import com.google.api.ClientProto;
1919
import com.google.api.FieldBehaviorProto;
20+
import com.google.api.FieldInfoProto;
2021
import com.google.api.ResourceProto;
2122
import com.google.api.RoutingProto;
2223
import com.google.cloud.ExtendedOperationsProto;
@@ -31,6 +32,7 @@ public static void registerAllExtensions(ExtensionRegistry extensionRegistry) {
3132
ClientProto.registerAllExtensions(extensionRegistry);
3233
ResourceProto.registerAllExtensions(extensionRegistry);
3334
FieldBehaviorProto.registerAllExtensions(extensionRegistry);
35+
FieldInfoProto.registerAllExtensions(extensionRegistry);
3436
ExtendedOperationsProto.registerAllExtensions(extensionRegistry);
3537
RoutingProto.registerAllExtensions(extensionRegistry);
3638
}

gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractTransportServiceStubClassComposer.java

+213-5
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import com.google.api.generator.engine.ast.MethodDefinition;
4343
import com.google.api.generator.engine.ast.MethodInvocationExpr;
4444
import com.google.api.generator.engine.ast.NewObjectExpr;
45+
import com.google.api.generator.engine.ast.Reference;
4546
import com.google.api.generator.engine.ast.ReferenceConstructorExpr;
4647
import com.google.api.generator.engine.ast.RelationalOperationExpr;
4748
import com.google.api.generator.engine.ast.ScopeNode;
@@ -58,6 +59,7 @@
5859
import com.google.api.generator.gapic.composer.comment.StubCommentComposer;
5960
import com.google.api.generator.gapic.composer.store.TypeStore;
6061
import com.google.api.generator.gapic.composer.utils.PackageChecker;
62+
import com.google.api.generator.gapic.model.Field;
6163
import com.google.api.generator.gapic.model.GapicClass;
6264
import com.google.api.generator.gapic.model.GapicClass.Kind;
6365
import com.google.api.generator.gapic.model.GapicContext;
@@ -70,8 +72,10 @@
7072
import com.google.api.generator.gapic.model.Transport;
7173
import com.google.api.generator.gapic.utils.JavaStyle;
7274
import com.google.api.pathtemplate.PathTemplate;
75+
import com.google.common.annotations.VisibleForTesting;
7376
import com.google.common.base.Preconditions;
7477
import com.google.common.base.Splitter;
78+
import com.google.common.base.Strings;
7579
import com.google.common.collect.ImmutableList;
7680
import com.google.common.collect.ImmutableMap;
7781
import com.google.longrunning.Operation;
@@ -84,9 +88,11 @@
8488
import java.util.List;
8589
import java.util.Map;
8690
import java.util.Optional;
91+
import java.util.UUID;
8792
import java.util.concurrent.TimeUnit;
8893
import java.util.function.BiFunction;
8994
import java.util.function.Function;
95+
import java.util.function.Predicate;
9096
import java.util.stream.Collectors;
9197
import javax.annotation.Generated;
9298
import javax.annotation.Nullable;
@@ -136,6 +142,7 @@ private static TypeStore createStaticTypes() {
136142
OperationCallable.class,
137143
OperationSnapshot.class,
138144
RequestParamsExtractor.class,
145+
UUID.class,
139146
ServerStreamingCallable.class,
140147
TimeUnit.class,
141148
TypeRegistry.class,
@@ -277,7 +284,8 @@ protected Expr createTransportSettingsInitExpr(
277284
Method method,
278285
VariableExpr transportSettingsVarExpr,
279286
VariableExpr methodDescriptorVarExpr,
280-
List<Statement> classStatements) {
287+
List<Statement> classStatements,
288+
ImmutableMap<String, Message> messageTypes) {
281289
MethodInvocationExpr callSettingsBuilderExpr =
282290
MethodInvocationExpr.builder()
283291
.setStaticReferenceType(getTransportContext().transportCallSettingsType())
@@ -318,6 +326,15 @@ protected Expr createTransportSettingsInitExpr(
318326
.build();
319327
}
320328

329+
if (method.hasAutoPopulatedFields() && shouldGenerateRequestMutator(method, messageTypes)) {
330+
callSettingsBuilderExpr =
331+
MethodInvocationExpr.builder()
332+
.setExprReferenceExpr(callSettingsBuilderExpr)
333+
.setMethodName("setRequestMutator")
334+
.setArguments(createRequestMutatorClassInstance(method, messageTypes))
335+
.build();
336+
}
337+
321338
callSettingsBuilderExpr =
322339
MethodInvocationExpr.builder()
323340
.setExprReferenceExpr(callSettingsBuilderExpr)
@@ -760,7 +777,8 @@ protected List<MethodDefinition> createConstructorMethods(
760777
javaStyleMethodNameToTransportSettingsVarExprs.get(
761778
JavaStyle.toLowerCamelCase(m.name())),
762779
protoMethodNameToDescriptorVarExprs.get(m.name()),
763-
classStatements))
780+
classStatements,
781+
context.messages()))
764782
.collect(Collectors.toList()));
765783
secondCtorStatements.addAll(
766784
secondCtorExprs.stream().map(ExprStatement::withExpr).collect(Collectors.toList()));
@@ -1233,12 +1251,197 @@ protected TypeNode getTransportOperationsStubType(Service service) {
12331251
return transportOpeationsStubType;
12341252
}
12351253

1254+
protected static LambdaExpr createRequestMutatorClassInstance(
1255+
Method method, ImmutableMap<String, Message> messageTypes) {
1256+
List<Statement> bodyStatements = new ArrayList<>();
1257+
VariableExpr requestVarExpr = createRequestVarExpr(method);
1258+
1259+
Reference requestBuilderRef =
1260+
VaporReference.builder()
1261+
.setEnclosingClassNames(method.inputType().reference().name())
1262+
.setName("Builder")
1263+
.setPakkage(method.inputType().reference().pakkage())
1264+
.build();
1265+
1266+
TypeNode requestBuilderType = TypeNode.withReference(requestBuilderRef);
1267+
1268+
VariableExpr requestBuilderVarExpr =
1269+
VariableExpr.builder()
1270+
.setVariable(
1271+
Variable.builder().setName("requestBuilder").setType(requestBuilderType).build())
1272+
.setIsDecl(true)
1273+
.build();
1274+
1275+
MethodInvocationExpr setRequestBuilderInvocationExpr =
1276+
MethodInvocationExpr.builder()
1277+
.setExprReferenceExpr(requestVarExpr)
1278+
.setMethodName("toBuilder")
1279+
.setReturnType(requestBuilderType)
1280+
.build();
1281+
1282+
Expr requestBuilderExpr =
1283+
AssignmentExpr.builder()
1284+
.setVariableExpr(requestBuilderVarExpr)
1285+
.setValueExpr(setRequestBuilderInvocationExpr)
1286+
.build();
1287+
1288+
bodyStatements.add(ExprStatement.withExpr(requestBuilderExpr));
1289+
1290+
VariableExpr returnBuilderVarExpr =
1291+
VariableExpr.builder()
1292+
.setVariable(
1293+
Variable.builder().setName("requestBuilder").setType(requestBuilderType).build())
1294+
.setIsDecl(false)
1295+
.build();
1296+
1297+
MethodInvocationExpr.Builder returnExpr =
1298+
MethodInvocationExpr.builder()
1299+
.setExprReferenceExpr(returnBuilderVarExpr)
1300+
.setMethodName("build");
1301+
1302+
createRequestMutatorBody(method, messageTypes, bodyStatements, returnBuilderVarExpr);
1303+
1304+
return LambdaExpr.builder()
1305+
.setArguments(requestVarExpr.toBuilder().setIsDecl(true).build())
1306+
.setBody(bodyStatements)
1307+
.setReturnExpr(returnExpr.build())
1308+
.build();
1309+
}
1310+
1311+
@VisibleForTesting
1312+
static List<Statement> createRequestMutatorBody(
1313+
Method method,
1314+
ImmutableMap<String, Message> messageTypes,
1315+
List<Statement> bodyStatements,
1316+
VariableExpr returnBuilderVarExpr) {
1317+
1318+
Message methodRequestMessage = messageTypes.get(method.inputType().reference().fullName());
1319+
method.autoPopulatedFields().stream()
1320+
// Map each field name to its corresponding Field object, if present
1321+
.map(
1322+
fieldName ->
1323+
methodRequestMessage.fields().stream()
1324+
.filter(field -> field.name().equals(fieldName))
1325+
.findFirst())
1326+
.filter(Optional::isPresent) // Keep only the existing Fields
1327+
.map(Optional::get) // Extract the Field from the Optional
1328+
.filter(Field::canBeAutoPopulated) // Filter fields that can be autopopulated
1329+
.forEach(
1330+
matchedField -> {
1331+
// Create statements for each autopopulated Field
1332+
bodyStatements.add(
1333+
createAutoPopulatedRequestStatement(
1334+
method, matchedField.name(), returnBuilderVarExpr));
1335+
});
1336+
return bodyStatements;
1337+
}
1338+
1339+
@VisibleForTesting
1340+
static Statement createAutoPopulatedRequestStatement(
1341+
Method method, String fieldName, VariableExpr returnBuilderVarExpr) {
1342+
1343+
VariableExpr requestVarExpr = createRequestVarExpr(method);
1344+
1345+
// Expected expression: request.getRequestId()
1346+
MethodInvocationExpr getAutoPopulatedFieldInvocationExpr =
1347+
MethodInvocationExpr.builder()
1348+
.setExprReferenceExpr(requestVarExpr)
1349+
.setMethodName(String.format("get%s", JavaStyle.toUpperCamelCase(fieldName)))
1350+
.setReturnType(TypeNode.STRING)
1351+
.build();
1352+
1353+
VariableExpr stringsVar =
1354+
VariableExpr.withVariable(
1355+
Variable.builder()
1356+
.setType(TypeNode.withReference(ConcreteReference.withClazz(Strings.class)))
1357+
.setName("Strings")
1358+
.build());
1359+
1360+
// Expected expression: Strings.isNullOrEmpty(request.getRequestId())
1361+
MethodInvocationExpr isNullOrEmptyFieldInvocationExpr =
1362+
MethodInvocationExpr.builder()
1363+
.setExprReferenceExpr(stringsVar)
1364+
.setMethodName("isNullOrEmpty")
1365+
.setReturnType(TypeNode.BOOLEAN)
1366+
.setArguments(getAutoPopulatedFieldInvocationExpr)
1367+
.build();
1368+
1369+
// Note: Currently, autopopulation is only for UUID.
1370+
VariableExpr uuidVarExpr =
1371+
VariableExpr.withVariable(
1372+
Variable.builder()
1373+
.setType(
1374+
TypeNode.withReference(
1375+
ConcreteReference.builder().setClazz(UUID.class).build()))
1376+
.setName("UUID")
1377+
.build());
1378+
1379+
// Expected expression: UUID.randomUUID()
1380+
MethodInvocationExpr autoPopulatedFieldsArgsHelper =
1381+
MethodInvocationExpr.builder()
1382+
.setExprReferenceExpr(uuidVarExpr)
1383+
.setMethodName("randomUUID")
1384+
.setReturnType(
1385+
TypeNode.withReference(ConcreteReference.builder().setClazz(UUID.class).build()))
1386+
.build();
1387+
1388+
// Expected expression: UUID.randomUUID().toString()
1389+
MethodInvocationExpr autoPopulatedFieldsArgsToString =
1390+
MethodInvocationExpr.builder()
1391+
.setExprReferenceExpr(autoPopulatedFieldsArgsHelper)
1392+
.setMethodName("toString")
1393+
.setReturnType(TypeNode.STRING)
1394+
.build();
1395+
1396+
// Expected expression: requestBuilder().setField(UUID.randomUUID().toString())
1397+
MethodInvocationExpr setAutoPopulatedFieldInvocationExpr =
1398+
MethodInvocationExpr.builder()
1399+
.setArguments(autoPopulatedFieldsArgsToString)
1400+
.setExprReferenceExpr(returnBuilderVarExpr)
1401+
.setMethodName(String.format("set%s", JavaStyle.toUpperCamelCase(fieldName)))
1402+
.setReturnType(method.inputType())
1403+
.build();
1404+
1405+
return IfStatement.builder()
1406+
.setConditionExpr(isNullOrEmptyFieldInvocationExpr)
1407+
.setBody(Arrays.asList(ExprStatement.withExpr(setAutoPopulatedFieldInvocationExpr)))
1408+
.build();
1409+
}
1410+
1411+
/**
1412+
* The Request Mutator should only be generated if the field exists in the Message and is properly
1413+
* configured in the Message(see {@link Field#canBeAutoPopulated()})
1414+
*/
1415+
@VisibleForTesting
1416+
static Boolean shouldGenerateRequestMutator(
1417+
Method method, ImmutableMap<String, Message> messageTypes) {
1418+
if (method.inputType().reference() == null
1419+
|| method.inputType().reference().fullName() == null) {
1420+
return false;
1421+
}
1422+
String methodRequestName = method.inputType().reference().fullName();
1423+
1424+
Message methodRequestMessage = messageTypes.get(methodRequestName);
1425+
if (methodRequestMessage == null || methodRequestMessage.fields() == null) {
1426+
return false;
1427+
}
1428+
return method.autoPopulatedFields().stream().anyMatch(shouldAutoPopulate(methodRequestMessage));
1429+
}
1430+
1431+
/**
1432+
* The field has to exist in the Message and properly configured in the Message(see {@link
1433+
* Field#canBeAutoPopulated()})
1434+
*/
1435+
private static Predicate<String> shouldAutoPopulate(Message methodRequestMessage) {
1436+
return fieldName ->
1437+
methodRequestMessage.fields().stream()
1438+
.anyMatch(field -> field.name().equals(fieldName) && field.canBeAutoPopulated());
1439+
}
1440+
12361441
protected LambdaExpr createRequestParamsExtractorClassInstance(
12371442
Method method, List<Statement> classStatements) {
12381443
List<Statement> bodyStatements = new ArrayList<>();
1239-
VariableExpr requestVarExpr =
1240-
VariableExpr.withVariable(
1241-
Variable.builder().setType(method.inputType()).setName("request").build());
1444+
VariableExpr requestVarExpr = createRequestVarExpr(method);
12421445
TypeNode returnType =
12431446
TypeNode.withReference(
12441447
ConcreteReference.builder()
@@ -1499,4 +1702,9 @@ private MethodInvocationExpr createRequestFieldGetterExpr(
14991702
}
15001703
return requestFieldGetterExprBuilder.build();
15011704
}
1705+
1706+
private static VariableExpr createRequestVarExpr(Method method) {
1707+
return VariableExpr.withVariable(
1708+
Variable.builder().setType(method.inputType()).setName("request").build());
1709+
}
15021710
}

gapic-generator-java/src/main/java/com/google/api/generator/gapic/model/Field.java

+11
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,17 @@ public boolean hasResourceReference() {
7272
return type().equals(TypeNode.STRING) && resourceReference() != null;
7373
}
7474

75+
// Check that the field format is of UUID, it is not annotated as required, and is of type String.
76+
// Unless
77+
// those three conditions are met, do not autopopulate the field.
78+
// In the future, if additional formats are supported for autopopulation, this will need to be
79+
// refactored to support those formats.
80+
public boolean canBeAutoPopulated() {
81+
return Format.UUID4.equals(fieldInfoFormat())
82+
&& !isRequired()
83+
&& TypeNode.STRING.equals(type());
84+
}
85+
7586
@Override
7687
public boolean equals(Object o) {
7788
if (!(o instanceof Field)) {

gapic-generator-java/src/main/java/com/google/api/generator/gapic/protoparser/Parser.java

+7-4
Original file line numberDiff line numberDiff line change
@@ -1025,10 +1025,13 @@ private static Field parseField(
10251025
if (fieldOptions.hasExtension(FieldInfoProto.fieldInfo)) {
10261026
fieldInfoFormat = fieldOptions.getExtension(FieldInfoProto.fieldInfo).getFormat();
10271027
}
1028-
if (fieldOptions.getExtensionCount(FieldBehaviorProto.fieldBehavior) > 0) {
1029-
if (fieldOptions
1030-
.getExtension(FieldBehaviorProto.fieldBehavior)
1031-
.contains(FieldBehavior.REQUIRED)) ;
1028+
1029+
// Cannot directly check fieldOptions.hasExtension(FieldBehaviorProto.fieldBehavior) because the
1030+
// default is null
1031+
if (fieldOptions.getExtensionCount(FieldBehaviorProto.fieldBehavior) > 0
1032+
&& fieldOptions
1033+
.getExtension(FieldBehaviorProto.fieldBehavior)
1034+
.contains(FieldBehavior.REQUIRED)) {
10321035
isRequired = true;
10331036
}
10341037

0 commit comments

Comments
 (0)