Skip to content

Commit f5dc929

Browse files
committed
feat(generator): add model flag and allowlist parser for resumable upload RPCs
1 parent 438cda6 commit f5dc929

5 files changed

Lines changed: 191 additions & 1 deletion

File tree

sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/model/Method.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ public enum Stream {
4444

4545
public abstract boolean isBatching();
4646

47+
public abstract boolean isResumableUpload();
48+
4749
public boolean isPaged() {
4850
return pageSizeFieldName() != null;
4951
}
@@ -137,7 +139,8 @@ public static Builder builder() {
137139
.setIsInternalApi(false)
138140
.setIsBatching(false)
139141
.setIsDeprecated(false)
140-
.setOperationPollingMethod(false);
142+
.setOperationPollingMethod(false)
143+
.setIsResumableUpload(false);
141144
}
142145

143146
public static Stream toStream(boolean isClientStreaming, boolean isServerStreaming) {
@@ -177,6 +180,8 @@ public abstract static class Builder {
177180

178181
public abstract Builder setIsBatching(boolean isBatching);
179182

183+
public abstract Builder setIsResumableUpload(boolean isResumableUpload);
184+
180185
public abstract Builder setPageSizeFieldName(String pagedFieldName);
181186

182187
public abstract Builder setIsDeprecated(boolean isDeprecated);

sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/protoparser/Parser.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
import java.util.function.Function;
8989
import java.util.logging.Level;
9090
import java.util.logging.Logger;
91+
import java.util.regex.Pattern;
9192
import java.util.stream.Collectors;
9293
import java.util.stream.IntStream;
9394
import org.jspecify.annotations.NullMarked;
@@ -134,6 +135,9 @@ enum SelectiveGapicType {
134135
"google.cloud.bigquery.v2.ModelService.ListModels",
135136
"google.cloud.bigquery.v2.TableService.ListTables");
136137

138+
private static final ImmutableList<Pattern> RESUMABLE_UPLOAD_ALLOWLIST_PATTERNS =
139+
ImmutableList.of();
140+
137141
// Allow other parsers to access this.
138142
protected static final SourceCodeInfoParser SOURCE_CODE_INFO_PARSER = new SourceCodeInfoParser();
139143

@@ -817,6 +821,31 @@ static List<Method> parseMethods(
817821
Optional<com.google.api.Service> serviceYamlProtoOpt,
818822
Set<ResourceName> outputArgResourceNames,
819823
Transport transport) {
824+
return parseMethods(
825+
serviceDescriptor,
826+
protoPackage,
827+
servicePackage,
828+
messageTypes,
829+
resourceNames,
830+
serviceConfigOpt,
831+
serviceYamlProtoOpt,
832+
outputArgResourceNames,
833+
transport,
834+
RESUMABLE_UPLOAD_ALLOWLIST_PATTERNS);
835+
}
836+
837+
@VisibleForTesting
838+
static List<Method> parseMethods(
839+
ServiceDescriptor serviceDescriptor,
840+
String protoPackage,
841+
String servicePackage,
842+
Map<String, Message> messageTypes,
843+
Map<String, ResourceName> resourceNames,
844+
Optional<GapicServiceConfig> serviceConfigOpt,
845+
Optional<com.google.api.Service> serviceYamlProtoOpt,
846+
Set<ResourceName> outputArgResourceNames,
847+
Transport transport,
848+
List<Pattern> resumableUploadAllowlistPatterns) {
820849
List<Method> methods = new ArrayList<>();
821850

822851
// Parse the serviceYaml for autopopulated methods and fields once and put into a map
@@ -872,6 +901,9 @@ static List<Method> parseMethods(
872901
.getOptions()
873902
.getExtension(ExtendedOperationsProto.operationPollingMethod)
874903
: false;
904+
boolean isResumableUpload =
905+
resumableUploadAllowlistPatterns.stream()
906+
.anyMatch(pattern -> pattern.matcher(protoMethod.getFullName()).matches());
875907
RoutingHeaderRule routingHeaderRule =
876908
RoutingRuleParser.parse(protoMethod, inputMessage, messageTypes);
877909
methods.add(
@@ -895,6 +927,7 @@ static List<Method> parseMethods(
895927
.setAutoPopulatedFields(autoPopulatedFields)
896928
.setRoutingHeaderRule(routingHeaderRule)
897929
.setIsBatching(isBatching)
930+
.setIsResumableUpload(isResumableUpload)
898931
.setPageSizeFieldName(parsePageSizeFieldName(protoMethod, messageTypes, transport))
899932
.setIsDeprecated(isDeprecated)
900933
.setOperationPollingMethod(operationPollingMethod)

sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/protoparser/ParserTest.java

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
import com.google.protobuf.compiler.PluginProtos.CodeGeneratorRequest;
5656
import com.google.selective.generate.v1beta1.SelectiveApiGenerationOuterClass;
5757
import com.google.showcase.v1beta1.EchoOuterClass;
58+
import com.google.showcase.v1beta1.ResumableUpload;
5859
import com.google.showcase.v1beta1.TestingOuterClass;
5960
import com.google.testgapic.v1beta1.LockerProto;
6061
import java.nio.file.Path;
@@ -66,6 +67,7 @@
6667
import java.util.Map;
6768
import java.util.Optional;
6869
import java.util.Set;
70+
import java.util.regex.Pattern;
6971
import java.util.stream.Collectors;
7072
import org.junit.Assert;
7173
import org.junit.jupiter.api.BeforeEach;
@@ -163,6 +165,7 @@ void parseMethods_basic() {
163165
assertEquals(echoMethod.name(), "Echo");
164166
assertEquals(echoMethod.stream(), Method.Stream.NONE);
165167
assertEquals(false, echoMethod.hasAutoPopulatedFields());
168+
assertFalse(echoMethod.isResumableUpload());
166169

167170
// Detailed method signature parsing tests are in a separate unit test.
168171
List<List<MethodArgument>> methodSignatures = echoMethod.methodSignatures();
@@ -203,6 +206,67 @@ void parseMethods_basic() {
203206
assertEquals(false, chatMethod.hasAutoPopulatedFields());
204207
}
205208

209+
@Test
210+
void parseMethods_resumableUpload() {
211+
FileDescriptor resumableUploadFileDescriptor = ResumableUpload.getDescriptor();
212+
ServiceDescriptor resumableUploadService = resumableUploadFileDescriptor.getServices().get(0);
213+
Map<String, Message> messageTypes = Parser.parseMessages(resumableUploadFileDescriptor);
214+
Map<String, ResourceName> resourceNames =
215+
Parser.parseResourceNames(resumableUploadFileDescriptor);
216+
Set<ResourceName> outputResourceNames = new HashSet<>();
217+
String protoPackage = resumableUploadFileDescriptor.getPackage();
218+
String servicePackage = TypeParser.getPackage(resumableUploadFileDescriptor);
219+
List<Method> methods =
220+
Parser.parseMethods(
221+
resumableUploadService,
222+
protoPackage,
223+
servicePackage,
224+
messageTypes,
225+
resourceNames,
226+
Optional.empty(),
227+
Optional.empty(),
228+
outputResourceNames,
229+
Transport.GRPC);
230+
231+
assertEquals(1, methods.size());
232+
Method uploadMethod = methods.get(0);
233+
assertEquals("UploadMedia", uploadMethod.name());
234+
assertFalse(uploadMethod.isResumableUpload());
235+
}
236+
237+
@Test
238+
void parseMethods_resumableUpload_withConfiguredAllowlist() {
239+
FileDescriptor resumableUploadFileDescriptor = ResumableUpload.getDescriptor();
240+
ServiceDescriptor resumableUploadService = resumableUploadFileDescriptor.getServices().get(0);
241+
Map<String, Message> messageTypes = Parser.parseMessages(resumableUploadFileDescriptor);
242+
Map<String, ResourceName> resourceNames =
243+
Parser.parseResourceNames(resumableUploadFileDescriptor);
244+
Set<ResourceName> outputResourceNames = new HashSet<>();
245+
String protoPackage = resumableUploadFileDescriptor.getPackage();
246+
String servicePackage = TypeParser.getPackage(resumableUploadFileDescriptor);
247+
List<Pattern> allowlist =
248+
Arrays.asList(
249+
Pattern.compile(
250+
"^google\\.showcase\\.v1beta1\\.ResumableUploadService\\.UploadMedia$"));
251+
List<Method> methods =
252+
Parser.parseMethods(
253+
resumableUploadService,
254+
protoPackage,
255+
servicePackage,
256+
messageTypes,
257+
resourceNames,
258+
Optional.empty(),
259+
Optional.empty(),
260+
outputResourceNames,
261+
Transport.GRPC,
262+
allowlist);
263+
264+
assertEquals(1, methods.size());
265+
Method uploadMethod = methods.get(0);
266+
assertEquals("UploadMedia", uploadMethod.name());
267+
assertTrue(uploadMethod.isResumableUpload());
268+
}
269+
206270
@Test
207271
void parseMethods_basicLro() {
208272
Map<String, Message> messageTypes = Parser.parseMessages(echoFileDescriptor);

sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/test/protoloader/TestProtoLoader.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import com.google.showcase.v1beta1.EchoOuterClass;
4646
import com.google.showcase.v1beta1.IdentityOuterClass;
4747
import com.google.showcase.v1beta1.MessagingOuterClass;
48+
import com.google.showcase.v1beta1.ResumableUpload;
4849
import com.google.showcase.v1beta1.TestingOuterClass;
4950
import com.google.test.callablenamingtype.CallableNameType;
5051
import com.google.testdata.v1.DeprecatedServiceOuterClass;
@@ -278,6 +279,46 @@ public GapicContext parseShowcaseTesting() {
278279
.build();
279280
}
280281

282+
public GapicContext parseShowcaseResumableUpload() {
283+
FileDescriptor fileDescriptor = ResumableUpload.getDescriptor();
284+
ServiceDescriptor serviceDescriptor = fileDescriptor.getServices().get(0);
285+
assertEquals("ResumableUploadService", serviceDescriptor.getName());
286+
287+
Map<String, Message> messageTypes = Parser.parseMessages(fileDescriptor);
288+
Map<String, ResourceName> resourceNames = Parser.parseResourceNames(fileDescriptor);
289+
Set<ResourceName> outputResourceNames = new HashSet<>();
290+
List<Service> services =
291+
Parser.parseService(
292+
fileDescriptor, messageTypes, resourceNames, Optional.empty(), outputResourceNames);
293+
294+
return GapicContext.builder()
295+
.setMessages(messageTypes)
296+
.setResourceNames(resourceNames)
297+
.setServices(adaptShowcaseResumableUploadForTest(services))
298+
.setHelperResourceNames(outputResourceNames)
299+
.setTransport(transport)
300+
.setServiceConfig(GapicServiceConfig.create(Optional.empty()))
301+
.build();
302+
}
303+
304+
// Temporary test scaffolding; removed in PR #14325 once allowlist patterns are activated.
305+
private static List<Service> adaptShowcaseResumableUploadForTest(List<Service> services) {
306+
return services.stream()
307+
.map(
308+
s ->
309+
s.toBuilder()
310+
.setMethods(
311+
s.methods().stream()
312+
.map(
313+
m ->
314+
m.name().equals("UploadMedia")
315+
? m.toBuilder().setIsResumableUpload(true).build()
316+
: m)
317+
.collect(Collectors.toList()))
318+
.build())
319+
.collect(Collectors.toList());
320+
}
321+
281322
public GapicContext parseExplicitDynamicRoutingHeaderTesting() {
282323
FileDescriptor testingFileDescriptor =
283324
ExplicitDynamicRoutingHeaderTestingOuterClass.getDescriptor();
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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+
// https://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+
syntax = "proto3";
16+
17+
package google.showcase.v1beta1;
18+
19+
import "google/api/annotations.proto";
20+
import "google/api/client.proto";
21+
22+
option go_package = "github.com/googleapis/gapic-showcase/server/genproto";
23+
option java_package = "com.google.showcase.v1beta1";
24+
option java_multiple_files = true;
25+
option ruby_package = "Google::Showcase::V1beta1";
26+
27+
// A service showcasing universal resumable upload protocol support.
28+
service ResumableUploadService {
29+
option (google.api.default_host) = "localhost:7469";
30+
31+
// A method with media_upload annotation enabled.
32+
rpc UploadMedia(UploadMediaRequest) returns (UploadMediaResponse) {
33+
option (google.api.http) = {
34+
post: "/v1beta1/files:upload"
35+
body: "*"
36+
};
37+
}
38+
}
39+
40+
message UploadMediaRequest {
41+
string name = 1;
42+
}
43+
44+
message UploadMediaResponse {
45+
string name = 1;
46+
int64 size = 2;
47+
}

0 commit comments

Comments
 (0)