Skip to content

Commit c820361

Browse files
authored
feat: Implement field presence support for DIREGAPIC (#774)
1 parent 68de8da commit c820361

File tree

10 files changed

+187
-80
lines changed

10 files changed

+187
-80
lines changed

src/main/java/com/google/api/generator/gapic/composer/grpc/GrpcServiceStubClassComposer.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import com.google.api.generator.engine.ast.VariableExpr;
3535
import com.google.api.generator.gapic.composer.common.AbstractServiceStubClassComposer;
3636
import com.google.api.generator.gapic.composer.store.TypeStore;
37+
import com.google.api.generator.gapic.model.HttpBindings.HttpBinding;
3738
import com.google.api.generator.gapic.model.Method;
3839
import com.google.api.generator.gapic.model.Service;
3940
import com.google.api.generator.gapic.utils.JavaStyle;
@@ -288,11 +289,11 @@ private AnonymousClassExpr createRequestParamsExtractorAnonClass(Method method)
288289
VariableExpr.withVariable(
289290
Variable.builder().setType(method.inputType()).setName("request").build());
290291

291-
for (String httpBindingFieldName : method.httpBindings().pathParameters()) {
292+
for (HttpBinding httpBindingFieldBinding : method.httpBindings().pathParameters()) {
292293
// Handle foo.bar cases by descending into the subfields.
293294
MethodInvocationExpr.Builder requestFieldGetterExprBuilder =
294295
MethodInvocationExpr.builder().setExprReferenceExpr(requestVarExpr);
295-
String[] descendantFields = httpBindingFieldName.split("\\.");
296+
String[] descendantFields = httpBindingFieldBinding.name().split("\\.");
296297
for (int i = 0; i < descendantFields.length; i++) {
297298
String currFieldName = descendantFields[i];
298299
String bindingFieldMethodName =
@@ -319,7 +320,7 @@ private AnonymousClassExpr createRequestParamsExtractorAnonClass(Method method)
319320
.setExprReferenceExpr(paramsVarExpr)
320321
.setMethodName("put")
321322
.setArguments(
322-
ValueExpr.withValue(StringObjectValue.withValue(httpBindingFieldName)),
323+
ValueExpr.withValue(StringObjectValue.withValue(httpBindingFieldBinding.name())),
323324
valueOfExpr)
324325
.build();
325326
bodyExprs.add(paramsPutExpr);

src/main/java/com/google/api/generator/gapic/composer/rest/HttpJsonServiceStubClassComposer.java

+36-9
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import com.google.api.generator.engine.ast.EnumRefExpr;
3131
import com.google.api.generator.engine.ast.Expr;
3232
import com.google.api.generator.engine.ast.ExprStatement;
33+
import com.google.api.generator.engine.ast.IfStatement;
3334
import com.google.api.generator.engine.ast.MethodDefinition;
3435
import com.google.api.generator.engine.ast.MethodInvocationExpr;
3536
import com.google.api.generator.engine.ast.NewObjectExpr;
@@ -42,6 +43,7 @@
4243
import com.google.api.generator.engine.ast.VariableExpr;
4344
import com.google.api.generator.gapic.composer.common.AbstractServiceStubClassComposer;
4445
import com.google.api.generator.gapic.composer.store.TypeStore;
46+
import com.google.api.generator.gapic.model.HttpBindings.HttpBinding;
4547
import com.google.api.generator.gapic.model.Method;
4648
import com.google.api.generator.gapic.model.Service;
4749
import com.google.api.generator.gapic.utils.JavaStyle;
@@ -357,9 +359,9 @@ private List<Expr> setResponseParserExpr(Method protoMethod) {
357359
private Expr createFieldsExtractorAnonClass(
358360
Method method,
359361
TypeNode extractorReturnType,
360-
Set<String> httpBindingFieldNames,
362+
Set<HttpBinding> httpBindingFieldNames,
361363
String serializerMethodName) {
362-
List<Expr> bodyExprs = new ArrayList<>();
364+
List<Statement> bodyStatements = new ArrayList<>();
363365

364366
Expr returnExpr = null;
365367
VariableExpr fieldsVarExpr = null;
@@ -389,7 +391,7 @@ private Expr createFieldsExtractorAnonClass(
389391
.build())
390392
.build();
391393

392-
bodyExprs.add(fieldsAssignExpr);
394+
bodyStatements.add(ExprStatement.withExpr(fieldsAssignExpr));
393395
returnExpr = fieldsVarExpr;
394396

395397
TypeNode serializerVarType =
@@ -417,40 +419,57 @@ private Expr createFieldsExtractorAnonClass(
417419

418420
serializerExpr = serializerVarExpr;
419421

420-
bodyExprs.add(serializerAssignExpr);
422+
bodyStatements.add(ExprStatement.withExpr(serializerAssignExpr));
421423
}
422424

423425
VariableExpr requestVarExpr =
424426
VariableExpr.withVariable(
425427
Variable.builder().setType(method.inputType()).setName("request").build());
426428

427-
for (String httpBindingFieldName : httpBindingFieldNames) {
429+
for (HttpBinding httpBindingFieldName : httpBindingFieldNames) {
428430
// Handle foo.bar cases by descending into the subfields.
429431
MethodInvocationExpr.Builder requestFieldGetterExprBuilder =
430432
MethodInvocationExpr.builder().setExprReferenceExpr(requestVarExpr);
431-
String[] descendantFields = httpBindingFieldName.split("\\.");
433+
MethodInvocationExpr.Builder requestFieldHasExprBuilder =
434+
MethodInvocationExpr.builder().setExprReferenceExpr(requestVarExpr);
435+
String[] descendantFields = httpBindingFieldName.name().split("\\.");
432436
for (int i = 0; i < descendantFields.length; i++) {
433437
String currFieldName = descendantFields[i];
434438
String bindingFieldMethodName =
435439
String.format("get%s", JavaStyle.toUpperCamelCase(currFieldName));
436440
requestFieldGetterExprBuilder =
437441
requestFieldGetterExprBuilder.setMethodName(bindingFieldMethodName);
442+
443+
String bindingFieldHasMethodName =
444+
(i < descendantFields.length - 1)
445+
? bindingFieldMethodName
446+
: String.format("has%s", JavaStyle.toUpperCamelCase(currFieldName));
447+
requestFieldHasExprBuilder =
448+
requestFieldHasExprBuilder
449+
.setMethodName(bindingFieldHasMethodName)
450+
.setReturnType(TypeNode.BOOLEAN);
451+
438452
if (i < descendantFields.length - 1) {
439453
requestFieldGetterExprBuilder =
440454
MethodInvocationExpr.builder()
441455
.setExprReferenceExpr(requestFieldGetterExprBuilder.build());
456+
requestFieldHasExprBuilder =
457+
MethodInvocationExpr.builder()
458+
.setExprReferenceExpr(requestFieldHasExprBuilder.build());
442459
}
443460
}
444461

445462
MethodInvocationExpr requestBuilderExpr = requestFieldGetterExprBuilder.build();
463+
MethodInvocationExpr requestHasExpr = requestFieldHasExprBuilder.build();
446464

447465
ImmutableList.Builder<Expr> paramsPutArgs = ImmutableList.builder();
448466
if (fieldsVarExpr != null) {
449467
paramsPutArgs.add(fieldsVarExpr);
450468
}
451469
paramsPutArgs.add(
452470
ValueExpr.withValue(
453-
StringObjectValue.withValue(JavaStyle.toLowerCamelCase(httpBindingFieldName))));
471+
StringObjectValue.withValue(
472+
JavaStyle.toLowerCamelCase(httpBindingFieldName.name()))));
454473
paramsPutArgs.add(requestBuilderExpr);
455474

456475
Expr paramsPutExpr =
@@ -464,7 +483,15 @@ private Expr createFieldsExtractorAnonClass(
464483
if (fieldsVarExpr == null) {
465484
returnExpr = paramsPutExpr;
466485
} else {
467-
bodyExprs.add(paramsPutExpr);
486+
if (httpBindingFieldName.isOptional()) {
487+
bodyStatements.add(
488+
IfStatement.builder()
489+
.setConditionExpr(requestHasExpr)
490+
.setBody(Arrays.asList(ExprStatement.withExpr(paramsPutExpr)))
491+
.build());
492+
} else {
493+
bodyStatements.add(ExprStatement.withExpr(paramsPutExpr));
494+
}
468495
}
469496
}
470497

@@ -475,7 +502,7 @@ private Expr createFieldsExtractorAnonClass(
475502
.setReturnType(extractorReturnType)
476503
.setName("extract")
477504
.setArguments(requestVarExpr.toBuilder().setIsDecl(true).build())
478-
.setBody(bodyExprs.stream().map(ExprStatement::withExpr).collect(Collectors.toList()))
505+
.setBody(bodyStatements)
479506
.setReturnExpr(returnExpr)
480507
.build();
481508

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

+8-1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ public abstract class Field {
3535

3636
public abstract boolean isContainedInOneof();
3737

38+
public abstract boolean isProto3Optional();
39+
3840
@Nullable
3941
public abstract ResourceReference resourceReference();
4042

@@ -63,6 +65,7 @@ && isEnum() == other.isEnum()
6365
&& isRepeated() == other.isRepeated()
6466
&& isMap() == other.isMap()
6567
&& isContainedInOneof() == other.isContainedInOneof()
68+
&& isProto3Optional() == other.isProto3Optional()
6669
&& Objects.equals(resourceReference(), other.resourceReference())
6770
&& Objects.equals(description(), other.description());
6871
}
@@ -76,6 +79,7 @@ public int hashCode() {
7679
+ (isRepeated() ? 1 : 0) * 31
7780
+ (isMap() ? 1 : 0) * 37
7881
+ (isContainedInOneof() ? 1 : 0) * 41
82+
+ (isProto3Optional() ? 1 : 0) * 43
7983
+ (resourceReference() == null ? 0 : resourceReference().hashCode())
8084
+ (description() == null ? 0 : description().hashCode());
8185
}
@@ -88,7 +92,8 @@ public static Builder builder() {
8892
.setIsEnum(false)
8993
.setIsRepeated(false)
9094
.setIsMap(false)
91-
.setIsContainedInOneof(false);
95+
.setIsContainedInOneof(false)
96+
.setIsProto3Optional(false);
9297
}
9398

9499
@AutoValue.Builder
@@ -107,6 +112,8 @@ public abstract static class Builder {
107112

108113
public abstract Builder setIsContainedInOneof(boolean isContainedInOneof);
109114

115+
public abstract Builder setIsProto3Optional(boolean isProto3Optional);
116+
110117
public abstract Builder setResourceReference(ResourceReference resourceReference);
111118

112119
public abstract Builder setDescription(String description);

src/main/java/com/google/api/generator/gapic/model/HttpBindings.java

+26-8
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,33 @@ public enum HttpVerb {
2929
PATCH,
3030
}
3131

32+
@AutoValue
33+
public abstract static class HttpBinding implements Comparable<HttpBinding> {
34+
public abstract String name();
35+
36+
public abstract boolean isOptional();
37+
38+
public static HttpBinding create(String name, boolean isOptional) {
39+
return new AutoValue_HttpBindings_HttpBinding(name, isOptional);
40+
}
41+
42+
// Do not forget to keep it in sync with equals() implementation.
43+
@Override
44+
public int compareTo(HttpBinding o) {
45+
int res = name().compareTo(o.name());
46+
return res == 0 ? Boolean.compare(isOptional(), o.isOptional()) : res;
47+
}
48+
}
49+
3250
public abstract HttpVerb httpVerb();
3351

3452
public abstract String pattern();
3553

36-
public abstract Set<String> pathParameters();
54+
public abstract Set<HttpBinding> pathParameters();
3755

38-
public abstract Set<String> queryParameters();
56+
public abstract Set<HttpBinding> queryParameters();
3957

40-
public abstract Set<String> bodyParameters();
58+
public abstract Set<HttpBinding> bodyParameters();
4159

4260
public static HttpBindings.Builder builder() {
4361
return new AutoValue_HttpBindings.Builder()
@@ -53,10 +71,10 @@ public static HttpBindings.Builder builder() {
5371
// in .java file: "/global/instanceTemplates/{instanceTemplate=*}"
5472
public String patternLowerCamel() {
5573
String lowerCamelPattern = pattern();
56-
for (String pathParam : pathParameters()) {
74+
for (HttpBinding pathParam : pathParameters()) {
5775
lowerCamelPattern =
5876
lowerCamelPattern.replaceAll(
59-
"\\{" + pathParam, "{" + JavaStyle.toLowerCamelCase(pathParam));
77+
"\\{" + pathParam.name(), "{" + JavaStyle.toLowerCamelCase(pathParam.name()));
6078
}
6179
return lowerCamelPattern;
6280
}
@@ -69,11 +87,11 @@ public abstract static class Builder {
6987

7088
abstract String pattern();
7189

72-
public abstract HttpBindings.Builder setPathParameters(Set<String> pathParameters);
90+
public abstract HttpBindings.Builder setPathParameters(Set<HttpBinding> pathParameters);
7391

74-
public abstract HttpBindings.Builder setQueryParameters(Set<String> queryParameters);
92+
public abstract HttpBindings.Builder setQueryParameters(Set<HttpBinding> queryParameters);
7593

76-
public abstract HttpBindings.Builder setBodyParameters(Set<String> bodyParameters);
94+
public abstract HttpBindings.Builder setBodyParameters(Set<HttpBinding> bodyParameters);
7795

7896
public abstract HttpBindings autoBuild();
7997

src/main/java/com/google/api/generator/gapic/protoparser/HttpRuleParser.java

+57-40
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import com.google.api.HttpRule.PatternCase;
2020
import com.google.api.generator.gapic.model.Field;
2121
import com.google.api.generator.gapic.model.HttpBindings;
22+
import com.google.api.generator.gapic.model.HttpBindings.HttpBinding;
2223
import com.google.api.generator.gapic.model.Message;
2324
import com.google.api.pathtemplate.PathTemplate;
2425
import com.google.common.base.Preconditions;
@@ -70,63 +71,79 @@ private static HttpBindings parseHttpRuleHelper(
7071
}
7172
}
7273

73-
Set<String> bindings = bindingsBuilder.build();
74-
75-
// Binding validation.
76-
for (String binding : bindings) {
77-
// Handle foo.bar cases by descending into the subfields.
78-
String[] descendantBindings = binding.split("\\.");
79-
Optional<Message> containingMessageOpt = inputMessageOpt;
80-
for (int i = 0; i < descendantBindings.length; i++) {
81-
String subField = descendantBindings[i];
82-
if (!containingMessageOpt.isPresent()) {
83-
continue;
84-
}
85-
86-
if (i < descendantBindings.length - 1) {
87-
Field field = containingMessageOpt.get().fieldMap().get(subField);
88-
containingMessageOpt = Optional.of(messageTypes.get(field.type().reference().fullName()));
89-
Preconditions.checkNotNull(
90-
containingMessageOpt.get(),
91-
String.format(
92-
"No containing message found for field %s with type %s",
93-
field.name(), field.type().reference().simpleName()));
94-
} else {
95-
checkHttpFieldIsValid(subField, containingMessageOpt.get(), false);
96-
}
97-
}
98-
}
74+
Set<String> pathParamNames = bindingsBuilder.build();
9975

10076
// TODO: support nested message fields bindings
10177
String body = httpRule.getBody();
102-
Set<String> bodyParameters;
103-
Set<String> queryParameters;
78+
Set<String> bodyParamNames;
79+
Set<String> queryParamNames;
10480
if (!inputMessageOpt.isPresent()) {
10581
// Must be a mixin, do not support full HttpRuleBindings for now
106-
bodyParameters = ImmutableSet.of();
107-
queryParameters = ImmutableSet.of();
82+
bodyParamNames = ImmutableSet.of();
83+
queryParamNames = ImmutableSet.of();
10884
} else if (Strings.isNullOrEmpty(body)) {
109-
bodyParameters = ImmutableSet.of();
110-
queryParameters = Sets.difference(inputMessageOpt.get().fieldMap().keySet(), bindings);
85+
bodyParamNames = ImmutableSet.of();
86+
queryParamNames = Sets.difference(inputMessageOpt.get().fieldMap().keySet(), pathParamNames);
11187
} else if (body.equals(ASTERISK)) {
112-
bodyParameters = Sets.difference(inputMessageOpt.get().fieldMap().keySet(), bindings);
113-
queryParameters = ImmutableSet.of();
88+
bodyParamNames = Sets.difference(inputMessageOpt.get().fieldMap().keySet(), pathParamNames);
89+
queryParamNames = ImmutableSet.of();
11490
} else {
115-
bodyParameters = ImmutableSet.of(body);
116-
Set<String> bodyBinidngsUnion = Sets.union(bodyParameters, bindings);
117-
queryParameters =
91+
bodyParamNames = ImmutableSet.of(body);
92+
Set<String> bodyBinidngsUnion = Sets.union(bodyParamNames, pathParamNames);
93+
queryParamNames =
11894
Sets.difference(inputMessageOpt.get().fieldMap().keySet(), bodyBinidngsUnion);
11995
}
12096

97+
Message message = inputMessageOpt.orElse(null);
12198
return HttpBindings.builder()
12299
.setHttpVerb(HttpBindings.HttpVerb.valueOf(httpRule.getPatternCase().toString()))
123100
.setPattern(pattern)
124-
.setPathParameters(ImmutableSortedSet.copyOf(bindings))
125-
.setQueryParameters(ImmutableSortedSet.copyOf(queryParameters))
126-
.setBodyParameters(ImmutableSortedSet.copyOf(bodyParameters))
101+
.setPathParameters(
102+
validateAndConstructHttpBindings(pathParamNames, message, messageTypes, true))
103+
.setQueryParameters(
104+
validateAndConstructHttpBindings(queryParamNames, message, messageTypes, false))
105+
.setBodyParameters(
106+
validateAndConstructHttpBindings(bodyParamNames, message, messageTypes, false))
127107
.build();
128108
}
129109

110+
private static Set<HttpBinding> validateAndConstructHttpBindings(
111+
Set<String> paramNames,
112+
Message inputMessage,
113+
Map<String, Message> messageTypes,
114+
boolean isPath) {
115+
ImmutableSortedSet.Builder<HttpBinding> httpBindings = ImmutableSortedSet.naturalOrder();
116+
for (String paramName : paramNames) {
117+
// Handle foo.bar cases by descending into the subfields.
118+
String[] subFields = paramName.split("\\.");
119+
if (inputMessage == null) {
120+
httpBindings.add(HttpBinding.create(paramName, false));
121+
continue;
122+
}
123+
Message nestedMessage = inputMessage;
124+
for (int i = 0; i < subFields.length; i++) {
125+
String subFieldName = subFields[i];
126+
if (i < subFields.length - 1) {
127+
Field field = nestedMessage.fieldMap().get(subFieldName);
128+
nestedMessage = messageTypes.get(field.type().reference().fullName());
129+
Preconditions.checkNotNull(
130+
nestedMessage,
131+
String.format(
132+
"No containing message found for field %s with type %s",
133+
field.name(), field.type().reference().simpleName()));
134+
135+
} else {
136+
if (isPath) {
137+
checkHttpFieldIsValid(subFieldName, nestedMessage, !isPath);
138+
}
139+
Field field = nestedMessage.fieldMap().get(subFieldName);
140+
httpBindings.add(HttpBinding.create(paramName, field.isProto3Optional()));
141+
}
142+
}
143+
}
144+
return httpBindings.build();
145+
}
146+
130147
private static String getHttpVerbPattern(HttpRule httpRule) {
131148
PatternCase patternCase = httpRule.getPatternCase();
132149
switch (patternCase) {

0 commit comments

Comments
 (0)