Skip to content

Commit 136e9ce

Browse files
committed
feat(generator): wire transport stub delegation to resumable upload stubs
1 parent 46d6fdf commit 136e9ce

9 files changed

Lines changed: 1154 additions & 28 deletions

File tree

sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/Composer.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,12 @@
2929
import com.google.api.generator.gapic.composer.grpcrest.HttpJsonServiceClientTestClassComposer;
3030
import com.google.api.generator.gapic.composer.resourcename.ResourceNameHelperClassComposer;
3131
import com.google.api.generator.gapic.composer.rest.HttpJsonServiceCallableFactoryClassComposer;
32+
import com.google.api.generator.gapic.composer.rest.HttpJsonServiceResumableUploadStubClassComposer;
3233
import com.google.api.generator.gapic.composer.rest.HttpJsonServiceStubClassComposer;
3334
import com.google.api.generator.gapic.model.GapicClass;
3435
import com.google.api.generator.gapic.model.GapicContext;
3536
import com.google.api.generator.gapic.model.GapicPackageInfo;
37+
import com.google.api.generator.gapic.model.Method;
3638
import com.google.api.generator.gapic.model.ReflectConfig;
3739
import com.google.api.generator.gapic.model.Sample;
3840
import com.google.api.generator.gapic.model.Service;
@@ -91,6 +93,11 @@ public static List<GapicClass> generateStubClasses(GapicContext context) {
9193
.services()
9294
.forEach(
9395
s -> {
96+
if (s.methods().stream().anyMatch(Method::isResumableUpload)) {
97+
clazzes.add(
98+
HttpJsonServiceResumableUploadStubClassComposer.instance()
99+
.generate(context, s));
100+
}
94101
if (context.transport() == Transport.REST) {
95102
clazzes.add(
96103
com.google.api.generator.gapic.composer.rest.ServiceStubClassComposer.instance()

sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractTransportServiceStubClassComposer.java

Lines changed: 210 additions & 26 deletions
Large diffs are not rendered by default.

sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/grpc/GrpcServiceStubClassComposer.java

Lines changed: 377 additions & 1 deletion
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.api.generator.gapic.composer.common;
16+
17+
import static com.google.common.truth.Truth.assertThat;
18+
19+
import com.google.api.generator.engine.writer.JavaWriterVisitor;
20+
import com.google.api.generator.gapic.composer.grpc.GrpcServiceStubClassComposer;
21+
import com.google.api.generator.gapic.composer.rest.HttpJsonServiceStubClassComposer;
22+
import com.google.api.generator.gapic.model.GapicClass;
23+
import com.google.api.generator.gapic.model.GapicClass.Kind;
24+
import com.google.api.generator.gapic.model.GapicContext;
25+
import com.google.api.generator.gapic.model.Service;
26+
import com.google.api.generator.test.protoloader.GrpcTestProtoLoader;
27+
import com.google.api.generator.test.protoloader.RestTestProtoLoader;
28+
import org.junit.jupiter.api.Test;
29+
30+
class ResumableUploadTransportStubComposerTest {
31+
32+
private static String generateGrpcStubCode() {
33+
GapicContext context = GrpcTestProtoLoader.instance().parseShowcaseResumableUpload();
34+
Service service = context.services().get(0);
35+
36+
GapicClass clazz = GrpcServiceStubClassComposer.instance().generate(context, service);
37+
assertThat(clazz.kind()).isEqualTo(Kind.STUB);
38+
39+
JavaWriterVisitor visitor = new JavaWriterVisitor();
40+
clazz.classDefinition().accept(visitor);
41+
return visitor.write();
42+
}
43+
44+
private static String generateRestStubCode() {
45+
GapicContext context = RestTestProtoLoader.instance().parseShowcaseResumableUpload();
46+
Service service = context.services().get(0);
47+
48+
GapicClass clazz = HttpJsonServiceStubClassComposer.instance().generate(context, service);
49+
assertThat(clazz.kind()).isEqualTo(Kind.STUB);
50+
51+
JavaWriterVisitor visitor = new JavaWriterVisitor();
52+
clazz.classDefinition().accept(visitor);
53+
return visitor.write();
54+
}
55+
56+
@Test
57+
void generate_grpcStub_withCredentials_initializesHttpStub() {
58+
String code = generateGrpcStubCode();
59+
60+
// Verify key imports
61+
assertThat(code).contains("import com.google.api.gax.grpc.GrpcStatusCode;");
62+
assertThat(code).contains("import com.google.api.gax.httpjson.HttpJsonCallContext;");
63+
assertThat(code).contains("import com.google.api.gax.httpjson.HttpJsonTransportChannel;");
64+
assertThat(code).contains("import com.google.api.gax.httpjson.ManagedHttpJsonChannel;");
65+
assertThat(code).contains("import com.google.api.gax.rpc.FailedPreconditionException;");
66+
assertThat(code).contains("import com.google.api.gax.rpc.ResumableUploadCallable;");
67+
assertThat(code).contains("import com.google.common.collect.ImmutableList;");
68+
assertThat(code).contains("import io.grpc.Status;");
69+
assertThat(code).contains("import java.util.Arrays;");
70+
71+
// Verify class declaration and nullable stub field
72+
assertThat(code)
73+
.contains("public class GrpcResumableUploadServiceStub extends ResumableUploadServiceStub");
74+
assertThat(code)
75+
.contains(
76+
"private final @Nullable HttpJsonResumableUploadServiceResumableUploadStub"
77+
+ " resumableUploadStub;");
78+
79+
// Verify constructor credentials check and HTTP channel instantiation
80+
assertThat(code).contains("if (clientContext.getCredentials() != null) {");
81+
assertThat(code).contains("ManagedHttpJsonChannel httpJsonManagedChannel =");
82+
assertThat(code).contains("HttpJsonTransportChannel httpJsonTransportChannel =");
83+
assertThat(code).contains("ClientContext httpJsonClientContext =");
84+
assertThat(code).contains(".setCredentials(clientContext.getCredentials())");
85+
assertThat(code).contains(".setEndpoint(settings.getEndpoint())");
86+
assertThat(code).contains(".setExecutor(clientContext.getExecutor())");
87+
assertThat(code).contains(".setHeaders(clientContext.getHeaders())");
88+
assertThat(code).contains(".setInternalHeaders(clientContext.getInternalHeaders())");
89+
assertThat(code).contains(".setTransportChannel(httpJsonTransportChannel)");
90+
assertThat(code).contains(".withTransportChannel(httpJsonTransportChannel)");
91+
assertThat(code).contains(".setBackgroundResources(Arrays.asList(httpJsonTransportChannel))");
92+
assertThat(code).contains("this.resumableUploadStub =");
93+
assertThat(code)
94+
.contains(
95+
"HttpJsonResumableUploadServiceResumableUploadStub.create(httpJsonClientContext,"
96+
+ " settings);");
97+
}
98+
99+
@Test
100+
void generate_grpcStub_withoutCredentials_assignsNull() {
101+
String code = generateGrpcStubCode();
102+
103+
assertThat(code).contains("} else {");
104+
assertThat(code).contains("this.resumableUploadStub = null;");
105+
}
106+
107+
@Test
108+
void generate_grpcStub_uploadMediaCallable_guardsNullWithFailedPrecondition() {
109+
String code = generateGrpcStubCode();
110+
111+
// Verify callable getter throwing FailedPreconditionException when resumableUploadStub == null
112+
assertThat(code)
113+
.contains(
114+
"public ResumableUploadCallable<UploadMediaRequest, UploadMediaResponse>"
115+
+ " uploadMediaCallable() {");
116+
assertThat(code).contains("if (resumableUploadStub == null) {");
117+
assertThat(code).contains("throw new FailedPreconditionException(");
118+
assertThat(code)
119+
.contains("\"Resumable uploads execute over HTTP/REST and require credentials.");
120+
assertThat(code).contains("GrpcStatusCode.of(Status.Code.FAILED_PRECONDITION)");
121+
assertThat(code).contains("return resumableUploadStub.uploadMediaCallable();");
122+
}
123+
124+
@Test
125+
void generate_grpcStub_implementsBackgroundResource() {
126+
String code = generateGrpcStubCode();
127+
128+
// Verify BackgroundResource aggregation with fallback
129+
assertThat(code).contains("if (resumableUploadStub != null) {");
130+
assertThat(code).contains("this.backgroundResources =");
131+
assertThat(code).contains("new BackgroundResourceAggregation(");
132+
assertThat(code).contains("ImmutableList.<BackgroundResource>builder()");
133+
assertThat(code).contains(".addAll(clientContext.getBackgroundResources())");
134+
assertThat(code).contains(".add(resumableUploadStub)");
135+
assertThat(code).contains(".build());");
136+
assertThat(code)
137+
.contains(
138+
"this.backgroundResources =\n"
139+
+ " new"
140+
+ " BackgroundResourceAggregation(clientContext.getBackgroundResources());");
141+
142+
// Verify close() delegates only to backgroundResources
143+
assertThat(code).contains("backgroundResources.close();");
144+
assertThat(code).doesNotContain("resumableUploadStub.close();");
145+
}
146+
147+
@Test
148+
void generate_restStub_delegatesToResumableUploadStub() {
149+
String code = generateRestStubCode();
150+
151+
// Verify class declaration and non-null stub field
152+
assertThat(code)
153+
.contains(
154+
"public class HttpJsonResumableUploadServiceStub extends ResumableUploadServiceStub");
155+
assertThat(code)
156+
.contains(
157+
"private final HttpJsonResumableUploadServiceResumableUploadStub resumableUploadStub;");
158+
assertThat(code)
159+
.doesNotContain(
160+
"private final @Nullable HttpJsonResumableUploadServiceResumableUploadStub"
161+
+ " resumableUploadStub;");
162+
163+
// Verify REST constructor directly creates the upload stub
164+
assertThat(code).contains("this.resumableUploadStub =");
165+
assertThat(code)
166+
.contains(
167+
"HttpJsonResumableUploadServiceResumableUploadStub.create(clientContext, settings);");
168+
169+
// Verify callable getter delegates directly
170+
assertThat(code)
171+
.contains(
172+
"public ResumableUploadCallable<UploadMediaRequest, UploadMediaResponse>"
173+
+ " uploadMediaCallable() {");
174+
assertThat(code).contains("return resumableUploadStub.uploadMediaCallable();");
175+
176+
// Verify BackgroundResource aggregation
177+
assertThat(code).contains("ImmutableList.<BackgroundResource>builder()");
178+
assertThat(code).contains(".addAll(clientContext.getBackgroundResources())");
179+
assertThat(code).contains(".add(resumableUploadStub)");
180+
assertThat(code).contains("backgroundResources.close();");
181+
assertThat(code).doesNotContain("resumableUploadStub.close();");
182+
}
183+
184+
@Test
185+
void generateGrpcServiceStubClass_serviceWithoutResumableUpload_noUploadStub() {
186+
GapicContext context = GrpcTestProtoLoader.instance().parseShowcaseEcho();
187+
Service service = context.services().get(0);
188+
189+
GapicClass clazz = GrpcServiceStubClassComposer.instance().generate(context, service);
190+
assertThat(clazz.kind()).isEqualTo(Kind.STUB);
191+
192+
JavaWriterVisitor visitor = new JavaWriterVisitor();
193+
clazz.classDefinition().accept(visitor);
194+
String code = visitor.write();
195+
196+
assertThat(code).doesNotContain("resumableUploadStub");
197+
assertThat(code).doesNotContain("HttpJsonCallContext");
198+
assertThat(code).doesNotContain("FailedPreconditionException");
199+
}
200+
201+
@Test
202+
void generateHttpJsonServiceStubClass_serviceWithoutResumableUpload_noUploadStub() {
203+
GapicContext context = RestTestProtoLoader.instance().parseCompliance();
204+
Service service = context.services().get(0);
205+
206+
GapicClass clazz = HttpJsonServiceStubClassComposer.instance().generate(context, service);
207+
assertThat(clazz.kind()).isEqualTo(Kind.STUB);
208+
209+
JavaWriterVisitor visitor = new JavaWriterVisitor();
210+
clazz.classDefinition().accept(visitor);
211+
String code = visitor.write();
212+
213+
assertThat(code).doesNotContain("resumableUploadStub");
214+
}
215+
}

sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/GrpcServiceStubClassComposerTest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,4 +122,14 @@ void generateGrpcServiceStubClass_clientResourceNameExtractor() {
122122
Assert.assertGoldenClass(this.getClass(), clazz, "GrpcResourceNameExtractorStub.golden");
123123
Assert.assertEmptySamples(clazz.samples());
124124
}
125+
126+
@Test
127+
void generateGrpcServiceStubClass_resumableUpload() {
128+
GapicContext context = GrpcTestProtoLoader.instance().parseShowcaseResumableUpload();
129+
Service service = context.services().get(0);
130+
GapicClass clazz = GrpcServiceStubClassComposer.instance().generate(context, service);
131+
132+
Assert.assertGoldenClass(this.getClass(), clazz, "GrpcResumableUploadServiceStub.golden");
133+
Assert.assertEmptySamples(clazz.samples());
134+
}
125135
}

0 commit comments

Comments
 (0)