From 81dd15fc3cdeb3e8471c09cff692f37b623e523c Mon Sep 17 00:00:00 2001 From: Diego Date: Mon, 27 Apr 2026 11:56:10 -0400 Subject: [PATCH 01/12] fix(generator): use json_name when available --- .../HttpJsonServiceStubClassComposer.java | 4 ++- .../api/generator/gapic/model/Field.java | 7 ++++ .../generator/gapic/model/HttpBindings.java | 7 ++++ .../gapic/protoparser/HttpRuleParser.java | 27 ++++++++++++-- .../generator/gapic/protoparser/Parser.java | 1 + .../goldens/HttpJsonComplianceStub.golden | 10 ++++++ .../gapic/model/HttpBindingsTest.java | 8 +++++ .../gapic/protoparser/HttpRuleParserTest.java | 35 ++++++++++++++++++- .../src/test/proto/compliance.proto | 3 ++ 9 files changed, 97 insertions(+), 5 deletions(-) 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..ae26206d3f76 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 @@ -926,7 +926,9 @@ private Expr createFieldsExtractorClassInstance( paramsPutArgs.add( ValueExpr.withValue( StringObjectValue.withValue( - JavaStyle.toLowerCamelCase(httpBindingFieldName.name())))); + (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..5e019983c1f8 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 @@ -42,6 +42,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 +75,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); 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..3b97c74d6e91 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,25 @@ 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 +178,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/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..3ca2ad0f908a 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 @@ -94,6 +94,8 @@ public class HttpJsonComplianceStub extends ComplianceStub { Map> fields = new HashMap<>(); ProtoRestSerializer serializer = ProtoRestSerializer.create(); + serializer.putQueryParam( + fields, "custom-kebab-name", request.getCustomKebabName()); serializer.putQueryParam(fields, "name", request.getName()); serializer.putQueryParam( fields, "serverVerify", request.getServerVerify()); @@ -132,6 +134,8 @@ public class HttpJsonComplianceStub extends ComplianceStub { 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( @@ -181,6 +185,8 @@ public class HttpJsonComplianceStub extends ComplianceStub { 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( @@ -228,6 +234,8 @@ public class HttpJsonComplianceStub extends ComplianceStub { 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( @@ -272,6 +280,8 @@ public class HttpJsonComplianceStub extends ComplianceStub { 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( 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..3b4048f6c8e1 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("RepeatDataQuery")) + .findAny() + .get(); + + Message inputMessage = messages.get("com.google.showcase.v1beta1.RepeatRequest"); + 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/proto/compliance.proto b/sdk-platform-java/gapic-generator-java/src/test/proto/compliance.proto index 00f71a9c054c..4b8e1c8d5bdd 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 @@ -127,6 +127,9 @@ message RepeatRequest { // If true, the server will verify that the received request matches // the request with the same name in the compliance test suite. bool server_verify = 3; + + // New field for testing json_name with dashes + string custom_kebab_name = 4 [json_name = "custom-kebab-name"]; } message RepeatResponse { From 78c5a45423add267f027db3b367a7a73b2ade056 Mon Sep 17 00:00:00 2001 From: Diego Date: Mon, 27 Apr 2026 12:14:59 -0400 Subject: [PATCH 02/12] fix: remove unnecessary null check --- .../api/generator/gapic/protoparser/HttpRuleParser.java | 6 ------ 1 file changed, 6 deletions(-) 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 3b97c74d6e91..3f8a27467765 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 @@ -150,13 +150,7 @@ private static Set validateAndConstructHttpBindings( 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 From 4ec47433a40ac6940ef2cb68995c20404e8699c5 Mon Sep 17 00:00:00 2001 From: Diego Date: Mon, 27 Apr 2026 12:37:40 -0400 Subject: [PATCH 03/12] chore: cleanup --- .../rest/HttpJsonServiceStubClassComposer.java | 5 +---- .../gapic/protoparser/HttpRuleParser.java | 2 +- .../rest/goldens/ComplianceClientTest.golden | 2 ++ .../gapic/protoparser/HttpRuleParserTest.java | 14 -------------- .../generator/gapic/protoparser/ParserTest.java | 7 ++++++- 5 files changed, 10 insertions(+), 20 deletions(-) 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 ae26206d3f76..418d8ab8fcac 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 @@ -925,10 +925,7 @@ private Expr createFieldsExtractorClassInstance( paramsPutArgs.add( ValueExpr.withValue( - StringObjectValue.withValue( - (httpBindingFieldName.jsonName() != null) - ? httpBindingFieldName.jsonName() - : JavaStyle.toLowerCamelCase(httpBindingFieldName.name())))); + StringObjectValue.withValue(httpBindingFieldName.jsonName()))); paramsPutArgs.add(requestBuilderExpr); Expr paramsPutExpr = 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 3f8a27467765..3b015bb58f40 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 @@ -147,7 +147,7 @@ private static Set validateAndConstructHttpBindings( continue; } Message nestedMessage = inputMessage; - List jsonNameParts = new ArrayList(); + List jsonNameParts = new ArrayList<>(); for (int i = 0; i < subFields.length; i++) { String subFieldName = subFields[i]; Field field = nestedMessage.fieldMap().get(subFieldName); 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..8b81403ff085 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 @@ -66,6 +66,7 @@ public class ComplianceClientTest { .setName("name3373707") .setInfo(ComplianceData.newBuilder().build()) .setServerVerify(true) + .setCustomKebabName("customKebabName-2062111197") .build(); RepeatResponse actualResponse = client.repeatDataBody(request); @@ -99,6 +100,7 @@ public class ComplianceClientTest { .setName("name3373707") .setInfo(ComplianceData.newBuilder().build()) .setServerVerify(true) + .setCustomKebabName("customKebabName-2062111197") .build(); client.repeatDataBody(request); Assert.fail("No exception raised"); 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 3b4048f6c8e1..1bdc9c352bb4 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 @@ -80,21 +80,7 @@ void parseHttpAnnotation_multipleBindings() { .containsExactly("answer", "foo", "name", "test_to_verify.name", "type"); } - @Test - void parseHttpAnnotation_missingFieldFromMessage() { - FileDescriptor testingFileDescriptor = TestingOuterClass.getDescriptor(); - ServiceDescriptor testingService = testingFileDescriptor.getServices().get(0); - assertEquals("Testing", testingService.getName()); - - Map messages = Parser.parseMessages(testingFileDescriptor); - // VerityTest method. - MethodDescriptor rpcMethod = - testingService.getMethods().get(testingService.getMethods().size() - 1); - Message inputMessage = messages.get("com.google.showcase.v1beta1.CreateSessionRequest"); - assertThrows( - IllegalStateException.class, () -> HttpRuleParser.parse(rpcMethod, inputMessage, messages)); - } @Test void parseHttpAnnotation_shouldPutAllFieldsIntoQueryParamsIfPathParamAndBodyAreNotConfigured() { 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..5c56b91ff9b9 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,15 @@ 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())) From 821cec455b2f70a242f0a7357e5048a126975e8e Mon Sep 17 00:00:00 2001 From: Diego Date: Mon, 27 Apr 2026 12:41:12 -0400 Subject: [PATCH 04/12] chore: format --- .../rest/HttpJsonServiceStubClassComposer.java | 3 +-- .../composer/rest/goldens/ComplianceClientTest.golden | 10 ++++++++++ .../gapic/protoparser/HttpRuleParserTest.java | 3 --- .../api/generator/gapic/protoparser/ParserTest.java | 6 +----- 4 files changed, 12 insertions(+), 10 deletions(-) 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 418d8ab8fcac..07bab62b8a49 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 @@ -924,8 +924,7 @@ private Expr createFieldsExtractorClassInstance( paramsPutArgs.add(fieldsVarExpr); paramsPutArgs.add( - ValueExpr.withValue( - StringObjectValue.withValue(httpBindingFieldName.jsonName()))); + ValueExpr.withValue(StringObjectValue.withValue(httpBindingFieldName.jsonName()))); paramsPutArgs.add(requestBuilderExpr); Expr paramsPutExpr = 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 8b81403ff085..c9f4552ba6ed 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 @@ -120,6 +120,7 @@ public class ComplianceClientTest { .setName("name3373707") .setInfo(ComplianceData.newBuilder().build()) .setServerVerify(true) + .setCustomKebabName("customKebabName-2062111197") .build(); RepeatResponse actualResponse = client.repeatDataBodyInfo(request); @@ -153,6 +154,7 @@ public class ComplianceClientTest { .setName("name3373707") .setInfo(ComplianceData.newBuilder().build()) .setServerVerify(true) + .setCustomKebabName("customKebabName-2062111197") .build(); client.repeatDataBodyInfo(request); Assert.fail("No exception raised"); @@ -172,6 +174,7 @@ public class ComplianceClientTest { .setName("name3373707") .setInfo(ComplianceData.newBuilder().build()) .setServerVerify(true) + .setCustomKebabName("customKebabName-2062111197") .build(); RepeatResponse actualResponse = client.repeatDataQuery(request); @@ -205,6 +208,7 @@ public class ComplianceClientTest { .setName("name3373707") .setInfo(ComplianceData.newBuilder().build()) .setServerVerify(true) + .setCustomKebabName("customKebabName-2062111197") .build(); client.repeatDataQuery(request); Assert.fail("No exception raised"); @@ -247,6 +251,7 @@ public class ComplianceClientTest { .setPChild(ComplianceDataChild.newBuilder().build()) .build()) .setServerVerify(true) + .setCustomKebabName("customKebabName-2062111197") .build(); RepeatResponse actualResponse = client.repeatDataSimplePath(request); @@ -303,6 +308,7 @@ public class ComplianceClientTest { .setPChild(ComplianceDataChild.newBuilder().build()) .build()) .setServerVerify(true) + .setCustomKebabName("customKebabName-2062111197") .build(); client.repeatDataSimplePath(request); Assert.fail("No exception raised"); @@ -359,6 +365,7 @@ public class ComplianceClientTest { .setPChild(ComplianceDataChild.newBuilder().build()) .build()) .setServerVerify(true) + .setCustomKebabName("customKebabName-2062111197") .build(); RepeatResponse actualResponse = client.repeatDataPathResource(request); @@ -429,6 +436,7 @@ public class ComplianceClientTest { .setPChild(ComplianceDataChild.newBuilder().build()) .build()) .setServerVerify(true) + .setCustomKebabName("customKebabName-2062111197") .build(); client.repeatDataPathResource(request); Assert.fail("No exception raised"); @@ -485,6 +493,7 @@ public class ComplianceClientTest { .setPChild(ComplianceDataChild.newBuilder().build()) .build()) .setServerVerify(true) + .setCustomKebabName("customKebabName-2062111197") .build(); RepeatResponse actualResponse = client.repeatDataPathTrailingResource(request); @@ -555,6 +564,7 @@ public class ComplianceClientTest { .setPChild(ComplianceDataChild.newBuilder().build()) .build()) .setServerVerify(true) + .setCustomKebabName("customKebabName-2062111197") .build(); client.repeatDataPathTrailingResource(request); Assert.fail("No exception raised"); 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 1bdc9c352bb4..f487f0b15637 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 @@ -16,7 +16,6 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import com.google.api.generator.gapic.model.HttpBindings; @@ -80,8 +79,6 @@ void parseHttpAnnotation_multipleBindings() { .containsExactly("answer", "foo", "name", "test_to_verify.name", "type"); } - - @Test void parseHttpAnnotation_shouldPutAllFieldsIntoQueryParamsIfPathParamAndBodyAreNotConfigured() { FileDescriptor fileDescriptor = HttpRuleParserTestingOuterClass.getDescriptor(); 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 5c56b91ff9b9..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,11 +102,7 @@ void parseMessages_basic() { String echoResponseName = "EchoResponse"; Field echoResponseContentField = - Field.builder() - .setName("content") - .setJsonName("content") - .setType(TypeNode.STRING) - .build(); + Field.builder().setName("content").setJsonName("content").setType(TypeNode.STRING).build(); Field echoResponseSeverityField = Field.builder() .setName("severity") From 0967e2c5b32bd0357b1ab7153a10e61163bfff12 Mon Sep 17 00:00:00 2001 From: Diego Date: Mon, 27 Apr 2026 12:45:32 -0400 Subject: [PATCH 05/12] test: retore missing test --- .../gapic/protoparser/HttpRuleParser.java | 5 +++++ .../gapic/protoparser/HttpRuleParserTest.java | 16 ++++++++++++++++ 2 files changed, 21 insertions(+) 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 3b015bb58f40..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 @@ -151,6 +151,11 @@ private static Set validateAndConstructHttpBindings( 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 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 f487f0b15637..f76292e964aa 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 @@ -79,6 +79,22 @@ void parseHttpAnnotation_multipleBindings() { .containsExactly("answer", "foo", "name", "test_to_verify.name", "type"); } + @Test + void parseHttpAnnotation_missingFieldFromMessage() { + FileDescriptor testingFileDescriptor = TestingOuterClass.getDescriptor(); + ServiceDescriptor testingService = testingFileDescriptor.getServices().get(0); + assertEquals("Testing", testingService.getName()); + + Map messages = Parser.parseMessages(testingFileDescriptor); + + // VerityTest method. + MethodDescriptor rpcMethod = + testingService.getMethods().get(testingService.getMethods().size() - 1); + Message inputMessage = messages.get("com.google.showcase.v1beta1.CreateSessionRequest"); + assertThrows( + IllegalStateException.class, () -> HttpRuleParser.parse(rpcMethod, inputMessage, messages)); + } + @Test void parseHttpAnnotation_shouldPutAllFieldsIntoQueryParamsIfPathParamAndBodyAreNotConfigured() { FileDescriptor fileDescriptor = HttpRuleParserTestingOuterClass.getDescriptor(); From 2c7cd12c93a05c0c93e448a10c686605274e766c Mon Sep 17 00:00:00 2001 From: Diego Date: Mon, 27 Apr 2026 12:50:27 -0400 Subject: [PATCH 06/12] fix: use jupiter assertion --- .../api/generator/gapic/protoparser/HttpRuleParserTest.java | 1 + 1 file changed, 1 insertion(+) 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 f76292e964aa..3b4048f6c8e1 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 @@ -16,6 +16,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import com.google.api.generator.gapic.model.HttpBindings; From aa4005b76728a8807cee240f87edf1bc5a85ab14 Mon Sep 17 00:00:00 2001 From: Diego Date: Mon, 27 Apr 2026 13:32:40 -0400 Subject: [PATCH 07/12] fix: use fallback in stub composer --- .../composer/rest/HttpJsonServiceStubClassComposer.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 07bab62b8a49..ae26206d3f76 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 @@ -924,7 +924,11 @@ private Expr createFieldsExtractorClassInstance( paramsPutArgs.add(fieldsVarExpr); paramsPutArgs.add( - ValueExpr.withValue(StringObjectValue.withValue(httpBindingFieldName.jsonName()))); + ValueExpr.withValue( + StringObjectValue.withValue( + (httpBindingFieldName.jsonName() != null) + ? httpBindingFieldName.jsonName() + : JavaStyle.toLowerCamelCase(httpBindingFieldName.name())))); paramsPutArgs.add(requestBuilderExpr); Expr paramsPutExpr = From b1ed1a876f423e6b7ff77658ad108603384d18e3 Mon Sep 17 00:00:00 2001 From: Diego Date: Wed, 29 Apr 2026 15:13:03 -0400 Subject: [PATCH 08/12] chore: ignore ide files --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 From ed9d0a93ebf8cb66676e0a174844eae1d627a5dd Mon Sep 17 00:00:00 2001 From: Diego Date: Thu, 30 Apr 2026 09:47:01 -0400 Subject: [PATCH 09/12] fix: include json_name consideration for body and path params --- .../HttpJsonServiceStubClassComposer.java | 13 +- .../generator/gapic/model/HttpBindings.java | 9 +- .../grpcrest/goldens/HttpJsonEchoStub.golden | 2 +- .../rest/goldens/ComplianceClientTest.golden | 234 +++++++++++++++++- .../rest/goldens/ComplianceSettings.golden | 34 +++ .../goldens/ComplianceStubSettings.golden | 83 ++++++- .../goldens/HttpJsonComplianceStub.golden | 203 ++++++++++++++- .../gapic/protoparser/HttpRuleParserTest.java | 4 +- .../src/test/proto/compliance.proto | 32 ++- 9 files changed, 582 insertions(+), 32 deletions(-) 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 ae26206d3f76..3899b9996ee8 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,13 @@ 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 (e.g., avoiding 'case_' generated to prevent keywords + // conflict). + 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,6 +933,10 @@ private Expr createFieldsExtractorClassInstance( paramsPutArgs.add( ValueExpr.withValue( StringObjectValue.withValue( + // 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())))); 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 5e019983c1f8..f5050e9f5aa6 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 @@ -17,6 +17,7 @@ import com.google.api.generator.gapic.utils.JavaStyle; import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableSet; +import com.google.common.base.Strings; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -140,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/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 c9f4552ba6ed..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 @@ -66,7 +66,6 @@ public class ComplianceClientTest { .setName("name3373707") .setInfo(ComplianceData.newBuilder().build()) .setServerVerify(true) - .setCustomKebabName("customKebabName-2062111197") .build(); RepeatResponse actualResponse = client.repeatDataBody(request); @@ -100,7 +99,6 @@ public class ComplianceClientTest { .setName("name3373707") .setInfo(ComplianceData.newBuilder().build()) .setServerVerify(true) - .setCustomKebabName("customKebabName-2062111197") .build(); client.repeatDataBody(request); Assert.fail("No exception raised"); @@ -120,7 +118,6 @@ public class ComplianceClientTest { .setName("name3373707") .setInfo(ComplianceData.newBuilder().build()) .setServerVerify(true) - .setCustomKebabName("customKebabName-2062111197") .build(); RepeatResponse actualResponse = client.repeatDataBodyInfo(request); @@ -154,7 +151,6 @@ public class ComplianceClientTest { .setName("name3373707") .setInfo(ComplianceData.newBuilder().build()) .setServerVerify(true) - .setCustomKebabName("customKebabName-2062111197") .build(); client.repeatDataBodyInfo(request); Assert.fail("No exception raised"); @@ -174,7 +170,6 @@ public class ComplianceClientTest { .setName("name3373707") .setInfo(ComplianceData.newBuilder().build()) .setServerVerify(true) - .setCustomKebabName("customKebabName-2062111197") .build(); RepeatResponse actualResponse = client.repeatDataQuery(request); @@ -208,7 +203,6 @@ public class ComplianceClientTest { .setName("name3373707") .setInfo(ComplianceData.newBuilder().build()) .setServerVerify(true) - .setCustomKebabName("customKebabName-2062111197") .build(); client.repeatDataQuery(request); Assert.fail("No exception raised"); @@ -244,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) @@ -251,7 +246,6 @@ public class ComplianceClientTest { .setPChild(ComplianceDataChild.newBuilder().build()) .build()) .setServerVerify(true) - .setCustomKebabName("customKebabName-2062111197") .build(); RepeatResponse actualResponse = client.repeatDataSimplePath(request); @@ -301,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) @@ -308,7 +303,6 @@ public class ComplianceClientTest { .setPChild(ComplianceDataChild.newBuilder().build()) .build()) .setServerVerify(true) - .setCustomKebabName("customKebabName-2062111197") .build(); client.repeatDataSimplePath(request); Assert.fail("No exception raised"); @@ -358,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) @@ -365,7 +360,6 @@ public class ComplianceClientTest { .setPChild(ComplianceDataChild.newBuilder().build()) .build()) .setServerVerify(true) - .setCustomKebabName("customKebabName-2062111197") .build(); RepeatResponse actualResponse = client.repeatDataPathResource(request); @@ -429,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) @@ -436,7 +431,6 @@ public class ComplianceClientTest { .setPChild(ComplianceDataChild.newBuilder().build()) .build()) .setServerVerify(true) - .setCustomKebabName("customKebabName-2062111197") .build(); client.repeatDataPathResource(request); Assert.fail("No exception raised"); @@ -486,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) @@ -493,7 +488,6 @@ public class ComplianceClientTest { .setPChild(ComplianceDataChild.newBuilder().build()) .build()) .setServerVerify(true) - .setCustomKebabName("customKebabName-2062111197") .build(); RepeatResponse actualResponse = client.repeatDataPathTrailingResource(request); @@ -557,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) @@ -564,7 +559,6 @@ public class ComplianceClientTest { .setPChild(ComplianceDataChild.newBuilder().build()) .build()) .setServerVerify(true) - .setCustomKebabName("customKebabName-2062111197") .build(); client.repeatDataPathTrailingResource(request); Assert.fail("No exception raised"); @@ -670,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 3ca2ad0f908a..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; @@ -94,8 +95,6 @@ public class HttpJsonComplianceStub extends ComplianceStub { Map> fields = new HashMap<>(); ProtoRestSerializer serializer = ProtoRestSerializer.create(); - serializer.putQueryParam( - fields, "custom-kebab-name", request.getCustomKebabName()); serializer.putQueryParam(fields, "name", request.getName()); serializer.putQueryParam( fields, "serverVerify", request.getServerVerify()); @@ -134,8 +133,6 @@ public class HttpJsonComplianceStub extends ComplianceStub { 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( @@ -185,8 +182,6 @@ public class HttpJsonComplianceStub extends ComplianceStub { 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( @@ -234,8 +229,6 @@ public class HttpJsonComplianceStub extends ComplianceStub { 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( @@ -280,8 +273,6 @@ public class HttpJsonComplianceStub extends ComplianceStub { 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( @@ -361,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; @@ -369,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; @@ -482,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( @@ -515,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()); @@ -531,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; } @@ -574,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/protoparser/HttpRuleParserTest.java b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/protoparser/HttpRuleParserTest.java index 3b4048f6c8e1..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 @@ -190,11 +190,11 @@ void parseHttpAnnotation_respectsJsonNameWithDashes() { MethodDescriptor rpcMethod = complianceService.getMethods().stream() - .filter(m -> m.getName().equals("RepeatDataQuery")) + .filter(m -> m.getName().equals("RepeatDataCustomQuery")) .findAny() .get(); - Message inputMessage = messages.get("com.google.showcase.v1beta1.RepeatRequest"); + Message inputMessage = messages.get("com.google.showcase.v1beta1.CustomBindingRequest"); HttpBindings httpBindings = HttpRuleParser.parse(rpcMethod, inputMessage, messages); HttpBinding customBinding = 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 4b8e1c8d5bdd..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 { @@ -127,9 +150,15 @@ message RepeatRequest { // If true, the server will verify that the received request matches // the request with the same name in the compliance test suite. bool server_verify = 3; +} + +message CustomBindingRequest { + string name = 1; + ComplianceData info = 2; + bool server_verify = 3; - // New field for testing json_name with dashes string custom_kebab_name = 4 [json_name = "custom-kebab-name"]; + ComplianceData custom_body_message = 5 [json_name = "custom-body-message"]; } message RepeatResponse { @@ -192,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 From a9fcffcc5da016bfa8109f542877f699d2f404ef Mon Sep 17 00:00:00 2001 From: Diego Date: Thu, 30 Apr 2026 09:58:32 -0400 Subject: [PATCH 10/12] docs: explain that response parsing considers json_name --- .../java/com/google/api/gax/httpjson/ProtoRestSerializer.java | 3 +++ 1 file changed, 3 insertions(+) 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) { From 1c31d3df5a94c855629b45e66f403f377abdb9f2 Mon Sep 17 00:00:00 2001 From: Diego Date: Thu, 30 Apr 2026 09:59:25 -0400 Subject: [PATCH 11/12] chore: format --- .../java/com/google/api/generator/gapic/model/HttpBindings.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 f5050e9f5aa6..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,8 +16,8 @@ import com.google.api.generator.gapic.utils.JavaStyle; import com.google.auto.value.AutoValue; -import com.google.common.collect.ImmutableSet; import com.google.common.base.Strings; +import com.google.common.collect.ImmutableSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; From 76bc472d1eabe539dcc62363179e9f0232b21453 Mon Sep 17 00:00:00 2001 From: Diego Date: Thu, 30 Apr 2026 11:31:25 -0400 Subject: [PATCH 12/12] docs: improve comment about "case_" --- .../rest/HttpJsonServiceStubClassComposer.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) 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 3899b9996ee8..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 @@ -765,8 +765,11 @@ private Expr createBodyFieldsExtractorClassInstance( MethodInvocationExpr.Builder requestFieldMethodExprBuilder = MethodInvocationExpr.builder().setExprReferenceExpr(prevExpr); // 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). + // 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() @@ -934,8 +937,7 @@ private Expr createFieldsExtractorClassInstance( ValueExpr.withValue( StringObjectValue.withValue( // 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 + // name over Java-escaped identifiers (e.g., avoiding 'case_' generated to prevent // keywords conflict). (httpBindingFieldName.jsonName() != null) ? httpBindingFieldName.jsonName()