Skip to content

Commit

Permalink
fix: Unescape Java keyword field names when generating HttpJson unit …
Browse files Browse the repository at this point in the history
…tests. (#1654)
  • Loading branch information
blakeli0 committed Apr 25, 2023
1 parent c2d6d15 commit 5fd79ea
Show file tree
Hide file tree
Showing 12 changed files with 382 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

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

import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;

public class Keyword {
Expand Down Expand Up @@ -71,11 +72,27 @@ public class Keyword {
"native",
"super",
"while");
private static final String ESCAPE_CHAR = "_";

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

public static String unescapeKeyword(String str) {
if (Strings.isNullOrEmpty(str)) {
return str;
}
if (!str.endsWith(ESCAPE_CHAR)) {
return str;
}
String strWithoutEscapeChar = str.substring(0, str.lastIndexOf(ESCAPE_CHAR));
return isKeyword(strWithoutEscapeChar) ? strWithoutEscapeChar : str;
}

public static String escapeKeyword(String str) {
return Keyword.isKeyword(str) ? str + ESCAPE_CHAR : str;
}

public static boolean isInvalidFieldName(String s) {
return KEYWORDS.contains(s);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import com.google.api.generator.engine.ast.VaporReference;
import com.google.api.generator.engine.ast.Variable;
import com.google.api.generator.engine.ast.VariableExpr;
import com.google.api.generator.engine.lexicon.Keyword;
import com.google.api.generator.gapic.composer.resourcename.ResourceNameTokenizer;
import com.google.api.generator.gapic.model.Field;
import com.google.api.generator.gapic.model.HttpBindings;
Expand Down Expand Up @@ -149,6 +150,7 @@ public static Expr createValue(
Map<String, String> nestedValuePatterns = new HashMap<>();
for (Map.Entry<String, String> entry : valuePatterns.entrySet()) {
String lowerCamelNestedFieldName = JavaStyle.toLowerCamelCase(nestedFieldName);
lowerCamelNestedFieldName = Keyword.unescapeKeyword(lowerCamelNestedFieldName);
if (entry.getKey().startsWith(lowerCamelNestedFieldName + '.')) {
nestedValuePatterns.put(
entry.getKey().substring(lowerCamelNestedFieldName.length() + 1), entry.getValue());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public static String toLowerCamelCase(String s) {

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

public static String toUpperCamelCase(String s) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,34 @@ public void keywordDetected() {
assertThat(Keyword.isKeyword("class")).isTrue();
assertThat(Keyword.isInvalidFieldName("class")).isFalse();
}

@Test
public void unescapedKeyword_shouldReturnItselfIfEmpty() {
assertThat(Keyword.unescapeKeyword("")).isEqualTo("");
}

@Test
public void unescapedKeyword_shouldReturnItselfIfDoesNotEndWithEscapeChar() {
assertThat(Keyword.unescapeKeyword("hello")).isEqualTo("hello");
}

@Test
public void unescapedKeyword_shouldReturnItselfIfEndsWithEscapeCharButNotAKeyword() {
assertThat(Keyword.unescapeKeyword("important_")).isEqualTo("important_");
}

@Test
public void unescapedKeyword_shouldUnescapeIfEndsWithEscapeCharAndAKeyword() {
assertThat(Keyword.unescapeKeyword("import_")).isEqualTo("import");
}

@Test
public void escapeKeyword_shouldEscapeIfIsAKeyword() {
assertThat(Keyword.escapeKeyword("final")).isEqualTo("final_");
}

@Test
public void escapeKeyword_shouldNotEscapeIfIsNotAKeyword() {
assertThat(Keyword.escapeKeyword("fantasy")).isEqualTo("fantasy");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import com.google.api.resourcenames.ResourceName;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.longrunning.Operation;
import com.google.protobuf.Duration;
import com.google.protobuf.FieldMask;
import com.google.protobuf.Timestamp;
import com.google.rpc.Status;
import com.google.showcase.grpcrest.v1beta1.stub.EchoStub;
Expand Down Expand Up @@ -1045,6 +1046,80 @@ public class EchoClient implements BackgroundResource {
return stub.noBindingCallable();
}

// AUTO-GENERATED DOCUMENTATION AND METHOD.
/**
* Sample code:
*
* <pre>{@code
* // This snippet has been automatically generated and should be regarded as a code template only.
* // It will require modifications to work:
* // - It may require correct/in-range values for request initialization.
* // - It may require specifying regional endpoints when creating the service client as shown in
* // https://cloud.google.com/java/docs/setup#configure_endpoints_for_the_client_library
* try (EchoClient echoClient = EchoClient.create()) {
* Case case_ = Case.newBuilder().build();
* FieldMask updateMask = FieldMask.newBuilder().build();
* Case response = echoClient.updateCase(case_, updateMask);
* }
* }</pre>
*
* @param case_
* @param updateMask
* @throws com.google.api.gax.rpc.ApiException if the remote call fails
*/
public final Case updateCase(Case case_, FieldMask updateMask) {
UpdateCaseRequest request =
UpdateCaseRequest.newBuilder().setCase(case_).setUpdateMask(updateMask).build();
return updateCase(request);
}

// AUTO-GENERATED DOCUMENTATION AND METHOD.
/**
* Sample code:
*
* <pre>{@code
* // This snippet has been automatically generated and should be regarded as a code template only.
* // It will require modifications to work:
* // - It may require correct/in-range values for request initialization.
* // - It may require specifying regional endpoints when creating the service client as shown in
* // https://cloud.google.com/java/docs/setup#configure_endpoints_for_the_client_library
* try (EchoClient echoClient = EchoClient.create()) {
* UpdateCaseRequest request =
* UpdateCaseRequest.newBuilder().setCase(Case.newBuilder().build()).build();
* Case response = echoClient.updateCase(request);
* }
* }</pre>
*
* @param request The request object containing all of the parameters for the API call.
* @throws com.google.api.gax.rpc.ApiException if the remote call fails
*/
public final Case updateCase(UpdateCaseRequest request) {
return updateCaseCallable().call(request);
}

// AUTO-GENERATED DOCUMENTATION AND METHOD.
/**
* Sample code:
*
* <pre>{@code
* // This snippet has been automatically generated and should be regarded as a code template only.
* // It will require modifications to work:
* // - It may require correct/in-range values for request initialization.
* // - It may require specifying regional endpoints when creating the service client as shown in
* // https://cloud.google.com/java/docs/setup#configure_endpoints_for_the_client_library
* try (EchoClient echoClient = EchoClient.create()) {
* UpdateCaseRequest request =
* UpdateCaseRequest.newBuilder().setCase(Case.newBuilder().build()).build();
* ApiFuture<Case> future = echoClient.updateCaseCallable().futureCall(request);
* // Do something.
* Case response = future.get();
* }
* }</pre>
*/
public final UnaryCallable<UpdateCaseRequest, Case> updateCaseCallable() {
return stub.updateCaseCallable();
}

@Override
public final void close() {
stub.close();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import com.google.common.collect.Lists;
import com.google.longrunning.Operation;
import com.google.protobuf.Any;
import com.google.protobuf.Duration;
import com.google.protobuf.FieldMask;
import com.google.protobuf.Timestamp;
import com.google.protobuf.Value;
import com.google.rpc.Status;
Expand Down Expand Up @@ -826,4 +827,62 @@ public class EchoClientHttpJsonTest {
// The noBinding() method is not supported in REST transport.
// This empty test is generated for technical reasons.
}

@Test
public void updateCaseTest() throws Exception {
Case expectedResponse =
Case.newBuilder()
.setName("name3373707")
.setDisplayName("displayName1714148973")
.setDescription("description-1724546052")
.build();
mockService.addResponse(expectedResponse);

Case case_ =
Case.newBuilder()
.setName("projects/project-3807/cases/case-3807")
.setDisplayName("displayName1714148973")
.setDescription("description-1724546052")
.build();
FieldMask updateMask = FieldMask.newBuilder().build();

Case actualResponse = client.updateCase(case_, updateMask);
Assert.assertEquals(expectedResponse, actualResponse);

List<String> 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 updateCaseExceptionTest() throws Exception {
ApiException exception =
ApiExceptionFactory.createException(
new Exception(), FakeStatusCode.of(StatusCode.Code.INVALID_ARGUMENT), false);
mockService.addException(exception);

try {
Case case_ =
Case.newBuilder()
.setName("projects/project-3807/cases/case-3807")
.setDisplayName("displayName1714148973")
.setDescription("description-1724546052")
.build();
FieldMask updateMask = FieldMask.newBuilder().build();
client.updateCase(case_, updateMask);
Assert.fail("No exception raised");
} catch (InvalidArgumentException e) {
// Expected exception.
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import com.google.longrunning.Operation;
import com.google.protobuf.AbstractMessage;
import com.google.protobuf.Any;
import com.google.protobuf.Duration;
import com.google.protobuf.FieldMask;
import com.google.protobuf.Timestamp;
import com.google.protobuf.Value;
import com.google.rpc.Status;
Expand Down Expand Up @@ -900,4 +901,47 @@ public class EchoClientTest {
// Expected exception.
}
}

@Test
public void updateCaseTest() throws Exception {
Case expectedResponse =
Case.newBuilder()
.setName("name3373707")
.setDisplayName("displayName1714148973")
.setDescription("description-1724546052")
.build();
mockEcho.addResponse(expectedResponse);

Case case_ = Case.newBuilder().build();
FieldMask updateMask = FieldMask.newBuilder().build();

Case actualResponse = client.updateCase(case_, updateMask);
Assert.assertEquals(expectedResponse, actualResponse);

List<AbstractMessage> actualRequests = mockEcho.getRequests();
Assert.assertEquals(1, actualRequests.size());
UpdateCaseRequest actualRequest = ((UpdateCaseRequest) actualRequests.get(0));

Assert.assertEquals(case_, actualRequest.getCase());
Assert.assertEquals(updateMask, actualRequest.getUpdateMask());
Assert.assertTrue(
channelProvider.isHeaderSent(
ApiClientHeaderProvider.getDefaultApiClientHeaderKey(),
GaxGrpcProperties.getDefaultApiClientHeaderPattern()));
}

@Test
public void updateCaseExceptionTest() throws Exception {
StatusRuntimeException exception = new StatusRuntimeException(io.grpc.Status.INVALID_ARGUMENT);
mockEcho.addException(exception);

try {
Case case_ = Case.newBuilder().build();
FieldMask updateMask = FieldMask.newBuilder().build();
client.updateCase(case_, updateMask);
Assert.fail("No exception raised");
} catch (InvalidArgumentException e) {
// Expected exception.
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,11 @@ public class EchoSettings extends ClientSettings<EchoSettings> {
return ((EchoStubSettings) getStubSettings()).noBindingSettings();
}

/** Returns the object with the settings used for calls to updateCase. */
public UnaryCallSettings<UpdateCaseRequest, Case> updateCaseSettings() {
return ((EchoStubSettings) getStubSettings()).updateCaseSettings();
}

public static final EchoSettings create(EchoStubSettings stub) throws IOException {
return new EchoSettings.Builder(stub.toBuilder()).build();
}
Expand Down Expand Up @@ -296,6 +301,11 @@ public class EchoSettings extends ClientSettings<EchoSettings> {
return getStubSettingsBuilder().noBindingSettings();
}

/** Returns the builder for the settings used for calls to updateCase. */
public UnaryCallSettings.Builder<UpdateCaseRequest, Case> updateCaseSettings() {
return getStubSettingsBuilder().updateCaseSettings();
}

@Override
public EchoSettings build() throws IOException {
return new EchoSettings(this);
Expand Down
Loading

0 comments on commit 5fd79ea

Please sign in to comment.