Skip to content

Commit 38e3b32

Browse files
committed
feat(gax): add ApiCallContext and request-level settings overloads to ResumableUploadCallable
Add 2-, 3-, and 4-argument overloads to ResumableUploadCallable for futureCall and resumeCall, supporting: 1. Default context and settings (used by generated ServiceClient convenience methods). 2. Per-request ApiCallContext overrides for transport metadata (extra headers, credentials). 3. Per-request ResumableUploadCallSettings overrides for state-machine knobs per go/sdk:java-scotty-design. 4. Full method accepting both call context and settings overrides. ResumableUploadCallableImpl performs a 3-tier precedence merge on settings and merges ApiCallContext.
1 parent 73a0620 commit 38e3b32

3 files changed

Lines changed: 123 additions & 5 deletions

File tree

sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/rpc/ResumableUploadCallable.java

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,19 +48,57 @@ public abstract class ResumableUploadCallable<RequestT, ResponseT> {
4848
protected ResumableUploadCallable() {}
4949

5050
/**
51-
* Performs a new resumable upload asynchronously.
51+
* Performs a new resumable upload asynchronously with default call context and default settings.
5252
*
5353
* <p>The provided {@code payload} stream is consumed asynchronously by the returned {@link
5454
* ResumableUploadFuture} and will be closed automatically upon completion, failure, or
5555
* cancellation.
5656
*
5757
* @param request the request message
5858
* @param payload the data payload input stream to upload and close
59-
* @param settings call settings overrides; may be {@code null}
59+
* @return future for tracking and controlling the upload
60+
*/
61+
public ResumableUploadFuture<ResponseT> futureCall(RequestT request, InputStream payload) {
62+
return futureCall(request, payload, null, null);
63+
}
64+
65+
/**
66+
* Performs a new resumable upload asynchronously with a call context override and default
67+
* settings.
68+
*
69+
* <p>The provided {@code payload} stream is consumed asynchronously by the returned {@link
70+
* ResumableUploadFuture} and will be closed automatically upon completion, failure, or
71+
* cancellation.
72+
*
73+
* @param request the request message
74+
* @param payload the data payload input stream to upload and close
75+
* @param context call context overrides (e.g. extra headers, credentials, timeout); may be {@code
76+
* null}
77+
* @return future for tracking and controlling the upload
78+
*/
79+
public ResumableUploadFuture<ResponseT> futureCall(
80+
RequestT request, InputStream payload, @Nullable ApiCallContext context) {
81+
return futureCall(request, payload, context, null);
82+
}
83+
84+
/**
85+
* Performs a new resumable upload asynchronously with call context and settings overrides.
86+
*
87+
* <p>The provided {@code payload} stream is consumed asynchronously by the returned {@link
88+
* ResumableUploadFuture} and will be closed automatically upon completion, failure, or
89+
* cancellation.
90+
*
91+
* @param request the request message
92+
* @param payload the data payload input stream to upload and close
93+
* @param context call context overrides; may be {@code null}
94+
* @param settings request-level call settings overrides; may be {@code null}
6095
* @return future for tracking and controlling the upload
6196
*/
6297
public abstract ResumableUploadFuture<ResponseT> futureCall(
63-
RequestT request, InputStream payload, @Nullable ResumableUploadCallSettings settings);
98+
RequestT request,
99+
InputStream payload,
100+
@Nullable ApiCallContext context,
101+
@Nullable ResumableUploadCallSettings settings);
64102

65103
/**
66104
* Resumes an existing resumable upload session asynchronously using a saved session URL.

sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/rpc/ResumableUploadCallableImpl.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,14 +75,18 @@ public ResumableUploadCallableImpl(
7575

7676
@Override
7777
public ResumableUploadFuture<ResponseT> futureCall(
78-
RequestT request, InputStream payload, @Nullable ResumableUploadCallSettings settings) {
78+
RequestT request,
79+
InputStream payload,
80+
@Nullable ApiCallContext context,
81+
@Nullable ResumableUploadCallSettings settings) {
7982
checkNotNull(request, "request must not be null");
8083
checkNotNull(payload, "payload must not be null");
8184
ResumableUploadCallSettings effectiveSettings = defaultCallSettings.merge(settings);
85+
ApiCallContext effectiveCallContext = defaultCallContext.merge(context);
8286

8387
ApiFuture<ResumableUploadSession> startFuture;
8488
try {
85-
startFuture = client.startUploadCallable().futureCall(request, defaultCallContext);
89+
startFuture = client.startUploadCallable().futureCall(request, effectiveCallContext);
8690
} catch (Throwable t) {
8791
startFuture = ApiFutures.immediateFailedFuture(t);
8892
}

sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/rpc/ResumableUploadCallableImplTest.java

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,82 @@ public void execute(Runnable command) {
357357
}
358358
}
359359

360+
@Test
361+
void testUploadCallable_convenienceOverload_noContext() throws Exception {
362+
stubStartSession("https://upload.url/convenience");
363+
when(mockChunkCallable.futureCall(any(ChunkUploadRequest.class), any()))
364+
.thenReturn(ApiFutures.immediateFuture(ChunkUploadResponse.create(true, "done-conv")));
365+
366+
ResumableUploadFuture<String> future = callable.futureCall("resource-path", streamOf("data"));
367+
assertThat(future.get()).isEqualTo("done-conv");
368+
}
369+
370+
@Test
371+
void testUploadCallable_withApiCallContext_mergesAndPassesContext() throws Exception {
372+
stubStartSession("https://upload.url/context");
373+
when(mockChunkCallable.futureCall(any(ChunkUploadRequest.class), any()))
374+
.thenReturn(ApiFutures.immediateFuture(ChunkUploadResponse.create(true, "done-ctx")));
375+
376+
ApiCallContext customContext =
377+
FakeCallContext.createDefault()
378+
.withExtraHeaders(
379+
java.util.Collections.singletonMap(
380+
"X-Custom", java.util.Collections.singletonList("val")));
381+
ResumableUploadFuture<String> future =
382+
callable.futureCall("resource-path", streamOf("data"), customContext);
383+
assertThat(future.get()).isEqualTo("done-ctx");
384+
385+
ArgumentCaptor<ApiCallContext> startContextCaptor =
386+
ArgumentCaptor.forClass(ApiCallContext.class);
387+
verify(mockStartCallable).futureCall(any(), startContextCaptor.capture());
388+
assertThat(startContextCaptor.getValue()).isNotNull();
389+
assertThat(((FakeCallContext) startContextCaptor.getValue()).getExtraHeaders())
390+
.containsKey("X-Custom");
391+
392+
ArgumentCaptor<ApiCallContext> chunkContextCaptor =
393+
ArgumentCaptor.forClass(ApiCallContext.class);
394+
verify(mockChunkCallable).futureCall(any(), chunkContextCaptor.capture());
395+
assertThat(chunkContextCaptor.getValue()).isNotNull();
396+
assertThat(((FakeCallContext) chunkContextCaptor.getValue()).getExtraHeaders())
397+
.doesNotContainKey("X-Custom");
398+
}
399+
400+
@Test
401+
void testUploadCallable_withSettings_mergesAndAppliesSettings() throws Exception {
402+
stubStartSession("https://upload.url/settings");
403+
when(mockChunkCallable.futureCall(any(ChunkUploadRequest.class), any()))
404+
.thenReturn(ApiFutures.immediateFuture(ChunkUploadResponse.create(true, "done-settings")));
405+
406+
ResumableUploadCallSettings customSettings =
407+
ResumableUploadCallSettings.newBuilder().setChunkSize(16).build();
408+
409+
ResumableUploadFuture<String> future =
410+
callable.futureCall("resource-path", streamOf("data"), null, customSettings);
411+
assertThat(future.get()).isEqualTo("done-settings");
412+
}
413+
414+
@Test
415+
void testUploadCallable_withContextAndSettings_appliesBoth() throws Exception {
416+
stubStartSession("https://upload.url/ctx-settings");
417+
when(mockChunkCallable.futureCall(any(ChunkUploadRequest.class), any()))
418+
.thenReturn(ApiFutures.immediateFuture(ChunkUploadResponse.create(true, "done-both")));
419+
420+
FakeCallContext customContext = FakeCallContext.createDefault();
421+
ResumableUploadCallSettings customSettings =
422+
ResumableUploadCallSettings.newBuilder().setChunkSize(16).build();
423+
424+
ResumableUploadFuture<String> future =
425+
callable.futureCall("resource-path", streamOf("data"), customContext, customSettings);
426+
assertThat(future.get()).isEqualTo("done-both");
427+
}
428+
429+
@Test
430+
void testResumeCall_throwsUnsupportedOperationException() {
431+
assertThrows(
432+
UnsupportedOperationException.class,
433+
() -> callable.resumeCall("https://upload.url/session", streamOf("data"), null));
434+
}
435+
360436
private void stubStartSession(String uploadUrl) {
361437
when(mockStartCallable.futureCall(any(), any()))
362438
.thenReturn(

0 commit comments

Comments
 (0)