attributes = new HashMap<>();
+
+ private MetricsRecorder metricsRecorder;
+
+ public MetricsTracer(MethodName methodName, MetricsRecorder metricsRecorder) {
+ this.attributes.put("method_name", methodName.toString());
+ this.metricsRecorder = metricsRecorder;
+ }
+
+ /**
+ * Signals that the overall operation has finished successfully. The tracer is now considered
+ * closed and should no longer be used. Successful operation adds "OK" value to the status
+ * attribute key.
+ */
+ @Override
+ public void operationSucceeded() {
+ attributes.put(STATUS_ATTRIBUTE, StatusCode.Code.OK.toString());
+ metricsRecorder.recordOperationLatency(
+ operationTimer.elapsed(TimeUnit.MILLISECONDS), attributes);
+ metricsRecorder.recordOperationCount(1, attributes);
+ }
+
+ /**
+ * Signals that the operation was cancelled by the user. The tracer is now considered closed and
+ * should no longer be used. Cancelled operation adds "CANCELLED" value to the status attribute
+ * key.
+ */
+ @Override
+ public void operationCancelled() {
+ attributes.put(STATUS_ATTRIBUTE, StatusCode.Code.CANCELLED.toString());
+ metricsRecorder.recordOperationLatency(
+ operationTimer.elapsed(TimeUnit.MILLISECONDS), attributes);
+ metricsRecorder.recordOperationCount(1, attributes);
+ }
+
+ /**
+ * Signals that the operation was cancelled by the user. The tracer is now considered closed and
+ * should no longer be used. Failed operation extracts the error from the throwable and adds it to
+ * the status attribute key.
+ */
+ @Override
+ public void operationFailed(Throwable error) {
+ attributes.put(STATUS_ATTRIBUTE, extractStatus(error));
+ metricsRecorder.recordOperationLatency(
+ operationTimer.elapsed(TimeUnit.MILLISECONDS), attributes);
+ metricsRecorder.recordOperationCount(1, attributes);
+ }
+
+ /**
+ * Adds an annotation that an attempt is about to start with additional information from the
+ * request. In general this should occur at the very start of the operation. The attemptNumber is
+ * zero based. So the initial attempt will be 0. When the attempt starts, the attemptTimer starts
+ * the stopwatch.
+ *
+ * @param attemptNumber the zero based sequential attempt number.
+ * @param request request of this attempt.
+ */
+ @Override
+ public void attemptStarted(Object request, int attemptNumber) {
+ attemptTimer = Stopwatch.createStarted();
+ }
+
+ /**
+ * Adds an annotation that the attempt succeeded. Successful attempt add "OK" value to the status
+ * attribute key.
+ */
+ @Override
+ public void attemptSucceeded() {
+
+ attributes.put(STATUS_ATTRIBUTE, StatusCode.Code.OK.toString());
+ metricsRecorder.recordAttemptLatency(attemptTimer.elapsed(TimeUnit.MILLISECONDS), attributes);
+ metricsRecorder.recordAttemptCount(1, attributes);
+ }
+
+ /**
+ * Add an annotation that the attempt was cancelled by the user. Cancelled attempt add "CANCELLED"
+ * to the status attribute key.
+ */
+ @Override
+ public void attemptCancelled() {
+
+ attributes.put(STATUS_ATTRIBUTE, StatusCode.Code.CANCELLED.toString());
+ metricsRecorder.recordAttemptLatency(attemptTimer.elapsed(TimeUnit.MILLISECONDS), attributes);
+ metricsRecorder.recordAttemptCount(1, attributes);
+ }
+
+ /**
+ * Adds an annotation that the attempt failed, but another attempt will be made after the delay.
+ *
+ * @param error the error that caused the attempt to fail.
+ * @param delay the amount of time to wait before the next attempt will start.
+ * Failed attempt extracts the error from the throwable and adds it to the status attribute
+ * key.
+ */
+ @Override
+ public void attemptFailed(Throwable error, Duration delay) {
+
+ attributes.put(STATUS_ATTRIBUTE, extractStatus(error));
+ metricsRecorder.recordAttemptLatency(attemptTimer.elapsed(TimeUnit.MILLISECONDS), attributes);
+ metricsRecorder.recordAttemptCount(1, attributes);
+ }
+
+ /**
+ * Adds an annotation that the attempt failed and that no further attempts will be made because
+ * retry limits have been reached. This extracts the error from the throwable and adds it to the
+ * status attribute key.
+ *
+ * @param error the last error received before retries were exhausted.
+ */
+ @Override
+ public void attemptFailedRetriesExhausted(Throwable error) {
+
+ attributes.put(STATUS_ATTRIBUTE, extractStatus(error));
+ metricsRecorder.recordAttemptLatency(attemptTimer.elapsed(TimeUnit.MILLISECONDS), attributes);
+ metricsRecorder.recordAttemptCount(1, attributes);
+ }
+
+ /**
+ * Adds an annotation that the attempt failed and that no further attempts will be made because
+ * the last error was not retryable. This extracts the error from the throwable and adds it to the
+ * status attribute key.
+ *
+ * @param error the error that caused the final attempt to fail.
+ */
+ @Override
+ public void attemptPermanentFailure(Throwable error) {
+
+ attributes.put(STATUS_ATTRIBUTE, extractStatus(error));
+ metricsRecorder.recordAttemptLatency(attemptTimer.elapsed(TimeUnit.MILLISECONDS), attributes);
+ metricsRecorder.recordAttemptCount(1, attributes);
+ }
+
+ /** Function to extract the status of the error as a string */
+ @VisibleForTesting
+ static String extractStatus(@Nullable Throwable error) {
+ final String statusString;
+
+ if (error == null) {
+ return StatusCode.Code.OK.toString();
+ } else if (error instanceof CancellationException) {
+ statusString = StatusCode.Code.CANCELLED.toString();
+ } else if (error instanceof ApiException) {
+ statusString = ((ApiException) error).getStatusCode().getCode().toString();
+ } else {
+ statusString = StatusCode.Code.UNKNOWN.toString();
+ }
+
+ return statusString;
+ }
/**
* Add attributes that will be attached to all metrics. This is expected to be called by
* handwritten client teams to add additional attributes that are not supposed be collected by
* Gax.
*/
- public void addAttributes(String key, String value) {};
+ public void addAttributes(String key, String value) {
+ attributes.put(key, value);
+ };
+
+ @VisibleForTesting
+ Map getAttributes() {
+ return attributes;
+ }
}
diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/MetricsTracerFactoryTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/MetricsTracerFactoryTest.java
new file mode 100644
index 0000000000..2c6a014658
--- /dev/null
+++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/MetricsTracerFactoryTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.google.api.gax.tracing;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.google.api.gax.tracing.ApiTracerFactory.OperationType;
+import com.google.common.truth.Truth;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+public class MetricsTracerFactoryTest {
+ @Mock private MetricsRecorder metricsRecorder;
+ @Mock private ApiTracer parent;
+ private SpanName spanName;
+ private MetricsTracerFactory metricsTracerFactory;
+
+ @Before
+ public void setUp() {
+ // Create an instance of MetricsTracerFactory with the mocked MetricsRecorder
+ metricsTracerFactory = new MetricsTracerFactory(metricsRecorder);
+
+ spanName = mock(SpanName.class);
+ when(spanName.getClientName()).thenReturn("testService");
+ when(spanName.getMethodName()).thenReturn("testMethod");
+ }
+
+ @Test
+ public void testNewTracer_notNull() {
+ // Call the newTracer method
+ ApiTracer apiTracer = metricsTracerFactory.newTracer(parent, spanName, OperationType.Unary);
+
+ // Assert that the apiTracer created has expected type and not null
+ Truth.assertThat(apiTracer).isInstanceOf(MetricsTracer.class);
+ Truth.assertThat(apiTracer).isNotNull();
+ }
+
+ @Test
+ public void testNewTracer_HasCorrectParameters() {
+
+ // Call the newTracer method
+ ApiTracer apiTracer = metricsTracerFactory.newTracer(parent, spanName, OperationType.Unary);
+
+ // Assert that the apiTracer created has expected type and not null
+ Truth.assertThat(apiTracer).isInstanceOf(MetricsTracer.class);
+ Truth.assertThat(apiTracer).isNotNull();
+
+ MetricsTracer metricsTracer = (MetricsTracer) apiTracer;
+ Truth.assertThat(metricsTracer.getAttributes().get("method_name"))
+ .isEqualTo("testService.testMethod");
+ }
+}
diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/MetricsTracerTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/MetricsTracerTest.java
new file mode 100644
index 0000000000..7b6b76f181
--- /dev/null
+++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/MetricsTracerTest.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.google.api.gax.tracing;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.anyDouble;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import com.google.api.gax.rpc.ApiException;
+import com.google.api.gax.rpc.DeadlineExceededException;
+import com.google.api.gax.rpc.NotFoundException;
+import com.google.api.gax.rpc.StatusCode.Code;
+import com.google.api.gax.rpc.testing.FakeStatusCode;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.truth.Truth;
+import java.util.Map;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.mockito.quality.Strictness;
+import org.threeten.bp.Duration;
+
+@RunWith(JUnit4.class)
+public class MetricsTracerTest {
+ // stricter way of testing for early detection of unused stubs and argument mismatches
+ @Rule
+ public final MockitoRule mockitoRule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
+
+ private MetricsTracer metricsTracer;
+ @Mock private MetricsRecorder metricsRecorder;
+
+ @Before
+ public void setUp() {
+ metricsTracer =
+ new MetricsTracer(MethodName.of("fake_service", "fake_method"), metricsRecorder);
+ }
+
+ @Test
+ public void testOperationSucceeded_recordsAttributes() {
+
+ metricsTracer.operationSucceeded();
+
+ Map attributes =
+ ImmutableMap.of(
+ "status", "OK",
+ "method_name", "fake_service.fake_method");
+
+ verify(metricsRecorder).recordOperationCount(1, attributes);
+ verify(metricsRecorder).recordOperationLatency(anyDouble(), eq(attributes));
+
+ verifyNoMoreInteractions(metricsRecorder);
+ }
+
+ @Test
+ public void testOperationFailed_recordsAttributes() {
+
+ ApiException error0 =
+ new NotFoundException(
+ "invalid argument", null, new FakeStatusCode(Code.INVALID_ARGUMENT), false);
+ metricsTracer.operationFailed(error0);
+
+ Map attributes =
+ ImmutableMap.of(
+ "status", "INVALID_ARGUMENT",
+ "method_name", "fake_service.fake_method");
+
+ verify(metricsRecorder).recordOperationCount(1, attributes);
+ verify(metricsRecorder).recordOperationLatency(anyDouble(), eq(attributes));
+
+ verifyNoMoreInteractions(metricsRecorder);
+ }
+
+ @Test
+ public void testOperationCancelled_recordsAttributes() {
+
+ metricsTracer.operationCancelled();
+
+ Map attributes =
+ ImmutableMap.of(
+ "status", "CANCELLED",
+ "method_name", "fake_service.fake_method");
+
+ verify(metricsRecorder).recordOperationCount(1, attributes);
+ verify(metricsRecorder).recordOperationLatency(anyDouble(), eq(attributes));
+
+ verifyNoMoreInteractions(metricsRecorder);
+ }
+
+ @Test
+ public void testAttemptSucceeded_recordsAttributes() {
+ // initialize mock-request
+ Object mockSuccessfulRequest = new Object();
+
+ // Attempt #1
+ metricsTracer.attemptStarted(mockSuccessfulRequest, 0);
+ metricsTracer.attemptSucceeded();
+
+ Map attributes =
+ ImmutableMap.of(
+ "status", "OK",
+ "method_name", "fake_service.fake_method");
+
+ verify(metricsRecorder).recordAttemptCount(1, attributes);
+ verify(metricsRecorder).recordAttemptLatency(anyDouble(), eq(attributes));
+
+ verifyNoMoreInteractions(metricsRecorder);
+ }
+
+ @Test
+ public void testAttemptFailed_recordsAttributes() {
+ // initialize mock-request
+ Object mockFailedRequest = new Object();
+
+ // Attempt #1
+ metricsTracer.attemptStarted(mockFailedRequest, 0);
+ ApiException error0 =
+ new NotFoundException(
+ "invalid argument", null, new FakeStatusCode(Code.INVALID_ARGUMENT), false);
+ metricsTracer.attemptFailed(error0, Duration.ofMillis(2));
+
+ Map attributes =
+ ImmutableMap.of(
+ "status", "INVALID_ARGUMENT",
+ "method_name", "fake_service.fake_method");
+
+ verify(metricsRecorder).recordAttemptCount(1, attributes);
+ verify(metricsRecorder).recordAttemptLatency(anyDouble(), eq(attributes));
+
+ verifyNoMoreInteractions(metricsRecorder);
+ }
+
+ @Test
+ public void testAttemptCancelled_recordsAttributes() {
+ // initialize mock-request
+ Object mockCancelledRequest = new Object();
+ // Attempt #1
+ metricsTracer.attemptStarted(mockCancelledRequest, 0);
+ metricsTracer.attemptCancelled();
+
+ Map attributes =
+ ImmutableMap.of(
+ "status", "CANCELLED",
+ "method_name", "fake_service.fake_method");
+
+ verify(metricsRecorder).recordAttemptCount(1, attributes);
+ verify(metricsRecorder).recordAttemptLatency(anyDouble(), eq(attributes));
+
+ verifyNoMoreInteractions(metricsRecorder);
+ }
+
+ @Test
+ public void testAttemptFailedRetriesExhausted_recordsAttributes() {
+ // initialize mock-request
+ Object mockRequest = new Object();
+ // Attempt #1
+ metricsTracer.attemptStarted(mockRequest, 0);
+ ApiException error0 =
+ new DeadlineExceededException(
+ "deadline exceeded", null, new FakeStatusCode(Code.DEADLINE_EXCEEDED), false);
+ metricsTracer.attemptFailedRetriesExhausted(error0);
+
+ Map attributes =
+ ImmutableMap.of(
+ "status", "DEADLINE_EXCEEDED",
+ "method_name", "fake_service.fake_method");
+
+ verify(metricsRecorder).recordAttemptCount(1, attributes);
+ verify(metricsRecorder).recordAttemptLatency(anyDouble(), eq(attributes));
+
+ verifyNoMoreInteractions(metricsRecorder);
+ }
+
+ @Test
+ public void testAttemptPermanentFailure_recordsAttributes() {
+
+ // initialize mock-request
+ Object mockRequest = new Object();
+ // Attempt #1
+ metricsTracer.attemptStarted(mockRequest, 0);
+ ApiException error0 =
+ new NotFoundException("not found", null, new FakeStatusCode(Code.NOT_FOUND), false);
+ metricsTracer.attemptFailedRetriesExhausted(error0);
+
+ Map attributes =
+ ImmutableMap.of(
+ "status", "NOT_FOUND",
+ "method_name", "fake_service.fake_method");
+
+ verify(metricsRecorder).recordAttemptCount(1, attributes);
+ verify(metricsRecorder).recordAttemptLatency(anyDouble(), eq(attributes));
+
+ verifyNoMoreInteractions(metricsRecorder);
+ }
+
+ @Test
+ public void testAddAttributes_recordsAttributes() {
+
+ metricsTracer.addAttributes("FakeTableId", "12345");
+ Truth.assertThat(metricsTracer.getAttributes().get("FakeTableId").equals("12345"));
+ }
+
+ @Test
+ public void testExtractStatus_errorConversion_apiExceptions() {
+
+ ApiException error =
+ new ApiException("fake_error", null, new FakeStatusCode(Code.INVALID_ARGUMENT), false);
+ String errorCode = metricsTracer.extractStatus(error);
+ assertThat(errorCode).isEqualTo("INVALID_ARGUMENT");
+ }
+
+ @Test
+ public void testExtractStatus_errorConversion_noError() {
+
+ // test "OK", which corresponds to a "null" error.
+ String successCode = metricsTracer.extractStatus(null);
+ assertThat(successCode).isEqualTo("OK");
+ }
+
+ @Test
+ public void testExtractStatus_errorConversion_unknownException() {
+
+ // test "UNKNOWN"
+ Throwable unknownException = new RuntimeException();
+ String errorCode2 = metricsTracer.extractStatus(unknownException);
+ assertThat(errorCode2).isEqualTo("UNKNOWN");
+ }
+}
From 99165403902ff91ecb0b14b858333855e7a10c60 Mon Sep 17 00:00:00 2001
From: Blake Li
Date: Wed, 31 Jan 2024 00:10:37 -0500
Subject: [PATCH 09/11] fix: Move direct path misconfiguration log to before
creating the first channel (#2430)
Fixes #2423
The root cause of the issue is that `logDirectPathMisconfig()` is called in the builder of `InstantiatingGrpcChannelProvider`, which could be called multiple times before it is fully instantiated. We should only call `logDirectPathMisconfig()` right before `createChannel()` which creates the first channel.
We can not move it to before `createSingleChannel()` because it is used for resizing channel regularly after a client is initialized, and we only want to log direct path misconfiguration once.
---
.../InstantiatingGrpcChannelProvider.java | 5 ++-
.../InstantiatingGrpcChannelProviderTest.java | 39 +++++++++++++++++--
2 files changed, 39 insertions(+), 5 deletions(-)
diff --git a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java
index 280f9cf78e..bf8ffc81da 100644
--- a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java
+++ b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java
@@ -145,7 +145,6 @@ private InstantiatingGrpcChannelProvider(Builder builder) {
builder.directPathServiceConfig == null
? getDefaultDirectPathServiceConfig()
: builder.directPathServiceConfig;
- logDirectPathMisconfig();
}
/**
@@ -234,6 +233,7 @@ public TransportChannel getTransportChannel() throws IOException {
} else if (needsEndpoint()) {
throw new IllegalStateException("getTransportChannel() called when needsEndpoint() is true");
} else {
+ logDirectPathMisconfig();
return createChannel();
}
}
@@ -272,6 +272,9 @@ boolean isDirectPathXdsEnabled() {
return false;
}
+ // This method should be called once per client initialization, hence can not be called in the
+ // builder or createSingleChannel, only in getTransportChannel which creates the first channel
+ // for a client.
private void logDirectPathMisconfig() {
if (isDirectPathXdsEnabled()) {
// Case 1: does not enable DirectPath
diff --git a/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProviderTest.java b/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProviderTest.java
index f6fb9d00ac..a58d10ffc6 100644
--- a/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProviderTest.java
+++ b/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProviderTest.java
@@ -539,11 +539,19 @@ protected Object getMtlsObjectFromTransportChannel(MtlsProvider provider)
}
@Test
- public void testLogDirectPathMisconfigAttrempDirectPathNotSet() {
+ public void testLogDirectPathMisconfigAttrempDirectPathNotSet() throws Exception {
FakeLogHandler logHandler = new FakeLogHandler();
InstantiatingGrpcChannelProvider.LOG.addHandler(logHandler);
InstantiatingGrpcChannelProvider provider =
- InstantiatingGrpcChannelProvider.newBuilder().setAttemptDirectPathXds().build();
+ InstantiatingGrpcChannelProvider.newBuilder()
+ .setAttemptDirectPathXds()
+ .setHeaderProvider(Mockito.mock(HeaderProvider.class))
+ .setExecutor(Mockito.mock(Executor.class))
+ .setEndpoint("localhost:8080")
+ .build();
+
+ provider.getTransportChannel();
+
assertThat(logHandler.getAllMessages())
.contains(
"DirectPath is misconfigured. Please set the attemptDirectPath option along with the"
@@ -552,15 +560,33 @@ public void testLogDirectPathMisconfigAttrempDirectPathNotSet() {
}
@Test
- public void testLogDirectPathMisconfigWrongCredential() {
+ public void testLogDirectPathMisconfig_shouldNotLogInTheBuilder() {
+ FakeLogHandler logHandler = new FakeLogHandler();
+ InstantiatingGrpcChannelProvider.LOG.addHandler(logHandler);
+ InstantiatingGrpcChannelProvider.newBuilder()
+ .setAttemptDirectPathXds()
+ .setAttemptDirectPath(true)
+ .build();
+
+ assertThat(logHandler.getAllMessages()).isEmpty();
+ InstantiatingGrpcChannelProvider.LOG.removeHandler(logHandler);
+ }
+
+ @Test
+ public void testLogDirectPathMisconfigWrongCredential() throws Exception {
FakeLogHandler logHandler = new FakeLogHandler();
InstantiatingGrpcChannelProvider.LOG.addHandler(logHandler);
InstantiatingGrpcChannelProvider provider =
InstantiatingGrpcChannelProvider.newBuilder()
.setAttemptDirectPathXds()
.setAttemptDirectPath(true)
+ .setHeaderProvider(Mockito.mock(HeaderProvider.class))
+ .setExecutor(Mockito.mock(Executor.class))
.setEndpoint("test.googleapis.com:443")
.build();
+
+ provider.getTransportChannel();
+
assertThat(logHandler.getAllMessages())
.contains(
"DirectPath is misconfigured. Please make sure the credential is an instance of"
@@ -569,7 +595,7 @@ public void testLogDirectPathMisconfigWrongCredential() {
}
@Test
- public void testLogDirectPathMisconfigNotOnGCE() {
+ public void testLogDirectPathMisconfigNotOnGCE() throws Exception {
FakeLogHandler logHandler = new FakeLogHandler();
InstantiatingGrpcChannelProvider.LOG.addHandler(logHandler);
InstantiatingGrpcChannelProvider provider =
@@ -577,8 +603,13 @@ public void testLogDirectPathMisconfigNotOnGCE() {
.setAttemptDirectPathXds()
.setAttemptDirectPath(true)
.setAllowNonDefaultServiceAccount(true)
+ .setHeaderProvider(Mockito.mock(HeaderProvider.class))
+ .setExecutor(Mockito.mock(Executor.class))
.setEndpoint("test.googleapis.com:443")
.build();
+
+ provider.getTransportChannel();
+
if (!InstantiatingGrpcChannelProvider.isOnComputeEngine()) {
assertThat(logHandler.getAllMessages())
.contains(
From b28235ab20fd174deddafc0426b8d20352af6e85 Mon Sep 17 00:00:00 2001
From: Alice <65933803+alicejli@users.noreply.github.com>
Date: Wed, 31 Jan 2024 17:19:38 -0500
Subject: [PATCH 10/11] feat: autopopulate fields in the request (#2353)
* feat: autopopulate fields in the request
* revert showcase golden changes
* add AbstractTransportServiceStubClassComposerTest
* add REST implementation
* add GrpcDirectCallableTest
* remove unnecessary null check
* add field info proto registry and more unit test cases
* add HttpJsonDirectCallableTest
* refactor AbstractTransportServiceSTubClassComposer
* add more unit tests for Field
* feat: refactor request mutator expression
* fix goldens
* add showcase test for autopopulation
* fix lint
* change assertion in showcase test
* refactor for sonarcloud
* sonarcloud fixes
* sonarcloud
* sonarcloud fix
* fix sonarcloud
* slight refactoring
* revert changes to directCallable and replace with retryable Callable
* overload retrying Callables method
* change license header format
* fix license header
* fix showcase lint
* add comment
* add showcase comment
* add CallableTest and httpjson Retrying test
* fix lint
* add RetryingCallable test and some refactoring
* refactor GrpcCallableFactory
* remove extraneous from HttpJsonDirectCallableTest
* remove FakeHttpJsonChannel
* revert changes to tests for extra param
* refactoring
* Update gapic-generator-java/src/test/java/com/google/api/generator/gapic/model/FieldTest.java
Co-authored-by: Blake Li
* Update gax-java/gax/src/test/java/com/google/api/gax/rpc/RetryingCallableTest.java
Co-authored-by: Blake Li
---------
Co-authored-by: Blake Li
---
.../google/api/generator/ProtoRegistry.java | 2 +
...ractTransportServiceStubClassComposer.java | 218 +++++++-
.../api/generator/gapic/model/Field.java | 11 +
.../generator/gapic/protoparser/Parser.java | 11 +-
...TransportServiceStubClassComposerTest.java | 529 ++++++++++++++++++
.../DefaultValueComposerTest.java | 5 +
.../composer/grpc/goldens/EchoClient.golden | 35 ++
.../grpc/goldens/EchoClientTest.golden | 50 ++
.../composer/grpc/goldens/GrpcEchoStub.golden | 13 +
.../samples/echoclient/AsyncChat.golden | 5 +
.../samples/echoclient/AsyncChatAgain.golden | 5 +
.../samples/echoclient/AsyncCollect.golden | 5 +
.../echoclient/AsyncCollideName.golden | 5 +
.../samples/echoclient/AsyncEcho.golden | 5 +
.../samples/echoclient/SyncCollideName.golden | 5 +
.../samples/echoclient/SyncEcho.golden | 5 +
.../rest/goldens/HttpJsonEchoStub.golden | 13 +
...lientCallableMethodSampleComposerTest.java | 15 +
...ServiceClientHeaderSampleComposerTest.java | 5 +
...ServiceClientMethodSampleComposerTest.java | 10 +
.../api/generator/gapic/model/FieldTest.java | 76 +++
.../gapic/protoparser/ParserTest.java | 31 +-
.../test/protoloader/TestProtoLoader.java | 9 +-
.../src/test/proto/echo.proto | 28 +
.../src/test/resources/echo_v1beta1.yaml | 7 +-
.../google/api/gax/grpc/GrpcCallSettings.java | 15 +
.../api/gax/grpc/GrpcCallableFactory.java | 8 +-
.../api/gax/grpc/GrpcDirectCallable.java | 2 +
.../gax/httpjson/HttpJsonCallSettings.java | 15 +
.../gax/httpjson/HttpJsonCallableFactory.java | 29 +-
.../google/api/gax/httpjson/RetryingTest.java | 154 +++--
.../com/google/api/gax/rpc/Callables.java | 45 +-
.../google/api/gax/rpc/RequestMutator.java | 52 ++
.../google/api/gax/rpc/RetryingCallable.java | 17 +-
.../com/google/api/gax/rpc/CallableTest.java | 38 +-
.../api/gax/rpc/RetryingCallableTest.java | 74 +++
.../showcase/v1beta1/stub/GrpcEchoStub.java | 10 +
.../v1beta1/stub/HttpJsonEchoStub.java | 10 +
.../v1beta1/it/ITAutoPopulatedFields.java | 373 ++++++++++++
.../it/util/TestClientInitializer.java | 51 ++
40 files changed, 1928 insertions(+), 68 deletions(-)
create mode 100644 gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/common/AbstractTransportServiceStubClassComposerTest.java
create mode 100644 gapic-generator-java/src/test/java/com/google/api/generator/gapic/model/FieldTest.java
create mode 100644 gax-java/gax/src/main/java/com/google/api/gax/rpc/RequestMutator.java
create mode 100644 gax-java/gax/src/test/java/com/google/api/gax/rpc/RetryingCallableTest.java
create mode 100644 showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITAutoPopulatedFields.java
diff --git a/gapic-generator-java/src/main/java/com/google/api/generator/ProtoRegistry.java b/gapic-generator-java/src/main/java/com/google/api/generator/ProtoRegistry.java
index 6f08ccfa3b..ae9c8cc76e 100644
--- a/gapic-generator-java/src/main/java/com/google/api/generator/ProtoRegistry.java
+++ b/gapic-generator-java/src/main/java/com/google/api/generator/ProtoRegistry.java
@@ -17,6 +17,7 @@
import com.google.api.AnnotationsProto;
import com.google.api.ClientProto;
import com.google.api.FieldBehaviorProto;
+import com.google.api.FieldInfoProto;
import com.google.api.ResourceProto;
import com.google.api.RoutingProto;
import com.google.cloud.ExtendedOperationsProto;
@@ -31,6 +32,7 @@ public static void registerAllExtensions(ExtensionRegistry extensionRegistry) {
ClientProto.registerAllExtensions(extensionRegistry);
ResourceProto.registerAllExtensions(extensionRegistry);
FieldBehaviorProto.registerAllExtensions(extensionRegistry);
+ FieldInfoProto.registerAllExtensions(extensionRegistry);
ExtendedOperationsProto.registerAllExtensions(extensionRegistry);
RoutingProto.registerAllExtensions(extensionRegistry);
}
diff --git a/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractTransportServiceStubClassComposer.java b/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractTransportServiceStubClassComposer.java
index 191e834a40..2647647443 100644
--- a/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractTransportServiceStubClassComposer.java
+++ b/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractTransportServiceStubClassComposer.java
@@ -42,6 +42,7 @@
import com.google.api.generator.engine.ast.MethodDefinition;
import com.google.api.generator.engine.ast.MethodInvocationExpr;
import com.google.api.generator.engine.ast.NewObjectExpr;
+import com.google.api.generator.engine.ast.Reference;
import com.google.api.generator.engine.ast.ReferenceConstructorExpr;
import com.google.api.generator.engine.ast.RelationalOperationExpr;
import com.google.api.generator.engine.ast.ScopeNode;
@@ -58,6 +59,7 @@
import com.google.api.generator.gapic.composer.comment.StubCommentComposer;
import com.google.api.generator.gapic.composer.store.TypeStore;
import com.google.api.generator.gapic.composer.utils.PackageChecker;
+import com.google.api.generator.gapic.model.Field;
import com.google.api.generator.gapic.model.GapicClass;
import com.google.api.generator.gapic.model.GapicClass.Kind;
import com.google.api.generator.gapic.model.GapicContext;
@@ -70,8 +72,10 @@
import com.google.api.generator.gapic.model.Transport;
import com.google.api.generator.gapic.utils.JavaStyle;
import com.google.api.pathtemplate.PathTemplate;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
+import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.longrunning.Operation;
@@ -84,9 +88,11 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import java.util.function.Function;
+import java.util.function.Predicate;
import java.util.stream.Collectors;
import javax.annotation.Generated;
import javax.annotation.Nullable;
@@ -136,6 +142,7 @@ private static TypeStore createStaticTypes() {
OperationCallable.class,
OperationSnapshot.class,
RequestParamsExtractor.class,
+ UUID.class,
ServerStreamingCallable.class,
TimeUnit.class,
TypeRegistry.class,
@@ -277,7 +284,8 @@ protected Expr createTransportSettingsInitExpr(
Method method,
VariableExpr transportSettingsVarExpr,
VariableExpr methodDescriptorVarExpr,
- List classStatements) {
+ List classStatements,
+ ImmutableMap messageTypes) {
MethodInvocationExpr callSettingsBuilderExpr =
MethodInvocationExpr.builder()
.setStaticReferenceType(getTransportContext().transportCallSettingsType())
@@ -318,6 +326,15 @@ protected Expr createTransportSettingsInitExpr(
.build();
}
+ if (method.hasAutoPopulatedFields() && shouldGenerateRequestMutator(method, messageTypes)) {
+ callSettingsBuilderExpr =
+ MethodInvocationExpr.builder()
+ .setExprReferenceExpr(callSettingsBuilderExpr)
+ .setMethodName("setRequestMutator")
+ .setArguments(createRequestMutatorClassInstance(method, messageTypes))
+ .build();
+ }
+
callSettingsBuilderExpr =
MethodInvocationExpr.builder()
.setExprReferenceExpr(callSettingsBuilderExpr)
@@ -760,7 +777,8 @@ protected List createConstructorMethods(
javaStyleMethodNameToTransportSettingsVarExprs.get(
JavaStyle.toLowerCamelCase(m.name())),
protoMethodNameToDescriptorVarExprs.get(m.name()),
- classStatements))
+ classStatements,
+ context.messages()))
.collect(Collectors.toList()));
secondCtorStatements.addAll(
secondCtorExprs.stream().map(ExprStatement::withExpr).collect(Collectors.toList()));
@@ -1233,12 +1251,197 @@ protected TypeNode getTransportOperationsStubType(Service service) {
return transportOpeationsStubType;
}
+ protected static LambdaExpr createRequestMutatorClassInstance(
+ Method method, ImmutableMap messageTypes) {
+ List bodyStatements = new ArrayList<>();
+ VariableExpr requestVarExpr = createRequestVarExpr(method);
+
+ Reference requestBuilderRef =
+ VaporReference.builder()
+ .setEnclosingClassNames(method.inputType().reference().name())
+ .setName("Builder")
+ .setPakkage(method.inputType().reference().pakkage())
+ .build();
+
+ TypeNode requestBuilderType = TypeNode.withReference(requestBuilderRef);
+
+ VariableExpr requestBuilderVarExpr =
+ VariableExpr.builder()
+ .setVariable(
+ Variable.builder().setName("requestBuilder").setType(requestBuilderType).build())
+ .setIsDecl(true)
+ .build();
+
+ MethodInvocationExpr setRequestBuilderInvocationExpr =
+ MethodInvocationExpr.builder()
+ .setExprReferenceExpr(requestVarExpr)
+ .setMethodName("toBuilder")
+ .setReturnType(requestBuilderType)
+ .build();
+
+ Expr requestBuilderExpr =
+ AssignmentExpr.builder()
+ .setVariableExpr(requestBuilderVarExpr)
+ .setValueExpr(setRequestBuilderInvocationExpr)
+ .build();
+
+ bodyStatements.add(ExprStatement.withExpr(requestBuilderExpr));
+
+ VariableExpr returnBuilderVarExpr =
+ VariableExpr.builder()
+ .setVariable(
+ Variable.builder().setName("requestBuilder").setType(requestBuilderType).build())
+ .setIsDecl(false)
+ .build();
+
+ MethodInvocationExpr.Builder returnExpr =
+ MethodInvocationExpr.builder()
+ .setExprReferenceExpr(returnBuilderVarExpr)
+ .setMethodName("build");
+
+ createRequestMutatorBody(method, messageTypes, bodyStatements, returnBuilderVarExpr);
+
+ return LambdaExpr.builder()
+ .setArguments(requestVarExpr.toBuilder().setIsDecl(true).build())
+ .setBody(bodyStatements)
+ .setReturnExpr(returnExpr.build())
+ .build();
+ }
+
+ @VisibleForTesting
+ static List createRequestMutatorBody(
+ Method method,
+ ImmutableMap messageTypes,
+ List bodyStatements,
+ VariableExpr returnBuilderVarExpr) {
+
+ Message methodRequestMessage = messageTypes.get(method.inputType().reference().fullName());
+ method.autoPopulatedFields().stream()
+ // Map each field name to its corresponding Field object, if present
+ .map(
+ fieldName ->
+ methodRequestMessage.fields().stream()
+ .filter(field -> field.name().equals(fieldName))
+ .findFirst())
+ .filter(Optional::isPresent) // Keep only the existing Fields
+ .map(Optional::get) // Extract the Field from the Optional
+ .filter(Field::canBeAutoPopulated) // Filter fields that can be autopopulated
+ .forEach(
+ matchedField -> {
+ // Create statements for each autopopulated Field
+ bodyStatements.add(
+ createAutoPopulatedRequestStatement(
+ method, matchedField.name(), returnBuilderVarExpr));
+ });
+ return bodyStatements;
+ }
+
+ @VisibleForTesting
+ static Statement createAutoPopulatedRequestStatement(
+ Method method, String fieldName, VariableExpr returnBuilderVarExpr) {
+
+ VariableExpr requestVarExpr = createRequestVarExpr(method);
+
+ // Expected expression: request.getRequestId()
+ MethodInvocationExpr getAutoPopulatedFieldInvocationExpr =
+ MethodInvocationExpr.builder()
+ .setExprReferenceExpr(requestVarExpr)
+ .setMethodName(String.format("get%s", JavaStyle.toUpperCamelCase(fieldName)))
+ .setReturnType(TypeNode.STRING)
+ .build();
+
+ VariableExpr stringsVar =
+ VariableExpr.withVariable(
+ Variable.builder()
+ .setType(TypeNode.withReference(ConcreteReference.withClazz(Strings.class)))
+ .setName("Strings")
+ .build());
+
+ // Expected expression: Strings.isNullOrEmpty(request.getRequestId())
+ MethodInvocationExpr isNullOrEmptyFieldInvocationExpr =
+ MethodInvocationExpr.builder()
+ .setExprReferenceExpr(stringsVar)
+ .setMethodName("isNullOrEmpty")
+ .setReturnType(TypeNode.BOOLEAN)
+ .setArguments(getAutoPopulatedFieldInvocationExpr)
+ .build();
+
+ // Note: Currently, autopopulation is only for UUID.
+ VariableExpr uuidVarExpr =
+ VariableExpr.withVariable(
+ Variable.builder()
+ .setType(
+ TypeNode.withReference(
+ ConcreteReference.builder().setClazz(UUID.class).build()))
+ .setName("UUID")
+ .build());
+
+ // Expected expression: UUID.randomUUID()
+ MethodInvocationExpr autoPopulatedFieldsArgsHelper =
+ MethodInvocationExpr.builder()
+ .setExprReferenceExpr(uuidVarExpr)
+ .setMethodName("randomUUID")
+ .setReturnType(
+ TypeNode.withReference(ConcreteReference.builder().setClazz(UUID.class).build()))
+ .build();
+
+ // Expected expression: UUID.randomUUID().toString()
+ MethodInvocationExpr autoPopulatedFieldsArgsToString =
+ MethodInvocationExpr.builder()
+ .setExprReferenceExpr(autoPopulatedFieldsArgsHelper)
+ .setMethodName("toString")
+ .setReturnType(TypeNode.STRING)
+ .build();
+
+ // Expected expression: requestBuilder().setField(UUID.randomUUID().toString())
+ MethodInvocationExpr setAutoPopulatedFieldInvocationExpr =
+ MethodInvocationExpr.builder()
+ .setArguments(autoPopulatedFieldsArgsToString)
+ .setExprReferenceExpr(returnBuilderVarExpr)
+ .setMethodName(String.format("set%s", JavaStyle.toUpperCamelCase(fieldName)))
+ .setReturnType(method.inputType())
+ .build();
+
+ return IfStatement.builder()
+ .setConditionExpr(isNullOrEmptyFieldInvocationExpr)
+ .setBody(Arrays.asList(ExprStatement.withExpr(setAutoPopulatedFieldInvocationExpr)))
+ .build();
+ }
+
+ /**
+ * The Request Mutator should only be generated if the field exists in the Message and is properly
+ * configured in the Message(see {@link Field#canBeAutoPopulated()})
+ */
+ @VisibleForTesting
+ static Boolean shouldGenerateRequestMutator(
+ Method method, ImmutableMap messageTypes) {
+ if (method.inputType().reference() == null
+ || method.inputType().reference().fullName() == null) {
+ return false;
+ }
+ String methodRequestName = method.inputType().reference().fullName();
+
+ Message methodRequestMessage = messageTypes.get(methodRequestName);
+ if (methodRequestMessage == null || methodRequestMessage.fields() == null) {
+ return false;
+ }
+ return method.autoPopulatedFields().stream().anyMatch(shouldAutoPopulate(methodRequestMessage));
+ }
+
+ /**
+ * The field has to exist in the Message and properly configured in the Message(see {@link
+ * Field#canBeAutoPopulated()})
+ */
+ private static Predicate shouldAutoPopulate(Message methodRequestMessage) {
+ return fieldName ->
+ methodRequestMessage.fields().stream()
+ .anyMatch(field -> field.name().equals(fieldName) && field.canBeAutoPopulated());
+ }
+
protected LambdaExpr createRequestParamsExtractorClassInstance(
Method method, List classStatements) {
List bodyStatements = new ArrayList<>();
- VariableExpr requestVarExpr =
- VariableExpr.withVariable(
- Variable.builder().setType(method.inputType()).setName("request").build());
+ VariableExpr requestVarExpr = createRequestVarExpr(method);
TypeNode returnType =
TypeNode.withReference(
ConcreteReference.builder()
@@ -1499,4 +1702,9 @@ private MethodInvocationExpr createRequestFieldGetterExpr(
}
return requestFieldGetterExprBuilder.build();
}
+
+ private static VariableExpr createRequestVarExpr(Method method) {
+ return VariableExpr.withVariable(
+ Variable.builder().setType(method.inputType()).setName("request").build());
+ }
}
diff --git a/gapic-generator-java/src/main/java/com/google/api/generator/gapic/model/Field.java b/gapic-generator-java/src/main/java/com/google/api/generator/gapic/model/Field.java
index 997ea54e5a..39213909de 100644
--- a/gapic-generator-java/src/main/java/com/google/api/generator/gapic/model/Field.java
+++ b/gapic-generator-java/src/main/java/com/google/api/generator/gapic/model/Field.java
@@ -72,6 +72,17 @@ public boolean hasResourceReference() {
return type().equals(TypeNode.STRING) && resourceReference() != null;
}
+ // Check that the field format is of UUID, it is not annotated as required, and is of type String.
+ // Unless
+ // those three conditions are met, do not autopopulate the field.
+ // In the future, if additional formats are supported for autopopulation, this will need to be
+ // refactored to support those formats.
+ public boolean canBeAutoPopulated() {
+ return Format.UUID4.equals(fieldInfoFormat())
+ && !isRequired()
+ && TypeNode.STRING.equals(type());
+ }
+
@Override
public boolean equals(Object o) {
if (!(o instanceof Field)) {
diff --git a/gapic-generator-java/src/main/java/com/google/api/generator/gapic/protoparser/Parser.java b/gapic-generator-java/src/main/java/com/google/api/generator/gapic/protoparser/Parser.java
index 9f1b395940..e960ad3f6d 100644
--- a/gapic-generator-java/src/main/java/com/google/api/generator/gapic/protoparser/Parser.java
+++ b/gapic-generator-java/src/main/java/com/google/api/generator/gapic/protoparser/Parser.java
@@ -1025,10 +1025,13 @@ private static Field parseField(
if (fieldOptions.hasExtension(FieldInfoProto.fieldInfo)) {
fieldInfoFormat = fieldOptions.getExtension(FieldInfoProto.fieldInfo).getFormat();
}
- if (fieldOptions.getExtensionCount(FieldBehaviorProto.fieldBehavior) > 0) {
- if (fieldOptions
- .getExtension(FieldBehaviorProto.fieldBehavior)
- .contains(FieldBehavior.REQUIRED)) ;
+
+ // Cannot directly check fieldOptions.hasExtension(FieldBehaviorProto.fieldBehavior) because the
+ // default is null
+ if (fieldOptions.getExtensionCount(FieldBehaviorProto.fieldBehavior) > 0
+ && fieldOptions
+ .getExtension(FieldBehaviorProto.fieldBehavior)
+ .contains(FieldBehavior.REQUIRED)) {
isRequired = true;
}
diff --git a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/common/AbstractTransportServiceStubClassComposerTest.java b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/common/AbstractTransportServiceStubClassComposerTest.java
new file mode 100644
index 0000000000..77205f0122
--- /dev/null
+++ b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/common/AbstractTransportServiceStubClassComposerTest.java
@@ -0,0 +1,529 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.google.api.generator.gapic.composer.common;
+
+import static com.google.api.generator.gapic.composer.common.AbstractTransportServiceStubClassComposer.shouldGenerateRequestMutator;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.google.api.FieldInfo.Format;
+import com.google.api.generator.engine.ast.LambdaExpr;
+import com.google.api.generator.engine.ast.Reference;
+import com.google.api.generator.engine.ast.Statement;
+import com.google.api.generator.engine.ast.TypeNode;
+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.writer.JavaWriterVisitor;
+import com.google.api.generator.gapic.model.Field;
+import com.google.api.generator.gapic.model.Message;
+import com.google.api.generator.gapic.model.Method;
+import com.google.api.generator.test.utils.LineFormatter;
+import com.google.common.collect.ImmutableMap;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+
+public class AbstractTransportServiceStubClassComposerTest {
+ private JavaWriterVisitor writerVisitor;
+
+ @Before
+ public void setUp() {
+ writerVisitor = new JavaWriterVisitor();
+ }
+
+ @Test
+ public void shouldGenerateRequestMutator_fieldConfiguredCorrectly() {
+ String ECHO_PACKAGE = "com.google.showcase.v1beta1";
+ List autoPopulatedFieldList = new ArrayList<>();
+ autoPopulatedFieldList.add("TestField");
+
+ Method METHOD =
+ Method.builder()
+ .setName("TestMethod")
+ .setInputType(
+ TypeNode.withReference(
+ VaporReference.builder()
+ .setName("SampleRequest")
+ .setPakkage(ECHO_PACKAGE)
+ .build()))
+ .setOutputType(TypeNode.STRING)
+ .setAutoPopulatedFields(autoPopulatedFieldList)
+ .build();
+
+ Field FIELD =
+ Field.builder()
+ .setName("TestField")
+ .setFieldInfoFormat(Format.UUID4)
+ .setType(TypeNode.STRING)
+ .build();
+ List fieldList = new ArrayList<>();
+ fieldList.add(FIELD);
+
+ Message MESSAGE =
+ Message.builder()
+ .setFullProtoName("com.google.showcase.v1beta1.SampleRequest")
+ .setName("SampleRequest")
+ .setType(TypeNode.STRING)
+ .setFields(fieldList)
+ .build();
+
+ ImmutableMap messageTypes =
+ ImmutableMap.of("com.google.showcase.v1beta1.SampleRequest", MESSAGE);
+
+ assertTrue(shouldGenerateRequestMutator(METHOD, messageTypes));
+ }
+
+ @Test
+ public void shouldNotGenerateRequestMutator_fieldConfiguredIncorrectly() {
+ String ECHO_PACKAGE = "com.google.showcase.v1beta1";
+ List autoPopulatedFieldList = new ArrayList<>();
+ autoPopulatedFieldList.add("TestField");
+
+ Method METHOD =
+ Method.builder()
+ .setName("TestMethod")
+ .setInputType(
+ TypeNode.withReference(
+ VaporReference.builder()
+ .setName("SampleRequest")
+ .setPakkage(ECHO_PACKAGE)
+ .build()))
+ .setOutputType(TypeNode.STRING)
+ .setAutoPopulatedFields(autoPopulatedFieldList)
+ .build();
+
+ Field FIELD =
+ Field.builder()
+ .setName("TestField")
+ .setFieldInfoFormat(Format.IPV6)
+ .setType(TypeNode.STRING)
+ .build();
+ List fieldList = new ArrayList<>();
+ fieldList.add(FIELD);
+
+ Message MESSAGE =
+ Message.builder()
+ .setFullProtoName("com.google.showcase.v1beta1.SampleRequest")
+ .setName("SampleRequest")
+ .setType(TypeNode.STRING)
+ .setFields(fieldList)
+ .build();
+
+ ImmutableMap messageTypes =
+ ImmutableMap.of("com.google.showcase.v1beta1.SampleRequest", MESSAGE);
+
+ assertFalse(shouldGenerateRequestMutator(METHOD, messageTypes));
+ }
+
+ // TODO: add unit tests where the field is not found in the messageTypes map
+ @Test
+ public void createAutoPopulatedRequestStatement_sampleField() {
+ Reference RequestBuilderRef =
+ VaporReference.builder()
+ .setName("EchoRequest")
+ .setPakkage("com.google.example.examples.library.v1")
+ .build();
+
+ TypeNode testType = TypeNode.withReference(RequestBuilderRef);
+
+ Method METHOD =
+ Method.builder()
+ .setName("TestMethod")
+ .setInputType(testType)
+ .setOutputType(TypeNode.STRING)
+ .build();
+
+ Reference RequestVarBuilderRef =
+ VaporReference.builder()
+ .setEnclosingClassNames(METHOD.inputType().reference().name())
+ .setName("Builder")
+ .setPakkage(METHOD.inputType().reference().pakkage())
+ .build();
+
+ TypeNode requestBuilderType = TypeNode.withReference(RequestVarBuilderRef);
+
+ VariableExpr requestBuilderVarExpr =
+ VariableExpr.builder()
+ .setVariable(
+ Variable.builder().setName("requestBuilder").setType(requestBuilderType).build())
+ .setIsDecl(false)
+ .build();
+
+ Statement autoPopulatedFieldStatement =
+ AbstractTransportServiceStubClassComposer.createAutoPopulatedRequestStatement(
+ METHOD, "sampleField", requestBuilderVarExpr);
+
+ autoPopulatedFieldStatement.accept(writerVisitor);
+ String expected =
+ LineFormatter.lines(
+ "if (Strings.isNullOrEmpty(request.getSampleField())) {\n",
+ "requestBuilder.setSampleField(UUID.randomUUID().toString());\n",
+ "}\n");
+ assertEquals(expected, writerVisitor.write());
+ }
+
+ @Test
+ public void createRequestMutatorBody_TestField() {
+ List bodyStatements = new ArrayList<>();
+ String ECHO_PACKAGE = "com.google.showcase.v1beta1";
+ List autoPopulatedFieldList = new ArrayList<>();
+ autoPopulatedFieldList.add("TestField");
+
+ Method METHOD =
+ Method.builder()
+ .setName("TestMethod")
+ .setInputType(
+ TypeNode.withReference(
+ VaporReference.builder()
+ .setName("SampleRequest")
+ .setPakkage(ECHO_PACKAGE)
+ .build()))
+ .setOutputType(TypeNode.STRING)
+ .setAutoPopulatedFields(autoPopulatedFieldList)
+ .build();
+
+ Field FIELD =
+ Field.builder()
+ .setName("TestField")
+ .setFieldInfoFormat(Format.UUID4)
+ .setType(TypeNode.STRING)
+ .build();
+ List fieldList = new ArrayList<>();
+ fieldList.add(FIELD);
+
+ Message MESSAGE =
+ Message.builder()
+ .setFullProtoName("com.google.showcase.v1beta1.SampleRequest")
+ .setName("SampleRequest")
+ .setType(TypeNode.STRING)
+ .setFields(fieldList)
+ .build();
+
+ Reference RequestBuilderRef =
+ VaporReference.builder()
+ .setEnclosingClassNames(METHOD.inputType().reference().name())
+ .setName("Builder")
+ .setPakkage(METHOD.inputType().reference().pakkage())
+ .build();
+
+ TypeNode requestBuilderType = TypeNode.withReference(RequestBuilderRef);
+
+ VariableExpr requestBuilderVarExpr =
+ VariableExpr.builder()
+ .setVariable(
+ Variable.builder().setName("requestBuilder").setType(requestBuilderType).build())
+ .setIsDecl(false)
+ .build();
+
+ ImmutableMap messageTypes =
+ ImmutableMap.of("com.google.showcase.v1beta1.SampleRequest", MESSAGE);
+
+ List listOfAutoPopulatedStatements =
+ AbstractTransportServiceStubClassComposer.createRequestMutatorBody(
+ METHOD, messageTypes, bodyStatements, requestBuilderVarExpr);
+
+ for (Statement statement : listOfAutoPopulatedStatements) {
+ statement.accept(writerVisitor);
+ }
+
+ String expected =
+ LineFormatter.lines(
+ "if (Strings.isNullOrEmpty(request.getTestField())) {\n",
+ "requestBuilder.setTestField(UUID.randomUUID().toString());\n",
+ "}\n");
+ assertEquals(expected, writerVisitor.write());
+ }
+
+ @Test
+ public void createRequestMutatorBody_TestFieldNotString_shouldReturnNull() {
+ List bodyStatements = new ArrayList<>();
+
+ String ECHO_PACKAGE = "com.google.showcase.v1beta1";
+ List autoPopulatedFieldList = new ArrayList<>();
+ autoPopulatedFieldList.add("TestField");
+
+ Method METHOD =
+ Method.builder()
+ .setName("TestMethod")
+ .setInputType(
+ TypeNode.withReference(
+ VaporReference.builder()
+ .setName("SampleRequest")
+ .setPakkage(ECHO_PACKAGE)
+ .build()))
+ .setOutputType(TypeNode.STRING)
+ .setAutoPopulatedFields(autoPopulatedFieldList)
+ .build();
+
+ Field FIELD =
+ Field.builder()
+ .setName("TestField")
+ .setFieldInfoFormat(Format.UUID4)
+ .setType(TypeNode.BOOLEAN)
+ .build();
+ List fieldList = new ArrayList<>();
+ fieldList.add(FIELD);
+
+ Message MESSAGE =
+ Message.builder()
+ .setFullProtoName("com.google.showcase.v1beta1.SampleRequest")
+ .setName("SampleRequest")
+ .setType(TypeNode.STRING)
+ .setFields(fieldList)
+ .build();
+
+ Reference RequestBuilderRef =
+ VaporReference.builder()
+ .setEnclosingClassNames(METHOD.inputType().reference().name())
+ .setName("Builder")
+ .setPakkage(METHOD.inputType().reference().pakkage())
+ .build();
+
+ TypeNode requestBuilderType = TypeNode.withReference(RequestBuilderRef);
+
+ VariableExpr requestBuilderVarExpr =
+ VariableExpr.builder()
+ .setVariable(
+ Variable.builder().setName("requestBuilder").setType(requestBuilderType).build())
+ .setIsDecl(false)
+ .build();
+
+ ImmutableMap messageTypes =
+ ImmutableMap.of("com.google.showcase.v1beta1.SampleRequest", MESSAGE);
+
+ List listOfAutoPopulatedStatements =
+ AbstractTransportServiceStubClassComposer.createRequestMutatorBody(
+ METHOD, messageTypes, bodyStatements, requestBuilderVarExpr);
+
+ for (Statement statement : listOfAutoPopulatedStatements) {
+ statement.accept(writerVisitor);
+ }
+
+ String expected = LineFormatter.lines("");
+ assertEquals(expected, writerVisitor.write());
+ }
+
+ @Test
+ public void createRequestMutatorBody_TestFieldFormatNotUUID_shouldReturnNull() {
+ List bodyStatements = new ArrayList<>();
+ String ECHO_PACKAGE = "com.google.showcase.v1beta1";
+ List autoPopulatedFieldList = new ArrayList<>();
+ autoPopulatedFieldList.add("TestField");
+
+ Method METHOD =
+ Method.builder()
+ .setName("TestMethod")
+ .setInputType(
+ TypeNode.withReference(
+ VaporReference.builder()
+ .setName("SampleRequest")
+ .setPakkage(ECHO_PACKAGE)
+ .build()))
+ .setOutputType(TypeNode.STRING)
+ .setAutoPopulatedFields(autoPopulatedFieldList)
+ .build();
+
+ Field FIELD =
+ Field.builder()
+ .setName("TestField")
+ .setFieldInfoFormat(Format.IPV4_OR_IPV6)
+ .setType(TypeNode.STRING)
+ .build();
+ List fieldList = new ArrayList<>();
+ fieldList.add(FIELD);
+
+ Message MESSAGE =
+ Message.builder()
+ .setFullProtoName("com.google.showcase.v1beta1.SampleRequest")
+ .setName("SampleRequest")
+ .setType(TypeNode.STRING)
+ .setFields(fieldList)
+ .build();
+
+ Reference RequestBuilderRef =
+ VaporReference.builder()
+ .setEnclosingClassNames(METHOD.inputType().reference().name())
+ .setName("Builder")
+ .setPakkage(METHOD.inputType().reference().pakkage())
+ .build();
+
+ TypeNode requestBuilderType = TypeNode.withReference(RequestBuilderRef);
+
+ VariableExpr requestBuilderVarExpr =
+ VariableExpr.builder()
+ .setVariable(
+ Variable.builder().setName("requestBuilder").setType(requestBuilderType).build())
+ .setIsDecl(false)
+ .build();
+
+ ImmutableMap messageTypes =
+ ImmutableMap.of("com.google.showcase.v1beta1.SampleRequest", MESSAGE);
+
+ List listOfAutoPopulatedStatements =
+ AbstractTransportServiceStubClassComposer.createRequestMutatorBody(
+ METHOD, messageTypes, bodyStatements, requestBuilderVarExpr);
+
+ for (Statement statement : listOfAutoPopulatedStatements) {
+ statement.accept(writerVisitor);
+ }
+
+ String expected = LineFormatter.lines("");
+ assertEquals(expected, writerVisitor.write());
+ }
+
+ @Test
+ public void createRequestMutatorBody_TestFieldIncorrectName_shouldReturnNull() {
+ List bodyStatements = new ArrayList<>();
+ String ECHO_PACKAGE = "com.google.showcase.v1beta1";
+ List autoPopulatedFieldList = new ArrayList<>();
+ autoPopulatedFieldList.add("TestField");
+
+ Method METHOD =
+ Method.builder()
+ .setName("TestMethod")
+ .setInputType(
+ TypeNode.withReference(
+ VaporReference.builder()
+ .setName("SampleRequest")
+ .setPakkage(ECHO_PACKAGE)
+ .build()))
+ .setOutputType(TypeNode.STRING)
+ .setAutoPopulatedFields(autoPopulatedFieldList)
+ .build();
+
+ Field FIELD =
+ Field.builder()
+ .setName("TestIncorrectField")
+ .setFieldInfoFormat(Format.UUID4)
+ .setType(TypeNode.STRING)
+ .build();
+ List fieldList = new ArrayList<>();
+ fieldList.add(FIELD);
+
+ Message MESSAGE =
+ Message.builder()
+ .setFullProtoName("com.google.showcase.v1beta1.SampleRequest")
+ .setName("SampleRequest")
+ .setType(TypeNode.STRING)
+ .setFields(fieldList)
+ .build();
+
+ Reference RequestBuilderRef =
+ VaporReference.builder()
+ .setEnclosingClassNames(METHOD.inputType().reference().name())
+ .setName("Builder")
+ .setPakkage(METHOD.inputType().reference().pakkage())
+ .build();
+
+ TypeNode requestBuilderType = TypeNode.withReference(RequestBuilderRef);
+
+ VariableExpr requestBuilderVarExpr =
+ VariableExpr.builder()
+ .setVariable(
+ Variable.builder().setName("requestBuilder").setType(requestBuilderType).build())
+ .setIsDecl(false)
+ .build();
+
+ ImmutableMap messageTypes =
+ ImmutableMap.of("com.google.showcase.v1beta1.SampleRequest", MESSAGE);
+
+ List listOfAutoPopulatedStatements =
+ AbstractTransportServiceStubClassComposer.createRequestMutatorBody(
+ METHOD, messageTypes, bodyStatements, requestBuilderVarExpr);
+
+ for (Statement statement : listOfAutoPopulatedStatements) {
+ statement.accept(writerVisitor);
+ }
+
+ String expected = LineFormatter.lines("");
+ assertEquals(expected, writerVisitor.write());
+ }
+
+ @Test
+ public void createRequestMutator_TestField() {
+ String ECHO_PACKAGE = "com.google.showcase.v1beta1";
+ List autoPopulatedFieldList = new ArrayList<>();
+ autoPopulatedFieldList.add("TestField");
+
+ Method METHOD =
+ Method.builder()
+ .setName("TestMethod")
+ .setInputType(
+ TypeNode.withReference(
+ VaporReference.builder()
+ .setName("SampleRequest")
+ .setPakkage(ECHO_PACKAGE)
+ .build()))
+ .setOutputType(TypeNode.STRING)
+ .setAutoPopulatedFields(autoPopulatedFieldList)
+ .build();
+
+ Field FIELD =
+ Field.builder()
+ .setName("TestField")
+ .setFieldInfoFormat(Format.UUID4)
+ .setType(TypeNode.STRING)
+ .build();
+ List fieldList = new ArrayList<>();
+ fieldList.add(FIELD);
+
+ Message MESSAGE =
+ Message.builder()
+ .setFullProtoName("com.google.showcase.v1beta1.SampleRequest")
+ .setName("SampleRequest")
+ .setType(TypeNode.STRING)
+ .setFields(fieldList)
+ .build();
+
+ ImmutableMap messageTypes =
+ ImmutableMap.of("com.google.showcase.v1beta1.SampleRequest", MESSAGE);
+
+ LambdaExpr requestMutator =
+ AbstractTransportServiceStubClassComposer.createRequestMutatorClassInstance(
+ METHOD, messageTypes);
+
+ requestMutator.accept(writerVisitor);
+
+ String expected =
+ LineFormatter.lines(
+ "request -> {\n",
+ "SampleRequest.Builder requestBuilder = request.toBuilder();\n",
+ "if (Strings.isNullOrEmpty(request.getTestField())) {\n",
+ "requestBuilder.setTestField(UUID.randomUUID().toString());\n",
+ "}\n",
+ "return requestBuilder.build();\n",
+ "}");
+ assertEquals(expected, writerVisitor.write());
+ }
+}
diff --git a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/defaultvalue/DefaultValueComposerTest.java b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/defaultvalue/DefaultValueComposerTest.java
index 43e5411b30..3345645c8c 100644
--- a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/defaultvalue/DefaultValueComposerTest.java
+++ b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/defaultvalue/DefaultValueComposerTest.java
@@ -569,6 +569,11 @@ public void createSimpleMessage_containsMessagesEnumsAndResourceName() {
+ "FoobarName.ofProjectFoobarName(\"[PROJECT]\", \"[FOOBAR]\").toString())"
+ ".setParent(FoobarName.ofProjectFoobarName(\"[PROJECT]\", \"[FOOBAR]\").toString())"
+ ".setRequestId(\"requestId693933066\")"
+ + ".setSecondRequestId(\"secondRequestId344404470\")"
+ + ".setThirdRequestId(true)"
+ + ".setFourthRequestId(\"fourthRequestId-2116417776\")"
+ + ".setFifthRequestId(\"fifthRequestId959024147\")"
+ + ".setSixthRequestId(\"sixthRequestId1005218260\")"
+ ".setSeverity(Severity.forNumber(0))"
+ ".setFoobar(Foobar.newBuilder().build()).build()",
writerVisitor.write());
diff --git a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/EchoClient.golden b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/EchoClient.golden
index f67a50d9c0..f1bf7437b4 100644
--- a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/EchoClient.golden
+++ b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/EchoClient.golden
@@ -518,6 +518,11 @@ public class EchoClient implements BackgroundResource {
* .setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
* .setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
* .setRequestId("requestId693933066")
+ * .setSecondRequestId("secondRequestId344404470")
+ * .setThirdRequestId(true)
+ * .setFourthRequestId("fourthRequestId-2116417776")
+ * .setFifthRequestId("fifthRequestId959024147")
+ * .setSixthRequestId("sixthRequestId1005218260")
* .setSeverity(Severity.forNumber(0))
* .setFoobar(Foobar.newBuilder().build())
* .build();
@@ -548,6 +553,11 @@ public class EchoClient implements BackgroundResource {
* .setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
* .setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
* .setRequestId("requestId693933066")
+ * .setSecondRequestId("secondRequestId344404470")
+ * .setThirdRequestId(true)
+ * .setFourthRequestId("fourthRequestId-2116417776")
+ * .setFifthRequestId("fifthRequestId959024147")
+ * .setSixthRequestId("sixthRequestId1005218260")
* .setSeverity(Severity.forNumber(0))
* .setFoobar(Foobar.newBuilder().build())
* .build();
@@ -624,6 +634,11 @@ public class EchoClient implements BackgroundResource {
* .setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
* .setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
* .setRequestId("requestId693933066")
+ * .setSecondRequestId("secondRequestId344404470")
+ * .setThirdRequestId(true)
+ * .setFourthRequestId("fourthRequestId-2116417776")
+ * .setFifthRequestId("fifthRequestId959024147")
+ * .setSixthRequestId("sixthRequestId1005218260")
* .setSeverity(Severity.forNumber(0))
* .setFoobar(Foobar.newBuilder().build())
* .build();
@@ -652,6 +667,11 @@ public class EchoClient implements BackgroundResource {
* .setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
* .setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
* .setRequestId("requestId693933066")
+ * .setSecondRequestId("secondRequestId344404470")
+ * .setThirdRequestId(true)
+ * .setFourthRequestId("fourthRequestId-2116417776")
+ * .setFifthRequestId("fifthRequestId959024147")
+ * .setSixthRequestId("sixthRequestId1005218260")
* .setSeverity(Severity.forNumber(0))
* .setFoobar(Foobar.newBuilder().build())
* .build();
@@ -683,6 +703,11 @@ public class EchoClient implements BackgroundResource {
* .setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
* .setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
* .setRequestId("requestId693933066")
+ * .setSecondRequestId("secondRequestId344404470")
+ * .setThirdRequestId(true)
+ * .setFourthRequestId("fourthRequestId-2116417776")
+ * .setFifthRequestId("fifthRequestId959024147")
+ * .setSixthRequestId("sixthRequestId1005218260")
* .setSeverity(Severity.forNumber(0))
* .setFoobar(Foobar.newBuilder().build())
* .build();
@@ -1092,6 +1117,11 @@ public class EchoClient implements BackgroundResource {
* .setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
* .setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
* .setRequestId("requestId693933066")
+ * .setSecondRequestId("secondRequestId344404470")
+ * .setThirdRequestId(true)
+ * .setFourthRequestId("fourthRequestId-2116417776")
+ * .setFifthRequestId("fifthRequestId959024147")
+ * .setSixthRequestId("sixthRequestId1005218260")
* .setSeverity(Severity.forNumber(0))
* .setFoobar(Foobar.newBuilder().build())
* .build();
@@ -1122,6 +1152,11 @@ public class EchoClient implements BackgroundResource {
* .setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
* .setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
* .setRequestId("requestId693933066")
+ * .setSecondRequestId("secondRequestId344404470")
+ * .setThirdRequestId(true)
+ * .setFourthRequestId("fourthRequestId-2116417776")
+ * .setFifthRequestId("fifthRequestId959024147")
+ * .setSixthRequestId("sixthRequestId1005218260")
* .setSeverity(Severity.forNumber(0))
* .setFoobar(Foobar.newBuilder().build())
* .build();
diff --git a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/EchoClientTest.golden b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/EchoClientTest.golden
index 009d8688df..a2a88d6bf3 100644
--- a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/EchoClientTest.golden
+++ b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/EchoClientTest.golden
@@ -109,6 +109,11 @@ public class EchoClientTest {
.setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
.setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
.setRequestId("requestId693933066")
+ .setSecondRequestId("secondRequestId344404470")
+ .setThirdRequestId(true)
+ .setFourthRequestId("fourthRequestId-2116417776")
+ .setFifthRequestId("fifthRequestId959024147")
+ .setSixthRequestId("sixthRequestId1005218260")
.setSeverity(Severity.forNumber(0))
.setFoobar(Foobar.newBuilder().build())
.build();
@@ -459,6 +464,11 @@ public class EchoClientTest {
.setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
.setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
.setRequestId("requestId693933066")
+ .setSecondRequestId("secondRequestId344404470")
+ .setThirdRequestId(true)
+ .setFourthRequestId("fourthRequestId-2116417776")
+ .setFifthRequestId("fifthRequestId959024147")
+ .setSixthRequestId("sixthRequestId1005218260")
.setSeverity(Severity.forNumber(0))
.setFoobar(Foobar.newBuilder().build())
.build();
@@ -485,6 +495,11 @@ public class EchoClientTest {
.setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
.setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
.setRequestId("requestId693933066")
+ .setSecondRequestId("secondRequestId344404470")
+ .setThirdRequestId(true)
+ .setFourthRequestId("fourthRequestId-2116417776")
+ .setFifthRequestId("fifthRequestId959024147")
+ .setSixthRequestId("sixthRequestId1005218260")
.setSeverity(Severity.forNumber(0))
.setFoobar(Foobar.newBuilder().build())
.build();
@@ -519,6 +534,11 @@ public class EchoClientTest {
.setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
.setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
.setRequestId("requestId693933066")
+ .setSecondRequestId("secondRequestId344404470")
+ .setThirdRequestId(true)
+ .setFourthRequestId("fourthRequestId-2116417776")
+ .setFifthRequestId("fifthRequestId959024147")
+ .setSixthRequestId("sixthRequestId1005218260")
.setSeverity(Severity.forNumber(0))
.setFoobar(Foobar.newBuilder().build())
.build();
@@ -545,6 +565,11 @@ public class EchoClientTest {
.setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
.setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
.setRequestId("requestId693933066")
+ .setSecondRequestId("secondRequestId344404470")
+ .setThirdRequestId(true)
+ .setFourthRequestId("fourthRequestId-2116417776")
+ .setFifthRequestId("fifthRequestId959024147")
+ .setSixthRequestId("sixthRequestId1005218260")
.setSeverity(Severity.forNumber(0))
.setFoobar(Foobar.newBuilder().build())
.build();
@@ -579,6 +604,11 @@ public class EchoClientTest {
.setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
.setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
.setRequestId("requestId693933066")
+ .setSecondRequestId("secondRequestId344404470")
+ .setThirdRequestId(true)
+ .setFourthRequestId("fourthRequestId-2116417776")
+ .setFifthRequestId("fifthRequestId959024147")
+ .setSixthRequestId("sixthRequestId1005218260")
.setSeverity(Severity.forNumber(0))
.setFoobar(Foobar.newBuilder().build())
.build();
@@ -605,6 +635,11 @@ public class EchoClientTest {
.setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
.setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
.setRequestId("requestId693933066")
+ .setSecondRequestId("secondRequestId344404470")
+ .setThirdRequestId(true)
+ .setFourthRequestId("fourthRequestId-2116417776")
+ .setFifthRequestId("fifthRequestId959024147")
+ .setSixthRequestId("sixthRequestId1005218260")
.setSeverity(Severity.forNumber(0))
.setFoobar(Foobar.newBuilder().build())
.build();
@@ -864,6 +899,11 @@ public class EchoClientTest {
.setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
.setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
.setRequestId("requestId693933066")
+ .setSecondRequestId("secondRequestId344404470")
+ .setThirdRequestId(true)
+ .setFourthRequestId("fourthRequestId-2116417776")
+ .setFifthRequestId("fifthRequestId959024147")
+ .setSixthRequestId("sixthRequestId1005218260")
.setSeverity(Severity.forNumber(0))
.setFoobar(Foobar.newBuilder().build())
.build();
@@ -878,6 +918,11 @@ public class EchoClientTest {
Assert.assertEquals(request.getName(), actualRequest.getName());
Assert.assertEquals(request.getParent(), actualRequest.getParent());
Assert.assertEquals(request.getRequestId(), actualRequest.getRequestId());
+ Assert.assertEquals(request.getSecondRequestId(), actualRequest.getSecondRequestId());
+ Assert.assertEquals(request.getThirdRequestId(), actualRequest.getThirdRequestId());
+ Assert.assertEquals(request.getFourthRequestId(), actualRequest.getFourthRequestId());
+ Assert.assertEquals(request.getFifthRequestId(), actualRequest.getFifthRequestId());
+ Assert.assertEquals(request.getSixthRequestId(), actualRequest.getSixthRequestId());
Assert.assertEquals(request.getContent(), actualRequest.getContent());
Assert.assertEquals(request.getError(), actualRequest.getError());
Assert.assertEquals(request.getSeverity(), actualRequest.getSeverity());
@@ -899,6 +944,11 @@ public class EchoClientTest {
.setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
.setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
.setRequestId("requestId693933066")
+ .setSecondRequestId("secondRequestId344404470")
+ .setThirdRequestId(true)
+ .setFourthRequestId("fourthRequestId-2116417776")
+ .setFifthRequestId("fifthRequestId959024147")
+ .setSixthRequestId("sixthRequestId1005218260")
.setSeverity(Severity.forNumber(0))
.setFoobar(Foobar.newBuilder().build())
.build();
diff --git a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcEchoStub.golden b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcEchoStub.golden
index 940ab7d4c4..c74e415fef 100644
--- a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcEchoStub.golden
+++ b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcEchoStub.golden
@@ -14,6 +14,7 @@ import com.google.api.gax.rpc.ClientStreamingCallable;
import com.google.api.gax.rpc.OperationCallable;
import com.google.api.gax.rpc.ServerStreamingCallable;
import com.google.api.gax.rpc.UnaryCallable;
+import com.google.common.base.Strings;
import com.google.longrunning.Operation;
import com.google.longrunning.stub.GrpcOperationsStub;
import com.google.showcase.v1beta1.BlockRequest;
@@ -30,6 +31,7 @@ import com.google.showcase.v1beta1.WaitResponse;
import io.grpc.MethodDescriptor;
import io.grpc.protobuf.ProtoUtils;
import java.io.IOException;
+import java.util.UUID;
import java.util.concurrent.TimeUnit;
import javax.annotation.Generated;
@@ -183,6 +185,17 @@ public class GrpcEchoStub extends EchoStub {
GrpcCallSettings echoTransportSettings =
GrpcCallSettings.newBuilder()
.setMethodDescriptor(echoMethodDescriptor)
+ .setRequestMutator(
+ request -> {
+ EchoRequest.Builder requestBuilder = request.toBuilder();
+ if (Strings.isNullOrEmpty(request.getRequestId())) {
+ requestBuilder.setRequestId(UUID.randomUUID().toString());
+ }
+ if (Strings.isNullOrEmpty(request.getSecondRequestId())) {
+ requestBuilder.setSecondRequestId(UUID.randomUUID().toString());
+ }
+ return requestBuilder.build();
+ })
.build();
GrpcCallSettings expandTransportSettings =
GrpcCallSettings.newBuilder()
diff --git a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/AsyncChat.golden b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/AsyncChat.golden
index e17e9367c8..e67e1d5045 100644
--- a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/AsyncChat.golden
+++ b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/AsyncChat.golden
@@ -44,6 +44,11 @@ public class AsyncChat {
.setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
.setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
.setRequestId("requestId693933066")
+ .setSecondRequestId("secondRequestId344404470")
+ .setThirdRequestId(true)
+ .setFourthRequestId("fourthRequestId-2116417776")
+ .setFifthRequestId("fifthRequestId959024147")
+ .setSixthRequestId("sixthRequestId1005218260")
.setSeverity(Severity.forNumber(0))
.setFoobar(Foobar.newBuilder().build())
.build();
diff --git a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/AsyncChatAgain.golden b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/AsyncChatAgain.golden
index 02e38bd9db..16fdf6e73a 100644
--- a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/AsyncChatAgain.golden
+++ b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/AsyncChatAgain.golden
@@ -44,6 +44,11 @@ public class AsyncChatAgain {
.setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
.setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
.setRequestId("requestId693933066")
+ .setSecondRequestId("secondRequestId344404470")
+ .setThirdRequestId(true)
+ .setFourthRequestId("fourthRequestId-2116417776")
+ .setFifthRequestId("fifthRequestId959024147")
+ .setSixthRequestId("sixthRequestId1005218260")
.setSeverity(Severity.forNumber(0))
.setFoobar(Foobar.newBuilder().build())
.build();
diff --git a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/AsyncCollect.golden b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/AsyncCollect.golden
index cd8c21c72d..756c28f9b2 100644
--- a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/AsyncCollect.golden
+++ b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/AsyncCollect.golden
@@ -62,6 +62,11 @@ public class AsyncCollect {
.setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
.setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
.setRequestId("requestId693933066")
+ .setSecondRequestId("secondRequestId344404470")
+ .setThirdRequestId(true)
+ .setFourthRequestId("fourthRequestId-2116417776")
+ .setFifthRequestId("fifthRequestId959024147")
+ .setSixthRequestId("sixthRequestId1005218260")
.setSeverity(Severity.forNumber(0))
.setFoobar(Foobar.newBuilder().build())
.build();
diff --git a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/AsyncCollideName.golden b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/AsyncCollideName.golden
index 5ca5cd0bcc..8b22c05f7d 100644
--- a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/AsyncCollideName.golden
+++ b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/AsyncCollideName.golden
@@ -43,6 +43,11 @@ public class AsyncCollideName {
.setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
.setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
.setRequestId("requestId693933066")
+ .setSecondRequestId("secondRequestId344404470")
+ .setThirdRequestId(true)
+ .setFourthRequestId("fourthRequestId-2116417776")
+ .setFifthRequestId("fifthRequestId959024147")
+ .setSixthRequestId("sixthRequestId1005218260")
.setSeverity(Severity.forNumber(0))
.setFoobar(Foobar.newBuilder().build())
.build();
diff --git a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/AsyncEcho.golden b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/AsyncEcho.golden
index 087faaebf5..685c2e16cb 100644
--- a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/AsyncEcho.golden
+++ b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/AsyncEcho.golden
@@ -43,6 +43,11 @@ public class AsyncEcho {
.setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
.setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
.setRequestId("requestId693933066")
+ .setSecondRequestId("secondRequestId344404470")
+ .setThirdRequestId(true)
+ .setFourthRequestId("fourthRequestId-2116417776")
+ .setFifthRequestId("fifthRequestId959024147")
+ .setSixthRequestId("sixthRequestId1005218260")
.setSeverity(Severity.forNumber(0))
.setFoobar(Foobar.newBuilder().build())
.build();
diff --git a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/SyncCollideName.golden b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/SyncCollideName.golden
index f299524598..e4beee3fcd 100644
--- a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/SyncCollideName.golden
+++ b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/SyncCollideName.golden
@@ -42,6 +42,11 @@ public class SyncCollideName {
.setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
.setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
.setRequestId("requestId693933066")
+ .setSecondRequestId("secondRequestId344404470")
+ .setThirdRequestId(true)
+ .setFourthRequestId("fourthRequestId-2116417776")
+ .setFifthRequestId("fifthRequestId959024147")
+ .setSixthRequestId("sixthRequestId1005218260")
.setSeverity(Severity.forNumber(0))
.setFoobar(Foobar.newBuilder().build())
.build();
diff --git a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/SyncEcho.golden b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/SyncEcho.golden
index a0918f576d..29c754d9ae 100644
--- a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/SyncEcho.golden
+++ b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/SyncEcho.golden
@@ -42,6 +42,11 @@ public class SyncEcho {
.setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
.setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString())
.setRequestId("requestId693933066")
+ .setSecondRequestId("secondRequestId344404470")
+ .setThirdRequestId(true)
+ .setFourthRequestId("fourthRequestId-2116417776")
+ .setFifthRequestId("fifthRequestId959024147")
+ .setSixthRequestId("sixthRequestId1005218260")
.setSeverity(Severity.forNumber(0))
.setFoobar(Foobar.newBuilder().build())
.build();
diff --git a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/goldens/HttpJsonEchoStub.golden b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/goldens/HttpJsonEchoStub.golden
index 18904bdfbe..d58ce093b9 100644
--- a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/goldens/HttpJsonEchoStub.golden
+++ b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/goldens/HttpJsonEchoStub.golden
@@ -23,6 +23,7 @@ import com.google.api.gax.rpc.ClientStreamingCallable;
import com.google.api.gax.rpc.OperationCallable;
import com.google.api.gax.rpc.ServerStreamingCallable;
import com.google.api.gax.rpc.UnaryCallable;
+import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.longrunning.Operation;
import com.google.protobuf.TypeRegistry;
@@ -42,6 +43,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.UUID;
import java.util.concurrent.TimeUnit;
import javax.annotation.Generated;
@@ -408,6 +410,17 @@ public class HttpJsonEchoStub extends EchoStub {
HttpJsonCallSettings.newBuilder()
.setMethodDescriptor(echoMethodDescriptor)
.setTypeRegistry(typeRegistry)
+ .setRequestMutator(
+ request -> {
+ EchoRequest.Builder requestBuilder = request.toBuilder();
+ if (Strings.isNullOrEmpty(request.getRequestId())) {
+ requestBuilder.setRequestId(UUID.randomUUID().toString());
+ }
+ if (Strings.isNullOrEmpty(request.getSecondRequestId())) {
+ requestBuilder.setSecondRequestId(UUID.randomUUID().toString());
+ }
+ return requestBuilder.build();
+ })
.build();
HttpJsonCallSettings expandTransportSettings =
HttpJsonCallSettings.newBuilder()
diff --git a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/samplecode/ServiceClientCallableMethodSampleComposerTest.java b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/samplecode/ServiceClientCallableMethodSampleComposerTest.java
index 44a921f3b0..47be643ffa 100644
--- a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/samplecode/ServiceClientCallableMethodSampleComposerTest.java
+++ b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/samplecode/ServiceClientCallableMethodSampleComposerTest.java
@@ -580,6 +580,11 @@ public void valid_composeStreamCallableMethod_bidiStream() {
" .setParent(FoobarName.ofProjectFoobarName(\"[PROJECT]\","
+ " \"[FOOBAR]\").toString())\n",
" .setRequestId(\"requestId693933066\")\n",
+ " .setSecondRequestId(\"secondRequestId344404470\")\n",
+ " .setThirdRequestId(true)\n",
+ " .setFourthRequestId(\"fourthRequestId-2116417776\")\n",
+ " .setFifthRequestId(\"fifthRequestId959024147\")\n",
+ " .setSixthRequestId(\"sixthRequestId1005218260\")\n",
" .setSeverity(Severity.forNumber(0))\n",
" .setFoobar(Foobar.newBuilder().build())\n",
" .build();\n",
@@ -713,6 +718,11 @@ public void valid_composeStreamCallableMethod_clientStream() {
" .setParent(FoobarName.ofProjectFoobarName(\"[PROJECT]\","
+ " \"[FOOBAR]\").toString())\n",
" .setRequestId(\"requestId693933066\")\n",
+ " .setSecondRequestId(\"secondRequestId344404470\")\n",
+ " .setThirdRequestId(true)\n",
+ " .setFourthRequestId(\"fourthRequestId-2116417776\")\n",
+ " .setFifthRequestId(\"fifthRequestId959024147\")\n",
+ " .setSixthRequestId(\"sixthRequestId1005218260\")\n",
" .setSeverity(Severity.forNumber(0))\n",
" .setFoobar(Foobar.newBuilder().build())\n",
" .build();\n",
@@ -820,6 +830,11 @@ public void valid_composeRegularCallableMethod_unaryRpc() {
" .setParent(FoobarName.ofProjectFoobarName(\"[PROJECT]\","
+ " \"[FOOBAR]\").toString())\n",
" .setRequestId(\"requestId693933066\")\n",
+ " .setSecondRequestId(\"secondRequestId344404470\")\n",
+ " .setThirdRequestId(true)\n",
+ " .setFourthRequestId(\"fourthRequestId-2116417776\")\n",
+ " .setFifthRequestId(\"fifthRequestId959024147\")\n",
+ " .setSixthRequestId(\"sixthRequestId1005218260\")\n",
" .setSeverity(Severity.forNumber(0))\n",
" .setFoobar(Foobar.newBuilder().build())\n",
" .build();\n",
diff --git a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/samplecode/ServiceClientHeaderSampleComposerTest.java b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/samplecode/ServiceClientHeaderSampleComposerTest.java
index 265882ac3f..affffb9c09 100644
--- a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/samplecode/ServiceClientHeaderSampleComposerTest.java
+++ b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/samplecode/ServiceClientHeaderSampleComposerTest.java
@@ -224,6 +224,11 @@ public void composeClassHeaderSample_firstMethodHasNoSignatures() {
" .setParent(FoobarName.ofProjectFoobarName(\"[PROJECT]\","
+ " \"[FOOBAR]\").toString())\n",
" .setRequestId(\"requestId693933066\")\n",
+ " .setSecondRequestId(\"secondRequestId344404470\")\n",
+ " .setThirdRequestId(true)\n",
+ " .setFourthRequestId(\"fourthRequestId-2116417776\")\n",
+ " .setFifthRequestId(\"fifthRequestId959024147\")\n",
+ " .setSixthRequestId(\"sixthRequestId1005218260\")\n",
" .setSeverity(Severity.forNumber(0))\n",
" .setFoobar(Foobar.newBuilder().build())\n",
" .build();\n",
diff --git a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/samplecode/ServiceClientMethodSampleComposerTest.java b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/samplecode/ServiceClientMethodSampleComposerTest.java
index 9839af8f31..6d473be885 100644
--- a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/samplecode/ServiceClientMethodSampleComposerTest.java
+++ b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/samplecode/ServiceClientMethodSampleComposerTest.java
@@ -336,6 +336,11 @@ public void valid_composeDefaultSample_pureUnaryReturnVoid() {
" .setParent(FoobarName.ofProjectFoobarName(\"[PROJECT]\","
+ " \"[FOOBAR]\").toString())\n",
" .setRequestId(\"requestId693933066\")\n",
+ " .setSecondRequestId(\"secondRequestId344404470\")\n",
+ " .setThirdRequestId(true)\n",
+ " .setFourthRequestId(\"fourthRequestId-2116417776\")\n",
+ " .setFifthRequestId(\"fifthRequestId959024147\")\n",
+ " .setSixthRequestId(\"sixthRequestId1005218260\")\n",
" .setSeverity(Severity.forNumber(0))\n",
" .setFoobar(Foobar.newBuilder().build())\n",
" .build();\n",
@@ -399,6 +404,11 @@ public void valid_composeDefaultSample_pureUnaryReturnResponse() {
" .setParent(FoobarName.ofProjectFoobarName(\"[PROJECT]\","
+ " \"[FOOBAR]\").toString())\n",
" .setRequestId(\"requestId693933066\")\n",
+ " .setSecondRequestId(\"secondRequestId344404470\")\n",
+ " .setThirdRequestId(true)\n",
+ " .setFourthRequestId(\"fourthRequestId-2116417776\")\n",
+ " .setFifthRequestId(\"fifthRequestId959024147\")\n",
+ " .setSixthRequestId(\"sixthRequestId1005218260\")\n",
" .setSeverity(Severity.forNumber(0))\n",
" .setFoobar(Foobar.newBuilder().build())\n",
" .build();\n",
diff --git a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/model/FieldTest.java b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/model/FieldTest.java
new file mode 100644
index 0000000000..89ba60eabd
--- /dev/null
+++ b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/model/FieldTest.java
@@ -0,0 +1,76 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.api.generator.gapic.model;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.api.FieldInfo.Format;
+import com.google.api.generator.engine.ast.TypeNode;
+import org.junit.Test;
+
+public class FieldTest {
+
+ @Test
+ public void shouldAutoPopulate() {
+ Field FIELD =
+ Field.builder()
+ .setName("SampleField")
+ .setIsRequired(false)
+ .setFieldInfoFormat(Format.UUID4)
+ .setType(TypeNode.STRING)
+ .build();
+
+ assertEquals(true, FIELD.canBeAutoPopulated());
+ }
+
+ @Test
+ public void isRequired_shouldNotAutoPopulate() {
+ Field FIELD =
+ Field.builder()
+ .setName("SampleField")
+ .setIsRequired(true)
+ .setFieldInfoFormat(Format.UUID4)
+ .setType(TypeNode.STRING)
+ .build();
+
+ assertEquals(false, FIELD.canBeAutoPopulated());
+ }
+
+ @Test
+ public void fieldInfoFormatNotUUID4_shouldNotAutoPopulate() {
+ Field FIELD =
+ Field.builder()
+ .setName("SampleField")
+ .setIsRequired(true)
+ .setFieldInfoFormat(Format.IPV6)
+ .setType(TypeNode.STRING)
+ .build();
+
+ assertEquals(false, FIELD.canBeAutoPopulated());
+ }
+
+ @Test
+ public void typeNotString_shouldNotAutoPopulate() {
+ Field FIELD =
+ Field.builder()
+ .setName("SampleField")
+ .setIsRequired(true)
+ .setFieldInfoFormat(Format.UUID4)
+ .setType(TypeNode.BOOLEAN)
+ .build();
+
+ assertEquals(false, FIELD.canBeAutoPopulated());
+ }
+}
diff --git a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/protoparser/ParserTest.java b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/protoparser/ParserTest.java
index 16340e0a6b..8fdf2576c9 100644
--- a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/protoparser/ParserTest.java
+++ b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/protoparser/ParserTest.java
@@ -145,7 +145,14 @@ public void parseMethods_basic() {
assertEquals(echoMethod.name(), "Echo");
assertEquals(echoMethod.stream(), Method.Stream.NONE);
assertEquals(true, echoMethod.hasAutoPopulatedFields());
- assertEquals(Arrays.asList("request_id"), echoMethod.autoPopulatedFields());
+ assertEquals(
+ Arrays.asList(
+ "request_id",
+ "second_request_id",
+ "third_request_id",
+ "fourth_request_id",
+ "non_existent_field"),
+ echoMethod.autoPopulatedFields());
// Detailed method signature parsing tests are in a separate unit test.
List> methodSignatures = echoMethod.methodSignatures();
@@ -451,6 +458,21 @@ public void parseFields_autoPopulated() {
field = message.fieldMap().get("severity");
assertEquals(false, field.isRequired());
assertEquals(null, field.fieldInfoFormat());
+ field = message.fieldMap().get("second_request_id");
+ assertEquals(false, field.isRequired());
+ assertEquals(Format.UUID4, field.fieldInfoFormat());
+ field = message.fieldMap().get("third_request_id");
+ assertEquals(false, field.isRequired());
+ assertEquals(Format.UUID4, field.fieldInfoFormat());
+ field = message.fieldMap().get("fourth_request_id");
+ assertEquals(false, field.isRequired());
+ assertEquals(Format.IPV4_OR_IPV6, field.fieldInfoFormat());
+ field = message.fieldMap().get("fifth_request_id");
+ assertEquals(false, field.isRequired());
+ assertEquals(Format.UUID4, field.fieldInfoFormat());
+ field = message.fieldMap().get("sixth_request_id");
+ assertEquals(true, field.isRequired());
+ assertEquals(Format.UUID4, field.fieldInfoFormat());
message = messageTypes.get("com.google.showcase.v1beta1.ExpandRequest");
field = message.fieldMap().get("request_id");
@@ -465,7 +487,12 @@ public void parseAutoPopulatedMethodsAndFields_exists() {
assertEquals(
true, autoPopulatedMethodsWithFields.containsKey("google.showcase.v1beta1.Echo.Echo"));
assertEquals(
- Arrays.asList("request_id"),
+ Arrays.asList(
+ "request_id",
+ "second_request_id",
+ "third_request_id",
+ "fourth_request_id",
+ "non_existent_field"),
autoPopulatedMethodsWithFields.get("google.showcase.v1beta1.Echo.Echo"));
}
diff --git a/gapic-generator-java/src/test/java/com/google/api/generator/test/protoloader/TestProtoLoader.java b/gapic-generator-java/src/test/java/com/google/api/generator/test/protoloader/TestProtoLoader.java
index 79d37baf4f..8a812ea437 100644
--- a/gapic-generator-java/src/test/java/com/google/api/generator/test/protoloader/TestProtoLoader.java
+++ b/gapic-generator-java/src/test/java/com/google/api/generator/test/protoloader/TestProtoLoader.java
@@ -27,6 +27,7 @@
import com.google.api.generator.gapic.protoparser.BatchingSettingsConfigParser;
import com.google.api.generator.gapic.protoparser.Parser;
import com.google.api.generator.gapic.protoparser.ServiceConfigParser;
+import com.google.api.generator.gapic.protoparser.ServiceYamlParser;
import com.google.bookshop.v1beta1.BookshopProto;
import com.google.explicit.dynamic.routing.header.ExplicitDynamicRoutingHeaderTestingOuterClass;
import com.google.logging.v2.LogEntryProto;
@@ -162,12 +163,18 @@ public GapicContext parseShowcaseEcho() {
ServiceDescriptor echoServiceDescriptor = echoFileDescriptor.getServices().get(0);
assertEquals(echoServiceDescriptor.getName(), "Echo");
+ String serviceYamlFilename = "echo_v1beta1.yaml";
+ Path serviceYamlPath = Paths.get(testFilesDirectory, serviceYamlFilename);
+ Optional serviceYamlOpt =
+ ServiceYamlParser.parse(serviceYamlPath.toString());
+ assertTrue(serviceYamlOpt.isPresent());
+
Map messageTypes = Parser.parseMessages(echoFileDescriptor);
Map resourceNames = Parser.parseResourceNames(echoFileDescriptor);
Set outputResourceNames = new HashSet<>();
List services =
Parser.parseService(
- echoFileDescriptor, messageTypes, resourceNames, Optional.empty(), outputResourceNames);
+ echoFileDescriptor, messageTypes, resourceNames, serviceYamlOpt, outputResourceNames);
// Explicitly adds service description, since this is not parsed from source code location
// in test protos, as it would from a protoc CodeGeneratorRequest
diff --git a/gapic-generator-java/src/test/proto/echo.proto b/gapic-generator-java/src/test/proto/echo.proto
index d963447340..effa0325cd 100644
--- a/gapic-generator-java/src/test/proto/echo.proto
+++ b/gapic-generator-java/src/test/proto/echo.proto
@@ -185,6 +185,34 @@ message EchoRequest {
(google.api.field_info).format = UUID4
];
+ // This field is added to test that autopopulation works for multiple fields
+ string second_request_id = 8 [
+ (google.api.field_behavior) = OPTIONAL,
+ (google.api.field_info).format = UUID4
+ ];
+
+ // This field is added to test that autopopulation should not populate this field since it is not of type String
+ bool third_request_id = 9 [
+ (google.api.field_info).format = UUID4
+ ];
+
+ // This field is added to test that autopopulation should not populate this field since it is not annotated with UUID4 format
+ string fourth_request_id = 10 [
+ (google.api.field_info).format = IPV4_OR_IPV6
+ ];
+
+ // This field is added to test that autopopulation should not populate this field since it is not designated in the service_yaml
+ string fifth_request_id = 11 [
+ (google.api.field_info).format = UUID4
+ ];
+
+ // This field is added to test that autopopulation should not populate this field since it marked as Required
+ string sixth_request_id = 12 [
+ (google.api.field_info).format = UUID4,
+ (google.api.field_behavior) = REQUIRED
+ ];
+
+
oneof response {
// The content to be echoed by the server.
string content = 1;
diff --git a/gapic-generator-java/src/test/resources/echo_v1beta1.yaml b/gapic-generator-java/src/test/resources/echo_v1beta1.yaml
index 57d9f90115..a6aea48e87 100644
--- a/gapic-generator-java/src/test/resources/echo_v1beta1.yaml
+++ b/gapic-generator-java/src/test/resources/echo_v1beta1.yaml
@@ -97,7 +97,10 @@ http:
- post: '/v1beta3/{name=operations/**}:cancel'
publishing:
method_settings:
- # TODO: Add more test cases for scenarios where the field does not exist, the field is not a String, etc. Eventually the API Linter should handle some of those cases.
- selector: google.showcase.v1beta1.Echo.Echo
auto_populated_fields:
- - request_id
\ No newline at end of file
+ - request_id
+ - second_request_id
+ - third_request_id
+ - fourth_request_id
+ - non_existent_field
\ No newline at end of file
diff --git a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallSettings.java b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallSettings.java
index a5aef3d69f..fae4ae9d25 100644
--- a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallSettings.java
+++ b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallSettings.java
@@ -30,6 +30,7 @@
package com.google.api.gax.grpc;
import com.google.api.core.BetaApi;
+import com.google.api.gax.rpc.RequestMutator;
import com.google.api.gax.rpc.RequestParamsExtractor;
import io.grpc.MethodDescriptor;
@@ -37,11 +38,13 @@
public class GrpcCallSettings {
private final MethodDescriptor methodDescriptor;
private final RequestParamsExtractor paramsExtractor;
+ private final RequestMutator requestMutator;
private final boolean alwaysAwaitTrailers;
private GrpcCallSettings(Builder builder) {
this.methodDescriptor = builder.methodDescriptor;
this.paramsExtractor = builder.paramsExtractor;
+ this.requestMutator = builder.requestMutator;
this.alwaysAwaitTrailers = builder.shouldAwaitTrailers;
}
@@ -53,6 +56,10 @@ public RequestParamsExtractor getParamsExtractor() {
return paramsExtractor;
}
+ public RequestMutator getRequestMutator() {
+ return requestMutator;
+ }
+
@BetaApi
public boolean shouldAwaitTrailers() {
return alwaysAwaitTrailers;
@@ -76,6 +83,8 @@ public Builder toBuilder() {
public static class Builder {
private MethodDescriptor methodDescriptor;
private RequestParamsExtractor paramsExtractor;
+
+ private RequestMutator requestMutator;
private boolean shouldAwaitTrailers;
private Builder() {}
@@ -83,6 +92,7 @@ private Builder() {}
private Builder(GrpcCallSettings settings) {
this.methodDescriptor = settings.methodDescriptor;
this.paramsExtractor = settings.paramsExtractor;
+ this.requestMutator = settings.requestMutator;
this.shouldAwaitTrailers = settings.alwaysAwaitTrailers;
}
@@ -98,6 +108,11 @@ public Builder setParamsExtractor(
return this;
}
+ public Builder setRequestMutator(RequestMutator requestMutator) {
+ this.requestMutator = requestMutator;
+ return this;
+ }
+
@BetaApi
public Builder setShouldAwaitTrailers(boolean b) {
this.shouldAwaitTrailers = b;
diff --git a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallableFactory.java b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallableFactory.java
index 8a0c4e0b37..974feb0c43 100644
--- a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallableFactory.java
+++ b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallableFactory.java
@@ -85,7 +85,13 @@ public static UnaryCallable createBas
GrpcRawCallableFactory.createUnaryCallable(
grpcCallSettings, callSettings.getRetryableCodes());
- callable = Callables.retrying(callable, callSettings, clientContext);
+ if (grpcCallSettings.getRequestMutator() != null) {
+ callable =
+ Callables.retrying(
+ callable, callSettings, clientContext, grpcCallSettings.getRequestMutator());
+ } else {
+ callable = Callables.retrying(callable, callSettings, clientContext);
+ }
return callable;
}
diff --git a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcDirectCallable.java b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcDirectCallable.java
index 5b6a5f1bad..33041145dd 100644
--- a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcDirectCallable.java
+++ b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcDirectCallable.java
@@ -30,6 +30,7 @@
package com.google.api.gax.grpc;
import com.google.api.core.ApiFuture;
+import com.google.api.core.InternalApi;
import com.google.api.core.ListenableFutureToApiFuture;
import com.google.api.gax.rpc.ApiCallContext;
import com.google.api.gax.rpc.UnaryCallable;
@@ -43,6 +44,7 @@
*
* Package-private for internal use.
*/
+@InternalApi
class GrpcDirectCallable extends UnaryCallable {
private final MethodDescriptor descriptor;
private final boolean awaitTrailers;
diff --git a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallSettings.java b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallSettings.java
index 7dd7732175..04411fc3d7 100644
--- a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallSettings.java
+++ b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallSettings.java
@@ -29,6 +29,7 @@
*/
package com.google.api.gax.httpjson;
+import com.google.api.gax.rpc.RequestMutator;
import com.google.api.gax.rpc.RequestParamsExtractor;
import com.google.protobuf.TypeRegistry;
@@ -36,11 +37,14 @@
public class HttpJsonCallSettings {
private final ApiMethodDescriptor methodDescriptor;
private final RequestParamsExtractor paramsExtractor;
+
+ private final RequestMutator requestMutator;
private final TypeRegistry typeRegistry;
private HttpJsonCallSettings(Builder builder) {
this.methodDescriptor = builder.methodDescriptor;
this.paramsExtractor = builder.paramsExtractor;
+ this.requestMutator = builder.requestMutator;
this.typeRegistry = builder.typeRegistry;
}
@@ -52,6 +56,10 @@ public RequestParamsExtractor getParamsExtractor() {
return paramsExtractor;
}
+ public RequestMutator getRequestMutator() {
+ return requestMutator;
+ }
+
public TypeRegistry getTypeRegistry() {
return typeRegistry;
}
@@ -72,6 +80,8 @@ public Builder toBuilder() {
}
public static class Builder {
+
+ private RequestMutator requestMutator;
private ApiMethodDescriptor methodDescriptor;
private RequestParamsExtractor paramsExtractor;
private TypeRegistry typeRegistry;
@@ -94,6 +104,11 @@ public Builder setParamsExtractor(
return this;
}
+ public Builder setRequestMutator(RequestMutator requestMutator) {
+ this.requestMutator = requestMutator;
+ return this;
+ }
+
public Builder setTypeRegistry(TypeRegistry typeRegistry) {
this.typeRegistry = typeRegistry;
return this;
diff --git a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallableFactory.java b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallableFactory.java
index d95751e3b0..33e2ff886e 100644
--- a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallableFactory.java
+++ b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallableFactory.java
@@ -30,6 +30,7 @@
package com.google.api.gax.httpjson;
import com.google.api.core.InternalApi;
+import com.google.api.core.ObsoleteApi;
import com.google.api.gax.longrunning.OperationSnapshot;
import com.google.api.gax.rpc.BatchingCallSettings;
import com.google.api.gax.rpc.Callables;
@@ -72,6 +73,22 @@ private static UnaryCallable createDi
return callable;
}
+ /** Create httpJson UnaryCallable with request mutator. */
+ static UnaryCallable createUnaryCallable(
+ UnaryCallable innerCallable,
+ UnaryCallSettings, ?> callSettings,
+ HttpJsonCallSettings httpJsonCallSettings,
+ ClientContext clientContext) {
+ UnaryCallable callable =
+ new HttpJsonExceptionCallable<>(innerCallable, callSettings.getRetryableCodes());
+ callable =
+ Callables.retrying(
+ callable, callSettings, clientContext, httpJsonCallSettings.getRequestMutator());
+ return callable.withDefaultCallContext(clientContext.getDefaultCallContext());
+ }
+
+ /** Use {@link #createUnaryCallable createUnaryCallable} method instead. */
+ @ObsoleteApi("Please use other httpJson UnaryCallable method instead")
static UnaryCallable createUnaryCallable(
UnaryCallable innerCallable,
UnaryCallSettings, ?> callSettings,
@@ -96,7 +113,9 @@ public static UnaryCallable createBas
ClientContext clientContext) {
UnaryCallable callable = createDirectUnaryCallable(httpJsonCallSettings);
callable = new HttpJsonExceptionCallable<>(callable, callSettings.getRetryableCodes());
- callable = Callables.retrying(callable, callSettings, clientContext);
+ callable =
+ Callables.retrying(
+ callable, callSettings, clientContext, httpJsonCallSettings.getRequestMutator());
return callable;
}
@@ -123,7 +142,7 @@ public static UnaryCallable createUna
clientContext.getTracerFactory(),
getSpanName(httpJsonCallSettings.getMethodDescriptor()));
- return createUnaryCallable(innerCallable, callSettings, clientContext);
+ return createUnaryCallable(innerCallable, callSettings, httpJsonCallSettings, clientContext);
}
/**
@@ -141,7 +160,8 @@ UnaryCallable createPagedCallable(
PagedCallSettings pagedCallSettings,
ClientContext clientContext) {
UnaryCallable callable = createDirectUnaryCallable(httpJsonCallSettings);
- callable = createUnaryCallable(callable, pagedCallSettings, clientContext);
+ callable =
+ createUnaryCallable(callable, pagedCallSettings, httpJsonCallSettings, clientContext);
UnaryCallable pagedCallable =
Callables.paged(callable, pagedCallSettings);
return pagedCallable.withDefaultCallContext(clientContext.getDefaultCallContext());
@@ -162,7 +182,8 @@ public static UnaryCallable createBat
BatchingCallSettings batchingCallSettings,
ClientContext clientContext) {
UnaryCallable callable = createDirectUnaryCallable(httpJsonCallSettings);
- callable = createUnaryCallable(callable, batchingCallSettings, clientContext);
+ callable =
+ createUnaryCallable(callable, batchingCallSettings, httpJsonCallSettings, clientContext);
callable = Callables.batching(callable, batchingCallSettings, clientContext);
return callable.withDefaultCallContext(clientContext.getDefaultCallContext());
}
diff --git a/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/RetryingTest.java b/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/RetryingTest.java
index d03d7e57f0..f7b9935d31 100644
--- a/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/RetryingTest.java
+++ b/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/RetryingTest.java
@@ -29,9 +29,14 @@
*/
package com.google.api.gax.httpjson;
+import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.verify;
import com.google.api.client.http.HttpHeaders;
+import com.google.api.client.http.HttpMethods;
import com.google.api.client.http.HttpResponseException;
import com.google.api.client.http.HttpStatusCodes;
import com.google.api.core.ApiFuture;
@@ -50,15 +55,16 @@
import com.google.api.gax.rpc.UnaryCallable;
import com.google.api.gax.rpc.UnknownException;
import com.google.common.collect.ImmutableSet;
-import com.google.common.truth.Truth;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.UncheckedExecutionException;
+import com.google.protobuf.TypeRegistry;
import java.util.Set;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.threeten.bp.Duration;
@@ -68,10 +74,27 @@ public class RetryingTest {
@SuppressWarnings("unchecked")
private final UnaryCallable callInt = Mockito.mock(UnaryCallable.class);
+ private final ApiMethodDescriptor FAKE_METHOD_DESCRIPTOR_FOR_REQUEST_MUTATOR =
+ ApiMethodDescriptor.newBuilder()
+ .setFullMethodName("google.cloud.v1.Fake/FakeMethodForRequestMutator")
+ .setHttpMethod(HttpMethods.POST)
+ .setRequestFormatter(Mockito.mock(HttpRequestFormatter.class))
+ .setResponseParser(Mockito.mock(HttpResponseParser.class))
+ .build();
+
+ private final Integer initialRequest = 1;
+ private final Integer modifiedRequest = 0;
+
+ private final HttpJsonCallSettings httpJsonCallSettings =
+ HttpJsonCallSettings.newBuilder()
+ .setRequestMutator(request -> modifiedRequest)
+ .setMethodDescriptor(FAKE_METHOD_DESCRIPTOR_FOR_REQUEST_MUTATOR)
+ .setTypeRegistry(TypeRegistry.newBuilder().build())
+ .build();
+
private RecordingScheduler executor;
private FakeApiClock fakeClock;
private ClientContext clientContext;
-
private static final int HTTP_CODE_PRECONDITION_FAILED = 412;
private HttpResponseException HTTP_SERVICE_UNAVAILABLE_EXCEPTION =
@@ -112,7 +135,7 @@ public void teardown() {
@Test
public void retry() {
ImmutableSet retryable = ImmutableSet.of(Code.UNAVAILABLE);
- Mockito.when(callInt.futureCall((Integer) Mockito.any(), (ApiCallContext) Mockito.any()))
+ Mockito.when(callInt.futureCall((Integer) any(), (ApiCallContext) any()))
.thenReturn(ApiFutures.immediateFailedFuture(HTTP_SERVICE_UNAVAILABLE_EXCEPTION))
.thenReturn(ApiFutures.immediateFailedFuture(HTTP_SERVICE_UNAVAILABLE_EXCEPTION))
.thenReturn(ApiFutures.immediateFailedFuture(HTTP_SERVICE_UNAVAILABLE_EXCEPTION))
@@ -121,8 +144,14 @@ public void retry() {
UnaryCallSettings callSettings =
createSettings(retryable, FAST_RETRY_SETTINGS);
UnaryCallable callable =
- HttpJsonCallableFactory.createUnaryCallable(callInt, callSettings, clientContext);
- Truth.assertThat(callable.call(1)).isEqualTo(2);
+ HttpJsonCallableFactory.createUnaryCallable(
+ callInt, callSettings, httpJsonCallSettings, clientContext);
+ assertThat(callable.call(initialRequest)).isEqualTo(2);
+
+ // Capture the argument passed to futureCall
+ ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(callInt, atLeastOnce()).futureCall(argumentCaptor.capture(), any(ApiCallContext.class));
+ assertThat(argumentCaptor.getAllValues()).containsExactly(0, 0, 0, 0).inOrder();
}
@Test
@@ -140,7 +169,7 @@ public void retryTotalTimeoutExceeded() {
httpResponseException,
HttpJsonStatusCode.of(Code.FAILED_PRECONDITION),
false);
- Mockito.when(callInt.futureCall((Integer) Mockito.any(), (ApiCallContext) Mockito.any()))
+ Mockito.when(callInt.futureCall((Integer) any(), (ApiCallContext) any()))
.thenReturn(ApiFutures.immediateFailedFuture(apiException))
.thenReturn(ApiFutures.immediateFuture(2));
@@ -152,14 +181,19 @@ public void retryTotalTimeoutExceeded() {
.build();
UnaryCallSettings callSettings = createSettings(retryable, retrySettings);
UnaryCallable callable =
- HttpJsonCallableFactory.createUnaryCallable(callInt, callSettings, clientContext);
- assertThrows(ApiException.class, () -> callable.call(1));
+ HttpJsonCallableFactory.createUnaryCallable(
+ callInt, callSettings, httpJsonCallSettings, clientContext);
+ assertThrows(ApiException.class, () -> callable.call(initialRequest));
+ // Capture the argument passed to futureCall
+ ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(callInt, atLeastOnce()).futureCall(argumentCaptor.capture(), any(ApiCallContext.class));
+ assertThat(argumentCaptor.getAllValues()).containsExactly(0);
}
@Test
public void retryMaxAttemptsExceeded() {
ImmutableSet retryable = ImmutableSet.of(Code.UNAVAILABLE);
- Mockito.when(callInt.futureCall((Integer) Mockito.any(), (ApiCallContext) Mockito.any()))
+ Mockito.when(callInt.futureCall((Integer) any(), (ApiCallContext) any()))
.thenReturn(ApiFutures.immediateFailedFuture(HTTP_SERVICE_UNAVAILABLE_EXCEPTION))
.thenReturn(ApiFutures.immediateFailedFuture(HTTP_SERVICE_UNAVAILABLE_EXCEPTION))
.thenReturn(ApiFutures.immediateFuture(2));
@@ -167,14 +201,19 @@ public void retryMaxAttemptsExceeded() {
RetrySettings retrySettings = FAST_RETRY_SETTINGS.toBuilder().setMaxAttempts(2).build();
UnaryCallSettings callSettings = createSettings(retryable, retrySettings);
UnaryCallable callable =
- HttpJsonCallableFactory.createUnaryCallable(callInt, callSettings, clientContext);
- assertThrows(ApiException.class, () -> callable.call(1));
+ HttpJsonCallableFactory.createUnaryCallable(
+ callInt, callSettings, httpJsonCallSettings, clientContext);
+ assertThrows(ApiException.class, () -> callable.call(initialRequest));
+ // Capture the argument passed to futureCall
+ ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(callInt, atLeastOnce()).futureCall(argumentCaptor.capture(), any(ApiCallContext.class));
+ assertThat(argumentCaptor.getAllValues()).containsExactly(0, 0).inOrder();
}
@Test
public void retryWithinMaxAttempts() {
ImmutableSet retryable = ImmutableSet.of(Code.UNAVAILABLE);
- Mockito.when(callInt.futureCall((Integer) Mockito.any(), (ApiCallContext) Mockito.any()))
+ Mockito.when(callInt.futureCall((Integer) any(), (ApiCallContext) any()))
.thenReturn(ApiFutures.immediateFailedFuture(HTTP_SERVICE_UNAVAILABLE_EXCEPTION))
.thenReturn(ApiFutures.immediateFailedFuture(HTTP_SERVICE_UNAVAILABLE_EXCEPTION))
.thenReturn(ApiFutures.immediateFuture(2));
@@ -182,9 +221,13 @@ public void retryWithinMaxAttempts() {
RetrySettings retrySettings = FAST_RETRY_SETTINGS.toBuilder().setMaxAttempts(3).build();
UnaryCallSettings callSettings = createSettings(retryable, retrySettings);
UnaryCallable callable =
- HttpJsonCallableFactory.createUnaryCallable(callInt, callSettings, clientContext);
- callable.call(1);
- Truth.assertThat(callable.call(1)).isEqualTo(2);
+ HttpJsonCallableFactory.createUnaryCallable(
+ callInt, callSettings, httpJsonCallSettings, clientContext);
+ assertThat(callable.call(initialRequest)).isEqualTo(2);
+ // Capture the argument passed to futureCall
+ ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(callInt, atLeastOnce()).futureCall(argumentCaptor.capture(), any(ApiCallContext.class));
+ assertThat(argumentCaptor.getAllValues()).containsExactly(0, 0, 0).inOrder();
}
@Test
@@ -196,7 +239,7 @@ public void retryOnStatusUnknown() {
"temporary redirect",
new HttpHeaders())
.build();
- Mockito.when(callInt.futureCall((Integer) Mockito.any(), (ApiCallContext) Mockito.any()))
+ Mockito.when(callInt.futureCall((Integer) any(), (ApiCallContext) any()))
.thenReturn(ApiFutures.immediateFailedFuture(throwable))
.thenReturn(ApiFutures.immediateFailedFuture(throwable))
.thenReturn(ApiFutures.immediateFailedFuture(throwable))
@@ -204,22 +247,32 @@ public void retryOnStatusUnknown() {
UnaryCallSettings callSettings =
createSettings(retryable, FAST_RETRY_SETTINGS);
UnaryCallable callable =
- HttpJsonCallableFactory.createUnaryCallable(callInt, callSettings, clientContext);
- Truth.assertThat(callable.call(1)).isEqualTo(2);
+ HttpJsonCallableFactory.createUnaryCallable(
+ callInt, callSettings, httpJsonCallSettings, clientContext);
+ assertThat(callable.call(initialRequest)).isEqualTo(2);
+ // Capture the argument passed to futureCall
+ ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(callInt, atLeastOnce()).futureCall(argumentCaptor.capture(), any(ApiCallContext.class));
+ assertThat(argumentCaptor.getAllValues()).containsExactly(0, 0, 0, 0).inOrder();
}
@Test
public void retryOnUnexpectedException() {
ImmutableSet retryable = ImmutableSet.of(Code.UNKNOWN);
Throwable throwable = new RuntimeException("foobar");
- Mockito.when(callInt.futureCall((Integer) Mockito.any(), (ApiCallContext) Mockito.any()))
+ Mockito.when(callInt.futureCall((Integer) any(), (ApiCallContext) any()))
.thenReturn(ApiFutures.immediateFailedFuture(throwable));
UnaryCallSettings callSettings =
createSettings(retryable, FAST_RETRY_SETTINGS);
UnaryCallable callable =
- HttpJsonCallableFactory.createUnaryCallable(callInt, callSettings, clientContext);
- ApiException exception = assertThrows(ApiException.class, () -> callable.call(1));
- Truth.assertThat(exception).hasCauseThat().isSameInstanceAs(throwable);
+ HttpJsonCallableFactory.createUnaryCallable(
+ callInt, callSettings, httpJsonCallSettings, clientContext);
+ ApiException exception = assertThrows(ApiException.class, () -> callable.call(initialRequest));
+ assertThat(exception).hasCauseThat().isSameInstanceAs(throwable);
+ // Capture the argument passed to futureCall
+ ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(callInt, atLeastOnce()).futureCall(argumentCaptor.capture(), any(ApiCallContext.class));
+ assertThat(argumentCaptor.getAllValues()).containsExactly(0).inOrder();
}
@Test
@@ -235,15 +288,20 @@ public void retryNoRecover() {
httpResponseException,
HttpJsonStatusCode.of(Code.FAILED_PRECONDITION),
false);
- Mockito.when(callInt.futureCall((Integer) Mockito.any(), (ApiCallContext) Mockito.any()))
+ Mockito.when(callInt.futureCall((Integer) any(), (ApiCallContext) any()))
.thenReturn(ApiFutures.immediateFailedFuture(apiException))
.thenReturn(ApiFutures.immediateFuture(2));
UnaryCallSettings callSettings =
createSettings(retryable, FAST_RETRY_SETTINGS);
UnaryCallable callable =
- HttpJsonCallableFactory.createUnaryCallable(callInt, callSettings, clientContext);
- ApiException exception = assertThrows(ApiException.class, () -> callable.call(1));
- Truth.assertThat(exception).isSameInstanceAs(apiException);
+ HttpJsonCallableFactory.createUnaryCallable(
+ callInt, callSettings, httpJsonCallSettings, clientContext);
+ ApiException exception = assertThrows(ApiException.class, () -> callable.call(initialRequest));
+ assertThat(exception).isSameInstanceAs(apiException);
+ // Capture the argument passed to futureCall
+ ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(callInt, atLeastOnce()).futureCall(argumentCaptor.capture(), any(ApiCallContext.class));
+ assertThat(argumentCaptor.getAllValues()).containsExactly(0);
}
@Test
@@ -253,19 +311,24 @@ public void retryKeepFailing() {
new HttpResponseException.Builder(
HttpStatusCodes.STATUS_CODE_SERVICE_UNAVAILABLE, "Unavailable", new HttpHeaders())
.build();
- Mockito.when(callInt.futureCall((Integer) Mockito.any(), (ApiCallContext) Mockito.any()))
+ Mockito.when(callInt.futureCall((Integer) any(), (ApiCallContext) any()))
.thenReturn(ApiFutures.immediateFailedFuture(throwable));
UnaryCallSettings callSettings =
createSettings(retryable, FAST_RETRY_SETTINGS);
UnaryCallable callable =
- HttpJsonCallableFactory.createUnaryCallable(callInt, callSettings, clientContext);
+ HttpJsonCallableFactory.createUnaryCallable(
+ callInt, callSettings, httpJsonCallSettings, clientContext);
// Need to advance time inside the call.
- ApiFuture future = callable.futureCall(1);
+ ApiFuture future = callable.futureCall(initialRequest);
UncheckedExecutionException exception =
assertThrows(UncheckedExecutionException.class, () -> Futures.getUnchecked(future));
- Truth.assertThat(exception).hasCauseThat().isInstanceOf(ApiException.class);
- Truth.assertThat(exception).hasCauseThat().hasMessageThat().contains("Unavailable");
+ assertThat(exception).hasCauseThat().isInstanceOf(ApiException.class);
+ assertThat(exception).hasCauseThat().hasMessageThat().contains("Unavailable");
+ // Capture the argument passed to futureCall
+ ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(callInt, atLeastOnce()).futureCall(argumentCaptor.capture(), any(ApiCallContext.class));
+ assertThat(argumentCaptor.getValue()).isEqualTo(0);
}
@Test
@@ -289,34 +352,45 @@ public void testKnownStatusCode() {
HTTP_CODE_PRECONDITION_FAILED, "precondition failed", new HttpHeaders())
.setMessage(throwableMessage)
.build();
- Mockito.when(callInt.futureCall((Integer) Mockito.any(), (ApiCallContext) Mockito.any()))
+ Mockito.when(callInt.futureCall((Integer) any(), (ApiCallContext) any()))
.thenReturn(ApiFutures.immediateFailedFuture(throwable));
UnaryCallSettings callSettings =
UnaryCallSettings.newUnaryCallSettingsBuilder()
.setRetryableCodes(retryable)
.build();
UnaryCallable callable =
- HttpJsonCallableFactory.createUnaryCallable(callInt, callSettings, clientContext);
+ HttpJsonCallableFactory.createUnaryCallable(
+ callInt, callSettings, httpJsonCallSettings, clientContext);
ApiException exception =
- assertThrows(FailedPreconditionException.class, () -> callable.call(1));
- Truth.assertThat(exception.getStatusCode().getTransportCode())
+ assertThrows(FailedPreconditionException.class, () -> callable.call(initialRequest));
+ assertThat(exception.getStatusCode().getTransportCode())
.isEqualTo(HTTP_CODE_PRECONDITION_FAILED);
- Truth.assertThat(exception).hasMessageThat().contains("precondition failed");
+ assertThat(exception).hasMessageThat().contains("precondition failed");
+ // Capture the argument passed to futureCall
+ ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(callInt, atLeastOnce()).futureCall(argumentCaptor.capture(), any(ApiCallContext.class));
+ assertThat(argumentCaptor.getAllValues()).containsExactly(0).inOrder();
}
@Test
public void testUnknownStatusCode() {
ImmutableSet retryable = ImmutableSet.of();
- Mockito.when(callInt.futureCall((Integer) Mockito.any(), (ApiCallContext) Mockito.any()))
+ Mockito.when(callInt.futureCall((Integer) any(), (ApiCallContext) any()))
.thenReturn(ApiFutures.immediateFailedFuture(new RuntimeException("unknown")));
UnaryCallSettings callSettings =
UnaryCallSettings.newUnaryCallSettingsBuilder()
.setRetryableCodes(retryable)
.build();
UnaryCallable callable =
- HttpJsonCallableFactory.createUnaryCallable(callInt, callSettings, clientContext);
- UnknownException exception = assertThrows(UnknownException.class, () -> callable.call(1));
- Truth.assertThat(exception).hasMessageThat().isEqualTo("java.lang.RuntimeException: unknown");
+ HttpJsonCallableFactory.createUnaryCallable(
+ callInt, callSettings, httpJsonCallSettings, clientContext);
+ UnknownException exception =
+ assertThrows(UnknownException.class, () -> callable.call(initialRequest));
+ assertThat(exception).hasMessageThat().isEqualTo("java.lang.RuntimeException: unknown");
+ // Capture the argument passed to futureCall
+ ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(callInt, atLeastOnce()).futureCall(argumentCaptor.capture(), any(ApiCallContext.class));
+ assertThat(argumentCaptor.getAllValues()).containsExactly(0).inOrder();
}
public static UnaryCallSettings createSettings(
diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/Callables.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/Callables.java
index 28a76fe721..b1f4b51d6a 100644
--- a/gax-java/gax/src/main/java/com/google/api/gax/rpc/Callables.java
+++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/Callables.java
@@ -51,11 +51,53 @@ public class Callables {
private Callables() {}
+ /**
+ * Create a callable object that represents a Unary API method. Designed for use by generated
+ * code.
+ *
+ * @param innerCallable the callable to issue calls
+ * @param callSettings {@link UnaryCallSettings} to configure the unary call-related settings
+ * with.
+ * @param clientContext {@link ClientContext} to use to connect to the service.
+ * @return {@link UnaryCallable} callable object.
+ */
public static UnaryCallable retrying(
UnaryCallable innerCallable,
UnaryCallSettings, ?> callSettings,
ClientContext clientContext) {
+ ScheduledRetryingExecutor retryingExecutor =
+ getRetryingExecutor(callSettings, clientContext);
+ return new RetryingCallable<>(
+ clientContext.getDefaultCallContext(), innerCallable, retryingExecutor);
+ }
+
+ /**
+ * Create a callable object that represents a Unary API method that contains a Request Mutator.
+ * Designed for use by generated code.
+ *
+ * @param innerCallable the callable to issue calls
+ * @param callSettings {@link UnaryCallSettings} to configure the unary call-related settings
+ * with.
+ * @param clientContext {@link ClientContext} to use to connect to the service.
+ * @param requestMutator {@link RequestMutator} to modify the request. Currently only used for
+ * autopopulated fields.
+ * @return {@link UnaryCallable} callable object.
+ */
+ public static UnaryCallable retrying(
+ UnaryCallable innerCallable,
+ UnaryCallSettings, ?> callSettings,
+ ClientContext clientContext,
+ RequestMutator requestMutator) {
+
+ ScheduledRetryingExecutor retryingExecutor =
+ getRetryingExecutor(callSettings, clientContext);
+ return new RetryingCallable<>(
+ clientContext.getDefaultCallContext(), innerCallable, retryingExecutor, requestMutator);
+ }
+
+ private static ScheduledRetryingExecutor getRetryingExecutor(
+ UnaryCallSettings, ?> callSettings, ClientContext clientContext) {
UnaryCallSettings, ?> settings = callSettings;
if (areRetriesDisabled(settings.getRetryableCodes(), settings.getRetrySettings())) {
@@ -73,8 +115,7 @@ public static UnaryCallable retrying(
new ExponentialRetryAlgorithm(settings.getRetrySettings(), clientContext.getClock()));
ScheduledRetryingExecutor retryingExecutor =
new ScheduledRetryingExecutor<>(retryAlgorithm, clientContext.getExecutor());
- return new RetryingCallable<>(
- clientContext.getDefaultCallContext(), innerCallable, retryingExecutor);
+ return retryingExecutor;
}
public static ServerStreamingCallable retrying(
diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/RequestMutator.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/RequestMutator.java
new file mode 100644
index 0000000000..d26949d87d
--- /dev/null
+++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/RequestMutator.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.google.api.gax.rpc;
+
+import com.google.api.core.InternalApi;
+
+/**
+ * A request mutator takes a {@code request} message, applies some Function to it, and then returns
+ * the modified {@code request} message. This is currently only used for autopopulation of the
+ * request ID.
+ *
+ * Implementations of this interface are expected to be autogenerated.
+ *
+ * @param request message type
+ */
+@InternalApi("For use by transport-specific implementations")
+@FunctionalInterface
+public interface RequestMutator {
+ /**
+ * Applies a Function to {@code request} message
+ *
+ * @param request request message
+ */
+ RequestT apply(RequestT request);
+}
diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/RetryingCallable.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/RetryingCallable.java
index 0a92794a20..e4fe13295a 100644
--- a/gax-java/gax/src/main/java/com/google/api/gax/rpc/RetryingCallable.java
+++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/RetryingCallable.java
@@ -43,20 +43,35 @@ class RetryingCallable extends UnaryCallable callable;
private final RetryingExecutorWithContext executor;
+ private final RequestMutator requestMutator;
+
RetryingCallable(
ApiCallContext callContextPrototype,
UnaryCallable callable,
RetryingExecutorWithContext executor) {
+ this(callContextPrototype, callable, executor, null);
+ }
+
+ RetryingCallable(
+ ApiCallContext callContextPrototype,
+ UnaryCallable callable,
+ RetryingExecutorWithContext executor,
+ RequestMutator requestMutator) {
this.callContextPrototype = Preconditions.checkNotNull(callContextPrototype);
this.callable = Preconditions.checkNotNull(callable);
this.executor = Preconditions.checkNotNull(executor);
+ this.requestMutator = requestMutator;
}
@Override
public RetryingFuture futureCall(RequestT request, ApiCallContext inputContext) {
ApiCallContext context = callContextPrototype.nullToSelf(inputContext);
+ RequestT modifiedRequest = request;
+ if (this.requestMutator != null) {
+ modifiedRequest = requestMutator.apply(request);
+ }
AttemptCallable retryCallable =
- new AttemptCallable<>(callable, request, context);
+ new AttemptCallable<>(callable, modifiedRequest, context);
RetryingFuture retryingFuture = executor.createFuture(retryCallable, inputContext);
retryCallable.setExternalFuture(retryingFuture);
diff --git a/gax-java/gax/src/test/java/com/google/api/gax/rpc/CallableTest.java b/gax-java/gax/src/test/java/com/google/api/gax/rpc/CallableTest.java
index 04e025fecb..8d24a19c53 100644
--- a/gax-java/gax/src/test/java/com/google/api/gax/rpc/CallableTest.java
+++ b/gax-java/gax/src/test/java/com/google/api/gax/rpc/CallableTest.java
@@ -29,17 +29,20 @@
*/
package com.google.api.gax.rpc;
+import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import com.google.api.core.ApiFuture;
import com.google.api.core.SettableApiFuture;
import com.google.api.gax.retrying.RetrySettings;
import com.google.api.gax.rpc.testing.FakeCallContext;
import org.junit.Rule;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnit;
@@ -78,17 +81,28 @@ public void testNonRetriedCallable() throws Exception {
innerResult = SettableApiFuture.create();
when(innerCallable.futureCall(anyString(), any(ApiCallContext.class))).thenReturn(innerResult);
Duration timeout = Duration.ofMillis(5L);
+ String initialRequest = "Is your refrigerator running?";
+ String modifiedRequest = "What about now?";
+
+ RequestMutator requestMutator = (request -> modifiedRequest);
UnaryCallSettings