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 555af4f435..a5ec9caacb 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 @@ -17,12 +17,19 @@ import com.google.api.generator.engine.ast.TypeNode; import com.google.auto.value.AutoValue; import java.util.Objects; +import java.util.Optional; import javax.annotation.Nullable; @AutoValue public abstract class Field { + // The field's canonical name, potentially post-processed by conflict resolution logic. public abstract String name(); + // The field's name as it appeared in the protobuf. + // Not equal to name() only when there is a field name conflict, as per protoc's conflict + // resolution behavior. For more context, please see the invocation site of the setter method. + public abstract String originalName(); + public abstract TypeNode type(); public abstract boolean isMessage(); @@ -43,6 +50,10 @@ public abstract class Field { @Nullable public abstract String description(); + public boolean hasFieldNameConflict() { + return !name().equals(originalName()); + } + public boolean hasDescription() { return description() != null; } @@ -59,6 +70,7 @@ public boolean equals(Object o) { Field other = (Field) o; return name().equals(other.name()) + && originalName().equals(other.originalName()) && type().equals(other.type()) && isMessage() == other.isMessage() && isEnum() == other.isEnum() @@ -73,6 +85,7 @@ && isProto3Optional() == other.isProto3Optional() @Override public int hashCode() { return 17 * name().hashCode() + + 31 * originalName().hashCode() + 19 * type().hashCode() + (isMessage() ? 1 : 0) * 23 + (isEnum() ? 1 : 0) * 29 @@ -100,6 +113,8 @@ public static Builder builder() { public abstract static class Builder { public abstract Builder setName(String name); + public abstract Builder setOriginalName(String originalName); + public abstract Builder setType(TypeNode type); public abstract Builder setIsMessage(boolean isMessage); @@ -118,6 +133,18 @@ public abstract static class Builder { public abstract Builder setDescription(String description); - public abstract Field build(); + // Private accessors. + abstract String name(); + + abstract Optional originalName(); + + abstract Field autoBuild(); + + public Field build() { + if (!originalName().isPresent()) { + setOriginalName(name()); + } + return autoBuild(); + } } } diff --git a/src/main/java/com/google/api/generator/gapic/model/Message.java b/src/main/java/com/google/api/generator/gapic/model/Message.java index 6bfcbb86aa..c58e62582e 100644 --- a/src/main/java/com/google/api/generator/gapic/model/Message.java +++ b/src/main/java/com/google/api/generator/gapic/model/Message.java @@ -140,11 +140,14 @@ public Builder setEnumValues(List names, List numbers) { public Message build() { Message message = autoBuild(); if (!message.fields().isEmpty()) { - message = - message - .toBuilder() - .setFieldMap(fields().stream().collect(Collectors.toMap(f -> f.name(), f -> f))) - .autoBuild(); + Map fieldMap = + fields().stream().collect(Collectors.toMap(f -> f.name(), f -> f)); + // Handles string occurrences of a field's original name in a protobuf, such as + // in the method signature annotaiton. + fields().stream() + .filter(f -> f.hasFieldNameConflict()) + .forEach(f -> fieldMap.put(f.originalName(), f)); + message = message.toBuilder().setFieldMap(fieldMap).autoBuild(); } return message; } 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 8ba197b7d1..3344141c4e 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 @@ -830,14 +830,47 @@ private static List parseFields( // Sort by ascending field index order. This is important for paged responses, where the first // repeated type is taken. fields.sort((f1, f2) -> f1.getIndex() - f2.getIndex()); + + // Mirror protoc's name conflict resolution behavior for fields. + // If a singular field's name equals that of a repeated field with "Count" or "List" suffixed, + // append the protobuf's field number to both fields' names. + // See: + // https://github.com/protocolbuffers/protobuf/blob/9df42757f97da9f748a464deeda96427a8f7ade0/src/google/protobuf/compiler/java/java_context.cc#L60 + Map repeatedFieldNamesToNumber = + fields.stream() + .filter(f -> f.isRepeated()) + .collect(Collectors.toMap(f -> f.getName(), f -> f.getNumber())); + Set fieldNumbersWithConflicts = new HashSet<>(); + for (FieldDescriptor field : fields) { + Set conflictingRepeatedFieldNames = + repeatedFieldNamesToNumber.keySet().stream() + .filter( + n -> field.getName().equals(n + "_count") || field.getName().equals(n + "_list")) + .collect(Collectors.toSet()); + if (!conflictingRepeatedFieldNames.isEmpty()) { + fieldNumbersWithConflicts.addAll( + conflictingRepeatedFieldNames.stream() + .map(n -> repeatedFieldNamesToNumber.get(n)) + .collect(Collectors.toSet())); + fieldNumbersWithConflicts.add(field.getNumber()); + } + } + return fields.stream() - .map(f -> parseField(f, messageDescriptor, outputResourceReferencesSeen)) + .map( + f -> + parseField( + f, + messageDescriptor, + fieldNumbersWithConflicts.contains(f.getNumber()), + outputResourceReferencesSeen)) .collect(Collectors.toList()); } private static Field parseField( FieldDescriptor fieldDescriptor, Descriptor messageDescriptor, + boolean hasFieldNameConflict, Set outputResourceReferencesSeen) { FieldOptions fieldOptions = fieldDescriptor.getOptions(); MessageOptions messageOptions = messageDescriptor.getOptions(); @@ -882,8 +915,16 @@ private static Field parseField( } } + // Mirror protoc's name conflict resolution behavior for fields. + // For more context, trace hasFieldNameConflict back to where it gets passed in above. + String actualFieldName = + hasFieldNameConflict + ? fieldDescriptor.getName() + fieldDescriptor.getNumber() + : fieldDescriptor.getName(); + return fieldBuilder - .setName(fieldDescriptor.getName()) + .setName(actualFieldName) + .setOriginalName(fieldDescriptor.getName()) .setType(TypeParser.parseType(fieldDescriptor)) .setIsMessage(fieldDescriptor.getJavaType() == FieldDescriptor.JavaType.MESSAGE) .setIsEnum(fieldDescriptor.getJavaType() == FieldDescriptor.JavaType.ENUM) diff --git a/src/test/java/com/google/api/generator/gapic/composer/common/BUILD.bazel b/src/test/java/com/google/api/generator/gapic/composer/common/BUILD.bazel index 1d8c68ad72..159c97d9b1 100644 --- a/src/test/java/com/google/api/generator/gapic/composer/common/BUILD.bazel +++ b/src/test/java/com/google/api/generator/gapic/composer/common/BUILD.bazel @@ -29,6 +29,7 @@ TEST_DEPS = [ "//src/main/java/com/google/api/generator/gapic/protoparser", "//src/main/java/com/google/api/generator/gapic/composer/defaultvalue", "//src/test/java/com/google/api/generator/gapic/testdata:deprecated_service_java_proto", + "//src/test/java/com/google/api/generator/gapic/testdata:bookshop_java_proto", "//src/test/java/com/google/api/generator/gapic/testdata:showcase_java_proto", "//src/test/java/com/google/api/generator/gapic/testdata:testgapic_java_proto", "@com_google_api_api_common//jar", diff --git a/src/test/java/com/google/api/generator/gapic/composer/common/ServiceClientClassComposerTest.java b/src/test/java/com/google/api/generator/gapic/composer/common/ServiceClientClassComposerTest.java index 3510efaf3f..b7767f41a6 100644 --- a/src/test/java/com/google/api/generator/gapic/composer/common/ServiceClientClassComposerTest.java +++ b/src/test/java/com/google/api/generator/gapic/composer/common/ServiceClientClassComposerTest.java @@ -62,8 +62,20 @@ public void generateServiceClasses_methodSignatureHasNestedFields() { JavaWriterVisitor visitor = new JavaWriterVisitor(); clazz.classDefinition().accept(visitor); Utils.saveCodegenToFile(this.getClass(), "IdentityClient.golden", visitor.write()); - Path goldenFilePath = - Paths.get(Utils.getGoldenDir(this.getClass()), "IdentityClient.golden"); + Path goldenFilePath = Paths.get(Utils.getGoldenDir(this.getClass()), "IdentityClient.golden"); + assertCodeEquals(goldenFilePath, visitor.write()); + } + + @Test + public void generateServiceClasses_bookshopNameConflicts() { + GapicContext context = TestProtoLoader.instance().parseBookshopService(); + Service protoService = context.services().get(0); + GapicClass clazz = ServiceClientClassComposer.instance().generate(context, protoService); + + JavaWriterVisitor visitor = new JavaWriterVisitor(); + clazz.classDefinition().accept(visitor); + Utils.saveCodegenToFile(this.getClass(), "BookshopClient.golden", visitor.write()); + Path goldenFilePath = Paths.get(Utils.getGoldenDir(this.getClass()), "BookshopClient.golden"); assertCodeEquals(goldenFilePath, visitor.write()); } } diff --git a/src/test/java/com/google/api/generator/gapic/composer/common/TestProtoLoader.java b/src/test/java/com/google/api/generator/gapic/composer/common/TestProtoLoader.java index c1be33ad25..fd4d5942a4 100644 --- a/src/test/java/com/google/api/generator/gapic/composer/common/TestProtoLoader.java +++ b/src/test/java/com/google/api/generator/gapic/composer/common/TestProtoLoader.java @@ -27,6 +27,7 @@ import com.google.api.generator.gapic.protoparser.BatchingSettingsConfigParser; import com.google.api.generator.gapic.protoparser.Parser; import com.google.api.generator.gapic.protoparser.ServiceConfigParser; +import com.google.bookshop.v1beta1.BookshopProto; import com.google.logging.v2.LogEntryProto; import com.google.logging.v2.LoggingConfigProto; import com.google.logging.v2.LoggingMetricsProto; @@ -51,8 +52,7 @@ public class TestProtoLoader { private static final TestProtoLoader INSTANCE = - new TestProtoLoader( - Transport.GRPC, "src/test/java/com/google/api/generator/gapic/testdata/"); + new TestProtoLoader(Transport.GRPC, "src/test/java/com/google/api/generator/gapic/testdata/"); private final String testFilesDirectory; private final Transport transport; @@ -93,6 +93,34 @@ public GapicContext parseDeprecatedService() { .build(); } + public GapicContext parseBookshopService() { + FileDescriptor fileDescriptor = BookshopProto.getDescriptor(); + ServiceDescriptor serviceDescriptor = fileDescriptor.getServices().get(0); + assertEquals(serviceDescriptor.getName(), "Bookshop"); + + Map messageTypes = Parser.parseMessages(fileDescriptor); + Map resourceNames = new HashMap<>(); + Set outputResourceNames = new HashSet<>(); + List services = + Parser.parseService( + fileDescriptor, messageTypes, resourceNames, Optional.empty(), outputResourceNames); + + String jsonFilename = "bookshop_grpc_service_config.json"; + Path jsonPath = Paths.get(testFilesDirectory, jsonFilename); + Optional configOpt = ServiceConfigParser.parse(jsonPath.toString()); + assertTrue(configOpt.isPresent()); + GapicServiceConfig config = configOpt.get(); + + return GapicContext.builder() + .setMessages(messageTypes) + .setResourceNames(resourceNames) + .setServices(services) + .setServiceConfig(config) + .setHelperResourceNames(outputResourceNames) + .setTransport(transport) + .build(); + } + public GapicContext parseShowcaseEcho() { FileDescriptor echoFileDescriptor = EchoOuterClass.getDescriptor(); ServiceDescriptor echoServiceDescriptor = echoFileDescriptor.getServices().get(0); diff --git a/src/test/java/com/google/api/generator/gapic/composer/common/goldens/BookshopClient.golden b/src/test/java/com/google/api/generator/gapic/composer/common/goldens/BookshopClient.golden new file mode 100644 index 0000000000..069bd7a634 --- /dev/null +++ b/src/test/java/com/google/api/generator/gapic/composer/common/goldens/BookshopClient.golden @@ -0,0 +1,242 @@ +package com.google.bookshop.v1beta1; + +import com.google.api.core.BetaApi; +import com.google.api.gax.core.BackgroundResource; +import com.google.api.gax.rpc.UnaryCallable; +import com.google.bookshop.v1beta1.stub.BookshopStub; +import com.google.bookshop.v1beta1.stub.BookshopStubSettings; +import java.io.IOException; +import java.util.List; +import java.util.concurrent.TimeUnit; +import javax.annotation.Generated; + +// AUTO-GENERATED DOCUMENTATION AND CLASS. +/** + * This class provides the ability to make remote calls to the backing service through method calls + * that map to API methods. Sample code to get started: + * + *
{@code
+ * try (BookshopClient bookshopClient = BookshopClient.create()) {
+ *   int booksCount = 1618425911;
+ *   List books = new ArrayList<>();
+ *   Book response = bookshopClient.getBook(booksCount, books);
+ * }
+ * }
+ * + *

Note: close() needs to be called on the BookshopClient object to clean up resources such as + * threads. In the example above, try-with-resources is used, which automatically calls close(). + * + *

The surface of this class includes several types of Java methods for each of the API's + * methods: + * + *

    + *
  1. A "flattened" method. With this type of method, the fields of the request type have been + * converted into function parameters. It may be the case that not all fields are available as + * parameters, and not every API method will have a flattened method entry point. + *
  2. A "request object" method. This type of method only takes one parameter, a request object, + * which must be constructed before the call. Not every API method will have a request object + * method. + *
  3. A "callable" method. This type of method takes no parameters and returns an immutable API + * callable object, which can be used to initiate calls to the service. + *
+ * + *

See the individual methods for example code. + * + *

Many parameters require resource names to be formatted in a particular way. To assist with + * these names, this class includes a format method for each type of name, and additionally a parse + * method to extract the individual identifiers contained within names that are returned. + * + *

This class can be customized by passing in a custom instance of BookshopSettings to create(). + * For example: + * + *

To customize credentials: + * + *

{@code
+ * BookshopSettings bookshopSettings =
+ *     BookshopSettings.newBuilder()
+ *         .setCredentialsProvider(FixedCredentialsProvider.create(myCredentials))
+ *         .build();
+ * BookshopClient bookshopClient = BookshopClient.create(bookshopSettings);
+ * }
+ * + *

To customize the endpoint: + * + *

{@code
+ * BookshopSettings bookshopSettings =
+ *     BookshopSettings.newBuilder().setEndpoint(myEndpoint).build();
+ * BookshopClient bookshopClient = BookshopClient.create(bookshopSettings);
+ * }
+ * + *

Please refer to the GitHub repository's samples for more quickstart code snippets. + */ +@BetaApi +@Generated("by gapic-generator-java") +public class BookshopClient implements BackgroundResource { + private final BookshopSettings settings; + private final BookshopStub stub; + + /** Constructs an instance of BookshopClient with default settings. */ + public static final BookshopClient create() throws IOException { + return create(BookshopSettings.newBuilder().build()); + } + + /** + * Constructs an instance of BookshopClient, using the given settings. The channels are created + * based on the settings passed in, or defaults for any settings that are not set. + */ + public static final BookshopClient create(BookshopSettings settings) throws IOException { + return new BookshopClient(settings); + } + + /** + * Constructs an instance of BookshopClient, using the given stub for making calls. This is for + * advanced usage - prefer using create(BookshopSettings). + */ + @BetaApi("A restructuring of stub classes is planned, so this may break in the future") + public static final BookshopClient create(BookshopStub stub) { + return new BookshopClient(stub); + } + + /** + * Constructs an instance of BookshopClient, using the given settings. This is protected so that + * it is easy to make a subclass, but otherwise, the static factory methods should be preferred. + */ + protected BookshopClient(BookshopSettings settings) throws IOException { + this.settings = settings; + this.stub = ((BookshopStubSettings) settings.getStubSettings()).createStub(); + } + + @BetaApi("A restructuring of stub classes is planned, so this may break in the future") + protected BookshopClient(BookshopStub stub) { + this.settings = null; + this.stub = stub; + } + + public final BookshopSettings getSettings() { + return settings; + } + + @BetaApi("A restructuring of stub classes is planned, so this may break in the future") + public BookshopStub getStub() { + return stub; + } + + // AUTO-GENERATED DOCUMENTATION AND METHOD. + /** + * Sample code: + * + *

{@code
+   * try (BookshopClient bookshopClient = BookshopClient.create()) {
+   *   int booksCount = 1618425911;
+   *   List books = new ArrayList<>();
+   *   Book response = bookshopClient.getBook(booksCount, books);
+   * }
+   * }
+ * + * @param booksCount + * @param books + * @throws com.google.api.gax.rpc.ApiException if the remote call fails + */ + public final Book getBook(int booksCount, List books) { + GetBookRequest request = + GetBookRequest.newBuilder().setBooksCount(booksCount).addAllBooks(books).build(); + return getBook(request); + } + + // AUTO-GENERATED DOCUMENTATION AND METHOD. + /** + * Sample code: + * + *
{@code
+   * try (BookshopClient bookshopClient = BookshopClient.create()) {
+   *   String booksList = "booksList2-1119589686";
+   *   List books = new ArrayList<>();
+   *   Book response = bookshopClient.getBook(booksList, books);
+   * }
+   * }
+ * + * @param booksList + * @param books + * @throws com.google.api.gax.rpc.ApiException if the remote call fails + */ + public final Book getBook(String booksList, List books) { + GetBookRequest request = + GetBookRequest.newBuilder().setBooksList(booksList).addAllBooks(books).build(); + return getBook(request); + } + + // AUTO-GENERATED DOCUMENTATION AND METHOD. + /** + * Sample code: + * + *
{@code
+   * try (BookshopClient bookshopClient = BookshopClient.create()) {
+   *   GetBookRequest request =
+   *       GetBookRequest.newBuilder()
+   *           .setBooksCount1(1618425911)
+   *           .setBooksList2("booksList2-1119589686")
+   *           .addAllBooks3(new ArrayList())
+   *           .build();
+   *   Book response = bookshopClient.getBook(request);
+   * }
+   * }
+ * + * @param request The request object containing all of the parameters for the API call. + * @throws com.google.api.gax.rpc.ApiException if the remote call fails + */ + public final Book getBook(GetBookRequest request) { + return getBookCallable().call(request); + } + + // AUTO-GENERATED DOCUMENTATION AND METHOD. + /** + * Sample code: + * + *
{@code
+   * try (BookshopClient bookshopClient = BookshopClient.create()) {
+   *   GetBookRequest request =
+   *       GetBookRequest.newBuilder()
+   *           .setBooksCount1(1618425911)
+   *           .setBooksList2("booksList2-1119589686")
+   *           .addAllBooks3(new ArrayList())
+   *           .build();
+   *   ApiFuture future = bookshopClient.getBookCallable().futureCall(request);
+   *   // Do something.
+   *   Book response = future.get();
+   * }
+   * }
+ */ + public final UnaryCallable getBookCallable() { + return stub.getBookCallable(); + } + + @Override + public final void close() { + stub.close(); + } + + @Override + public void shutdown() { + stub.shutdown(); + } + + @Override + public boolean isShutdown() { + return stub.isShutdown(); + } + + @Override + public boolean isTerminated() { + return stub.isTerminated(); + } + + @Override + public void shutdownNow() { + stub.shutdownNow(); + } + + @Override + public boolean awaitTermination(long duration, TimeUnit unit) throws InterruptedException { + return stub.awaitTermination(duration, unit); + } +} 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 abf6a5e00a..f15a3d1a6e 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 @@ -41,6 +41,7 @@ filegroup( "//src/main/java/com/google/api/generator/gapic/model", "//src/main/java/com/google/api/generator/gapic/protoparser", "//src/main/java/com/google/api/generator/gapic/utils", + "//src/test/java/com/google/api/generator/gapic/testdata:bookshop_java_proto", "//src/test/java/com/google/api/generator/gapic/testdata:showcase_java_proto", "//src/test/java/com/google/api/generator/gapic/testdata:testgapic_java_proto", "@com_google_api_api_common//jar", 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 337f11c2eb..d9a31c9fd4 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 @@ -31,6 +31,7 @@ import com.google.api.generator.gapic.model.ResourceName; import com.google.api.generator.gapic.model.ResourceReference; import com.google.api.generator.gapic.model.Transport; +import com.google.bookshop.v1beta1.BookshopProto; import com.google.common.collect.ImmutableList; import com.google.protobuf.Descriptors.FileDescriptor; import com.google.protobuf.Descriptors.MethodDescriptor; @@ -97,6 +98,17 @@ public void parseMessages_basic() { echoResponseMessage, messageTypes.get("com.google.showcase.v1beta1." + echoResponseName)); } + @Test + public void parseMessages_fieldNameConflicts() { + FileDescriptor bookshopFileDescriptor = BookshopProto.getDescriptor(); + Map messageTypes = Parser.parseMessages(bookshopFileDescriptor); + Message requestMessage = messageTypes.get("com.google.bookshop.v1beta1.GetBookRequest"); + // Check that field names have been changed. + assertThat(requestMessage.fieldMap()).containsKey("books_count1"); + assertThat(requestMessage.fieldMap()).containsKey("books_list2"); + assertThat(requestMessage.fieldMap()).containsKey("books3"); + } + @Test public void parseMethods_basic() { Map messageTypes = Parser.parseMessages(echoFileDescriptor); diff --git a/src/test/java/com/google/api/generator/gapic/testdata/BUILD.bazel b/src/test/java/com/google/api/generator/gapic/testdata/BUILD.bazel index ba75a8f62b..1a5133b74c 100644 --- a/src/test/java/com/google/api/generator/gapic/testdata/BUILD.bazel +++ b/src/test/java/com/google/api/generator/gapic/testdata/BUILD.bazel @@ -33,13 +33,23 @@ genrule( ], ) +proto_library( + name = "bookshop_proto", + srcs = ["bookshop.proto"], + deps = [ + "@com_google_googleapis//google/api:annotations_proto", + "@com_google_googleapis//google/api:client_proto", + "@com_google_googleapis//google/api:field_behavior_proto", + ], +) + proto_library( name = "showcase_proto", srcs = [ + "compliance.proto", "echo.proto", "identity.proto", "testing.proto", - "compliance.proto", ], deps = [ "@com_google_googleapis//google/api:annotations_proto", @@ -101,6 +111,11 @@ java_proto_library( deps = [":deprecated_service_proto"], ) +java_proto_library( + name = "bookshop_java_proto", + deps = [":bookshop_proto"], +) + java_proto_library( name = "showcase_java_proto", deps = [":showcase_proto"], diff --git a/src/test/java/com/google/api/generator/gapic/testdata/bookshop.proto b/src/test/java/com/google/api/generator/gapic/testdata/bookshop.proto new file mode 100644 index 0000000000..0498b45430 --- /dev/null +++ b/src/test/java/com/google/api/generator/gapic/testdata/bookshop.proto @@ -0,0 +1,61 @@ +// Copyright 2021 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 +// +// https://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. + +syntax = "proto3"; + +import "google/api/annotations.proto"; +import "google/api/client.proto"; +import "google/api/field_behavior.proto"; + +package google.bookshop.v1beta1; + +option go_package = "github.com/googleapis/gapic-showcase/server/genproto"; +option java_package = "com.google.bookshop.v1beta1"; +option java_outer_classname = "BookshopProto"; +option java_multiple_files = true; + +// Exercises name conflict behavior in Java. +service Bookshop { + // This service is meant to only run locally on the port 7469 (keypad digits + // for "book"). + option (google.api.default_host) = "localhost:2665"; + option (google.api.oauth_scopes) = + "https://www.googleapis.com/auth/cloud-platform"; + + // This method simply echos the request. This method is showcases unary rpcs. + rpc GetBook(GetBookRequest) returns (Book) { + option (google.api.http) = { + post: "/v1beta1/echo:echo" + body: "*" + }; + option (google.api.method_signature) = "books_count,books"; + option (google.api.method_signature) = "books_list,books"; + } +} + +message GetBookRequest { + // The number of books. + int32 books_count = 1; + + // The name of the book list. + string books_list = 2; + + // The books. + repeated Book books = 3; +} + +message Book { + // The bookk title. + string title = 1; +} diff --git a/src/test/java/com/google/api/generator/gapic/testdata/bookshop_grpc_service_config.json b/src/test/java/com/google/api/generator/gapic/testdata/bookshop_grpc_service_config.json new file mode 100644 index 0000000000..4604425cb0 --- /dev/null +++ b/src/test/java/com/google/api/generator/gapic/testdata/bookshop_grpc_service_config.json @@ -0,0 +1,8 @@ +{ + "methodConfig": [ + { + "name": [{"service": "google.bookshop.v1beta1.Bookshop"}], + "timeout": "60s" + } + ] +}