diff --git a/.gitignore b/.gitignore index 241e3aa3b52c..b1e73de644f0 100644 --- a/.gitignore +++ b/.gitignore @@ -58,6 +58,7 @@ nosetests.xml .settings .DS_Store .classpath +.vscode # API key file containing value of GOOGLE_API_KEY for integration tests api_key @@ -79,4 +80,4 @@ monorepo *.tfstate.*.backup *.tfstate.lock.info -.jqwik-database \ No newline at end of file +.jqwik-database diff --git a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/rest/HttpJsonServiceStubClassComposer.java b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/rest/HttpJsonServiceStubClassComposer.java index 45ed87eeb0b5..cf228010fc9e 100644 --- a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/rest/HttpJsonServiceStubClassComposer.java +++ b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/rest/HttpJsonServiceStubClassComposer.java @@ -62,6 +62,7 @@ import com.google.api.generator.gapic.model.Service; import com.google.api.generator.gapic.utils.JavaStyle; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Strings; import com.google.common.collect.BiMap; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -763,7 +764,16 @@ private Expr createBodyFieldsExtractorClassInstance( // Handle foo.bar cases by descending into the subfields. MethodInvocationExpr.Builder requestFieldMethodExprBuilder = MethodInvocationExpr.builder().setExprReferenceExpr(prevExpr); - bodyParamName = JavaStyle.toLowerCamelCase(httpBindingFieldName.name()); + // Use explicit json_name if defined in the proto, prioritizing the actual wire name + // over Java-escaped identifiers. Note that trailing underscores (e.g., 'case_') result from: + // 1. protoc-gen-java: + // https://github.com/protocolbuffers/protobuf/blob/cecbbf41e43634c7c5b940dd336aa81b31fd4e5d/src/google/protobuf/compiler/java/names.cc#L189-L195 + // 2. gapic-generator-java Keyword implementation: + // com/google/api/generator/engine/lexicon/Keyword.java#L92-L94 + bodyParamName = + !Strings.isNullOrEmpty(httpBindingFieldName.jsonName()) + ? httpBindingFieldName.jsonName() + : JavaStyle.toLowerCamelCase(httpBindingFieldName.name()); String[] descendantFields = httpBindingFieldName.name().split("\\."); if (asteriskBody && descendantFields.length > 1) { // This is the `body: "*"` case, do not clean nested body fields as it a very rare, not @@ -926,7 +936,12 @@ private Expr createFieldsExtractorClassInstance( paramsPutArgs.add( ValueExpr.withValue( StringObjectValue.withValue( - JavaStyle.toLowerCamelCase(httpBindingFieldName.name())))); + // Use explicit json_name if defined in the proto, prioritizing the actual wire + // name over Java-escaped identifiers (e.g., avoiding 'case_' generated to prevent + // keywords conflict). + (httpBindingFieldName.jsonName() != null) + ? httpBindingFieldName.jsonName() + : JavaStyle.toLowerCamelCase(httpBindingFieldName.name())))); paramsPutArgs.add(requestBuilderExpr); Expr paramsPutExpr = diff --git a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/model/Field.java b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/model/Field.java index 39213909de01..ca2a98b56d27 100644 --- a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/model/Field.java +++ b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/model/Field.java @@ -31,6 +31,9 @@ public abstract class Field { // resolution behavior. For more context, please see the invocation site of the setter method. public abstract String originalName(); + @Nullable + public abstract String jsonName(); + public abstract TypeNode type(); // If the field is annotated with google.api.field_behavior = REQUIRED, then this is true. This is @@ -92,6 +95,7 @@ public boolean equals(Object o) { Field other = (Field) o; return name().equals(other.name()) && originalName().equals(other.originalName()) + && Objects.equals(jsonName(), other.jsonName()) && type().equals(other.type()) && isRequired() == other.isRequired() && fieldInfoFormat() == other.fieldInfoFormat() @@ -109,6 +113,7 @@ && isProto3Optional() == other.isProto3Optional() public int hashCode() { return 17 * name().hashCode() + 31 * originalName().hashCode() + + (jsonName() == null ? 0 : jsonName().hashCode()) + 19 * type().hashCode() + (isMessage() ? 1 : 0) * 23 + (isEnum() ? 1 : 0) * 29 @@ -141,6 +146,8 @@ public abstract static class Builder { public abstract Builder setOriginalName(String originalName); + public abstract Builder setJsonName(String jsonName); + public abstract Builder setType(TypeNode type); public abstract Builder setIsRequired(boolean isRequired); diff --git a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/model/HttpBindings.java b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/model/HttpBindings.java index 696683cefec8..ae9194eeb2b7 100644 --- a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/model/HttpBindings.java +++ b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/model/HttpBindings.java @@ -16,6 +16,7 @@ import com.google.api.generator.gapic.utils.JavaStyle; import com.google.auto.value.AutoValue; +import com.google.common.base.Strings; import com.google.common.collect.ImmutableSet; import java.util.LinkedHashMap; import java.util.List; @@ -42,6 +43,11 @@ public abstract static class HttpBinding implements Comparable { abstract String lowerCamelName(); + // The dot-separated json_name of the field. + // e.g. parent.iceberg-catalog-id + @Nullable + public abstract String jsonName(); + // An object that contains all info of the leaf level field @Nullable public abstract Field field(); @@ -70,6 +76,8 @@ public abstract static class Builder { public abstract HttpBindings.HttpBinding.Builder setName(String name); + public abstract HttpBindings.HttpBinding.Builder setJsonName(String jsonName); + public abstract HttpBindings.HttpBinding.Builder setField(Field field); abstract HttpBindings.HttpBinding.Builder setLowerCamelName(String lowerCamelName); @@ -133,9 +141,11 @@ public List lowerCamelAdditionalPatterns() { private static String lowerCamelPattern(String originalPattern, Set pathParameters) { String lowerCamelPattern = originalPattern; for (HttpBinding pathParam : pathParameters) { - lowerCamelPattern = - lowerCamelPattern.replaceAll( - "\\{" + pathParam.name(), "{" + JavaStyle.toLowerCamelCase(pathParam.name())); + String replacement = + !Strings.isNullOrEmpty(pathParam.jsonName()) + ? pathParam.jsonName() + : JavaStyle.toLowerCamelCase(pathParam.name()); + lowerCamelPattern = lowerCamelPattern.replaceAll("\\{" + pathParam.name(), "{" + replacement); } return lowerCamelPattern; } diff --git a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/protoparser/HttpRuleParser.java b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/protoparser/HttpRuleParser.java index 499f28427d4b..0ea1c5aa456e 100644 --- a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/protoparser/HttpRuleParser.java +++ b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/protoparser/HttpRuleParser.java @@ -21,6 +21,7 @@ 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.generator.gapic.utils.JavaStyle; import com.google.api.pathtemplate.PathTemplate; import com.google.common.base.Preconditions; import com.google.common.base.Strings; @@ -29,8 +30,10 @@ import com.google.common.collect.Sets; import com.google.protobuf.DescriptorProtos.MethodOptions; import com.google.protobuf.Descriptors.MethodDescriptor; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -144,10 +147,24 @@ private static Set validateAndConstructHttpBindings( continue; } Message nestedMessage = inputMessage; + List jsonNameParts = new ArrayList<>(); for (int i = 0; i < subFields.length; i++) { String subFieldName = subFields[i]; + Field field = nestedMessage.fieldMap().get(subFieldName); + Preconditions.checkState( + field != null, + "Expected message %s to contain field %s but none found", + nestedMessage.name(), + subFieldName); + + // Each component of the JSON name uses the json_name annotation of the field, + // or default to the field name + jsonNameParts.add( + !Strings.isNullOrEmpty(field.jsonName()) + ? field.jsonName() + : JavaStyle.toLowerCamelCase(field.name())); + if (i < subFields.length - 1) { - Field field = nestedMessage.fieldMap().get(subFieldName); nestedMessage = messageTypes.get(field.type().reference().fullName()); Preconditions.checkNotNull( nestedMessage, @@ -160,9 +177,12 @@ private static Set validateAndConstructHttpBindings( checkHttpFieldIsValid(subFieldName, nestedMessage, false); patternSampleValue = patternSampleValues.get(paramName); } - Field field = nestedMessage.fieldMap().get(subFieldName); httpBindings.add( - httpBindingBuilder.setValuePattern(patternSampleValue).setField(field).build()); + httpBindingBuilder + .setValuePattern(patternSampleValue) + .setField(field) + .setJsonName(String.join(".", jsonNameParts)) + .build()); } } } diff --git a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/protoparser/Parser.java b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/protoparser/Parser.java index 0ad6eec69482..1fbf0fc499eb 100644 --- a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/protoparser/Parser.java +++ b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/protoparser/Parser.java @@ -1181,6 +1181,7 @@ private static Field parseField( return fieldBuilder .setName(actualFieldName) .setOriginalName(fieldDescriptor.getName()) + .setJsonName(fieldDescriptor.getJsonName()) .setType(TypeParser.parseType(fieldDescriptor)) .setIsMessage(fieldDescriptor.getJavaType() == FieldDescriptor.JavaType.MESSAGE) .setIsEnum(fieldDescriptor.getJavaType() == FieldDescriptor.JavaType.ENUM) diff --git a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpcrest/goldens/HttpJsonEchoStub.golden b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpcrest/goldens/HttpJsonEchoStub.golden index 24ff0ee92874..1ca61a984099 100644 --- a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpcrest/goldens/HttpJsonEchoStub.golden +++ b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpcrest/goldens/HttpJsonEchoStub.golden @@ -362,7 +362,7 @@ public class HttpJsonEchoStub extends EchoStub { }) .setRequestBodyExtractor( request -> - ProtoRestSerializer.create().toBody("case_", request.getCase(), false)) + ProtoRestSerializer.create().toBody("case", request.getCase(), false)) .build()) .setResponseParser( ProtoMessageResponseParser.newBuilder() diff --git a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/goldens/ComplianceClientTest.golden b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/goldens/ComplianceClientTest.golden index 07e6b442e25e..bed9ee7453b0 100644 --- a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/goldens/ComplianceClientTest.golden +++ b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/goldens/ComplianceClientTest.golden @@ -238,6 +238,7 @@ public class ComplianceClientTest { .setFBool(true) .setFBytes(ByteString.EMPTY) .setFChild(ComplianceDataChild.newBuilder().build()) + .setCustomPathField("customPathField-1920204956") .setPString("pString-1191954271") .setPInt32(-858673665) .setPDouble(-991225216) @@ -294,6 +295,7 @@ public class ComplianceClientTest { .setFBool(true) .setFBytes(ByteString.EMPTY) .setFChild(ComplianceDataChild.newBuilder().build()) + .setCustomPathField("customPathField-1920204956") .setPString("pString-1191954271") .setPInt32(-858673665) .setPDouble(-991225216) @@ -350,6 +352,7 @@ public class ComplianceClientTest { .setPContinent(Continent.forNumber(0)) .setPChild(ComplianceDataGrandchild.newBuilder().build()) .build()) + .setCustomPathField("customPathField-1920204956") .setPString("pString-1191954271") .setPInt32(-858673665) .setPDouble(-991225216) @@ -420,6 +423,7 @@ public class ComplianceClientTest { .setPContinent(Continent.forNumber(0)) .setPChild(ComplianceDataGrandchild.newBuilder().build()) .build()) + .setCustomPathField("customPathField-1920204956") .setPString("pString-1191954271") .setPInt32(-858673665) .setPDouble(-991225216) @@ -476,6 +480,7 @@ public class ComplianceClientTest { .setPContinent(Continent.forNumber(0)) .setPChild(ComplianceDataGrandchild.newBuilder().build()) .build()) + .setCustomPathField("customPathField-1920204956") .setPString("pString-1191954271") .setPInt32(-858673665) .setPDouble(-991225216) @@ -546,6 +551,7 @@ public class ComplianceClientTest { .setPContinent(Continent.forNumber(0)) .setPChild(ComplianceDataGrandchild.newBuilder().build()) .build()) + .setCustomPathField("customPathField-1920204956") .setPString("pString-1191954271") .setPInt32(-858673665) .setPDouble(-991225216) @@ -658,4 +664,220 @@ public class ComplianceClientTest { // Expected exception. } } + + @Test + public void repeatDataCustomPathTest() throws Exception { + RepeatResponse expectedResponse = + RepeatResponse.newBuilder().setInfo(ComplianceData.newBuilder().build()).build(); + mockService.addResponse(expectedResponse); + + CustomBindingRequest request = + CustomBindingRequest.newBuilder() + .setName("name3373707") + .setInfo( + ComplianceData.newBuilder() + .setFString("fString-1477056489") + .setFInt32(-1143775883) + .setFSint32(-815756300) + .setFSfixed32(-763212615) + .setFUint32(-758497998) + .setFFixed32(1837548026) + .setFInt64(-1143775788) + .setFSint64(-815756205) + .setFSfixed64(-763212520) + .setFUint64(-758497903) + .setFFixed64(1837548121) + .setFDouble(-1239459382) + .setFFloat(-1146609341) + .setFBool(true) + .setFBytes(ByteString.EMPTY) + .setFChild(ComplianceDataChild.newBuilder().build()) + .setCustomPathField("customPathField-8550") + .setPString("pString-1191954271") + .setPInt32(-858673665) + .setPDouble(-991225216) + .setPBool(true) + .setPChild(ComplianceDataChild.newBuilder().build()) + .build()) + .setServerVerify(true) + .setCustomKebabName("customKebabName-2062111197") + .setCustomBodyMessage(ComplianceData.newBuilder().build()) + .build(); + + RepeatResponse actualResponse = client.repeatDataCustomPath(request); + Assert.assertEquals(expectedResponse, actualResponse); + + List actualRequests = mockService.getRequestPaths(); + Assert.assertEquals(1, actualRequests.size()); + + String apiClientHeaderKey = + mockService + .getRequestHeaders() + .get(ApiClientHeaderProvider.getDefaultApiClientHeaderKey()) + .iterator() + .next(); + Assert.assertTrue( + GaxHttpJsonProperties.getDefaultApiClientHeaderPattern() + .matcher(apiClientHeaderKey) + .matches()); + } + + @Test + public void repeatDataCustomPathExceptionTest() throws Exception { + ApiException exception = + ApiExceptionFactory.createException( + new Exception(), FakeStatusCode.of(StatusCode.Code.INVALID_ARGUMENT), false); + mockService.addException(exception); + + try { + CustomBindingRequest request = + CustomBindingRequest.newBuilder() + .setName("name3373707") + .setInfo( + ComplianceData.newBuilder() + .setFString("fString-1477056489") + .setFInt32(-1143775883) + .setFSint32(-815756300) + .setFSfixed32(-763212615) + .setFUint32(-758497998) + .setFFixed32(1837548026) + .setFInt64(-1143775788) + .setFSint64(-815756205) + .setFSfixed64(-763212520) + .setFUint64(-758497903) + .setFFixed64(1837548121) + .setFDouble(-1239459382) + .setFFloat(-1146609341) + .setFBool(true) + .setFBytes(ByteString.EMPTY) + .setFChild(ComplianceDataChild.newBuilder().build()) + .setCustomPathField("customPathField-8550") + .setPString("pString-1191954271") + .setPInt32(-858673665) + .setPDouble(-991225216) + .setPBool(true) + .setPChild(ComplianceDataChild.newBuilder().build()) + .build()) + .setServerVerify(true) + .setCustomKebabName("customKebabName-2062111197") + .setCustomBodyMessage(ComplianceData.newBuilder().build()) + .build(); + client.repeatDataCustomPath(request); + Assert.fail("No exception raised"); + } catch (InvalidArgumentException e) { + // Expected exception. + } + } + + @Test + public void repeatDataBodyCustomMessageTest() throws Exception { + RepeatResponse expectedResponse = + RepeatResponse.newBuilder().setInfo(ComplianceData.newBuilder().build()).build(); + mockService.addResponse(expectedResponse); + + CustomBindingRequest request = + CustomBindingRequest.newBuilder() + .setName("name3373707") + .setInfo(ComplianceData.newBuilder().build()) + .setServerVerify(true) + .setCustomKebabName("customKebabName-2062111197") + .setCustomBodyMessage(ComplianceData.newBuilder().build()) + .build(); + + RepeatResponse actualResponse = client.repeatDataBodyCustomMessage(request); + Assert.assertEquals(expectedResponse, actualResponse); + + List actualRequests = mockService.getRequestPaths(); + Assert.assertEquals(1, actualRequests.size()); + + String apiClientHeaderKey = + mockService + .getRequestHeaders() + .get(ApiClientHeaderProvider.getDefaultApiClientHeaderKey()) + .iterator() + .next(); + Assert.assertTrue( + GaxHttpJsonProperties.getDefaultApiClientHeaderPattern() + .matcher(apiClientHeaderKey) + .matches()); + } + + @Test + public void repeatDataBodyCustomMessageExceptionTest() throws Exception { + ApiException exception = + ApiExceptionFactory.createException( + new Exception(), FakeStatusCode.of(StatusCode.Code.INVALID_ARGUMENT), false); + mockService.addException(exception); + + try { + CustomBindingRequest request = + CustomBindingRequest.newBuilder() + .setName("name3373707") + .setInfo(ComplianceData.newBuilder().build()) + .setServerVerify(true) + .setCustomKebabName("customKebabName-2062111197") + .setCustomBodyMessage(ComplianceData.newBuilder().build()) + .build(); + client.repeatDataBodyCustomMessage(request); + Assert.fail("No exception raised"); + } catch (InvalidArgumentException e) { + // Expected exception. + } + } + + @Test + public void repeatDataCustomQueryTest() throws Exception { + RepeatResponse expectedResponse = + RepeatResponse.newBuilder().setInfo(ComplianceData.newBuilder().build()).build(); + mockService.addResponse(expectedResponse); + + CustomBindingRequest request = + CustomBindingRequest.newBuilder() + .setName("name3373707") + .setInfo(ComplianceData.newBuilder().build()) + .setServerVerify(true) + .setCustomKebabName("customKebabName-2062111197") + .setCustomBodyMessage(ComplianceData.newBuilder().build()) + .build(); + + RepeatResponse actualResponse = client.repeatDataCustomQuery(request); + Assert.assertEquals(expectedResponse, actualResponse); + + List actualRequests = mockService.getRequestPaths(); + Assert.assertEquals(1, actualRequests.size()); + + String apiClientHeaderKey = + mockService + .getRequestHeaders() + .get(ApiClientHeaderProvider.getDefaultApiClientHeaderKey()) + .iterator() + .next(); + Assert.assertTrue( + GaxHttpJsonProperties.getDefaultApiClientHeaderPattern() + .matcher(apiClientHeaderKey) + .matches()); + } + + @Test + public void repeatDataCustomQueryExceptionTest() throws Exception { + ApiException exception = + ApiExceptionFactory.createException( + new Exception(), FakeStatusCode.of(StatusCode.Code.INVALID_ARGUMENT), false); + mockService.addException(exception); + + try { + CustomBindingRequest request = + CustomBindingRequest.newBuilder() + .setName("name3373707") + .setInfo(ComplianceData.newBuilder().build()) + .setServerVerify(true) + .setCustomKebabName("customKebabName-2062111197") + .setCustomBodyMessage(ComplianceData.newBuilder().build()) + .build(); + client.repeatDataCustomQuery(request); + Assert.fail("No exception raised"); + } catch (InvalidArgumentException e) { + // Expected exception. + } + } } diff --git a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/goldens/ComplianceSettings.golden b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/goldens/ComplianceSettings.golden index 357b0a8bb0f1..9c210dc69564 100644 --- a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/goldens/ComplianceSettings.golden +++ b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/goldens/ComplianceSettings.golden @@ -109,6 +109,22 @@ public class ComplianceSettings extends ClientSettings { return ((ComplianceStubSettings) getStubSettings()).verifyEnumSettings(); } + /** Returns the object with the settings used for calls to repeatDataCustomPath. */ + public UnaryCallSettings repeatDataCustomPathSettings() { + return ((ComplianceStubSettings) getStubSettings()).repeatDataCustomPathSettings(); + } + + /** Returns the object with the settings used for calls to repeatDataBodyCustomMessage. */ + public UnaryCallSettings + repeatDataBodyCustomMessageSettings() { + return ((ComplianceStubSettings) getStubSettings()).repeatDataBodyCustomMessageSettings(); + } + + /** Returns the object with the settings used for calls to repeatDataCustomQuery. */ + public UnaryCallSettings repeatDataCustomQuerySettings() { + return ((ComplianceStubSettings) getStubSettings()).repeatDataCustomQuerySettings(); + } + public static final ComplianceSettings create(ComplianceStubSettings stub) throws IOException { return new ComplianceSettings.Builder(stub.toBuilder()).build(); } @@ -247,6 +263,24 @@ public class ComplianceSettings extends ClientSettings { return getStubSettingsBuilder().verifyEnumSettings(); } + /** Returns the builder for the settings used for calls to repeatDataCustomPath. */ + public UnaryCallSettings.Builder + repeatDataCustomPathSettings() { + return getStubSettingsBuilder().repeatDataCustomPathSettings(); + } + + /** Returns the builder for the settings used for calls to repeatDataBodyCustomMessage. */ + public UnaryCallSettings.Builder + repeatDataBodyCustomMessageSettings() { + return getStubSettingsBuilder().repeatDataBodyCustomMessageSettings(); + } + + /** Returns the builder for the settings used for calls to repeatDataCustomQuery. */ + public UnaryCallSettings.Builder + repeatDataCustomQuerySettings() { + return getStubSettingsBuilder().repeatDataCustomQuerySettings(); + } + @Override public ComplianceSettings build() throws IOException { return new ComplianceSettings(this); diff --git a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/goldens/ComplianceStubSettings.golden b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/goldens/ComplianceStubSettings.golden index 1633188ee0e7..83665083d865 100644 --- a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/goldens/ComplianceStubSettings.golden +++ b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/goldens/ComplianceStubSettings.golden @@ -21,6 +21,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; +import com.google.showcase.v1beta1.CustomBindingRequest; import com.google.showcase.v1beta1.EnumRequest; import com.google.showcase.v1beta1.EnumResponse; import com.google.showcase.v1beta1.RepeatRequest; @@ -95,6 +96,12 @@ public class ComplianceStubSettings extends StubSettings repeatDataPathTrailingResourceSettings; private final UnaryCallSettings getEnumSettings; private final UnaryCallSettings verifyEnumSettings; + private final UnaryCallSettings + repeatDataCustomPathSettings; + private final UnaryCallSettings + repeatDataBodyCustomMessageSettings; + private final UnaryCallSettings + repeatDataCustomQuerySettings; /** Returns the object with the settings used for calls to repeatDataBody. */ public UnaryCallSettings repeatDataBodySettings() { @@ -136,6 +143,22 @@ public class ComplianceStubSettings extends StubSettings return verifyEnumSettings; } + /** Returns the object with the settings used for calls to repeatDataCustomPath. */ + public UnaryCallSettings repeatDataCustomPathSettings() { + return repeatDataCustomPathSettings; + } + + /** Returns the object with the settings used for calls to repeatDataBodyCustomMessage. */ + public UnaryCallSettings + repeatDataBodyCustomMessageSettings() { + return repeatDataBodyCustomMessageSettings; + } + + /** Returns the object with the settings used for calls to repeatDataCustomQuery. */ + public UnaryCallSettings repeatDataCustomQuerySettings() { + return repeatDataCustomQuerySettings; + } + public ComplianceStub createStub() throws IOException { if (getTransportChannelProvider() .getTransportName() @@ -221,6 +244,10 @@ public class ComplianceStubSettings extends StubSettings settingsBuilder.repeatDataPathTrailingResourceSettings().build(); getEnumSettings = settingsBuilder.getEnumSettings().build(); verifyEnumSettings = settingsBuilder.verifyEnumSettings().build(); + repeatDataCustomPathSettings = settingsBuilder.repeatDataCustomPathSettings().build(); + repeatDataBodyCustomMessageSettings = + settingsBuilder.repeatDataBodyCustomMessageSettings().build(); + repeatDataCustomQuerySettings = settingsBuilder.repeatDataCustomQuerySettings().build(); } @Override @@ -243,6 +270,12 @@ public class ComplianceStubSettings extends StubSettings repeatDataPathTrailingResourceSettings; private final UnaryCallSettings.Builder getEnumSettings; private final UnaryCallSettings.Builder verifyEnumSettings; + private final UnaryCallSettings.Builder + repeatDataCustomPathSettings; + private final UnaryCallSettings.Builder + repeatDataBodyCustomMessageSettings; + private final UnaryCallSettings.Builder + repeatDataCustomQuerySettings; private static final ImmutableMap> RETRYABLE_CODE_DEFINITIONS; @@ -278,6 +311,9 @@ public class ComplianceStubSettings extends StubSettings repeatDataPathTrailingResourceSettings = UnaryCallSettings.newUnaryCallSettingsBuilder(); getEnumSettings = UnaryCallSettings.newUnaryCallSettingsBuilder(); verifyEnumSettings = UnaryCallSettings.newUnaryCallSettingsBuilder(); + repeatDataCustomPathSettings = UnaryCallSettings.newUnaryCallSettingsBuilder(); + repeatDataBodyCustomMessageSettings = UnaryCallSettings.newUnaryCallSettingsBuilder(); + repeatDataCustomQuerySettings = UnaryCallSettings.newUnaryCallSettingsBuilder(); unaryMethodSettingsBuilders = ImmutableList.>of( @@ -288,7 +324,10 @@ public class ComplianceStubSettings extends StubSettings repeatDataPathResourceSettings, repeatDataPathTrailingResourceSettings, getEnumSettings, - verifyEnumSettings); + verifyEnumSettings, + repeatDataCustomPathSettings, + repeatDataBodyCustomMessageSettings, + repeatDataCustomQuerySettings); initDefaults(this); } @@ -304,6 +343,10 @@ public class ComplianceStubSettings extends StubSettings settings.repeatDataPathTrailingResourceSettings.toBuilder(); getEnumSettings = settings.getEnumSettings.toBuilder(); verifyEnumSettings = settings.verifyEnumSettings.toBuilder(); + repeatDataCustomPathSettings = settings.repeatDataCustomPathSettings.toBuilder(); + repeatDataBodyCustomMessageSettings = + settings.repeatDataBodyCustomMessageSettings.toBuilder(); + repeatDataCustomQuerySettings = settings.repeatDataCustomQuerySettings.toBuilder(); unaryMethodSettingsBuilders = ImmutableList.>of( @@ -314,7 +357,10 @@ public class ComplianceStubSettings extends StubSettings repeatDataPathResourceSettings, repeatDataPathTrailingResourceSettings, getEnumSettings, - verifyEnumSettings); + verifyEnumSettings, + repeatDataCustomPathSettings, + repeatDataBodyCustomMessageSettings, + repeatDataCustomQuerySettings); } private static Builder createDefault() { @@ -370,6 +416,21 @@ public class ComplianceStubSettings extends StubSettings .setRetryableCodes(RETRYABLE_CODE_DEFINITIONS.get("no_retry_codes")) .setRetrySettings(RETRY_PARAM_DEFINITIONS.get("no_retry_params")); + builder + .repeatDataCustomPathSettings() + .setRetryableCodes(RETRYABLE_CODE_DEFINITIONS.get("no_retry_codes")) + .setRetrySettings(RETRY_PARAM_DEFINITIONS.get("no_retry_params")); + + builder + .repeatDataBodyCustomMessageSettings() + .setRetryableCodes(RETRYABLE_CODE_DEFINITIONS.get("no_retry_codes")) + .setRetrySettings(RETRY_PARAM_DEFINITIONS.get("no_retry_params")); + + builder + .repeatDataCustomQuerySettings() + .setRetryableCodes(RETRYABLE_CODE_DEFINITIONS.get("no_retry_codes")) + .setRetrySettings(RETRY_PARAM_DEFINITIONS.get("no_retry_params")); + return builder; } @@ -430,6 +491,24 @@ public class ComplianceStubSettings extends StubSettings return verifyEnumSettings; } + /** Returns the builder for the settings used for calls to repeatDataCustomPath. */ + public UnaryCallSettings.Builder + repeatDataCustomPathSettings() { + return repeatDataCustomPathSettings; + } + + /** Returns the builder for the settings used for calls to repeatDataBodyCustomMessage. */ + public UnaryCallSettings.Builder + repeatDataBodyCustomMessageSettings() { + return repeatDataBodyCustomMessageSettings; + } + + /** Returns the builder for the settings used for calls to repeatDataCustomQuery. */ + public UnaryCallSettings.Builder + repeatDataCustomQuerySettings() { + return repeatDataCustomQuerySettings; + } + @Override public ComplianceStubSettings build() throws IOException { return new ComplianceStubSettings(this); diff --git a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/goldens/HttpJsonComplianceStub.golden b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/goldens/HttpJsonComplianceStub.golden index c808b9404eae..39dc2df47161 100644 --- a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/goldens/HttpJsonComplianceStub.golden +++ b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/goldens/HttpJsonComplianceStub.golden @@ -14,6 +14,7 @@ import com.google.api.gax.rpc.ClientContext; import com.google.api.gax.rpc.RequestParamsBuilder; import com.google.api.gax.rpc.UnaryCallable; import com.google.protobuf.TypeRegistry; +import com.google.showcase.v1beta1.CustomBindingRequest; import com.google.showcase.v1beta1.EnumRequest; import com.google.showcase.v1beta1.EnumResponse; import com.google.showcase.v1beta1.RepeatRequest; @@ -351,6 +352,135 @@ public class HttpJsonComplianceStub extends ComplianceStub { .build()) .build(); + private static final ApiMethodDescriptor + repeatDataCustomPathMethodDescriptor = + ApiMethodDescriptor.newBuilder() + .setFullMethodName("google.showcase.v1beta1.Compliance/RepeatDataCustomPath") + .setHttpMethod("GET") + .setType(ApiMethodDescriptor.MethodType.UNARY) + .setRequestFormatter( + ProtoMessageRequestFormatter.newBuilder() + .setPath( + "/v1beta1/repeat/{info.custom-path-field}:custompath", + request -> { + Map fields = new HashMap<>(); + ProtoRestSerializer serializer = + ProtoRestSerializer.create(); + serializer.putPathParam( + fields, + "info.custom-path-field", + request.getInfo().getCustomPathField()); + return fields; + }) + .setQueryParamsExtractor( + request -> { + Map> fields = new HashMap<>(); + ProtoRestSerializer serializer = + ProtoRestSerializer.create(); + serializer.putQueryParam( + fields, "custom-body-message", request.getCustomBodyMessage()); + serializer.putQueryParam( + fields, "custom-kebab-name", request.getCustomKebabName()); + serializer.putQueryParam(fields, "info", request.getInfo()); + serializer.putQueryParam(fields, "name", request.getName()); + serializer.putQueryParam( + fields, "serverVerify", request.getServerVerify()); + serializer.putQueryParam(fields, "$alt", "json;enum-encoding=int"); + return fields; + }) + .setRequestBodyExtractor(request -> null) + .build()) + .setResponseParser( + ProtoMessageResponseParser.newBuilder() + .setDefaultInstance(RepeatResponse.getDefaultInstance()) + .setDefaultTypeRegistry(typeRegistry) + .build()) + .build(); + + private static final ApiMethodDescriptor + repeatDataBodyCustomMessageMethodDescriptor = + ApiMethodDescriptor.newBuilder() + .setFullMethodName("google.showcase.v1beta1.Compliance/RepeatDataBodyCustomMessage") + .setHttpMethod("POST") + .setType(ApiMethodDescriptor.MethodType.UNARY) + .setRequestFormatter( + ProtoMessageRequestFormatter.newBuilder() + .setPath( + "/v1beta1/repeat:bodycustommessage", + request -> { + Map fields = new HashMap<>(); + ProtoRestSerializer serializer = + ProtoRestSerializer.create(); + return fields; + }) + .setQueryParamsExtractor( + request -> { + Map> fields = new HashMap<>(); + ProtoRestSerializer serializer = + ProtoRestSerializer.create(); + serializer.putQueryParam( + fields, "custom-kebab-name", request.getCustomKebabName()); + serializer.putQueryParam(fields, "info", request.getInfo()); + serializer.putQueryParam(fields, "name", request.getName()); + serializer.putQueryParam( + fields, "serverVerify", request.getServerVerify()); + serializer.putQueryParam(fields, "$alt", "json;enum-encoding=int"); + return fields; + }) + .setRequestBodyExtractor( + request -> + ProtoRestSerializer.create() + .toBody( + "custom-body-message", request.getCustomBodyMessage(), true)) + .build()) + .setResponseParser( + ProtoMessageResponseParser.newBuilder() + .setDefaultInstance(RepeatResponse.getDefaultInstance()) + .setDefaultTypeRegistry(typeRegistry) + .build()) + .build(); + + private static final ApiMethodDescriptor + repeatDataCustomQueryMethodDescriptor = + ApiMethodDescriptor.newBuilder() + .setFullMethodName("google.showcase.v1beta1.Compliance/RepeatDataCustomQuery") + .setHttpMethod("GET") + .setType(ApiMethodDescriptor.MethodType.UNARY) + .setRequestFormatter( + ProtoMessageRequestFormatter.newBuilder() + .setPath( + "/v1beta1/repeat:customquery", + request -> { + Map fields = new HashMap<>(); + ProtoRestSerializer serializer = + ProtoRestSerializer.create(); + return fields; + }) + .setQueryParamsExtractor( + request -> { + Map> fields = new HashMap<>(); + ProtoRestSerializer serializer = + ProtoRestSerializer.create(); + serializer.putQueryParam( + fields, "custom-body-message", request.getCustomBodyMessage()); + serializer.putQueryParam( + fields, "custom-kebab-name", request.getCustomKebabName()); + serializer.putQueryParam(fields, "info", request.getInfo()); + serializer.putQueryParam(fields, "name", request.getName()); + serializer.putQueryParam( + fields, "serverVerify", request.getServerVerify()); + serializer.putQueryParam(fields, "$alt", "json;enum-encoding=int"); + return fields; + }) + .setRequestBodyExtractor(request -> null) + .build()) + .setResponseParser( + ProtoMessageResponseParser.newBuilder() + .setDefaultInstance(RepeatResponse.getDefaultInstance()) + .setDefaultTypeRegistry(typeRegistry) + .build()) + .build(); + private final UnaryCallable repeatDataBodyCallable; private final UnaryCallable repeatDataBodyInfoCallable; private final UnaryCallable repeatDataQueryCallable; @@ -359,6 +489,10 @@ public class HttpJsonComplianceStub extends ComplianceStub { private final UnaryCallable repeatDataPathTrailingResourceCallable; private final UnaryCallable getEnumCallable; private final UnaryCallable verifyEnumCallable; + private final UnaryCallable repeatDataCustomPathCallable; + private final UnaryCallable + repeatDataBodyCustomMessageCallable; + private final UnaryCallable repeatDataCustomQueryCallable; private final BackgroundResource backgroundResources; private final HttpJsonStubCallableFactory callableFactory; @@ -472,6 +606,32 @@ public class HttpJsonComplianceStub extends ComplianceStub { .setMethodDescriptor(verifyEnumMethodDescriptor) .setTypeRegistry(typeRegistry) .build(); + HttpJsonCallSettings + repeatDataCustomPathTransportSettings = + HttpJsonCallSettings.newBuilder() + .setMethodDescriptor(repeatDataCustomPathMethodDescriptor) + .setTypeRegistry(typeRegistry) + .setParamsExtractor( + request -> { + RequestParamsBuilder builder = RequestParamsBuilder.create(); + builder.add( + "info.custom_path_field", + String.valueOf(request.getInfo().getCustomPathField())); + return builder.build(); + }) + .build(); + HttpJsonCallSettings + repeatDataBodyCustomMessageTransportSettings = + HttpJsonCallSettings.newBuilder() + .setMethodDescriptor(repeatDataBodyCustomMessageMethodDescriptor) + .setTypeRegistry(typeRegistry) + .build(); + HttpJsonCallSettings + repeatDataCustomQueryTransportSettings = + HttpJsonCallSettings.newBuilder() + .setMethodDescriptor(repeatDataCustomQueryMethodDescriptor) + .setTypeRegistry(typeRegistry) + .build(); this.repeatDataBodyCallable = callableFactory.createUnaryCallable( @@ -505,6 +665,21 @@ public class HttpJsonComplianceStub extends ComplianceStub { this.verifyEnumCallable = callableFactory.createUnaryCallable( verifyEnumTransportSettings, settings.verifyEnumSettings(), clientContext); + this.repeatDataCustomPathCallable = + callableFactory.createUnaryCallable( + repeatDataCustomPathTransportSettings, + settings.repeatDataCustomPathSettings(), + clientContext); + this.repeatDataBodyCustomMessageCallable = + callableFactory.createUnaryCallable( + repeatDataBodyCustomMessageTransportSettings, + settings.repeatDataBodyCustomMessageSettings(), + clientContext); + this.repeatDataCustomQueryCallable = + callableFactory.createUnaryCallable( + repeatDataCustomQueryTransportSettings, + settings.repeatDataCustomQuerySettings(), + clientContext); this.backgroundResources = new BackgroundResourceAggregation(clientContext.getBackgroundResources()); @@ -521,6 +696,9 @@ public class HttpJsonComplianceStub extends ComplianceStub { methodDescriptors.add(repeatDataPathTrailingResourceMethodDescriptor); methodDescriptors.add(getEnumMethodDescriptor); methodDescriptors.add(verifyEnumMethodDescriptor); + methodDescriptors.add(repeatDataCustomPathMethodDescriptor); + methodDescriptors.add(repeatDataBodyCustomMessageMethodDescriptor); + methodDescriptors.add(repeatDataCustomQueryMethodDescriptor); return methodDescriptors; } @@ -564,6 +742,21 @@ public class HttpJsonComplianceStub extends ComplianceStub { return verifyEnumCallable; } + @Override + public UnaryCallable repeatDataCustomPathCallable() { + return repeatDataCustomPathCallable; + } + + @Override + public UnaryCallable repeatDataBodyCustomMessageCallable() { + return repeatDataBodyCustomMessageCallable; + } + + @Override + public UnaryCallable repeatDataCustomQueryCallable() { + return repeatDataCustomQueryCallable; + } + @Override public final void close() { try { diff --git a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/model/HttpBindingsTest.java b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/model/HttpBindingsTest.java index 7ca27d167c75..8fe342885d53 100644 --- a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/model/HttpBindingsTest.java +++ b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/model/HttpBindingsTest.java @@ -96,4 +96,12 @@ void isEnum_shouldReturnTrueIfFieldExistsAndIsEnumIsTue() { Truth.assertThat(httpBinding.isEnum()).isTrue(); } + + @Test + void builder_preservesLiteralJsonName() { + final String jsonName = "iceberg-catalog-id"; + HttpBinding binding = + HttpBinding.builder().setName("doesNotMatter").setJsonName(jsonName).build(); + Truth.assertThat(binding.jsonName()).isEqualTo(jsonName); + } } diff --git a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/protoparser/HttpRuleParserTest.java b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/protoparser/HttpRuleParserTest.java index 77a0440319d6..2b7ac273b06f 100644 --- a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/protoparser/HttpRuleParserTest.java +++ b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/protoparser/HttpRuleParserTest.java @@ -114,10 +114,15 @@ void parseHttpAnnotation_shouldPutAllFieldsIntoQueryParamsIfPathParamAndBodyAreN HttpBindings actual = HttpRuleParser.parse(rpcMethod, inputMessage, messages); HttpBinding expected1 = - HttpBinding.builder().setName("name").setField(inputMessage.fieldMap().get("name")).build(); + HttpBinding.builder() + .setName("name") + .setJsonName("name") + .setField(inputMessage.fieldMap().get("name")) + .build(); HttpBinding expected2 = HttpBinding.builder() .setName("nested_object") + .setJsonName("nestedObject") .setField(inputMessage.fieldMap().get("nested_object")) .build(); Truth.assertThat(new HashSet<>(actual.queryParameters())).containsExactly(expected1, expected2); @@ -173,4 +178,32 @@ void parseHttpAnnotation_shouldExcludeFieldsFromQueryParamsIfPathParamsAreConfig Truth.assertThat(new HashSet<>(actual.queryParameters())).containsExactly(expected1, expected2); Truth.assertThat(new HashSet<>(actual.pathParameters())).containsExactly(expectedPathParam); } + + @Test + void parseHttpAnnotation_respectsJsonNameWithDashes() { + FileDescriptor complianceFileDescriptor = + com.google.showcase.v1beta1.ComplianceOuterClass.getDescriptor(); + ServiceDescriptor complianceService = complianceFileDescriptor.getServices().get(0); + assertEquals("Compliance", complianceService.getName()); + + Map messages = Parser.parseMessages(complianceFileDescriptor); + + MethodDescriptor rpcMethod = + complianceService.getMethods().stream() + .filter(m -> m.getName().equals("RepeatDataCustomQuery")) + .findAny() + .get(); + + Message inputMessage = messages.get("com.google.showcase.v1beta1.CustomBindingRequest"); + HttpBindings httpBindings = HttpRuleParser.parse(rpcMethod, inputMessage, messages); + + HttpBinding customBinding = + httpBindings.queryParameters().stream() + .filter(b -> b.name().equals("custom_kebab_name")) + .findAny() + .orElse(null); + + Truth.assertThat(customBinding).isNotNull(); + assertEquals("custom-kebab-name", customBinding.jsonName()); + } } diff --git a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/protoparser/ParserTest.java b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/protoparser/ParserTest.java index e6325ded6958..2f0e7459efab 100644 --- a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/protoparser/ParserTest.java +++ b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/protoparser/ParserTest.java @@ -102,10 +102,11 @@ void parseMessages_basic() { String echoResponseName = "EchoResponse"; Field echoResponseContentField = - Field.builder().setName("content").setType(TypeNode.STRING).build(); + Field.builder().setName("content").setJsonName("content").setType(TypeNode.STRING).build(); Field echoResponseSeverityField = Field.builder() .setName("severity") + .setJsonName("severity") .setType( TypeNode.withReference( VaporReference.builder().setName("Severity").setPakkage(ECHO_PACKAGE).build())) diff --git a/sdk-platform-java/gapic-generator-java/src/test/proto/compliance.proto b/sdk-platform-java/gapic-generator-java/src/test/proto/compliance.proto index 00f71a9c054c..6ee537fa57e2 100644 --- a/sdk-platform-java/gapic-generator-java/src/test/proto/compliance.proto +++ b/sdk-platform-java/gapic-generator-java/src/test/proto/compliance.proto @@ -105,6 +105,29 @@ service Compliance { }; } + // This method echoes the ComplianceData request. This method exercises + // sending some parameters as path variables with custom json_name. + rpc RepeatDataCustomPath(CustomBindingRequest) returns (RepeatResponse) { + option (google.api.http) = { + get: "/v1beta1/repeat/{info.custom_path_field}:custompath" + }; + } + + // Testing custom json_name option in custom message bodies + rpc RepeatDataBodyCustomMessage(CustomBindingRequest) returns (RepeatResponse) { + option (google.api.http) = { + post: "/v1beta1/repeat:bodycustommessage" + body: "custom_body_message" + }; + } + + // Testing custom query parameter mapping with dashes + rpc RepeatDataCustomQuery(CustomBindingRequest) returns (RepeatResponse) { + option (google.api.http) = { + get: "/v1beta1/repeat:customquery" + }; + } + } message EnumRequest { @@ -129,6 +152,15 @@ message RepeatRequest { bool server_verify = 3; } +message CustomBindingRequest { + string name = 1; + ComplianceData info = 2; + bool server_verify = 3; + + string custom_kebab_name = 4 [json_name = "custom-kebab-name"]; + ComplianceData custom_body_message = 5 [json_name = "custom-body-message"]; +} + message RepeatResponse { ComplianceData info = 1; } @@ -189,6 +221,7 @@ message ComplianceData { LifeKingdom f_kingdom = 22; ComplianceDataChild f_child = 16; + string custom_path_field = 24 [json_name = "custom-path-field"]; // optional fields diff --git a/sdk-platform-java/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoRestSerializer.java b/sdk-platform-java/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoRestSerializer.java index af4068c4383a..5d12920fea76 100644 --- a/sdk-platform-java/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoRestSerializer.java +++ b/sdk-platform-java/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoRestSerializer.java @@ -101,6 +101,9 @@ String toJson(Message message, boolean numericEnum) { @SuppressWarnings("unchecked") RequestT fromJson(Reader json, Message.Builder builder) { try { + // Supports mapping from both standard proto field names and explicit 'json_name' annotations. + // See: + // https://github.com/protocolbuffers/protobuf/blob/cecbbf41e43634c7c5b940dd336aa81b31fd4e5d/java/util/src/main/java/com/google/protobuf/util/JsonFormat.java#L1577-L1583 JsonFormat.parser().usingTypeRegistry(registry).ignoringUnknownFields().merge(json, builder); return (RequestT) builder.build(); } catch (IOException e) {