diff --git a/BUILD.bazel b/BUILD.bazel index ba95d15151..b5ac9a7d74 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1,11 +1,11 @@ -package(default_visibility = ["//visibility:public"]) - load( "//:gapic_generator_java.bzl", "google_java_format", "google_java_format_verification", ) +package(default_visibility = ["//visibility:public"]) + JAVA_SRCS = [ "//src/main/java/com/google/api/generator:generator_files", "//src/main/java/com/google/api/generator/engine:engine_files", diff --git a/src/main/java/com/google/api/generator/gapic/composer/DefaultValueComposer.java b/src/main/java/com/google/api/generator/gapic/composer/DefaultValueComposer.java index f53123c4c0..a01c898e87 100644 --- a/src/main/java/com/google/api/generator/gapic/composer/DefaultValueComposer.java +++ b/src/main/java/com/google/api/generator/gapic/composer/DefaultValueComposer.java @@ -23,6 +23,7 @@ import com.google.api.generator.engine.ast.TypeNode; import com.google.api.generator.engine.ast.ValueExpr; 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.api.generator.gapic.utils.JavaStyle; @@ -186,4 +187,54 @@ static Expr createDefaultValue(ResourceName resourceName, List res .setReturnType(resourceNameJavaType) .build(); } + + static Expr createSimpleMessageBuilderExpr( + Message message, Map resourceNames, Map messageTypes) { + MethodInvocationExpr builderExpr = + MethodInvocationExpr.builder() + .setStaticReferenceType(message.type()) + .setMethodName("newBuilder") + .build(); + for (Field field : message.fields()) { + if (field.isContainedInOneof() // Avoid colliding fields. + || ((field.isMessage() || field.isEnum()) // Avoid importing unparsed messages. + && !field.isRepeated() + && !messageTypes.containsKey(field.type().reference().name()))) { + continue; + } + String setterMethodNamePattern = "set%s"; + if (field.isRepeated()) { + setterMethodNamePattern = field.isMap() ? "putAll%s" : "addAll%s"; + } + Expr defaultExpr = null; + if (field.hasResourceReference() + && resourceNames.get(field.resourceReference().resourceTypeString()) != null) { + defaultExpr = + createDefaultValue( + resourceNames.get(field.resourceReference().resourceTypeString()), + resourceNames.values().stream().collect(Collectors.toList())); + defaultExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(defaultExpr) + .setMethodName("toString") + .setReturnType(TypeNode.STRING) + .build(); + } else { + defaultExpr = createDefaultValue(field); + } + builderExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(builderExpr) + .setMethodName( + String.format(setterMethodNamePattern, JavaStyle.toUpperCamelCase(field.name()))) + .setArguments(defaultExpr) + .build(); + } + + return MethodInvocationExpr.builder() + .setExprReferenceExpr(builderExpr) + .setMethodName("build") + .setReturnType(message.type()) + .build(); + } } diff --git a/src/main/java/com/google/api/generator/gapic/composer/ServiceClientTestClassComposer.java b/src/main/java/com/google/api/generator/gapic/composer/ServiceClientTestClassComposer.java index 3634f74d7e..3b3902b2ab 100644 --- a/src/main/java/com/google/api/generator/gapic/composer/ServiceClientTestClassComposer.java +++ b/src/main/java/com/google/api/generator/gapic/composer/ServiceClientTestClassComposer.java @@ -32,6 +32,7 @@ import com.google.api.gax.rpc.UnaryCallSettings; import com.google.api.generator.engine.ast.AnnotationNode; import com.google.api.generator.engine.ast.AssignmentExpr; +import com.google.api.generator.engine.ast.CastExpr; import com.google.api.generator.engine.ast.ClassDefinition; import com.google.api.generator.engine.ast.CommentStatement; import com.google.api.generator.engine.ast.ConcreteReference; @@ -69,6 +70,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; @@ -403,15 +405,26 @@ private static List createTestMethods( Map messageTypes) { List javaMethods = new ArrayList<>(); for (Method method : service.methods()) { - for (int i = 0; i < method.methodSignatures().size(); i++) { + if (method.methodSignatures().isEmpty()) { javaMethods.add( createRpcExceptionTestMethod( method, - method.methodSignatures().get(i), - i, + Collections.emptyList(), + 0, classMemberVarExprs, resourceNames, messageTypes)); + } else { + for (int i = 0; i < method.methodSignatures().size(); i++) { + javaMethods.add( + createRpcExceptionTestMethod( + method, + method.methodSignatures().get(i), + i, + classMemberVarExprs, + resourceNames, + messageTypes)); + } } } return javaMethods; @@ -462,25 +475,74 @@ private static MethodDefinition createRpcExceptionTestMethod( List argVarExprs = new ArrayList<>(); List tryBodyExprs = new ArrayList<>(); - for (MethodArgument methodArg : methodSignature) { + if (methodSignature.isEmpty()) { + // Construct the actual request. VariableExpr varExpr = VariableExpr.withVariable( - Variable.builder().setType(methodArg.type()).setName(methodArg.name()).build()); + Variable.builder().setType(method.inputType()).setName("request").build()); argVarExprs.add(varExpr); - Expr valExpr = DefaultValueComposer.createDefaultValue(methodArg, resourceNames); + Message requestMessage = messageTypes.get(method.inputType().reference().name()); + Preconditions.checkNotNull(requestMessage); + Expr valExpr = + DefaultValueComposer.createSimpleMessageBuilderExpr( + requestMessage, resourceNames, messageTypes); tryBodyExprs.add( AssignmentExpr.builder() .setVariableExpr(varExpr.toBuilder().setIsDecl(true).build()) .setValueExpr(valExpr) .build()); - // TODO(miraleung): Empty line here. + } else { + for (MethodArgument methodArg : methodSignature) { + VariableExpr varExpr = + VariableExpr.withVariable( + Variable.builder().setType(methodArg.type()).setName(methodArg.name()).build()); + argVarExprs.add(varExpr); + Expr valExpr = DefaultValueComposer.createDefaultValue(methodArg, resourceNames); + tryBodyExprs.add( + AssignmentExpr.builder() + .setVariableExpr(varExpr.toBuilder().setIsDecl(true).build()) + .setValueExpr(valExpr) + .build()); + // TODO(miraleung): Empty line here. + } } - tryBodyExprs.add( + String rpcJavaName = JavaStyle.toLowerCamelCase(method.name()); + if (method.hasLro()) { + rpcJavaName += "Async"; + } + MethodInvocationExpr rpcJavaMethodInvocationExpr = MethodInvocationExpr.builder() .setExprReferenceExpr(classMemberVarExprs.get("client")) - .setMethodName(JavaStyle.toLowerCamelCase(method.name())) + .setMethodName(rpcJavaName) .setArguments(argVarExprs.stream().map(e -> (Expr) e).collect(Collectors.toList())) - .build()); + .build(); + if (method.hasLro()) { + rpcJavaMethodInvocationExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(rpcJavaMethodInvocationExpr) + .setMethodName("get") + .build(); + } + tryBodyExprs.add(rpcJavaMethodInvocationExpr); + + VariableExpr catchExceptionVarExpr = + VariableExpr.builder() + .setVariable( + Variable.builder() + .setType( + TypeNode.withExceptionClazz( + method.hasLro() + ? ExecutionException.class + : InvalidArgumentException.class)) + .setName("e") + .build()) + .build(); + + List catchBody = + method.hasLro() + ? createRpcLroExceptionTestCatchBody(catchExceptionVarExpr) + : Arrays.asList( + CommentStatement.withComment(LineComment.withComment("Expected exception."))); // Assert a failure if no exception was raised. tryBodyExprs.add( @@ -496,18 +558,8 @@ private static MethodDefinition createRpcExceptionTestMethod( tryBodyExprs.stream() .map(e -> ExprStatement.withExpr(e)) .collect(Collectors.toList())) - .setCatchVariableExpr( - VariableExpr.builder() - .setVariable( - Variable.builder() - .setType(TypeNode.withExceptionClazz(InvalidArgumentException.class)) - .setName("e") - .build()) - .setIsDecl(true) - .build()) - .setCatchBody( - Arrays.asList( - CommentStatement.withComment(LineComment.withComment("Expected exception.")))) + .setCatchVariableExpr(catchExceptionVarExpr.toBuilder().setIsDecl(true).build()) + .setCatchBody(catchBody) .build(); return MethodDefinition.builder() @@ -524,6 +576,87 @@ private static MethodDefinition createRpcExceptionTestMethod( .build(); } + private static List createRpcLroExceptionTestCatchBody(VariableExpr exceptionExpr) { + List catchBodyExprs = new ArrayList<>(); + + Expr testExpectedValueExpr = + VariableExpr.builder() + .setVariable( + Variable.builder() + .setType(TypeNode.withReference(ConcreteReference.withClazz(Class.class))) + .setName("class") + .build()) + .setStaticReferenceType(STATIC_TYPES.get("InvalidArgumentException")) + .build(); + Expr getCauseExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(exceptionExpr) + .setMethodName("getCause") + .setReturnType(TypeNode.withReference(ConcreteReference.withClazz(Throwable.class))) + .build(); + Expr testActualValueExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(getCauseExpr) + .setMethodName("getClass") + .build(); + + // Constructs `Assert.assertEquals(InvalidArgumentException.class, e.getCaus().getClass());`. + catchBodyExprs.add( + MethodInvocationExpr.builder() + .setStaticReferenceType(STATIC_TYPES.get("Assert")) + .setMethodName("assertEquals") + .setArguments(testExpectedValueExpr, testActualValueExpr) + .build()); + + // Construct the apiException variable. + VariableExpr apiExceptionVarExpr = + VariableExpr.withVariable( + Variable.builder() + .setType(STATIC_TYPES.get("InvalidArgumentException")) + .setName("apiException") + .build()); + Expr castedCauseExpr = + CastExpr.builder() + .setType(STATIC_TYPES.get("InvalidArgumentException")) + .setExpr(getCauseExpr) + .build(); + catchBodyExprs.add( + AssignmentExpr.builder() + .setVariableExpr(apiExceptionVarExpr.toBuilder().setIsDecl(true).build()) + .setValueExpr(castedCauseExpr) + .build()); + + // Construct the last assert statement. + testExpectedValueExpr = + EnumRefExpr.builder() + .setType( + TypeNode.withReference( + ConcreteReference.builder() + .setClazz(StatusCode.Code.class) + .setIsStaticImport(false) + .build())) + .setName("INVALID_ARGUMENT") + .build(); + testActualValueExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(apiExceptionVarExpr) + .setMethodName("getStatusCode") + .build(); + testActualValueExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(testActualValueExpr) + .setMethodName("getCode") + .build(); + catchBodyExprs.add( + MethodInvocationExpr.builder() + .setStaticReferenceType(STATIC_TYPES.get("Assert")) + .setMethodName("assertEquals") + .setArguments(testExpectedValueExpr, testActualValueExpr) + .build()); + + return catchBodyExprs.stream().map(e -> ExprStatement.withExpr(e)).collect(Collectors.toList()); + } + /* ========================================= * Type creator methods. * ========================================= diff --git a/src/main/java/com/google/api/generator/gapic/model/Field.java b/src/main/java/com/google/api/generator/gapic/model/Field.java index 2008d8ab6d..890c7043a0 100644 --- a/src/main/java/com/google/api/generator/gapic/model/Field.java +++ b/src/main/java/com/google/api/generator/gapic/model/Field.java @@ -32,6 +32,8 @@ public abstract class Field { public abstract boolean isMap(); + public abstract boolean isContainedInOneof(); + @Nullable public abstract ResourceReference resourceReference(); @@ -51,7 +53,8 @@ public static Builder builder() { .setIsMessage(false) .setIsEnum(false) .setIsRepeated(false) - .setIsMap(false); + .setIsMap(false) + .setIsContainedInOneof(false); } @AutoValue.Builder @@ -68,6 +71,8 @@ public abstract static class Builder { public abstract Builder setIsMap(boolean isMap); + public abstract Builder setIsContainedInOneof(boolean isContainedInOneof); + public abstract Builder setResourceReference(ResourceReference resourceReference); public abstract Builder setDescription(String description); 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 5c9813198d..f667402210 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 @@ -404,6 +404,7 @@ private static Field parseField(FieldDescriptor fieldDescriptor, Descriptor mess .setType(TypeParser.parseType(fieldDescriptor)) .setIsMessage(fieldDescriptor.getJavaType() == FieldDescriptor.JavaType.MESSAGE) .setIsEnum(fieldDescriptor.getJavaType() == FieldDescriptor.JavaType.ENUM) + .setIsContainedInOneof(fieldDescriptor.getContainingOneof() != null) .setIsRepeated(fieldDescriptor.isRepeated()) .setIsMap(fieldDescriptor.isMapField()) .setResourceReference(resourceReference) diff --git a/src/test/java/com/google/api/generator/gapic/composer/DefaultValueComposerTest.java b/src/test/java/com/google/api/generator/gapic/composer/DefaultValueComposerTest.java index bb0083f013..65371700bc 100644 --- a/src/test/java/com/google/api/generator/gapic/composer/DefaultValueComposerTest.java +++ b/src/test/java/com/google/api/generator/gapic/composer/DefaultValueComposerTest.java @@ -21,9 +21,11 @@ import com.google.api.generator.engine.ast.TypeNode; import com.google.api.generator.engine.writer.JavaWriterVisitor; import com.google.api.generator.gapic.model.Field; +import com.google.api.generator.gapic.model.Message; import com.google.api.generator.gapic.model.ResourceName; import com.google.api.generator.gapic.protoparser.Parser; import com.google.protobuf.Descriptors.FileDescriptor; +import com.google.showcase.v1beta1.EchoOuterClass; import com.google.testgapic.v1beta1.LockerProto; import java.util.Collections; import java.util.Map; @@ -208,4 +210,71 @@ public void invalidDefaultValue_resourceNameWithOnlyWildcards() { IllegalStateException.class, () -> DefaultValueComposer.createDefaultValue(resourceName, Collections.emptyList())); } + + @Test + public void createSimpleMessage_basicPrimitivesOnly() { + FileDescriptor echoFileDescriptor = EchoOuterClass.getDescriptor(); + Map messageTypes = Parser.parseMessages(echoFileDescriptor); + Map typeStringsToResourceNames = + Parser.parseResourceNames(echoFileDescriptor); + Message message = messageTypes.get("Foobar"); + Expr expr = + DefaultValueComposer.createSimpleMessageBuilderExpr( + message, typeStringsToResourceNames, messageTypes); + expr.accept(writerVisitor); + assertEquals( + "Foobar.newBuilder().setName(FoobarName.ofProjectFoobarName(\"[PROJECT]\", \"[FOOBAR]\")" + + ".toString()).setInfo(\"info3237038\").build()", + writerVisitor.write()); + } + + @Test + public void createSimpleMessage_containsMessagesEnumsAndResourceName() { + FileDescriptor echoFileDescriptor = EchoOuterClass.getDescriptor(); + Map messageTypes = Parser.parseMessages(echoFileDescriptor); + Map typeStringsToResourceNames = + Parser.parseResourceNames(echoFileDescriptor); + Message message = messageTypes.get("EchoRequest"); + Expr expr = + DefaultValueComposer.createSimpleMessageBuilderExpr( + message, typeStringsToResourceNames, messageTypes); + expr.accept(writerVisitor); + assertEquals( + "EchoRequest.newBuilder().setName(" + + "FoobarName.ofProjectFoobarName(\"[PROJECT]\", \"[FOOBAR]\").toString())" + + ".setParent(FoobarName.ofProjectFoobarName(\"[PROJECT]\", \"[FOOBAR]\").toString())" + + ".setFoobar(Foobar.newBuilder().build()).build()", + writerVisitor.write()); + } + + @Test + public void createSimpleMessage_containsRepeatedField() { + FileDescriptor echoFileDescriptor = EchoOuterClass.getDescriptor(); + Map messageTypes = Parser.parseMessages(echoFileDescriptor); + Map typeStringsToResourceNames = + Parser.parseResourceNames(echoFileDescriptor); + Message message = messageTypes.get("PagedExpandResponse"); + Expr expr = + DefaultValueComposer.createSimpleMessageBuilderExpr( + message, typeStringsToResourceNames, messageTypes); + expr.accept(writerVisitor); + assertEquals( + "PagedExpandResponse.newBuilder().addAllResponses(new" + + " ArrayList<>()).setNextPageToken(\"next_page_token-1530815211\").build()", + writerVisitor.write()); + } + + @Test + public void createSimpleMessage_onlyOneofs() { + FileDescriptor echoFileDescriptor = EchoOuterClass.getDescriptor(); + Map messageTypes = Parser.parseMessages(echoFileDescriptor); + Map typeStringsToResourceNames = + Parser.parseResourceNames(echoFileDescriptor); + Message message = messageTypes.get("WaitRequest"); + Expr expr = + DefaultValueComposer.createSimpleMessageBuilderExpr( + message, typeStringsToResourceNames, messageTypes); + expr.accept(writerVisitor); + assertEquals("WaitRequest.newBuilder().build()", writerVisitor.write()); + } } 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 a096271069..47619ade83 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 @@ -70,12 +70,14 @@ public void generateServiceClasses() { + "import com.google.api.gax.grpc.testing.MockGrpcService;\n" + "import com.google.api.gax.grpc.testing.MockServiceHelper;\n" + "import com.google.api.gax.rpc.InvalidArgumentException;\n" + + "import com.google.api.gax.rpc.StatusCode;\n" + "import com.google.api.resourcenames.ResourceName;\n" + "import com.google.rpc.Status;\n" + "import io.grpc.StatusRuntimeException;\n" + "import java.io.IOException;\n" + "import java.util.Arrays;\n" + "import java.util.UUID;\n" + + "import java.util.concurrent.ExecutionException;\n" + "import javax.annotation.Generated;\n" + "import org.junit.After;\n" + "import org.junit.AfterClass;\n" @@ -254,6 +256,27 @@ public void generateServiceClasses() { + " }\n" + "\n" + " @Test\n" + + " public void chatExceptionTest() throws Exception {\n" + + " StatusRuntimeException exception = new" + + " StatusRuntimeException(io.grpc.Status.INVALID_ARGUMENT);\n" + + " addException(exception);\n" + + " try {\n" + + " EchoRequest request =\n" + + " EchoRequest.newBuilder()\n" + + " .setName(FoobarName.ofProjectFoobarName(\"[PROJECT]\"," + + " \"[FOOBAR]\").toString())\n" + + " .setParent(FoobarName.ofProjectFoobarName(\"[PROJECT]\"," + + " \"[FOOBAR]\").toString())\n" + + " .setFoobar(Foobar.newBuilder().build())\n" + + " .build();\n" + + " client.chat(request);\n" + + " Assert.fail(\"No exception raised\");\n" + + " } catch (InvalidArgumentException e) {\n" + + " // Expected exception.\n" + + " }\n" + + " }\n" + + "\n" + + " @Test\n" + " public void chatAgainExceptionTest() throws Exception {\n" + " StatusRuntimeException exception = new" + " StatusRuntimeException(io.grpc.Status.INVALID_ARGUMENT);\n" @@ -266,5 +289,56 @@ public void generateServiceClasses() { + " // Expected exception.\n" + " }\n" + " }\n" + + "\n" + + " @Test\n" + + " public void pagedExpandExceptionTest() throws Exception {\n" + + " StatusRuntimeException exception = new" + + " StatusRuntimeException(io.grpc.Status.INVALID_ARGUMENT);\n" + + " addException(exception);\n" + + " try {\n" + + " PagedExpandRequest request =\n" + + " PagedExpandRequest.newBuilder()\n" + + " .setContent(\"content951530617\")\n" + + " .setPageSize(883849137)\n" + + " .setPageToken(\"page_token1630607433\")\n" + + " .build();\n" + + " client.pagedExpand(request);\n" + + " Assert.fail(\"No exception raised\");\n" + + " } catch (InvalidArgumentException e) {\n" + + " // Expected exception.\n" + + " }\n" + + " }\n" + + "\n" + + " @Test\n" + + " public void waitExceptionTest() throws Exception {\n" + + " StatusRuntimeException exception = new" + + " StatusRuntimeException(io.grpc.Status.INVALID_ARGUMENT);\n" + + " addException(exception);\n" + + " try {\n" + + " WaitRequest request = WaitRequest.newBuilder().build();\n" + + " client.waitAsync(request).get();\n" + + " Assert.fail(\"No exception raised\");\n" + + " } catch (ExecutionException e) {\n" + + " Assert.assertEquals(InvalidArgumentException.class, e.getCause().getClass());\n" + + " InvalidArgumentException apiException = ((InvalidArgumentException)" + + " e.getCause());\n" + + " Assert.assertEquals(StatusCode.Code.INVALID_ARGUMENT," + + " apiException.getStatusCode().getCode());\n" + + " }\n" + + " }\n" + + "\n" + + " @Test\n" + + " public void blockExceptionTest() throws Exception {\n" + + " StatusRuntimeException exception = new" + + " StatusRuntimeException(io.grpc.Status.INVALID_ARGUMENT);\n" + + " addException(exception);\n" + + " try {\n" + + " BlockRequest request = BlockRequest.newBuilder().build();\n" + + " client.block(request);\n" + + " Assert.fail(\"No exception raised\");\n" + + " } catch (InvalidArgumentException e) {\n" + + " // Expected exception.\n" + + " }\n" + + " }\n" + "}\n"; }