From 8e31e27eff8a7df25d8270c7ba99d5dcc1426913 Mon Sep 17 00:00:00 2001 From: Mira Leung Date: Fri, 7 Aug 2020 16:16:31 -0700 Subject: [PATCH] [ggj] feat: extract arg resource names and parse parents (#158) * feat: add initial resource name def parsing * feat: add early failure for missing fields * feat: couple resnames and message parsing * fix!: refactor method arg parsing away from class creation * feat: add resource_reference parsing * fix: dot name parsing * feat: extract arg resource names and parse parents --- .../generator/gapic/model/GapicContext.java | 6 + .../generator/gapic/model/ResourceName.java | 58 +++++- .../protoparser/MethodSignatureParser.java | 73 +++++++- .../generator/gapic/protoparser/Parser.java | 45 ++++- .../protoparser/ResourceReferenceParser.java | 142 ++++++++++++++ ...rviceCallableFactoryClassComposerTest.java | 9 +- .../GrpcServiceStubClassComposerTest.java | 8 +- .../MockServiceClassComposerTest.java | 9 +- .../MockServiceImplClassComposerTest.java | 9 +- .../ServiceClientClassComposerTest.java | 9 +- .../ServiceClientTestClassComposerTest.java | 9 +- .../ServiceSettingsClassComposerTest.java | 9 +- .../ServiceStubClassComposerTest.java | 9 +- .../generator/gapic/protoparser/BUILD.bazel | 1 + .../gapic/protoparser/ParserTest.java | 39 +++- .../ResourceReferenceParserTest.java | 175 ++++++++++++++++++ 16 files changed, 583 insertions(+), 27 deletions(-) create mode 100644 src/main/java/com/google/api/generator/gapic/protoparser/ResourceReferenceParser.java create mode 100644 src/test/java/com/google/api/generator/gapic/protoparser/ResourceReferenceParserTest.java diff --git a/src/main/java/com/google/api/generator/gapic/model/GapicContext.java b/src/main/java/com/google/api/generator/gapic/model/GapicContext.java index ea93efeed7..15cf8bb012 100644 --- a/src/main/java/com/google/api/generator/gapic/model/GapicContext.java +++ b/src/main/java/com/google/api/generator/gapic/model/GapicContext.java @@ -17,8 +17,10 @@ import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import java.util.List; import java.util.Map; +import java.util.Set; @AutoValue public abstract class GapicContext { @@ -30,6 +32,8 @@ public abstract class GapicContext { public abstract ImmutableList services(); + public abstract ImmutableSet helperResourceNames(); + public static Builder builder() { return new AutoValue_GapicContext.Builder(); } @@ -42,6 +46,8 @@ public abstract static class Builder { public abstract Builder setServices(List services); + public abstract Builder setHelperResourceNames(Set helperResourceNames); + public abstract GapicContext build(); } } diff --git a/src/main/java/com/google/api/generator/gapic/model/ResourceName.java b/src/main/java/com/google/api/generator/gapic/model/ResourceName.java index 37815cda8f..0b4112e87e 100644 --- a/src/main/java/com/google/api/generator/gapic/model/ResourceName.java +++ b/src/main/java/com/google/api/generator/gapic/model/ResourceName.java @@ -14,20 +14,26 @@ package com.google.api.generator.gapic.model; +import com.google.api.generator.engine.ast.TypeNode; +import com.google.api.generator.engine.ast.VaporReference; import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; import java.util.List; +import java.util.Objects; import javax.annotation.Nullable; @AutoValue public abstract class ResourceName { + static final String SLASH = "/"; + // The original binding variable name. // This should be in lower_snake_case in the proto, and expected to be surrounded by braces. // Example: In projects/{project}/billingAccounts/billing_account, the variable name would be // "billing_account." public abstract String variableName(); - // The Java package where the resource name was defined. + // The Java package of the project, or that of a subpackage where the resource name was defined. + // That is, resource names defined outside of this project will still have the project's package. public abstract String pakkage(); // The resource type. @@ -36,6 +42,9 @@ public abstract class ResourceName { // A list of patterns such as projects/{project}/locations/{location}/resources/{this_resource}. public abstract ImmutableList patterns(); + // The Java TypeNode of the resource name helper class to generate. + public abstract TypeNode type(); + // The message in which this resource was defined. Optional. // This is expected to be empty for file-level definitions. @Nullable @@ -49,6 +58,34 @@ public static Builder builder() { return new AutoValue_ResourceName.Builder(); } + @Override + public boolean equals(Object o) { + if (!(o instanceof ResourceName)) { + return false; + } + + ResourceName other = (ResourceName) o; + return variableName().equals(other.variableName()) + && pakkage().equals(other.pakkage()) + && resourceTypeString().equals(other.resourceTypeString()) + && patterns().equals(other.patterns()) + && Objects.equals(parentMessageName(), other.parentMessageName()) + && Objects.equals(type(), other.type()); + } + + @Override + public int hashCode() { + int parentMessageNameHashCode = + parentMessageName() == null ? 0 : parentMessageName().hashCode(); + int typeHashCode = type() == null ? 0 : type().hashCode(); + return 17 * variableName().hashCode() + + 19 * pakkage().hashCode() + + 23 * resourceTypeString().hashCode() + + 31 * patterns().hashCode() + + 37 * parentMessageNameHashCode + + 41 * typeHashCode; + } + @AutoValue.Builder public abstract static class Builder { public abstract Builder setVariableName(String variableName); @@ -61,6 +98,23 @@ public abstract static class Builder { public abstract Builder setParentMessageName(String parentMessageName); - public abstract ResourceName build(); + // Private setter. + abstract Builder setType(TypeNode type); + + // Private accessors. + abstract String pakkage(); + + abstract String resourceTypeString(); + + // Private. + abstract ResourceName autoBuild(); + + public ResourceName build() { + String typeName = resourceTypeString().substring(resourceTypeString().lastIndexOf(SLASH) + 1); + setType( + TypeNode.withReference( + VaporReference.builder().setName(typeName).setPakkage(pakkage()).build())); + return autoBuild(); + } } } diff --git a/src/main/java/com/google/api/generator/gapic/protoparser/MethodSignatureParser.java b/src/main/java/com/google/api/generator/gapic/protoparser/MethodSignatureParser.java index bcec73fa2a..5147fccc72 100644 --- a/src/main/java/com/google/api/generator/gapic/protoparser/MethodSignatureParser.java +++ b/src/main/java/com/google/api/generator/gapic/protoparser/MethodSignatureParser.java @@ -19,12 +19,16 @@ import com.google.api.generator.gapic.model.Field; import com.google.api.generator.gapic.model.Message; import com.google.api.generator.gapic.model.MethodArgument; +import com.google.api.generator.gapic.model.ResourceName; import com.google.common.base.Preconditions; import com.google.protobuf.Descriptors.MethodDescriptor; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; // TODO(miraleung): Add tests for this class. Currently exercised in integration tests. public class MethodSignatureParser { @@ -34,8 +38,11 @@ public class MethodSignatureParser { /** Parses a list of method signature annotations out of an RPC. */ public static List> parseMethodSignatures( MethodDescriptor methodDescriptor, + String servicePackage, TypeNode methodInputType, - Map messageTypes) { + Map messageTypes, + Map resourceNames, + Set outputArgResourceNames) { List stringSigs = methodDescriptor.getOptions().getExtension(ClientProto.methodSignature); @@ -44,6 +51,7 @@ public static List> parseMethodSignatures( return signatures; } + Map patternsToResourceNames = createPatternResourceNameMap(resourceNames); String methodInputTypeName = methodInputType.reference().name(); Message inputMessage = messageTypes.get(methodInputTypeName); @@ -54,7 +62,15 @@ public static List> parseMethodSignatures( List arguments = new ArrayList<>(); for (String argumentName : stringSig.split(METHOD_SIGNATURE_DELIMITER)) { List argumentTypePath = - new ArrayList<>(parseTypeFromArgumentName(argumentName, inputMessage, messageTypes)); + new ArrayList<>( + parseTypeFromArgumentName( + argumentName, + servicePackage, + inputMessage, + messageTypes, + resourceNames, + patternsToResourceNames, + outputArgResourceNames)); int dotLastIndex = argumentName.lastIndexOf(DOT); String actualArgumentName = @@ -77,14 +93,32 @@ public static List> parseMethodSignatures( } private static List parseTypeFromArgumentName( - String argumentName, Message inputMessage, Map messageTypes) { - return parseTypeFromArgumentName(argumentName, inputMessage, messageTypes, new ArrayList<>()); + String argumentName, + String servicePackage, + Message inputMessage, + Map messageTypes, + Map resourceNames, + Map patternsToResourceNames, + Set outputArgResourceNames) { + return parseTypeFromArgumentName( + argumentName, + servicePackage, + inputMessage, + messageTypes, + resourceNames, + patternsToResourceNames, + outputArgResourceNames, + new ArrayList<>()); } private static List parseTypeFromArgumentName( String argumentName, + String servicePackage, Message inputMessage, Map messageTypes, + Map resourceNames, + Map patternsToResourceNames, + Set outputArgResourceNames, List typeAcc) { int dotIndex = argumentName.indexOf(DOT); // TODO(miraleung): Fake out resource names here. @@ -103,6 +137,17 @@ private static List parseTypeFromArgumentName( // Must be a sub-message for a type's subfield to be valid. Field firstField = inputMessage.fieldMap().get(firstFieldName); + if (firstField.hasResourceReference()) { + List resourceNameArgs = + ResourceReferenceParser.parseResourceNames( + firstField.resourceReference(), + servicePackage, + resourceNames, + patternsToResourceNames); + outputArgResourceNames.addAll(resourceNameArgs); + return resourceNameArgs.stream().map(r -> r.type()).collect(Collectors.toList()); + } + Preconditions.checkState( !firstField.isRepeated(), String.format("Cannot descend into repeated field %s", firstField.name())); @@ -122,6 +167,24 @@ private static List parseTypeFromArgumentName( List newAcc = new ArrayList<>(typeAcc); newAcc.add(firstFieldType); return parseTypeFromArgumentName( - remainingArgumentName, firstFieldMessage, messageTypes, newAcc); + remainingArgumentName, + servicePackage, + firstFieldMessage, + messageTypes, + resourceNames, + patternsToResourceNames, + outputArgResourceNames, + newAcc); + } + + private static Map createPatternResourceNameMap( + Map resourceNames) { + Map patternsToResourceNames = new HashMap<>(); + for (ResourceName resourceName : resourceNames.values()) { + for (String pattern : resourceName.patterns()) { + patternsToResourceNames.put(pattern, resourceName); + } + } + return patternsToResourceNames; } } diff --git a/src/main/java/com/google/api/generator/gapic/protoparser/Parser.java b/src/main/java/com/google/api/generator/gapic/protoparser/Parser.java index 21bc832294..d920683e8f 100644 --- a/src/main/java/com/google/api/generator/gapic/protoparser/Parser.java +++ b/src/main/java/com/google/api/generator/gapic/protoparser/Parser.java @@ -47,8 +47,10 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; public class Parser { @@ -65,16 +67,22 @@ public static GapicContext parse(CodeGeneratorRequest request) { Map messages = parseMessages(request); Map resourceNames = parseResourceNames(request); messages = updateResourceNamesInMessages(messages, resourceNames.values()); - List services = parseServices(request, messages); + Set outputArgResourceNames = new HashSet<>(); + List services = + parseServices(request, messages, resourceNames, outputArgResourceNames); return GapicContext.builder() .setServices(services) .setMessages(messages) .setResourceNames(resourceNames) + .setHelperResourceNames(outputArgResourceNames) .build(); } public static List parseServices( - CodeGeneratorRequest request, Map messageTypes) { + CodeGeneratorRequest request, + Map messageTypes, + Map resourceNames, + Set outputArgResourceNames) { Map fileDescriptors = getFilesToGenerate(request); List services = new ArrayList<>(); for (String fileToGenerate : request.getFileToGenerateList()) { @@ -84,14 +92,18 @@ public static List parseServices( "Missing file descriptor for [%s]", fileToGenerate); - services.addAll(parseService(fileDescriptor, messageTypes)); + services.addAll( + parseService(fileDescriptor, messageTypes, resourceNames, outputArgResourceNames)); } return services; } public static List parseService( - FileDescriptor fileDescriptor, Map messageTypes) { + FileDescriptor fileDescriptor, + Map messageTypes, + Map resourceNames, + Set outputArgResourceNames) { String pakkage = TypeParser.getPackage(fileDescriptor); return fileDescriptor.getServices().stream() .map( @@ -100,7 +112,9 @@ public static List parseService( .setName(s.getName()) .setPakkage(pakkage) .setProtoPakkage(fileDescriptor.getPackage()) - .setMethods(parseMethods(s, messageTypes)) + .setMethods( + parseMethods( + s, pakkage, messageTypes, resourceNames, outputArgResourceNames)) .build()) .collect(Collectors.toList()); } @@ -168,14 +182,23 @@ public static Map parseResourceNames(CodeGeneratorRequest fileDescriptors.get(fileToGenerate), "Missing file descriptor for [%s]", fileToGenerate); - resourceNames.putAll(ResourceNameParser.parseResourceNames(fileDescriptor)); + resourceNames.putAll(parseResourceNames(fileDescriptor)); } return resourceNames; } + // Convenience wrapper for package-external unit tests. + public static Map parseResourceNames(FileDescriptor fileDescriptor) { + return ResourceNameParser.parseResourceNames(fileDescriptor); + } + @VisibleForTesting static List parseMethods( - ServiceDescriptor serviceDescriptor, Map messageTypes) { + ServiceDescriptor serviceDescriptor, + String servicePackage, + Map messageTypes, + Map resourceNames, + Set outputArgResourceNames) { return serviceDescriptor.getMethods().stream() .map( md -> { @@ -187,7 +210,13 @@ static List parseMethods( .setStream(Method.toStream(md.isClientStreaming(), md.isServerStreaming())) .setLro(parseLro(md, messageTypes)) .setMethodSignatures( - MethodSignatureParser.parseMethodSignatures(md, inputType, messageTypes)) + MethodSignatureParser.parseMethodSignatures( + md, + servicePackage, + inputType, + messageTypes, + resourceNames, + outputArgResourceNames)) .setIsPaged(parseIsPaged(md, messageTypes)) .build(); }) diff --git a/src/main/java/com/google/api/generator/gapic/protoparser/ResourceReferenceParser.java b/src/main/java/com/google/api/generator/gapic/protoparser/ResourceReferenceParser.java new file mode 100644 index 0000000000..49abdb6081 --- /dev/null +++ b/src/main/java/com/google/api/generator/gapic/protoparser/ResourceReferenceParser.java @@ -0,0 +1,142 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.api.generator.gapic.protoparser; + +import com.google.api.generator.gapic.model.ResourceName; +import com.google.api.generator.gapic.model.ResourceReference; +import com.google.api.generator.gapic.utils.JavaStyle; +import com.google.api.generator.gapic.utils.ResourceNameConstants; +import com.google.api.pathtemplate.PathTemplate; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +public class ResourceReferenceParser { + private static final String SLASH = "/"; + + public static List parseResourceNames( + ResourceReference resourceReference, + String servicePackage, + Map resourceNames, + Map patternsToResourceNames) { + ResourceName resourceName = resourceNames.get(resourceReference.resourceTypeString()); + Preconditions.checkNotNull( + resourceName, + String.format( + "No resource definition found for reference with type %s", + resourceReference.resourceTypeString())); + if (!resourceReference.isChildType()) { + return Arrays.asList(resourceName); + } + + // Create a parent ResourceName for each pattern. + List parentResourceNames = new ArrayList<>(); + for (String pattern : resourceName.patterns()) { + Optional parentResourceNameOpt = + parseParentResourceName( + pattern, + servicePackage, + resourceName.pakkage(), + resourceName.resourceTypeString(), + patternsToResourceNames); + if (parentResourceNameOpt.isPresent()) { + parentResourceNames.add(parentResourceNameOpt.get()); + } + } + return parentResourceNames; + } + + @VisibleForTesting + static Optional parseParentResourceName( + String pattern, + String servicePackage, + String resourcePackage, + String resourceTypeString, + Map patternsToResourceNames) { + Optional parentPatternOpt = parseParentPattern(pattern); + if (!parentPatternOpt.isPresent()) { + return Optional.empty(); + } + + String parentPattern = parentPatternOpt.get(); + if (patternsToResourceNames.get(parentPattern) != null) { + return Optional.of(patternsToResourceNames.get(parentPattern)); + } + + String[] tokens = parentPattern.split(SLASH); + String lastToken = tokens[tokens.length - 1]; + Set variableNames = PathTemplate.create(parentPattern).vars(); + String parentVariableName = null; + for (String variableName : variableNames) { + if (lastToken.contains(variableName)) { + parentVariableName = variableName; + } + } + Preconditions.checkNotNull( + parentVariableName, + String.format("Could not parse variable name from pattern %s", parentPattern)); + + // Use the package where the resource was defined, only if that is a sub-package of the + // current service (which is assumed to be the project's package). + String pakkage = resolvePackages(resourcePackage, servicePackage); + String parentResourceTypeString = + String.format( + "%s/%s", + resourceTypeString.substring(0, resourceTypeString.indexOf(SLASH)), + JavaStyle.toUpperCamelCase(parentVariableName)); + ResourceName parentResourceName = + ResourceName.builder() + .setVariableName(parentVariableName) + .setPakkage(pakkage) + .setResourceTypeString(parentResourceTypeString) + .setPatterns(Arrays.asList(parentPattern)) + .build(); + patternsToResourceNames.put(parentPattern, parentResourceName); + + return Optional.of(parentResourceName); + } + + @VisibleForTesting + static Optional parseParentPattern(String pattern) { + String[] tokens = pattern.split(SLASH); + String lastToken = tokens[tokens.length - 1]; + if (lastToken.equals(ResourceNameConstants.DELETED_TOPIC_LITERAL) + || lastToken.equals(ResourceNameConstants.WILDCARD_PATTERN)) { + return Optional.empty(); + } + + // No fully-formed parent. Expected: ancestors/{ancestor}/childNodes/{child_node}. + if (tokens.length < 4) { + return Optional.empty(); + } + + Preconditions.checkState( + lastToken.contains("{"), + String.format( + "Pattern %s must end with a brace-encapsulated variable, e.g. {foobar}", pattern)); + + return Optional.of(String.join(SLASH, Arrays.asList(tokens).subList(0, tokens.length - 2))); + } + + @VisibleForTesting + static String resolvePackages(String resourceNamePackage, String servicePackage) { + return resourceNamePackage.contains(servicePackage) ? resourceNamePackage : servicePackage; + } +} diff --git a/src/test/java/com/google/api/generator/gapic/composer/GrpcServiceCallableFactoryClassComposerTest.java b/src/test/java/com/google/api/generator/gapic/composer/GrpcServiceCallableFactoryClassComposerTest.java index e75c4bb8c5..2498618e01 100644 --- a/src/test/java/com/google/api/generator/gapic/composer/GrpcServiceCallableFactoryClassComposerTest.java +++ b/src/test/java/com/google/api/generator/gapic/composer/GrpcServiceCallableFactoryClassComposerTest.java @@ -19,13 +19,16 @@ import com.google.api.generator.engine.writer.JavaWriterVisitor; import com.google.api.generator.gapic.model.GapicClass; import com.google.api.generator.gapic.model.Message; +import com.google.api.generator.gapic.model.ResourceName; import com.google.api.generator.gapic.model.Service; import com.google.api.generator.gapic.protoparser.Parser; import com.google.protobuf.Descriptors.FileDescriptor; import com.google.protobuf.Descriptors.ServiceDescriptor; import com.google.showcase.v1beta1.EchoOuterClass; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import org.junit.Before; import org.junit.Test; @@ -43,7 +46,11 @@ public void setUp() { @Test public void generateServiceClasses() { Map messageTypes = Parser.parseMessages(echoFileDescriptor); - List services = Parser.parseService(echoFileDescriptor, messageTypes); + Map resourceNames = Parser.parseResourceNames(echoFileDescriptor); + Set outputResourceNames = new HashSet<>(); + List services = + Parser.parseService(echoFileDescriptor, messageTypes, resourceNames, outputResourceNames); + Service echoProtoService = services.get(0); GapicClass clazz = GrpcServiceCallableFactoryClassComposer.instance().generate(echoProtoService, messageTypes); diff --git a/src/test/java/com/google/api/generator/gapic/composer/GrpcServiceStubClassComposerTest.java b/src/test/java/com/google/api/generator/gapic/composer/GrpcServiceStubClassComposerTest.java index 13f520abf6..e9747d6e6d 100644 --- a/src/test/java/com/google/api/generator/gapic/composer/GrpcServiceStubClassComposerTest.java +++ b/src/test/java/com/google/api/generator/gapic/composer/GrpcServiceStubClassComposerTest.java @@ -19,13 +19,16 @@ import com.google.api.generator.engine.writer.JavaWriterVisitor; import com.google.api.generator.gapic.model.GapicClass; import com.google.api.generator.gapic.model.Message; +import com.google.api.generator.gapic.model.ResourceName; import com.google.api.generator.gapic.model.Service; import com.google.api.generator.gapic.protoparser.Parser; import com.google.protobuf.Descriptors.FileDescriptor; import com.google.protobuf.Descriptors.ServiceDescriptor; import com.google.showcase.v1beta1.EchoOuterClass; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import org.junit.Before; import org.junit.Test; @@ -43,7 +46,10 @@ public void setUp() { @Test public void generateServiceClasses() { Map messageTypes = Parser.parseMessages(echoFileDescriptor); - List services = Parser.parseService(echoFileDescriptor, messageTypes); + Map resourceNames = Parser.parseResourceNames(echoFileDescriptor); + Set outputResourceNames = new HashSet<>(); + List services = + Parser.parseService(echoFileDescriptor, messageTypes, resourceNames, outputResourceNames); Service echoProtoService = services.get(0); GapicClass clazz = GrpcServiceStubClassComposer.instance().generate(echoProtoService, messageTypes); diff --git a/src/test/java/com/google/api/generator/gapic/composer/MockServiceClassComposerTest.java b/src/test/java/com/google/api/generator/gapic/composer/MockServiceClassComposerTest.java index 1248dae21b..35b0830dac 100644 --- a/src/test/java/com/google/api/generator/gapic/composer/MockServiceClassComposerTest.java +++ b/src/test/java/com/google/api/generator/gapic/composer/MockServiceClassComposerTest.java @@ -19,13 +19,16 @@ import com.google.api.generator.engine.writer.JavaWriterVisitor; import com.google.api.generator.gapic.model.GapicClass; import com.google.api.generator.gapic.model.Message; +import com.google.api.generator.gapic.model.ResourceName; import com.google.api.generator.gapic.model.Service; import com.google.api.generator.gapic.protoparser.Parser; import com.google.protobuf.Descriptors.FileDescriptor; import com.google.protobuf.Descriptors.ServiceDescriptor; import com.google.showcase.v1beta1.EchoOuterClass; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import org.junit.Before; import org.junit.Test; @@ -43,7 +46,11 @@ public void setUp() { @Test public void generateServiceClasses() { Map messageTypes = Parser.parseMessages(echoFileDescriptor); - List services = Parser.parseService(echoFileDescriptor, messageTypes); + Map resourceNames = Parser.parseResourceNames(echoFileDescriptor); + Set outputResourceNames = new HashSet<>(); + List services = + Parser.parseService(echoFileDescriptor, messageTypes, resourceNames, outputResourceNames); + Service echoProtoService = services.get(0); GapicClass clazz = MockServiceClassComposer.instance().generate(echoProtoService, messageTypes); diff --git a/src/test/java/com/google/api/generator/gapic/composer/MockServiceImplClassComposerTest.java b/src/test/java/com/google/api/generator/gapic/composer/MockServiceImplClassComposerTest.java index 25b6a01833..5234847638 100644 --- a/src/test/java/com/google/api/generator/gapic/composer/MockServiceImplClassComposerTest.java +++ b/src/test/java/com/google/api/generator/gapic/composer/MockServiceImplClassComposerTest.java @@ -19,13 +19,16 @@ import com.google.api.generator.engine.writer.JavaWriterVisitor; import com.google.api.generator.gapic.model.GapicClass; import com.google.api.generator.gapic.model.Message; +import com.google.api.generator.gapic.model.ResourceName; import com.google.api.generator.gapic.model.Service; import com.google.api.generator.gapic.protoparser.Parser; import com.google.protobuf.Descriptors.FileDescriptor; import com.google.protobuf.Descriptors.ServiceDescriptor; import com.google.showcase.v1beta1.EchoOuterClass; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import org.junit.Before; import org.junit.Test; @@ -43,7 +46,11 @@ public void setUp() { @Test public void generateServiceClasses() { Map messageTypes = Parser.parseMessages(echoFileDescriptor); - List services = Parser.parseService(echoFileDescriptor, messageTypes); + Map resourceNames = Parser.parseResourceNames(echoFileDescriptor); + Set outputResourceNames = new HashSet<>(); + List services = + Parser.parseService(echoFileDescriptor, messageTypes, resourceNames, outputResourceNames); + Service echoProtoService = services.get(0); GapicClass clazz = MockServiceImplClassComposer.instance().generate(echoProtoService, messageTypes); diff --git a/src/test/java/com/google/api/generator/gapic/composer/ServiceClientClassComposerTest.java b/src/test/java/com/google/api/generator/gapic/composer/ServiceClientClassComposerTest.java index ce766355b9..c7d1e4e273 100644 --- a/src/test/java/com/google/api/generator/gapic/composer/ServiceClientClassComposerTest.java +++ b/src/test/java/com/google/api/generator/gapic/composer/ServiceClientClassComposerTest.java @@ -19,13 +19,16 @@ import com.google.api.generator.engine.writer.JavaWriterVisitor; import com.google.api.generator.gapic.model.GapicClass; import com.google.api.generator.gapic.model.Message; +import com.google.api.generator.gapic.model.ResourceName; import com.google.api.generator.gapic.model.Service; import com.google.api.generator.gapic.protoparser.Parser; import com.google.protobuf.Descriptors.FileDescriptor; import com.google.protobuf.Descriptors.ServiceDescriptor; import com.google.showcase.v1beta1.EchoOuterClass; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import org.junit.Before; import org.junit.Test; @@ -43,7 +46,11 @@ public void setUp() { @Test public void generateServiceClasses() { Map messageTypes = Parser.parseMessages(echoFileDescriptor); - List services = Parser.parseService(echoFileDescriptor, messageTypes); + Map resourceNames = Parser.parseResourceNames(echoFileDescriptor); + Set outputResourceNames = new HashSet<>(); + List services = + Parser.parseService(echoFileDescriptor, messageTypes, resourceNames, outputResourceNames); + Service echoProtoService = services.get(0); GapicClass clazz = ServiceClientClassComposer.instance().generate(echoProtoService, messageTypes); diff --git a/src/test/java/com/google/api/generator/gapic/composer/ServiceClientTestClassComposerTest.java b/src/test/java/com/google/api/generator/gapic/composer/ServiceClientTestClassComposerTest.java index ab1925fa41..c7e48d8b0a 100644 --- a/src/test/java/com/google/api/generator/gapic/composer/ServiceClientTestClassComposerTest.java +++ b/src/test/java/com/google/api/generator/gapic/composer/ServiceClientTestClassComposerTest.java @@ -19,13 +19,16 @@ import com.google.api.generator.engine.writer.JavaWriterVisitor; import com.google.api.generator.gapic.model.GapicClass; import com.google.api.generator.gapic.model.Message; +import com.google.api.generator.gapic.model.ResourceName; import com.google.api.generator.gapic.model.Service; import com.google.api.generator.gapic.protoparser.Parser; import com.google.protobuf.Descriptors.FileDescriptor; import com.google.protobuf.Descriptors.ServiceDescriptor; import com.google.showcase.v1beta1.EchoOuterClass; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import org.junit.Before; import org.junit.Test; @@ -43,7 +46,11 @@ public void setUp() { @Test public void generateServiceClasses() { Map messageTypes = Parser.parseMessages(echoFileDescriptor); - List services = Parser.parseService(echoFileDescriptor, messageTypes); + Map resourceNames = Parser.parseResourceNames(echoFileDescriptor); + Set outputResourceNames = new HashSet<>(); + List services = + Parser.parseService(echoFileDescriptor, messageTypes, resourceNames, outputResourceNames); + Service echoProtoService = services.get(0); GapicClass clazz = ServiceClientTestClassComposer.instance().generate(echoProtoService, messageTypes); diff --git a/src/test/java/com/google/api/generator/gapic/composer/ServiceSettingsClassComposerTest.java b/src/test/java/com/google/api/generator/gapic/composer/ServiceSettingsClassComposerTest.java index 268d0805b6..efd9f30e6f 100644 --- a/src/test/java/com/google/api/generator/gapic/composer/ServiceSettingsClassComposerTest.java +++ b/src/test/java/com/google/api/generator/gapic/composer/ServiceSettingsClassComposerTest.java @@ -19,13 +19,16 @@ import com.google.api.generator.engine.writer.JavaWriterVisitor; import com.google.api.generator.gapic.model.GapicClass; import com.google.api.generator.gapic.model.Message; +import com.google.api.generator.gapic.model.ResourceName; import com.google.api.generator.gapic.model.Service; import com.google.api.generator.gapic.protoparser.Parser; import com.google.protobuf.Descriptors.FileDescriptor; import com.google.protobuf.Descriptors.ServiceDescriptor; import com.google.showcase.v1beta1.EchoOuterClass; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import org.junit.Before; import org.junit.Test; @@ -43,7 +46,11 @@ public void setUp() { @Test public void generateServiceClasses() { Map messageTypes = Parser.parseMessages(echoFileDescriptor); - List services = Parser.parseService(echoFileDescriptor, messageTypes); + Map resourceNames = Parser.parseResourceNames(echoFileDescriptor); + Set outputResourceNames = new HashSet<>(); + List services = + Parser.parseService(echoFileDescriptor, messageTypes, resourceNames, outputResourceNames); + Service echoProtoService = services.get(0); GapicClass clazz = ServiceSettingsClassComposer.instance().generate(echoProtoService, messageTypes); diff --git a/src/test/java/com/google/api/generator/gapic/composer/ServiceStubClassComposerTest.java b/src/test/java/com/google/api/generator/gapic/composer/ServiceStubClassComposerTest.java index 0422eda88a..f494c7afb4 100644 --- a/src/test/java/com/google/api/generator/gapic/composer/ServiceStubClassComposerTest.java +++ b/src/test/java/com/google/api/generator/gapic/composer/ServiceStubClassComposerTest.java @@ -19,13 +19,16 @@ import com.google.api.generator.engine.writer.JavaWriterVisitor; import com.google.api.generator.gapic.model.GapicClass; import com.google.api.generator.gapic.model.Message; +import com.google.api.generator.gapic.model.ResourceName; import com.google.api.generator.gapic.model.Service; import com.google.api.generator.gapic.protoparser.Parser; import com.google.protobuf.Descriptors.FileDescriptor; import com.google.protobuf.Descriptors.ServiceDescriptor; import com.google.showcase.v1beta1.EchoOuterClass; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import org.junit.Before; import org.junit.Test; @@ -43,7 +46,11 @@ public void setUp() { @Test public void generateServiceClasses() { Map messageTypes = Parser.parseMessages(echoFileDescriptor); - List services = Parser.parseService(echoFileDescriptor, messageTypes); + Map resourceNames = Parser.parseResourceNames(echoFileDescriptor); + Set outputResourceNames = new HashSet<>(); + List services = + Parser.parseService(echoFileDescriptor, messageTypes, resourceNames, outputResourceNames); + Service echoProtoService = services.get(0); GapicClass clazz = ServiceStubClassComposer.instance().generate(echoProtoService, messageTypes); diff --git a/src/test/java/com/google/api/generator/gapic/protoparser/BUILD.bazel b/src/test/java/com/google/api/generator/gapic/protoparser/BUILD.bazel index fa27517839..3694d5f68d 100644 --- a/src/test/java/com/google/api/generator/gapic/protoparser/BUILD.bazel +++ b/src/test/java/com/google/api/generator/gapic/protoparser/BUILD.bazel @@ -3,6 +3,7 @@ package(default_visibility = ["//visibility:public"]) TESTS = [ "ParserTest", "ResourceNameParserTest", + "ResourceReferenceParserTest", ] filegroup( diff --git a/src/test/java/com/google/api/generator/gapic/protoparser/ParserTest.java b/src/test/java/com/google/api/generator/gapic/protoparser/ParserTest.java index 7caaed4352..4f683cf8d7 100644 --- a/src/test/java/com/google/api/generator/gapic/protoparser/ParserTest.java +++ b/src/test/java/com/google/api/generator/gapic/protoparser/ParserTest.java @@ -36,8 +36,10 @@ import com.google.showcase.v1beta1.EchoOuterClass; import com.google.testgapic.v1beta1.LockerProto; import java.util.Arrays; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import org.junit.Before; import org.junit.Test; @@ -84,7 +86,12 @@ public void parseMessages_basic() { @Test public void parseMethods_basic() { Map messageTypes = Parser.parseMessages(echoFileDescriptor); - List methods = Parser.parseMethods(echoService, messageTypes); + Map resourceNames = Parser.parseResourceNames(echoFileDescriptor); + Set outputResourceNames = new HashSet<>(); + List methods = + Parser.parseMethods( + echoService, ECHO_PACKAGE, messageTypes, resourceNames, outputResourceNames); + assertEquals(methods.size(), 8); // Methods should appear in the same order as in the protobuf file. @@ -131,7 +138,12 @@ public void parseMethods_basic() { @Test public void parseMethods_basicLro() { Map messageTypes = Parser.parseMessages(echoFileDescriptor); - List methods = Parser.parseMethods(echoService, messageTypes); + Map resourceNames = Parser.parseResourceNames(echoFileDescriptor); + Set outputResourceNames = new HashSet<>(); + List methods = + Parser.parseMethods( + echoService, ECHO_PACKAGE, messageTypes, resourceNames, outputResourceNames); + assertEquals(methods.size(), 8); // Methods should appear in the same order as in the protobuf file. @@ -168,9 +180,20 @@ public void parseMethodSignatures_empty() { MethodDescriptor chatWithInfoMethodDescriptor = echoService.getMethods().get(5); TypeNode inputType = TypeParser.parseType(chatWithInfoMethodDescriptor.getInputType()); Map messageTypes = Parser.parseMessages(echoFileDescriptor); + Map resourceNames = Parser.parseResourceNames(echoFileDescriptor); + Set outputResourceNames = new HashSet<>(); + + List methods = + Parser.parseMethods( + echoService, ECHO_PACKAGE, messageTypes, resourceNames, outputResourceNames); assertThat( MethodSignatureParser.parseMethodSignatures( - chatWithInfoMethodDescriptor, inputType, messageTypes)) + chatWithInfoMethodDescriptor, + ECHO_PACKAGE, + inputType, + messageTypes, + resourceNames, + outputResourceNames)) .isEmpty(); } @@ -179,9 +202,17 @@ public void parseMethodSignatures_basic() { MethodDescriptor echoMethodDescriptor = echoService.getMethods().get(0); TypeNode inputType = TypeParser.parseType(echoMethodDescriptor.getInputType()); Map messageTypes = Parser.parseMessages(echoFileDescriptor); + Map resourceNames = Parser.parseResourceNames(echoFileDescriptor); + Set outputResourceNames = new HashSet<>(); List> methodSignatures = - MethodSignatureParser.parseMethodSignatures(echoMethodDescriptor, inputType, messageTypes); + MethodSignatureParser.parseMethodSignatures( + echoMethodDescriptor, + ECHO_PACKAGE, + inputType, + messageTypes, + resourceNames, + outputResourceNames); assertEquals(methodSignatures.size(), 3); // Signature contents: ["content"]. diff --git a/src/test/java/com/google/api/generator/gapic/protoparser/ResourceReferenceParserTest.java b/src/test/java/com/google/api/generator/gapic/protoparser/ResourceReferenceParserTest.java new file mode 100644 index 0000000000..ad9534d2f2 --- /dev/null +++ b/src/test/java/com/google/api/generator/gapic/protoparser/ResourceReferenceParserTest.java @@ -0,0 +1,175 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.api.generator.gapic.protoparser; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import com.google.api.generator.engine.ast.TypeNode; +import com.google.api.generator.engine.ast.VaporReference; +import com.google.api.generator.gapic.model.ResourceName; +import com.google.api.generator.gapic.utils.ResourceNameConstants; +import com.google.protobuf.Descriptors.FileDescriptor; +import com.google.protobuf.Descriptors.ServiceDescriptor; +import com.google.testgapic.v1beta1.LockerProto; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import org.junit.Before; +import org.junit.Test; + +public class ResourceReferenceParserTest { + private static final String MAIN_PACKAGE = "com.google.testgapic.v1beta1"; + + private ServiceDescriptor lockerService; + private FileDescriptor lockerServiceFileDescriptor; + + @Before + public void setUp() { + lockerServiceFileDescriptor = LockerProto.getDescriptor(); + lockerService = lockerServiceFileDescriptor.getServices().get(0); + } + + @Test + public void parseParentResourceName_createFromPattern() { + String resourceNamePackage = String.format("%s.common", MAIN_PACKAGE); + String domainName = "cloudbilling.googleapis.com"; + String resourceTypeString = String.format("%s/BillingAccount", domainName); + String parentResourceTypeString = String.format("%s/Project", domainName); + Map patternsToResourceNames = new HashMap<>(); + + String parentPattern = "projects/{project}"; + Optional parentResourceNameOpt = + ResourceReferenceParser.parseParentResourceName( + String.format("%s/billingAccounts/{billing_account}", parentPattern), + MAIN_PACKAGE, + resourceNamePackage, + resourceTypeString, + patternsToResourceNames); + assertTrue(parentResourceNameOpt.isPresent()); + + ResourceName parentResourceName = parentResourceNameOpt.get(); + assertEquals("project", parentResourceName.variableName()); + assertEquals(Arrays.asList(parentPattern), parentResourceName.patterns()); + assertEquals(parentResourceTypeString, parentResourceName.resourceTypeString()); + assertEquals(resourceNamePackage, parentResourceName.pakkage()); + assertEquals( + TypeNode.withReference( + VaporReference.builder().setName("Project").setPakkage(resourceNamePackage).build()), + parentResourceName.type()); + assertEquals(patternsToResourceNames.get(parentPattern), parentResourceName); + } + + @Test + public void parseParentResourceName_parentResourceNameExists() { + Map typeStringsToResourceNames = + ResourceNameParser.parseResourceNamesFromFile(lockerServiceFileDescriptor); + + Map patternsToResourceNames = new HashMap<>(); + for (ResourceName resourceName : typeStringsToResourceNames.values()) { + for (String pattern : resourceName.patterns()) { + patternsToResourceNames.put(pattern, resourceName); + } + } + + Optional parentResourceNameOpt = + ResourceReferenceParser.parseParentResourceName( + "projects/{project}/folders/{folder}/documents/{document}", + MAIN_PACKAGE, + MAIN_PACKAGE, + "cloudresourcemanager.googleapis.com/Document", + patternsToResourceNames); + + assertTrue(parentResourceNameOpt.isPresent()); + assertEquals( + typeStringsToResourceNames.get("cloudresourcemanager.googleapis.com/Folder"), + parentResourceNameOpt.get()); + } + + @Test + public void parseParentResourceName_badPattern() { + Optional parentResourceNameOpt = + ResourceReferenceParser.parseParentResourceName( + "projects/{project}/billingAccounts", + MAIN_PACKAGE, + MAIN_PACKAGE, + "cloudbilling.googleapis.com/Feature", + new HashMap()); + assertFalse(parentResourceNameOpt.isPresent()); + } + + @Test + public void parseParentPattern_basic() { + String parentPattern = "projects/{project}"; + String pattern = String.format("%s/folders/{folder}", parentPattern); + assertEquals(parentPattern, ResourceReferenceParser.parseParentPattern(pattern).get()); + } + + @Test + public void parseParentPattern_wildcard() { + Optional parentPatternOpt = + ResourceReferenceParser.parseParentPattern(ResourceNameConstants.WILDCARD_PATTERN); + assertFalse(parentPatternOpt.isPresent()); + } + + @Test + public void parseParentPattern_deletedTopicLiteral() { + Optional parentPatternOpt = + ResourceReferenceParser.parseParentPattern(ResourceNameConstants.DELETED_TOPIC_LITERAL); + assertFalse(parentPatternOpt.isPresent()); + } + + @Test + public void parseParentPattern_noParents() { + Optional parentPatternOpt = + ResourceReferenceParser.parseParentPattern("projects/{project}"); + assertFalse(parentPatternOpt.isPresent()); + } + + @Test + public void parseParentPattern_insufficientPathComponents() { + Optional parentPatternOpt = + ResourceReferenceParser.parseParentPattern("projects/foobars/{foobar}"); + assertFalse(parentPatternOpt.isPresent()); + } + + @Test + public void parseParentPattern_lastComponentIsNotAVariable() { + Optional parentPatternOpt = + ResourceReferenceParser.parseParentPattern("projects/{project}/foobars"); + assertFalse(parentPatternOpt.isPresent()); + } + + @Test + public void resolvePackages_resourcePackageIsSubpackageOfService() { + String resourcePackage = "com.google.testgapic.v1beta1.common"; + assertEquals( + resourcePackage, ResourceReferenceParser.resolvePackages(resourcePackage, MAIN_PACKAGE)); + } + + @Test + public void resolvePackages_resourcePackageIsSameAsService() { + String resourcePackage = "com.google.testgapic.v1beta1.common"; + assertEquals(MAIN_PACKAGE, ResourceReferenceParser.resolvePackages(MAIN_PACKAGE, MAIN_PACKAGE)); + } + + @Test + public void resolvePackages_resourcePackageIsNotSubpackageOfService() { + assertEquals( + MAIN_PACKAGE, ResourceReferenceParser.resolvePackages("com.google.cloud", MAIN_PACKAGE)); + } +}