Skip to content

Commit 5fd79ea

Browse files
authored
fix: Unescape Java keyword field names when generating HttpJson unit tests. (#1654)
1 parent c2d6d15 commit 5fd79ea

File tree

12 files changed

+382
-3
lines changed

12 files changed

+382
-3
lines changed

gapic-generator-java/src/main/java/com/google/api/generator/engine/lexicon/Keyword.java

+17
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
package com.google.api.generator.engine.lexicon;
1616

17+
import com.google.common.base.Strings;
1718
import com.google.common.collect.ImmutableList;
1819

1920
public class Keyword {
@@ -71,11 +72,27 @@ public class Keyword {
7172
"native",
7273
"super",
7374
"while");
75+
private static final String ESCAPE_CHAR = "_";
7476

7577
public static boolean isKeyword(String s) {
7678
return s.equals(CLASS_KEYWORD) || KEYWORDS.contains(s);
7779
}
7880

81+
public static String unescapeKeyword(String str) {
82+
if (Strings.isNullOrEmpty(str)) {
83+
return str;
84+
}
85+
if (!str.endsWith(ESCAPE_CHAR)) {
86+
return str;
87+
}
88+
String strWithoutEscapeChar = str.substring(0, str.lastIndexOf(ESCAPE_CHAR));
89+
return isKeyword(strWithoutEscapeChar) ? strWithoutEscapeChar : str;
90+
}
91+
92+
public static String escapeKeyword(String str) {
93+
return Keyword.isKeyword(str) ? str + ESCAPE_CHAR : str;
94+
}
95+
7996
public static boolean isInvalidFieldName(String s) {
8097
return KEYWORDS.contains(s);
8198
}

gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/defaultvalue/DefaultValueComposer.java

+2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import com.google.api.generator.engine.ast.VaporReference;
3131
import com.google.api.generator.engine.ast.Variable;
3232
import com.google.api.generator.engine.ast.VariableExpr;
33+
import com.google.api.generator.engine.lexicon.Keyword;
3334
import com.google.api.generator.gapic.composer.resourcename.ResourceNameTokenizer;
3435
import com.google.api.generator.gapic.model.Field;
3536
import com.google.api.generator.gapic.model.HttpBindings;
@@ -149,6 +150,7 @@ public static Expr createValue(
149150
Map<String, String> nestedValuePatterns = new HashMap<>();
150151
for (Map.Entry<String, String> entry : valuePatterns.entrySet()) {
151152
String lowerCamelNestedFieldName = JavaStyle.toLowerCamelCase(nestedFieldName);
153+
lowerCamelNestedFieldName = Keyword.unescapeKeyword(lowerCamelNestedFieldName);
152154
if (entry.getKey().startsWith(lowerCamelNestedFieldName + '.')) {
153155
nestedValuePatterns.put(
154156
entry.getKey().substring(lowerCamelNestedFieldName.length() + 1), entry.getValue());

gapic-generator-java/src/main/java/com/google/api/generator/gapic/utils/JavaStyle.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public static String toLowerCamelCase(String s) {
3939

4040
// Some APIs use legit java keywords as method names. Both protobuf and gGRPC add an underscore
4141
// in generated stubs to resolve name conflict, so we need to do the same.
42-
return Keyword.isKeyword(name) ? name + '_' : name;
42+
return Keyword.escapeKeyword(name);
4343
}
4444

4545
public static String toUpperCamelCase(String s) {

gapic-generator-java/src/test/java/com/google/api/generator/engine/lexicon/KeywordTest.java

+30
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,34 @@ public void keywordDetected() {
4545
assertThat(Keyword.isKeyword("class")).isTrue();
4646
assertThat(Keyword.isInvalidFieldName("class")).isFalse();
4747
}
48+
49+
@Test
50+
public void unescapedKeyword_shouldReturnItselfIfEmpty() {
51+
assertThat(Keyword.unescapeKeyword("")).isEqualTo("");
52+
}
53+
54+
@Test
55+
public void unescapedKeyword_shouldReturnItselfIfDoesNotEndWithEscapeChar() {
56+
assertThat(Keyword.unescapeKeyword("hello")).isEqualTo("hello");
57+
}
58+
59+
@Test
60+
public void unescapedKeyword_shouldReturnItselfIfEndsWithEscapeCharButNotAKeyword() {
61+
assertThat(Keyword.unescapeKeyword("important_")).isEqualTo("important_");
62+
}
63+
64+
@Test
65+
public void unescapedKeyword_shouldUnescapeIfEndsWithEscapeCharAndAKeyword() {
66+
assertThat(Keyword.unescapeKeyword("import_")).isEqualTo("import");
67+
}
68+
69+
@Test
70+
public void escapeKeyword_shouldEscapeIfIsAKeyword() {
71+
assertThat(Keyword.escapeKeyword("final")).isEqualTo("final_");
72+
}
73+
74+
@Test
75+
public void escapeKeyword_shouldNotEscapeIfIsNotAKeyword() {
76+
assertThat(Keyword.escapeKeyword("fantasy")).isEqualTo("fantasy");
77+
}
4878
}

gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpcrest/goldens/EchoClient.golden

+75
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import com.google.api.resourcenames.ResourceName;
1818
import com.google.common.util.concurrent.MoreExecutors;
1919
import com.google.longrunning.Operation;
2020
import com.google.protobuf.Duration;
21+
import com.google.protobuf.FieldMask;
2122
import com.google.protobuf.Timestamp;
2223
import com.google.rpc.Status;
2324
import com.google.showcase.grpcrest.v1beta1.stub.EchoStub;
@@ -1045,6 +1046,80 @@ public class EchoClient implements BackgroundResource {
10451046
return stub.noBindingCallable();
10461047
}
10471048

1049+
// AUTO-GENERATED DOCUMENTATION AND METHOD.
1050+
/**
1051+
* Sample code:
1052+
*
1053+
* <pre>{@code
1054+
* // This snippet has been automatically generated and should be regarded as a code template only.
1055+
* // It will require modifications to work:
1056+
* // - It may require correct/in-range values for request initialization.
1057+
* // - It may require specifying regional endpoints when creating the service client as shown in
1058+
* // https://cloud.google.com/java/docs/setup#configure_endpoints_for_the_client_library
1059+
* try (EchoClient echoClient = EchoClient.create()) {
1060+
* Case case_ = Case.newBuilder().build();
1061+
* FieldMask updateMask = FieldMask.newBuilder().build();
1062+
* Case response = echoClient.updateCase(case_, updateMask);
1063+
* }
1064+
* }</pre>
1065+
*
1066+
* @param case_
1067+
* @param updateMask
1068+
* @throws com.google.api.gax.rpc.ApiException if the remote call fails
1069+
*/
1070+
public final Case updateCase(Case case_, FieldMask updateMask) {
1071+
UpdateCaseRequest request =
1072+
UpdateCaseRequest.newBuilder().setCase(case_).setUpdateMask(updateMask).build();
1073+
return updateCase(request);
1074+
}
1075+
1076+
// AUTO-GENERATED DOCUMENTATION AND METHOD.
1077+
/**
1078+
* Sample code:
1079+
*
1080+
* <pre>{@code
1081+
* // This snippet has been automatically generated and should be regarded as a code template only.
1082+
* // It will require modifications to work:
1083+
* // - It may require correct/in-range values for request initialization.
1084+
* // - It may require specifying regional endpoints when creating the service client as shown in
1085+
* // https://cloud.google.com/java/docs/setup#configure_endpoints_for_the_client_library
1086+
* try (EchoClient echoClient = EchoClient.create()) {
1087+
* UpdateCaseRequest request =
1088+
* UpdateCaseRequest.newBuilder().setCase(Case.newBuilder().build()).build();
1089+
* Case response = echoClient.updateCase(request);
1090+
* }
1091+
* }</pre>
1092+
*
1093+
* @param request The request object containing all of the parameters for the API call.
1094+
* @throws com.google.api.gax.rpc.ApiException if the remote call fails
1095+
*/
1096+
public final Case updateCase(UpdateCaseRequest request) {
1097+
return updateCaseCallable().call(request);
1098+
}
1099+
1100+
// AUTO-GENERATED DOCUMENTATION AND METHOD.
1101+
/**
1102+
* Sample code:
1103+
*
1104+
* <pre>{@code
1105+
* // This snippet has been automatically generated and should be regarded as a code template only.
1106+
* // It will require modifications to work:
1107+
* // - It may require correct/in-range values for request initialization.
1108+
* // - It may require specifying regional endpoints when creating the service client as shown in
1109+
* // https://cloud.google.com/java/docs/setup#configure_endpoints_for_the_client_library
1110+
* try (EchoClient echoClient = EchoClient.create()) {
1111+
* UpdateCaseRequest request =
1112+
* UpdateCaseRequest.newBuilder().setCase(Case.newBuilder().build()).build();
1113+
* ApiFuture<Case> future = echoClient.updateCaseCallable().futureCall(request);
1114+
* // Do something.
1115+
* Case response = future.get();
1116+
* }
1117+
* }</pre>
1118+
*/
1119+
public final UnaryCallable<UpdateCaseRequest, Case> updateCaseCallable() {
1120+
return stub.updateCaseCallable();
1121+
}
1122+
10481123
@Override
10491124
public final void close() {
10501125
stub.close();

gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpcrest/goldens/EchoClientHttpJsonTest.golden

+59
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import com.google.common.collect.Lists;
1717
import com.google.longrunning.Operation;
1818
import com.google.protobuf.Any;
1919
import com.google.protobuf.Duration;
20+
import com.google.protobuf.FieldMask;
2021
import com.google.protobuf.Timestamp;
2122
import com.google.protobuf.Value;
2223
import com.google.rpc.Status;
@@ -826,4 +827,62 @@ public class EchoClientHttpJsonTest {
826827
// The noBinding() method is not supported in REST transport.
827828
// This empty test is generated for technical reasons.
828829
}
830+
831+
@Test
832+
public void updateCaseTest() throws Exception {
833+
Case expectedResponse =
834+
Case.newBuilder()
835+
.setName("name3373707")
836+
.setDisplayName("displayName1714148973")
837+
.setDescription("description-1724546052")
838+
.build();
839+
mockService.addResponse(expectedResponse);
840+
841+
Case case_ =
842+
Case.newBuilder()
843+
.setName("projects/project-3807/cases/case-3807")
844+
.setDisplayName("displayName1714148973")
845+
.setDescription("description-1724546052")
846+
.build();
847+
FieldMask updateMask = FieldMask.newBuilder().build();
848+
849+
Case actualResponse = client.updateCase(case_, updateMask);
850+
Assert.assertEquals(expectedResponse, actualResponse);
851+
852+
List<String> actualRequests = mockService.getRequestPaths();
853+
Assert.assertEquals(1, actualRequests.size());
854+
855+
String apiClientHeaderKey =
856+
mockService
857+
.getRequestHeaders()
858+
.get(ApiClientHeaderProvider.getDefaultApiClientHeaderKey())
859+
.iterator()
860+
.next();
861+
Assert.assertTrue(
862+
GaxHttpJsonProperties.getDefaultApiClientHeaderPattern()
863+
.matcher(apiClientHeaderKey)
864+
.matches());
865+
}
866+
867+
@Test
868+
public void updateCaseExceptionTest() throws Exception {
869+
ApiException exception =
870+
ApiExceptionFactory.createException(
871+
new Exception(), FakeStatusCode.of(StatusCode.Code.INVALID_ARGUMENT), false);
872+
mockService.addException(exception);
873+
874+
try {
875+
Case case_ =
876+
Case.newBuilder()
877+
.setName("projects/project-3807/cases/case-3807")
878+
.setDisplayName("displayName1714148973")
879+
.setDescription("description-1724546052")
880+
.build();
881+
FieldMask updateMask = FieldMask.newBuilder().build();
882+
client.updateCase(case_, updateMask);
883+
Assert.fail("No exception raised");
884+
} catch (InvalidArgumentException e) {
885+
// Expected exception.
886+
}
887+
}
829888
}

gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpcrest/goldens/EchoClientTest.golden

+44
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import com.google.longrunning.Operation;
2121
import com.google.protobuf.AbstractMessage;
2222
import com.google.protobuf.Any;
2323
import com.google.protobuf.Duration;
24+
import com.google.protobuf.FieldMask;
2425
import com.google.protobuf.Timestamp;
2526
import com.google.protobuf.Value;
2627
import com.google.rpc.Status;
@@ -900,4 +901,47 @@ public class EchoClientTest {
900901
// Expected exception.
901902
}
902903
}
904+
905+
@Test
906+
public void updateCaseTest() throws Exception {
907+
Case expectedResponse =
908+
Case.newBuilder()
909+
.setName("name3373707")
910+
.setDisplayName("displayName1714148973")
911+
.setDescription("description-1724546052")
912+
.build();
913+
mockEcho.addResponse(expectedResponse);
914+
915+
Case case_ = Case.newBuilder().build();
916+
FieldMask updateMask = FieldMask.newBuilder().build();
917+
918+
Case actualResponse = client.updateCase(case_, updateMask);
919+
Assert.assertEquals(expectedResponse, actualResponse);
920+
921+
List<AbstractMessage> actualRequests = mockEcho.getRequests();
922+
Assert.assertEquals(1, actualRequests.size());
923+
UpdateCaseRequest actualRequest = ((UpdateCaseRequest) actualRequests.get(0));
924+
925+
Assert.assertEquals(case_, actualRequest.getCase());
926+
Assert.assertEquals(updateMask, actualRequest.getUpdateMask());
927+
Assert.assertTrue(
928+
channelProvider.isHeaderSent(
929+
ApiClientHeaderProvider.getDefaultApiClientHeaderKey(),
930+
GaxGrpcProperties.getDefaultApiClientHeaderPattern()));
931+
}
932+
933+
@Test
934+
public void updateCaseExceptionTest() throws Exception {
935+
StatusRuntimeException exception = new StatusRuntimeException(io.grpc.Status.INVALID_ARGUMENT);
936+
mockEcho.addException(exception);
937+
938+
try {
939+
Case case_ = Case.newBuilder().build();
940+
FieldMask updateMask = FieldMask.newBuilder().build();
941+
client.updateCase(case_, updateMask);
942+
Assert.fail("No exception raised");
943+
} catch (InvalidArgumentException e) {
944+
// Expected exception.
945+
}
946+
}
903947
}

gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpcrest/goldens/EchoSettings.golden

+10
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,11 @@ public class EchoSettings extends ClientSettings<EchoSettings> {
122122
return ((EchoStubSettings) getStubSettings()).noBindingSettings();
123123
}
124124

125+
/** Returns the object with the settings used for calls to updateCase. */
126+
public UnaryCallSettings<UpdateCaseRequest, Case> updateCaseSettings() {
127+
return ((EchoStubSettings) getStubSettings()).updateCaseSettings();
128+
}
129+
125130
public static final EchoSettings create(EchoStubSettings stub) throws IOException {
126131
return new EchoSettings.Builder(stub.toBuilder()).build();
127132
}
@@ -296,6 +301,11 @@ public class EchoSettings extends ClientSettings<EchoSettings> {
296301
return getStubSettingsBuilder().noBindingSettings();
297302
}
298303

304+
/** Returns the builder for the settings used for calls to updateCase. */
305+
public UnaryCallSettings.Builder<UpdateCaseRequest, Case> updateCaseSettings() {
306+
return getStubSettingsBuilder().updateCaseSettings();
307+
}
308+
299309
@Override
300310
public EchoSettings build() throws IOException {
301311
return new EchoSettings(this);

0 commit comments

Comments
 (0)