Skip to content

Commit

Permalink
feat: Implement field presence support for DIREGAPIC (#774)
Browse files Browse the repository at this point in the history
  • Loading branch information
vam-google committed Jun 19, 2021
1 parent 68de8da commit c820361
Show file tree
Hide file tree
Showing 10 changed files with 187 additions and 80 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import com.google.api.generator.engine.ast.VariableExpr;
import com.google.api.generator.gapic.composer.common.AbstractServiceStubClassComposer;
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.Method;
import com.google.api.generator.gapic.model.Service;
import com.google.api.generator.gapic.utils.JavaStyle;
Expand Down Expand Up @@ -288,11 +289,11 @@ private AnonymousClassExpr createRequestParamsExtractorAnonClass(Method method)
VariableExpr.withVariable(
Variable.builder().setType(method.inputType()).setName("request").build());

for (String httpBindingFieldName : method.httpBindings().pathParameters()) {
for (HttpBinding httpBindingFieldBinding : method.httpBindings().pathParameters()) {
// Handle foo.bar cases by descending into the subfields.
MethodInvocationExpr.Builder requestFieldGetterExprBuilder =
MethodInvocationExpr.builder().setExprReferenceExpr(requestVarExpr);
String[] descendantFields = httpBindingFieldName.split("\\.");
String[] descendantFields = httpBindingFieldBinding.name().split("\\.");
for (int i = 0; i < descendantFields.length; i++) {
String currFieldName = descendantFields[i];
String bindingFieldMethodName =
Expand All @@ -319,7 +320,7 @@ private AnonymousClassExpr createRequestParamsExtractorAnonClass(Method method)
.setExprReferenceExpr(paramsVarExpr)
.setMethodName("put")
.setArguments(
ValueExpr.withValue(StringObjectValue.withValue(httpBindingFieldName)),
ValueExpr.withValue(StringObjectValue.withValue(httpBindingFieldBinding.name())),
valueOfExpr)
.build();
bodyExprs.add(paramsPutExpr);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
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.MethodDefinition;
import com.google.api.generator.engine.ast.MethodInvocationExpr;
import com.google.api.generator.engine.ast.NewObjectExpr;
Expand All @@ -42,6 +43,7 @@
import com.google.api.generator.engine.ast.VariableExpr;
import com.google.api.generator.gapic.composer.common.AbstractServiceStubClassComposer;
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.Method;
import com.google.api.generator.gapic.model.Service;
import com.google.api.generator.gapic.utils.JavaStyle;
Expand Down Expand Up @@ -357,9 +359,9 @@ private List<Expr> setResponseParserExpr(Method protoMethod) {
private Expr createFieldsExtractorAnonClass(
Method method,
TypeNode extractorReturnType,
Set<String> httpBindingFieldNames,
Set<HttpBinding> httpBindingFieldNames,
String serializerMethodName) {
List<Expr> bodyExprs = new ArrayList<>();
List<Statement> bodyStatements = new ArrayList<>();

Expr returnExpr = null;
VariableExpr fieldsVarExpr = null;
Expand Down Expand Up @@ -389,7 +391,7 @@ private Expr createFieldsExtractorAnonClass(
.build())
.build();

bodyExprs.add(fieldsAssignExpr);
bodyStatements.add(ExprStatement.withExpr(fieldsAssignExpr));
returnExpr = fieldsVarExpr;

TypeNode serializerVarType =
Expand Down Expand Up @@ -417,40 +419,57 @@ private Expr createFieldsExtractorAnonClass(

serializerExpr = serializerVarExpr;

bodyExprs.add(serializerAssignExpr);
bodyStatements.add(ExprStatement.withExpr(serializerAssignExpr));
}

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

for (String httpBindingFieldName : httpBindingFieldNames) {
for (HttpBinding httpBindingFieldName : httpBindingFieldNames) {
// Handle foo.bar cases by descending into the subfields.
MethodInvocationExpr.Builder requestFieldGetterExprBuilder =
MethodInvocationExpr.builder().setExprReferenceExpr(requestVarExpr);
String[] descendantFields = httpBindingFieldName.split("\\.");
MethodInvocationExpr.Builder requestFieldHasExprBuilder =
MethodInvocationExpr.builder().setExprReferenceExpr(requestVarExpr);
String[] descendantFields = httpBindingFieldName.name().split("\\.");
for (int i = 0; i < descendantFields.length; i++) {
String currFieldName = descendantFields[i];
String bindingFieldMethodName =
String.format("get%s", JavaStyle.toUpperCamelCase(currFieldName));
requestFieldGetterExprBuilder =
requestFieldGetterExprBuilder.setMethodName(bindingFieldMethodName);

String bindingFieldHasMethodName =
(i < descendantFields.length - 1)
? bindingFieldMethodName
: String.format("has%s", JavaStyle.toUpperCamelCase(currFieldName));
requestFieldHasExprBuilder =
requestFieldHasExprBuilder
.setMethodName(bindingFieldHasMethodName)
.setReturnType(TypeNode.BOOLEAN);

if (i < descendantFields.length - 1) {
requestFieldGetterExprBuilder =
MethodInvocationExpr.builder()
.setExprReferenceExpr(requestFieldGetterExprBuilder.build());
requestFieldHasExprBuilder =
MethodInvocationExpr.builder()
.setExprReferenceExpr(requestFieldHasExprBuilder.build());
}
}

MethodInvocationExpr requestBuilderExpr = requestFieldGetterExprBuilder.build();
MethodInvocationExpr requestHasExpr = requestFieldHasExprBuilder.build();

ImmutableList.Builder<Expr> paramsPutArgs = ImmutableList.builder();
if (fieldsVarExpr != null) {
paramsPutArgs.add(fieldsVarExpr);
}
paramsPutArgs.add(
ValueExpr.withValue(
StringObjectValue.withValue(JavaStyle.toLowerCamelCase(httpBindingFieldName))));
StringObjectValue.withValue(
JavaStyle.toLowerCamelCase(httpBindingFieldName.name()))));
paramsPutArgs.add(requestBuilderExpr);

Expr paramsPutExpr =
Expand All @@ -464,7 +483,15 @@ private Expr createFieldsExtractorAnonClass(
if (fieldsVarExpr == null) {
returnExpr = paramsPutExpr;
} else {
bodyExprs.add(paramsPutExpr);
if (httpBindingFieldName.isOptional()) {
bodyStatements.add(
IfStatement.builder()
.setConditionExpr(requestHasExpr)
.setBody(Arrays.asList(ExprStatement.withExpr(paramsPutExpr)))
.build());
} else {
bodyStatements.add(ExprStatement.withExpr(paramsPutExpr));
}
}
}

Expand All @@ -475,7 +502,7 @@ private Expr createFieldsExtractorAnonClass(
.setReturnType(extractorReturnType)
.setName("extract")
.setArguments(requestVarExpr.toBuilder().setIsDecl(true).build())
.setBody(bodyExprs.stream().map(ExprStatement::withExpr).collect(Collectors.toList()))
.setBody(bodyStatements)
.setReturnExpr(returnExpr)
.build();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ public abstract class Field {

public abstract boolean isContainedInOneof();

public abstract boolean isProto3Optional();

@Nullable
public abstract ResourceReference resourceReference();

Expand Down Expand Up @@ -63,6 +65,7 @@ && isEnum() == other.isEnum()
&& isRepeated() == other.isRepeated()
&& isMap() == other.isMap()
&& isContainedInOneof() == other.isContainedInOneof()
&& isProto3Optional() == other.isProto3Optional()
&& Objects.equals(resourceReference(), other.resourceReference())
&& Objects.equals(description(), other.description());
}
Expand All @@ -76,6 +79,7 @@ public int hashCode() {
+ (isRepeated() ? 1 : 0) * 31
+ (isMap() ? 1 : 0) * 37
+ (isContainedInOneof() ? 1 : 0) * 41
+ (isProto3Optional() ? 1 : 0) * 43
+ (resourceReference() == null ? 0 : resourceReference().hashCode())
+ (description() == null ? 0 : description().hashCode());
}
Expand All @@ -88,7 +92,8 @@ public static Builder builder() {
.setIsEnum(false)
.setIsRepeated(false)
.setIsMap(false)
.setIsContainedInOneof(false);
.setIsContainedInOneof(false)
.setIsProto3Optional(false);
}

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

public abstract Builder setIsContainedInOneof(boolean isContainedInOneof);

public abstract Builder setIsProto3Optional(boolean isProto3Optional);

public abstract Builder setResourceReference(ResourceReference resourceReference);

public abstract Builder setDescription(String description);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,33 @@ public enum HttpVerb {
PATCH,
}

@AutoValue
public abstract static class HttpBinding implements Comparable<HttpBinding> {
public abstract String name();

public abstract boolean isOptional();

public static HttpBinding create(String name, boolean isOptional) {
return new AutoValue_HttpBindings_HttpBinding(name, isOptional);
}

// Do not forget to keep it in sync with equals() implementation.
@Override
public int compareTo(HttpBinding o) {
int res = name().compareTo(o.name());
return res == 0 ? Boolean.compare(isOptional(), o.isOptional()) : res;
}
}

public abstract HttpVerb httpVerb();

public abstract String pattern();

public abstract Set<String> pathParameters();
public abstract Set<HttpBinding> pathParameters();

public abstract Set<String> queryParameters();
public abstract Set<HttpBinding> queryParameters();

public abstract Set<String> bodyParameters();
public abstract Set<HttpBinding> bodyParameters();

public static HttpBindings.Builder builder() {
return new AutoValue_HttpBindings.Builder()
Expand All @@ -53,10 +71,10 @@ public static HttpBindings.Builder builder() {
// in .java file: "/global/instanceTemplates/{instanceTemplate=*}"
public String patternLowerCamel() {
String lowerCamelPattern = pattern();
for (String pathParam : pathParameters()) {
for (HttpBinding pathParam : pathParameters()) {
lowerCamelPattern =
lowerCamelPattern.replaceAll(
"\\{" + pathParam, "{" + JavaStyle.toLowerCamelCase(pathParam));
"\\{" + pathParam.name(), "{" + JavaStyle.toLowerCamelCase(pathParam.name()));
}
return lowerCamelPattern;
}
Expand All @@ -69,11 +87,11 @@ public abstract static class Builder {

abstract String pattern();

public abstract HttpBindings.Builder setPathParameters(Set<String> pathParameters);
public abstract HttpBindings.Builder setPathParameters(Set<HttpBinding> pathParameters);

public abstract HttpBindings.Builder setQueryParameters(Set<String> queryParameters);
public abstract HttpBindings.Builder setQueryParameters(Set<HttpBinding> queryParameters);

public abstract HttpBindings.Builder setBodyParameters(Set<String> bodyParameters);
public abstract HttpBindings.Builder setBodyParameters(Set<HttpBinding> bodyParameters);

public abstract HttpBindings autoBuild();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.google.api.HttpRule.PatternCase;
import com.google.api.generator.gapic.model.Field;
import com.google.api.generator.gapic.model.HttpBindings;
import com.google.api.generator.gapic.model.HttpBindings.HttpBinding;
import com.google.api.generator.gapic.model.Message;
import com.google.api.pathtemplate.PathTemplate;
import com.google.common.base.Preconditions;
Expand Down Expand Up @@ -70,63 +71,79 @@ private static HttpBindings parseHttpRuleHelper(
}
}

Set<String> bindings = bindingsBuilder.build();

// Binding validation.
for (String binding : bindings) {
// Handle foo.bar cases by descending into the subfields.
String[] descendantBindings = binding.split("\\.");
Optional<Message> containingMessageOpt = inputMessageOpt;
for (int i = 0; i < descendantBindings.length; i++) {
String subField = descendantBindings[i];
if (!containingMessageOpt.isPresent()) {
continue;
}

if (i < descendantBindings.length - 1) {
Field field = containingMessageOpt.get().fieldMap().get(subField);
containingMessageOpt = Optional.of(messageTypes.get(field.type().reference().fullName()));
Preconditions.checkNotNull(
containingMessageOpt.get(),
String.format(
"No containing message found for field %s with type %s",
field.name(), field.type().reference().simpleName()));
} else {
checkHttpFieldIsValid(subField, containingMessageOpt.get(), false);
}
}
}
Set<String> pathParamNames = bindingsBuilder.build();

// TODO: support nested message fields bindings
String body = httpRule.getBody();
Set<String> bodyParameters;
Set<String> queryParameters;
Set<String> bodyParamNames;
Set<String> queryParamNames;
if (!inputMessageOpt.isPresent()) {
// Must be a mixin, do not support full HttpRuleBindings for now
bodyParameters = ImmutableSet.of();
queryParameters = ImmutableSet.of();
bodyParamNames = ImmutableSet.of();
queryParamNames = ImmutableSet.of();
} else if (Strings.isNullOrEmpty(body)) {
bodyParameters = ImmutableSet.of();
queryParameters = Sets.difference(inputMessageOpt.get().fieldMap().keySet(), bindings);
bodyParamNames = ImmutableSet.of();
queryParamNames = Sets.difference(inputMessageOpt.get().fieldMap().keySet(), pathParamNames);
} else if (body.equals(ASTERISK)) {
bodyParameters = Sets.difference(inputMessageOpt.get().fieldMap().keySet(), bindings);
queryParameters = ImmutableSet.of();
bodyParamNames = Sets.difference(inputMessageOpt.get().fieldMap().keySet(), pathParamNames);
queryParamNames = ImmutableSet.of();
} else {
bodyParameters = ImmutableSet.of(body);
Set<String> bodyBinidngsUnion = Sets.union(bodyParameters, bindings);
queryParameters =
bodyParamNames = ImmutableSet.of(body);
Set<String> bodyBinidngsUnion = Sets.union(bodyParamNames, pathParamNames);
queryParamNames =
Sets.difference(inputMessageOpt.get().fieldMap().keySet(), bodyBinidngsUnion);
}

Message message = inputMessageOpt.orElse(null);
return HttpBindings.builder()
.setHttpVerb(HttpBindings.HttpVerb.valueOf(httpRule.getPatternCase().toString()))
.setPattern(pattern)
.setPathParameters(ImmutableSortedSet.copyOf(bindings))
.setQueryParameters(ImmutableSortedSet.copyOf(queryParameters))
.setBodyParameters(ImmutableSortedSet.copyOf(bodyParameters))
.setPathParameters(
validateAndConstructHttpBindings(pathParamNames, message, messageTypes, true))
.setQueryParameters(
validateAndConstructHttpBindings(queryParamNames, message, messageTypes, false))
.setBodyParameters(
validateAndConstructHttpBindings(bodyParamNames, message, messageTypes, false))
.build();
}

private static Set<HttpBinding> validateAndConstructHttpBindings(
Set<String> paramNames,
Message inputMessage,
Map<String, Message> messageTypes,
boolean isPath) {
ImmutableSortedSet.Builder<HttpBinding> httpBindings = ImmutableSortedSet.naturalOrder();
for (String paramName : paramNames) {
// Handle foo.bar cases by descending into the subfields.
String[] subFields = paramName.split("\\.");
if (inputMessage == null) {
httpBindings.add(HttpBinding.create(paramName, false));
continue;
}
Message nestedMessage = inputMessage;
for (int i = 0; i < subFields.length; i++) {
String subFieldName = subFields[i];
if (i < subFields.length - 1) {
Field field = nestedMessage.fieldMap().get(subFieldName);
nestedMessage = messageTypes.get(field.type().reference().fullName());
Preconditions.checkNotNull(
nestedMessage,
String.format(
"No containing message found for field %s with type %s",
field.name(), field.type().reference().simpleName()));

} else {
if (isPath) {
checkHttpFieldIsValid(subFieldName, nestedMessage, !isPath);
}
Field field = nestedMessage.fieldMap().get(subFieldName);
httpBindings.add(HttpBinding.create(paramName, field.isProto3Optional()));
}
}
}
return httpBindings.build();
}

private static String getHttpVerbPattern(HttpRule httpRule) {
PatternCase patternCase = httpRule.getPatternCase();
switch (patternCase) {
Expand Down
Loading

0 comments on commit c820361

Please sign in to comment.