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.
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
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
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
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
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

0 comments on commit c820361

Please sign in to comment.