diff --git a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/comment/ServiceClientCommentComposer.java b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/comment/ServiceClientCommentComposer.java index 54e3086371f8..23f7fb4dab98 100644 --- a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/comment/ServiceClientCommentComposer.java +++ b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/comment/ServiceClientCommentComposer.java @@ -33,6 +33,7 @@ import java.util.Optional; import java.util.stream.Collectors; import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; @NullMarked public class ServiceClientCommentComposer { @@ -40,6 +41,11 @@ public class ServiceClientCommentComposer { private static final String EMPTY_STRING = ""; private static final String API_EXCEPTION_TYPE_NAME = "com.google.api.gax.rpc.ApiException"; private static final String EXCEPTION_CONDITION = "if the remote call fails"; + private static final String REQUEST_PARAM_NAME = "request"; + private static final String REQUEST_PARAM_DESCRIPTION = + "The request object containing all of the parameters for the API call."; + private static final String PAYLOAD_PARAM_NAME = "payload"; + private static final String PAYLOAD_PARAM_DESCRIPTION = "The payload data stream to upload."; // Constants. private static final String SERVICE_DESCRIPTION_INTRO_STRING = @@ -93,6 +99,11 @@ public class ServiceClientCommentComposer { + " that it is easy to make a subclass, but otherwise, the static factory methods" + " should be preferred."; + private static final String RESUMABLE_UPLOAD_CALL_CONTEXT_WARNING = + "Call context overrides (such as withTimeout, withRetrySettings, or credentials) apply" + + " strictly to the start request (session initiation). Per-chunk PUT calls rely on" + + " the configured timeout and retry settings from ResumableUploadCallSettings."; + // Comments. public static final CommentStatement GET_OPERATIONS_CLIENT_METHOD_COMMENT = toSimpleComment( @@ -105,9 +116,9 @@ public static List createClassHeaderComments( String classMethodSampleCode, String credentialsSampleCode, String endpointSampleCode, - String transportSampleCode, - String primaryTransport, - String secondaryTransport) { + @Nullable String transportSampleCode, + @Nullable String primaryTransport, + @Nullable String secondaryTransport) { JavaDocComment.Builder classHeaderJavadocBuilder = JavaDocComment.builder(); if (service.hasDescription()) { String descriptionComment = @@ -187,14 +198,17 @@ public static List createRpcMethodHeaderComment( methodJavadocBuilder = methodJavadocBuilder.addUnescapedComment(descriptionComment); } - if (sampleCodeOpt.isPresent()) { + if (method.isResumableUpload()) { + methodJavadocBuilder.addParagraph(RESUMABLE_UPLOAD_CALL_CONTEXT_WARNING); + } + + if (sampleCodeOpt.isPresent() && !method.isResumableUpload()) { methodJavadocBuilder.addParagraph(METHOD_DESCRIPTION_SAMPLE_CODE_SUMMARY_STRING); methodJavadocBuilder.addSampleCode(sampleCodeOpt.get()); } if (methodArguments.isEmpty()) { - methodJavadocBuilder.addParam( - "request", "The request object containing all of the parameters for the API call."); + methodJavadocBuilder.addParam(REQUEST_PARAM_NAME, REQUEST_PARAM_DESCRIPTION); } else { for (MethodArgument argument : methodArguments) { // TODO(miraleung): Remove the newline replacement when we support CommonMark. @@ -204,6 +218,10 @@ public static List createRpcMethodHeaderComment( } } + if (method.isResumableUpload()) { + methodJavadocBuilder.addParam(PAYLOAD_PARAM_NAME, PAYLOAD_PARAM_DESCRIPTION); + } + methodJavadocBuilder.setThrows(API_EXCEPTION_TYPE_NAME, EXCEPTION_CONDITION); if (method.isDeprecated()) { @@ -348,8 +366,12 @@ public static List createRpcCallableMethodHeaderComment( methodJavadocBuilder = methodJavadocBuilder.addUnescapedComment(descriptionComment); } - methodJavadocBuilder.addParagraph(METHOD_DESCRIPTION_SAMPLE_CODE_SUMMARY_STRING); - if (sampleCodeOpt.isPresent()) { + if (method.isResumableUpload()) { + methodJavadocBuilder.addParagraph(RESUMABLE_UPLOAD_CALL_CONTEXT_WARNING); + } + + if (sampleCodeOpt.isPresent() && !method.isResumableUpload()) { + methodJavadocBuilder.addParagraph(METHOD_DESCRIPTION_SAMPLE_CODE_SUMMARY_STRING); methodJavadocBuilder.addSampleCode(sampleCodeOpt.get()); } diff --git a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/comment/SettingsCommentComposer.java b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/comment/SettingsCommentComposer.java index fee47355b40e..ea379a222302 100644 --- a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/comment/SettingsCommentComposer.java +++ b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/comment/SettingsCommentComposer.java @@ -130,6 +130,16 @@ public static CommentStatement createCallSettingsGetterComment( isMethodInternal); } + private static final String RESUMABLE_UPLOAD_CALL_SETTINGS_DOC_NOTE = + "Note that custom retry settings and headers configured via ApiCallContext" + + " apply strictly to the initial session initiation request."; + + public static CommentStatement createResumableUploadCallSettingsGetterComment( + String javaMethodName, boolean isMethodDeprecated, boolean isMethodInternal) { + return createResumableUploadCallSettingsComment( + CALL_SETTINGS_METHOD_DOC_PATTERN, javaMethodName, isMethodDeprecated, isMethodInternal); + } + public static CommentStatement createBuilderClassComment(String outerClassName) { return toCommentStatement(String.format(BUILDER_CLASS_DOC_PATTERN, outerClassName)); } @@ -140,6 +150,30 @@ public static CommentStatement createCallSettingsBuilderGetterComment( return toCommentStatement(methodComment, isMethodDeprecated, isMethodInternal); } + public static CommentStatement createResumableUploadCallSettingsBuilderGetterComment( + String javaMethodName, boolean isMethodDeprecated, boolean isMethodInternal) { + return createResumableUploadCallSettingsComment( + CALL_SETTINGS_BUILDER_METHOD_DOC_PATTERN, + javaMethodName, + isMethodDeprecated, + isMethodInternal); + } + + private static CommentStatement createResumableUploadCallSettingsComment( + String pattern, String javaMethodName, boolean isMethodDeprecated, boolean isMethodInternal) { + JavaDocComment.Builder docBuilder = + JavaDocComment.builder() + .addComment(String.format(pattern, javaMethodName)) + .addParagraph(RESUMABLE_UPLOAD_CALL_SETTINGS_DOC_NOTE); + if (isMethodDeprecated) { + docBuilder.setDeprecated(CommentComposer.DEPRECATED_METHOD_STRING); + } + if (isMethodInternal) { + docBuilder.setInternalOnly(CommentComposer.INTERNAL_ONLY_METHOD_STRING); + } + return CommentStatement.withComment(docBuilder.build()); + } + public static List createClassHeaderComments( String configuredClassName, String defaultHost, diff --git a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractServiceClientClassComposer.java b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractServiceClientClassComposer.java index ec32a4649596..9eac76557867 100644 --- a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractServiceClientClassComposer.java +++ b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractServiceClientClassComposer.java @@ -24,10 +24,13 @@ import com.google.api.gax.paging.AbstractFixedSizeCollection; import com.google.api.gax.paging.AbstractPage; import com.google.api.gax.paging.AbstractPagedListResponse; +import com.google.api.gax.rpc.ApiExceptions; import com.google.api.gax.rpc.BidiStreamingCallable; import com.google.api.gax.rpc.ClientStreamingCallable; import com.google.api.gax.rpc.OperationCallable; import com.google.api.gax.rpc.PageContext; +import com.google.api.gax.rpc.ResumableUploadCallSettings; +import com.google.api.gax.rpc.ResumableUploadCallable; import com.google.api.gax.rpc.ServerStreamingCallable; import com.google.api.gax.rpc.UnaryCallable; import com.google.api.generator.engine.ast.AnnotationNode; @@ -88,6 +91,7 @@ import com.google.longrunning.Operation; import com.google.rpc.Status; import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -109,6 +113,7 @@ public abstract class AbstractServiceClientClassComposer implements ClassCompose private static final String CALLABLE_NAME_PATTERN = "%sCallable"; private static final String PAGED_CALLABLE_NAME_PATTERN = "%sPagedCallable"; private static final String OPERATION_CALLABLE_NAME_PATTERN = "%sOperationCallable"; + private static final String REQUEST_VAR_NAME = "request"; private static final Reference LIST_REFERENCE = ConcreteReference.withClazz(List.class); private static final Reference MAP_REFERENCE = ConcreteReference.withClazz(Map.class); @@ -650,42 +655,46 @@ private static List createServiceMethods( methodVariantsForClientHeader.put(method.name(), new ArrayList<>()); } if (method.stream().equals(Stream.NONE)) { - List generatedMethods = - createMethodVariants( - method, - ClassNames.getServiceClientClassName(service), - messageTypes, - typeStore, - resourceNames, - samples, - service); - - // Collect data for gapic_metadata.json. - grpcRpcToJavaMethodMetadata - .get(method.name()) - .addAll( - generatedMethods.stream() - .map(m -> javaMethodNameFn.apply(m)) - .collect(Collectors.toList())); - - // Collect data for Client header - methodVariantsForClientHeader - .get(method.name()) - .addAll( - generatedMethods.stream() - .map(AbstractServiceClientClassComposer::getJavaMethod) - .collect(Collectors.toList())); - javaMethods.addAll(generatedMethods); + if (!method.isResumableUpload()) { + List generatedMethods = + createMethodVariants( + method, + ClassNames.getServiceClientClassName(service), + messageTypes, + typeStore, + resourceNames, + samples, + service); + + // Collect data for gapic_metadata.json. + grpcRpcToJavaMethodMetadata + .get(method.name()) + .addAll( + generatedMethods.stream() + .map(m -> javaMethodNameFn.apply(m)) + .collect(Collectors.toList())); + + // Collect data for Client header + methodVariantsForClientHeader + .get(method.name()) + .addAll( + generatedMethods.stream() + .map(AbstractServiceClientClassComposer::getJavaMethod) + .collect(Collectors.toList())); + javaMethods.addAll(generatedMethods); + } MethodDefinition generatedMethod = - createMethodDefaultMethod( - method, - ClassNames.getServiceClientClassName(service), - messageTypes, - typeStore, - resourceNames, - samples, - service); + method.isResumableUpload() + ? createResumableUploadDefaultMethod(method, typeStore) + : createMethodDefaultMethod( + method, + ClassNames.getServiceClientClassName(service), + messageTypes, + typeStore, + resourceNames, + samples, + service); // Collect data for gapic_metadata.json and client header. grpcRpcToJavaMethodMetadata.get(method.name()).add(javaMethodNameFn.apply(generatedMethod)); @@ -778,7 +787,8 @@ private static List createMethodVariants( // Request proto builder. VariableExpr requestVarExpr = VariableExpr.builder() - .setVariable(Variable.builder().setName("request").setType(methodInputType).build()) + .setVariable( + Variable.builder().setName(REQUEST_VAR_NAME).setType(methodInputType).build()) .setIsDecl(true) .build(); @@ -873,7 +883,8 @@ private static MethodDefinition createMethodDefaultMethod( // Construct the method that accepts a request proto. VariableExpr requestArgVarExpr = VariableExpr.builder() - .setVariable(Variable.builder().setName("request").setType(methodInputType).build()) + .setVariable( + Variable.builder().setName(REQUEST_VAR_NAME).setType(methodInputType).build()) .setIsDecl(true) .build(); String callableMethodName = @@ -885,9 +896,8 @@ private static MethodDefinition createMethodDefaultMethod( } Optional defaultMethodSample = - Optional.of( - ServiceClientMethodSampleComposer.composeCanonicalSample( - method, typeStore.get(clientName), resourceNames, messageTypes, service)); + ServiceClientMethodSampleComposer.composeCanonicalSample( + method, typeStore.get(clientName), resourceNames, messageTypes, service); Optional defaultMethodDocSample = Optional.empty(); if (defaultMethodSample.isPresent()) { samples.add(defaultMethodSample.get()); @@ -914,14 +924,86 @@ private static MethodDefinition createMethodDefaultMethod( .setName(String.format(method.hasLro() ? "%sAsync" : "%s", methodName)) .setArguments(Arrays.asList(requestArgVarExpr)); + if (method.hasLro()) { + methodBuilder = + methodBuilder.setReturnExpr(callableMethodExpr).setReturnType(methodOutputType); + } else { + if (isProtoEmptyType(methodOutputType)) { + methodBuilder = + methodBuilder + .setBody(Arrays.asList(ExprStatement.withExpr(callableMethodExpr))) + .setReturnType(TypeNode.VOID); + } else { + methodBuilder = + methodBuilder.setReturnExpr(callableMethodExpr).setReturnType(methodOutputType); + } + } + + methodBuilder.setAnnotations(createMethodAnnotations(method, typeStore)); + return methodBuilder.build(); + } + + private static MethodDefinition createResumableUploadDefaultMethod( + Method method, TypeStore typeStore) { + String methodName = JavaStyle.toLowerCamelCase(method.name()); + TypeNode methodInputType = method.inputType(); + TypeNode methodOutputType = method.outputType(); + + VariableExpr requestArgVarExpr = + VariableExpr.builder() + .setVariable( + Variable.builder().setName(REQUEST_VAR_NAME).setType(methodInputType).build()) + .setIsDecl(true) + .build(); + VariableExpr payloadArgVarExpr = + VariableExpr.builder() + .setVariable( + Variable.builder().setName("payload").setType(typeStore.get("InputStream")).build()) + .setIsDecl(true) + .build(); + + String callableMethodName = String.format(CALLABLE_NAME_PATTERN, methodName); + MethodInvocationExpr callableMethodExpr = + MethodInvocationExpr.builder().setMethodName(callableMethodName).build(); + MethodInvocationExpr futureCallExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(callableMethodExpr) + .setMethodName("futureCall") + .setArguments( + Arrays.asList( + requestArgVarExpr.toBuilder().setIsDecl(false).build(), + payloadArgVarExpr.toBuilder().setIsDecl(false).build(), + CastExpr.builder() + .setType(typeStore.get("ResumableUploadCallSettings")) + .setExpr(ValueExpr.createNullExpr()) + .build())) + .build(); + + MethodInvocationExpr callAndTranslateExpr = + MethodInvocationExpr.builder() + .setStaticReferenceType(typeStore.get("ApiExceptions")) + .setMethodName("callAndTranslateApiException") + .setArguments(Arrays.asList(futureCallExpr)) + .setReturnType(methodOutputType) + .build(); + + MethodDefinition.Builder methodBuilder = + MethodDefinition.builder() + .setHeaderCommentStatements( + ServiceClientCommentComposer.createRpcMethodHeaderComment(method, Optional.empty())) + .setScope(ScopeNode.PUBLIC) + .setIsFinal(true) + .setName(methodName) + .setArguments(Arrays.asList(requestArgVarExpr, payloadArgVarExpr)); + if (isProtoEmptyType(methodOutputType)) { methodBuilder = methodBuilder - .setBody(Arrays.asList(ExprStatement.withExpr(callableMethodExpr))) + .setBody(Arrays.asList(ExprStatement.withExpr(callAndTranslateExpr))) .setReturnType(TypeNode.VOID); } else { methodBuilder = - methodBuilder.setReturnExpr(callableMethodExpr).setReturnType(methodOutputType); + methodBuilder.setReturnExpr(callAndTranslateExpr).setReturnType(methodOutputType); } methodBuilder.setAnnotations(createMethodAnnotations(method, typeStore)); @@ -992,7 +1074,9 @@ private static MethodDefinition createCallableMethod( case NONE: // Fall through. default: - rawCallableReturnType = typeStore.get("UnaryCallable"); + rawCallableReturnType = + typeStore.get( + method.isResumableUpload() ? "ResumableUploadCallable" : "UnaryCallable"); } } @@ -1038,13 +1122,12 @@ private static MethodDefinition createCallableMethod( } else if (callableMethodKind.equals(CallableMethodKind.REGULAR)) { if (method.stream().equals(Stream.NONE)) { sampleCode = - Optional.of( - ServiceClientCallableMethodSampleComposer.composeRegularCallableMethod( - method, - typeStore.get(ClassNames.getServiceClientClassName(service)), - resourceNames, - messageTypes, - service)); + ServiceClientCallableMethodSampleComposer.composeRegularCallableMethod( + method, + typeStore.get(ClassNames.getServiceClientClassName(service)), + resourceNames, + messageTypes, + service); } else { sampleCode = Optional.of( @@ -1797,6 +1880,7 @@ private static TypeStore createTypes(Service service, Map messa List> concreteClazzes = Arrays.asList( AbstractPagedListResponse.class, + ApiExceptions.class, ApiFunction.class, ApiFuture.class, ApiFutures.class, @@ -1806,6 +1890,7 @@ private static TypeStore createTypes(Service service, Map messa BidiStreamingCallable.class, ClientStreamingCallable.class, Generated.class, + InputStream.class, InterruptedException.class, IOException.class, MoreExecutors.class, @@ -1814,6 +1899,8 @@ private static TypeStore createTypes(Service service, Map messa Operation.class, OperationFuture.class, OperationCallable.class, + ResumableUploadCallSettings.class, + ResumableUploadCallable.class, ServerStreamingCallable.class, Status.class, Strings.class, diff --git a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractServiceClientTestClassComposer.java b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractServiceClientTestClassComposer.java index b6666e1707c8..56471be71b1b 100644 --- a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractServiceClientTestClassComposer.java +++ b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractServiceClientTestClassComposer.java @@ -81,6 +81,7 @@ import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; +import org.junit.Ignore; import org.junit.Test; @NullMarked @@ -237,6 +238,10 @@ private List createTestMethods( javaMethods.add(createUnsupportedTestMethod(method)); continue; } + if (method.isResumableUpload()) { + javaMethods.add(createResumableUploadTestMethod(method)); + continue; + } Service matchingService = service; if (method.isMixin()) { int dotIndex = method.mixedInApiName().lastIndexOf("."); @@ -786,6 +791,35 @@ protected MethodDefinition createUnsupportedTestMethod(Method method) { .build(); } + protected MethodDefinition createResumableUploadTestMethod(Method method) { + String javaMethodName = JavaStyle.toLowerCamelCase(method.name()); + String testMethodName = String.format("%sTest", javaMethodName); + + List methodBody = + Collections.singletonList( + CommentStatement.withComment( + LineComment.withComment( + "The " + + javaMethodName + + "() method requires a live HTTP/REST server supporting the resumable" + + " upload protocol and is tested in integration tests."))); + + return MethodDefinition.builder() + .setAnnotations( + Arrays.asList( + TEST_ANNOTATION, + AnnotationNode.withTypeAndDescription( + FIXED_TYPESTORE.get("Ignore"), + "Requires live HTTP/REST server supporting resumable upload protocol; tested in" + + " integration tests."))) + .setScope(ScopeNode.PUBLIC) + .setReturnType(TypeNode.VOID) + .setName(testMethodName) + .setThrowsExceptions(Arrays.asList(TypeNode.withExceptionClazz(Exception.class))) + .setBody(methodBody) + .build(); + } + protected List createRpcExceptionTestStatements( Method method, List methodSignature, @@ -927,6 +961,7 @@ private static TypeStore createStaticTypes() { ServerStreamingCallable.class, StatusCode.class, Test.class, + Ignore.class, UUID.class); return new TypeStore(concreteClazzes); } diff --git a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractServiceSettingsClassComposer.java b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractServiceSettingsClassComposer.java index e2d10d6c454c..c9930932d55e 100644 --- a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractServiceSettingsClassComposer.java +++ b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractServiceSettingsClassComposer.java @@ -25,6 +25,7 @@ import com.google.api.gax.rpc.ClientSettings; import com.google.api.gax.rpc.OperationCallSettings; import com.google.api.gax.rpc.PagedCallSettings; +import com.google.api.gax.rpc.ResumableUploadCallSettings; import com.google.api.gax.rpc.ServerStreamingCallSettings; import com.google.api.gax.rpc.StreamingCallSettings; import com.google.api.gax.rpc.StubSettings; @@ -153,7 +154,7 @@ private static List createClassHeaderComments( // list. List publicMethods = service.methods().stream() - .filter(m -> m.isInternalApi() == false) + .filter(m -> !m.isInternalApi() && !m.isResumableUpload()) .collect(Collectors.toList()); Optional methodOpt = publicMethods.isEmpty() @@ -311,12 +312,18 @@ private static List createSettingsGetterMethods( // Add method header comment statements and annotations. private static MethodDefinition methodBuilderHelper( Method protoMethod, MethodDefinition.Builder methodBuilder, String javaMethodName) { - return methodBuilder - .setHeaderCommentStatements( - SettingsCommentComposer.createCallSettingsGetterComment( + CommentStatement commentStatement = + protoMethod.isResumableUpload() + ? SettingsCommentComposer.createResumableUploadCallSettingsGetterComment( + getMethodNameFromSettingsVarName(javaMethodName), + protoMethod.isDeprecated(), + protoMethod.isInternalApi()) + : SettingsCommentComposer.createCallSettingsGetterComment( getMethodNameFromSettingsVarName(javaMethodName), protoMethod.isDeprecated(), - protoMethod.isInternalApi())) + protoMethod.isInternalApi()); + return methodBuilder + .setHeaderCommentStatements(commentStatement) .setAnnotations(createMethodAnnotations(protoMethod)) .build(); } @@ -794,13 +801,19 @@ private static List createNestedBuilderSettingsGetterMethods( String javaMethodName = String.format("%sSettings", javaStyleName); MethodDefinition.Builder methodBuilder = methodMakerFn.apply(getCallSettingsBuilderType(protoMethod, typeStore), javaMethodName); + CommentStatement commentStatement = + protoMethod.isResumableUpload() + ? SettingsCommentComposer.createResumableUploadCallSettingsBuilderGetterComment( + getMethodNameFromSettingsVarName(javaMethodName), + protoMethod.isDeprecated(), + protoMethod.isInternalApi()) + : SettingsCommentComposer.createCallSettingsBuilderGetterComment( + getMethodNameFromSettingsVarName(javaMethodName), + protoMethod.isDeprecated(), + protoMethod.isInternalApi()); javaMethods.add( methodBuilder - .setHeaderCommentStatements( - SettingsCommentComposer.createCallSettingsBuilderGetterComment( - getMethodNameFromSettingsVarName(javaMethodName), - protoMethod.isDeprecated(), - protoMethod.isInternalApi())) + .setHeaderCommentStatements(commentStatement) .setAnnotations(createMethodAnnotations(protoMethod)) .build()); @@ -856,6 +869,7 @@ private static TypeStore createStaticTypes() { Operation.class, OperationCallSettings.class, PagedCallSettings.class, + ResumableUploadCallSettings.class, ServerStreamingCallSettings.class, StreamingCallSettings.class, StubSettings.class, @@ -933,7 +947,13 @@ private static TypeNode getCallSettingsTypeHelper( Method protoMethod, TypeStore typeStore, boolean isBuilder) { Class callSettingsClazz = isBuilder ? UnaryCallSettings.Builder.class : UnaryCallSettings.class; - if (protoMethod.isPaged()) { + if (protoMethod.isResumableUpload()) { + return TypeNode.withReference( + ConcreteReference.withClazz( + isBuilder + ? ResumableUploadCallSettings.Builder.class + : ResumableUploadCallSettings.class)); + } else if (protoMethod.isPaged()) { callSettingsClazz = isBuilder ? PagedCallSettings.Builder.class : PagedCallSettings.class; } else if (protoMethod.isBatching()) { callSettingsClazz = diff --git a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractServiceStubClassComposer.java b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractServiceStubClassComposer.java index 51e6f410c079..5d6da88b5f1b 100644 --- a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractServiceStubClassComposer.java +++ b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractServiceStubClassComposer.java @@ -21,6 +21,7 @@ import com.google.api.gax.rpc.ClientStreamingCallable; import com.google.api.gax.rpc.LongRunningClient; import com.google.api.gax.rpc.OperationCallable; +import com.google.api.gax.rpc.ResumableUploadCallable; import com.google.api.gax.rpc.ServerStreamingCallable; import com.google.api.gax.rpc.UnaryCallable; import com.google.api.generator.engine.ast.AnnotationNode; @@ -52,6 +53,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; import javax.annotation.Generated; import org.jspecify.annotations.NullMarked; @@ -162,22 +164,7 @@ private MethodDefinition createCallableGetter(Method method, TypeStore typeStore private MethodDefinition createCallableGetterHelper( Method method, TypeStore typeStore, boolean isLroCallable, boolean isPaged) { - TypeNode returnType; - switch (method.stream()) { - case CLIENT: - returnType = typeStore.get("ClientStreamingCallable"); - break; - case SERVER: - returnType = typeStore.get("ServerStreamingCallable"); - break; - case BIDI: - returnType = typeStore.get("BidiStreamingCallable"); - break; - case NONE: - // Fall through. - default: - returnType = typeStore.get(isLroCallable ? "OperationCallable" : "UnaryCallable"); - } + TypeNode returnType = getCallableType(method, typeStore, isLroCallable); String methodName = String.format( @@ -270,6 +257,7 @@ private static TypeStore createTypes(Service service, Map messa Generated.class, Operation.class, OperationCallable.class, + ResumableUploadCallable.class, ServerStreamingCallable.class, UnaryCallable.class, UnsupportedOperationException.class, @@ -328,4 +316,22 @@ protected MethodDefinition createOperationsStubGetterMethodDefinition( .build()))) .build(); } + + private static TypeNode getCallableType( + Method method, TypeStore typeStore, boolean isLroCallable) { + if (method.isResumableUpload()) { + return typeStore.get("ResumableUploadCallable"); + } + switch (method.stream()) { + case CLIENT: + return typeStore.get("ClientStreamingCallable"); + case SERVER: + return typeStore.get("ServerStreamingCallable"); + case BIDI: + return typeStore.get("BidiStreamingCallable"); + case NONE: + default: + return typeStore.get(isLroCallable ? "OperationCallable" : "UnaryCallable"); + } + } } diff --git a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractServiceStubSettingsClassComposer.java b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractServiceStubSettingsClassComposer.java index d713a5e1b891..10c13902ab35 100644 --- a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractServiceStubSettingsClassComposer.java +++ b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractServiceStubSettingsClassComposer.java @@ -43,6 +43,7 @@ import com.google.api.gax.rpc.PagedCallSettings; import com.google.api.gax.rpc.PagedListDescriptor; import com.google.api.gax.rpc.PagedListResponseFactory; +import com.google.api.gax.rpc.ResumableUploadCallSettings; import com.google.api.gax.rpc.ServerStreamingCallSettings; import com.google.api.gax.rpc.StatusCode; import com.google.api.gax.rpc.StreamingCallSettings; @@ -91,6 +92,7 @@ import com.google.api.generator.gapic.model.GapicBatchingSettings; import com.google.api.generator.gapic.model.GapicClass; import com.google.api.generator.gapic.model.GapicContext; +import com.google.api.generator.gapic.model.GapicRetrySettings; import com.google.api.generator.gapic.model.GapicServiceConfig; import com.google.api.generator.gapic.model.Message; import com.google.api.generator.gapic.model.Method; @@ -106,6 +108,7 @@ import com.google.common.collect.Lists; import com.google.longrunning.Operation; import com.google.protobuf.Empty; +import com.google.protobuf.util.Durations; import java.io.IOException; import java.time.Duration; import java.util.ArrayList; @@ -132,6 +135,7 @@ public abstract class AbstractServiceStubSettingsClassComposer implements ClassC private static final String BATCHING_DESC_PATTERN = "%s_BATCHING_DESC"; private static final String PAGE_STR_DESC_PATTERN = "%s_PAGE_STR_DESC"; private static final String PAGED_RESPONSE_FACTORY_PATTERN = "%s_PAGE_STR_FACT"; + private static final String SETTINGS_METHOD_NAME_PATTERN = "%sSettings"; private static final String NESTED_BUILDER_CLASS_NAME = "Builder"; private static final String NESTED_UNARY_METHOD_SETTINGS_BUILDERS_VAR_NAME = "unaryMethodSettingsBuilders"; @@ -188,6 +192,7 @@ public GapicClass generate(GapicContext context, Service service) { List samples = new ArrayList<>(); Set deprecatedSettingVarNames = new HashSet<>(); Set internalSettingVarNames = new HashSet<>(); + Set resumableUploadSettingVarNames = new HashSet<>(); Map methodSettingsMemberVarExprs = createMethodSettingsClassMemberVarExprs( service, @@ -195,7 +200,8 @@ public GapicClass generate(GapicContext context, Service service) { typeStore, /* isNestedClass= */ false, deprecatedSettingVarNames, - internalSettingVarNames); + internalSettingVarNames, + resumableUploadSettingVarNames); String className = ClassNames.getServiceStubSettingsClassName(service); List classHeaderComments = createClassHeaderComments(service, typeStore.get(className), samples); @@ -217,6 +223,7 @@ public GapicClass generate(GapicContext context, Service service) { methodSettingsMemberVarExprs, deprecatedSettingVarNames, internalSettingVarNames, + resumableUploadSettingVarNames, typeStore)) .setNestedClasses( Arrays.asList(createNestedBuilderClass(service, serviceConfig, typeStore))) @@ -444,7 +451,7 @@ private static List createClassHeaderComments( // list. List publicMethods = service.methods().stream() - .filter(m -> m.isInternalApi() == false) + .filter(m -> !m.isInternalApi() && !m.isResumableUpload()) .collect(Collectors.toList()); Optional methodOpt = publicMethods.isEmpty() @@ -510,7 +517,8 @@ private static Map createMethodSettingsClassMemberVarExprs TypeStore typeStore, boolean isNestedClass, Set deprecatedSettingVarNames, - Set internalSettingVarNames) { + Set internalSettingVarNames, + Set resumableUploadSettingVarNames) { Map varExprs = new LinkedHashMap<>(); // Creates class variables Settings, e.g. echoSettings. @@ -519,13 +527,17 @@ private static Map createMethodSettingsClassMemberVarExprs !Objects.isNull(serviceConfig) && serviceConfig.hasBatchingSetting(service, method); TypeNode settingsType = getCallSettingsType(method, typeStore, hasBatchingSettings, isNestedClass); - String varName = String.format("%sSettings", JavaStyle.toLowerCamelCase(method.name())); + String varName = + String.format(SETTINGS_METHOD_NAME_PATTERN, JavaStyle.toLowerCamelCase(method.name())); if (method.isDeprecated()) { deprecatedSettingVarNames.add(varName); } if (method.isInternalApi()) { internalSettingVarNames.add(varName); } + if (method.isResumableUpload()) { + resumableUploadSettingVarNames.add(varName); + } varExprs.put( varName, VariableExpr.withVariable( @@ -1050,11 +1062,15 @@ private List createClassMethods( Map methodSettingsMemberVarExprs, Set deprecatedSettingVarNames, Set internalSettingVarNames, + Set resumableUploadSettingVarNames, TypeStore typeStore) { List javaMethods = new ArrayList<>(); javaMethods.addAll( createMethodSettingsGetterMethods( - methodSettingsMemberVarExprs, deprecatedSettingVarNames, internalSettingVarNames)); + methodSettingsMemberVarExprs, + deprecatedSettingVarNames, + internalSettingVarNames, + resumableUploadSettingVarNames)); javaMethods.add(createCreateStubMethod(service, typeStore)); javaMethods.addAll(createDefaultHelperAndGetterMethods(service, typeStore)); javaMethods.addAll( @@ -1087,15 +1103,21 @@ private static List createMethodAnnotation( private static List createMethodSettingsGetterMethods( Map methodSettingsMemberVarExprs, final Set deprecatedSettingVarNames, - final Set internalSettingVarNames) { + final Set internalSettingVarNames, + final Set resumableUploadSettingVarNames) { Function, MethodDefinition> varToMethodFn = e -> { boolean isDeprecated = deprecatedSettingVarNames.contains(e.getKey()); boolean isInternal = internalSettingVarNames.contains(e.getKey()); + boolean isResumableUpload = resumableUploadSettingVarNames.contains(e.getKey()); + CommentStatement commentStatement = + isResumableUpload + ? SettingsCommentComposer.createResumableUploadCallSettingsGetterComment( + getMethodNameFromSettingsVarName(e.getKey()), isDeprecated, isInternal) + : SettingsCommentComposer.createCallSettingsGetterComment( + getMethodNameFromSettingsVarName(e.getKey()), isDeprecated, isInternal); return MethodDefinition.builder() - .setHeaderCommentStatements( - SettingsCommentComposer.createCallSettingsGetterComment( - getMethodNameFromSettingsVarName(e.getKey()), isDeprecated, isInternal)) + .setHeaderCommentStatements(commentStatement) .setAnnotations(createMethodAnnotation(isDeprecated, isInternal)) .setScope(ScopeNode.PUBLIC) .setReturnType(e.getValue().type()) @@ -1441,6 +1463,7 @@ private ClassDefinition createNestedBuilderClass( Set nestedDeprecatedSettingVarNames = new HashSet<>(); Set nestedInternalSettingVarNames = new HashSet<>(); + Set nestedResumableUploadSettingVarNames = new HashSet<>(); Map nestedMethodSettingsMemberVarExprs = createMethodSettingsClassMemberVarExprs( service, @@ -1448,7 +1471,8 @@ private ClassDefinition createNestedBuilderClass( typeStore, /* isNestedClass= */ true, nestedDeprecatedSettingVarNames, - nestedInternalSettingVarNames); + nestedInternalSettingVarNames, + nestedResumableUploadSettingVarNames); // TODO(miraleung): Fill this out. return ClassDefinition.builder() @@ -1470,6 +1494,7 @@ private ClassDefinition createNestedBuilderClass( nestedMethodSettingsMemberVarExprs, nestedDeprecatedSettingVarNames, nestedInternalSettingVarNames, + nestedResumableUploadSettingVarNames, typeStore)) .build(); } @@ -1531,6 +1556,7 @@ private List createNestedClassMethods( Map nestedMethodSettingsMemberVarExprs, Set nestedDeprecatedSettingVarNames, Set nestedInternalSettingVarNames, + Set nestedResumableUploadSettingVarNames, TypeStore typeStore) { List nestedClassMethods = new ArrayList<>(); nestedClassMethods.addAll( @@ -1544,7 +1570,8 @@ private List createNestedClassMethods( createNestedClassSettingsBuilderGetterMethods( nestedMethodSettingsMemberVarExprs, nestedDeprecatedSettingVarNames, - nestedInternalSettingVarNames)); + nestedInternalSettingVarNames, + nestedResumableUploadSettingVarNames)); nestedClassMethods.add(createNestedClassBuildMethod(service, typeStore)); return nestedClassMethods; } @@ -1573,7 +1600,7 @@ private MethodDefinition createNestedClassInitDefaultsMethod( "No batching setting found for service %s, method %s", service.name(), method.name())); String settingsGetterMethodName = - String.format("%sSettings", JavaStyle.toLowerCamelCase(method.name())); + String.format(SETTINGS_METHOD_NAME_PATTERN, JavaStyle.toLowerCamelCase(method.name())); bodyStatements.add( ExprStatement.withExpr( RetrySettingsComposer.createBatchingBuilderSettingsExpr( @@ -1581,6 +1608,12 @@ private MethodDefinition createNestedClassInitDefaultsMethod( bodyStatements.add(EMPTY_LINE_STATEMENT); } + if (method.isResumableUpload()) { + addResumableUploadGlobalTimeoutStatements( + service, serviceConfig, method, builderVarExpr, bodyStatements); + continue; + } + bodyStatements.add( ExprStatement.withExpr( RetrySettingsComposer.createSimpleBuilderSettingsExpr( @@ -1621,6 +1654,54 @@ private MethodDefinition createNestedClassInitDefaultsMethod( .build(); } + private static void addResumableUploadGlobalTimeoutStatements( + Service service, + @Nullable GapicServiceConfig serviceConfig, + Method method, + VariableExpr builderVarExpr, + List bodyStatements) { + if (serviceConfig == null) { + return; + } + String retryParamsName = serviceConfig.getRetryParamsName(service, method); + GapicRetrySettings retrySettings = + retryParamsName != null + ? serviceConfig.getAllGapicRetrySettings(service).get(retryParamsName) + : null; + if (retrySettings != null + && retrySettings.timeout() != null + && Durations.toMillis(retrySettings.timeout()) > 0) { + String settingsGetterMethodName = + String.format(SETTINGS_METHOD_NAME_PATTERN, JavaStyle.toLowerCamelCase(method.name())); + Expr settingsExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(builderVarExpr) + .setMethodName(settingsGetterMethodName) + .build(); + long timeoutMillis = Durations.toMillis(retrySettings.timeout()); + ValueExpr timeoutMillisExpr = + ValueExpr.withValue( + PrimitiveValue.builder() + .setType(TypeNode.LONG) + .setValue(String.format("%dL", timeoutMillis)) + .build()); + Expr durationOfMillisExpr = + MethodInvocationExpr.builder() + .setStaticReferenceType(FIXED_TYPESTORE.get("Duration")) + .setMethodName("ofMillis") + .setArguments(timeoutMillisExpr) + .build(); + settingsExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(settingsExpr) + .setMethodName("setGlobalTimeout") + .setArguments(durationOfMillisExpr) + .build(); + bodyStatements.add(ExprStatement.withExpr(settingsExpr)); + bodyStatements.add(EMPTY_LINE_STATEMENT); + } + } + private static List createNestedClassConstructorMethods( Service service, GapicServiceConfig serviceConfig, @@ -2068,7 +2149,8 @@ private static MethodDefinition createNestedClassUnaryMethodSettingsBuilderGette private static List createNestedClassSettingsBuilderGetterMethods( Map nestedMethodSettingsMemberVarExprs, Set nestedDeprecatedSettingVarNames, - Set nestedInternalSettingVarNames) { + Set nestedInternalSettingVarNames, + Set nestedResumableUploadSettingVarNames) { Reference operationCallSettingsBuilderRef = ConcreteReference.withClazz(OperationCallSettings.Builder.class); Function isOperationCallSettingsBuilderFn = @@ -2085,12 +2167,18 @@ private static List createNestedClassSettingsBuilderGetterMeth boolean isDeprecated = nestedDeprecatedSettingVarNames.contains(varName); boolean isInternal = nestedInternalSettingVarNames.contains(varName); + boolean isResumableUpload = nestedResumableUploadSettingVarNames.contains(varName); + + CommentStatement commentStatement = + isResumableUpload + ? SettingsCommentComposer.createResumableUploadCallSettingsBuilderGetterComment( + getMethodNameFromSettingsVarName(varName), isDeprecated, isInternal) + : SettingsCommentComposer.createCallSettingsBuilderGetterComment( + getMethodNameFromSettingsVarName(varName), isDeprecated, isInternal); javaMethods.add( MethodDefinition.builder() - .setHeaderCommentStatements( - SettingsCommentComposer.createCallSettingsBuilderGetterComment( - getMethodNameFromSettingsVarName(varName), isDeprecated, isInternal)) + .setHeaderCommentStatements(commentStatement) .setAnnotations(createMethodAnnotation(isDeprecated, isInternal)) .setScope(ScopeNode.PUBLIC) .setReturnType(settingsVarExpr.type()) @@ -2222,6 +2310,7 @@ private static TypeStore createStaticTypes() { PagedListResponseFactory.class, PartitionKey.class, RequestBuilder.class, + ResumableUploadCallSettings.class, RetrySettings.class, ServerStreamingCallSettings.class, StatusCode.class, @@ -2341,6 +2430,13 @@ private static String getPagedResponseTypeName(String methodName) { CommonStrings.PAGED_RESPONSE_TYPE_NAME_PATTERN, JavaStyle.toUpperCamelCase(methodName)); } + private static TypeNode getResumableUploadCallSettingsType( + Function, TypeNode> typeMakerFn, boolean isSettingsBuilder) { + return isSettingsBuilder + ? typeMakerFn.apply(ResumableUploadCallSettings.Builder.class) + : typeMakerFn.apply(ResumableUploadCallSettings.class); + } + private static TypeNode getCallSettingsType( Method method, TypeStore typeStore, @@ -2348,6 +2444,9 @@ private static TypeNode getCallSettingsType( final boolean isSettingsBuilder) { Function, TypeNode> typeMakerFn = clz -> TypeNode.withReference(ConcreteReference.withClazz(clz)); + if (method.isResumableUpload()) { + return getResumableUploadCallSettingsType(typeMakerFn, isSettingsBuilder); + } // Default: No streaming. TypeNode callSettingsType = method.isPaged() diff --git a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/rest/HttpJsonDescriptorComposer.java b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/rest/HttpJsonDescriptorComposer.java new file mode 100644 index 000000000000..71e38aa5829b --- /dev/null +++ b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/rest/HttpJsonDescriptorComposer.java @@ -0,0 +1,675 @@ +// Copyright 2026 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.composer.rest; + +import com.google.api.core.InternalApi; +import com.google.api.gax.httpjson.ApiMethodDescriptor; +import com.google.api.gax.httpjson.ApiMethodDescriptor.MethodType; +import com.google.api.gax.httpjson.FieldsExtractor; +import com.google.api.gax.httpjson.ProtoMessageRequestFormatter; +import com.google.api.gax.httpjson.ProtoMessageResponseParser; +import com.google.api.gax.httpjson.ProtoRestSerializer; +import com.google.api.generator.engine.ast.AnnotationNode; +import com.google.api.generator.engine.ast.AssignmentExpr; +import com.google.api.generator.engine.ast.ConcreteReference; +import com.google.api.generator.engine.ast.EnumRefExpr; +import com.google.api.generator.engine.ast.Expr; +import com.google.api.generator.engine.ast.ExprStatement; +import com.google.api.generator.engine.ast.IfStatement; +import com.google.api.generator.engine.ast.LambdaExpr; +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.PrimitiveValue; +import com.google.api.generator.engine.ast.ScopeNode; +import com.google.api.generator.engine.ast.Statement; +import com.google.api.generator.engine.ast.StringObjectValue; +import com.google.api.generator.engine.ast.TypeNode; +import com.google.api.generator.engine.ast.ValueExpr; +import com.google.api.generator.engine.ast.Variable; +import com.google.api.generator.engine.ast.VariableExpr; +import com.google.api.generator.gapic.composer.store.TypeStore; +import com.google.api.generator.gapic.model.HttpBindings.HttpBinding; +import com.google.api.generator.gapic.model.Method; +import com.google.api.generator.gapic.model.Service; +import com.google.api.generator.gapic.utils.JavaStyle; +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; +import com.google.protobuf.TypeRegistry; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +final class HttpJsonDescriptorComposer { + + private static final String BUILD_METHOD_NAME = "build"; + private static final String NEW_BUILDER_METHOD_NAME = "newBuilder"; + private static final String SLASH = "/"; + + private static final TypeStore FIXED_REST_TYPESTORE = createStaticTypes(); + + private static final VariableExpr TYPE_REGISTRY_VAR_EXPR = + VariableExpr.builder() + .setVariable( + Variable.builder() + .setName("typeRegistry") + .setType(FIXED_REST_TYPESTORE.get(TypeRegistry.class.getSimpleName())) + .build()) + .build(); + + private HttpJsonDescriptorComposer() {} + + private static TypeStore createStaticTypes() { + return new TypeStore( + Arrays.asList( + ApiMethodDescriptor.class, + ArrayList.class, + FieldsExtractor.class, + HashMap.class, + InternalApi.class, + List.class, + ProtoMessageRequestFormatter.class, + ProtoMessageResponseParser.class, + ProtoRestSerializer.class, + TypeRegistry.class)); + } + + static BiFunction, Function> + getMethodMaker() { + return (mName, argExpr) -> + m -> + MethodInvocationExpr.builder() + .setMethodName(mName) + .setArguments(argExpr) + .setExprReferenceExpr(m) + .build(); + } + + static String prependPathPrefix(String path, @Nullable String pathPrefix) { + if (pathPrefix == null) { + return path; + } + String normalizedPrefix = pathPrefix.startsWith(SLASH) ? pathPrefix : SLASH + pathPrefix; + if (normalizedPrefix.endsWith(SLASH)) { + normalizedPrefix = normalizedPrefix.substring(0, normalizedPrefix.length() - 1); + } + return normalizedPrefix + (path.startsWith(SLASH) ? path : SLASH + path); + } + + static String getProtoRpcFullMethodName(Service protoService, Method protoMethod) { + if (protoMethod.isMixin()) { + return String.format("%s/%s", protoMethod.mixedInApiName(), protoMethod.name()); + } + return String.format( + "%s.%s/%s", protoService.protoPakkage(), protoService.name(), protoMethod.name()); + } + + static List getHttpMethodTypeExpr(Method protoMethod) { + return Collections.singletonList( + ValueExpr.withValue( + StringObjectValue.withValue(protoMethod.httpBindings().httpVerb().toString()))); + } + + static List getMethodTypeExpr(Method protoMethod) { + MethodType methodType; + switch (protoMethod.stream()) { + case NONE: + methodType = MethodType.UNARY; + break; + case SERVER: + methodType = MethodType.SERVER_STREAMING; + break; + case CLIENT: + case BIDI: + default: + throw new UnsupportedOperationException( + String.format( + "Methods of type %s are not supported by REST transport", protoMethod.stream())); + } + EnumRefExpr expr = + EnumRefExpr.builder() + .setName(methodType.toString()) + .setType( + TypeNode.withReference( + ConcreteReference.builder() + .setClazz(ApiMethodDescriptor.MethodType.class) + .build())) + .build(); + return Collections.singletonList(expr); + } + + static List getRequestFormatterExpr(Method protoMethod, boolean restNumericEnumsEnabled) { + return getRequestFormatterExpr(protoMethod, restNumericEnumsEnabled, null); + } + + static List getRequestFormatterExpr( + Method protoMethod, boolean restNumericEnumsEnabled, @Nullable String pathPrefix) { + BiFunction, Function> + methodMaker = getMethodMaker(); + + MethodInvocationExpr expr = + MethodInvocationExpr.builder() + .setStaticReferenceType( + FIXED_REST_TYPESTORE.get(ProtoMessageRequestFormatter.class.getSimpleName())) + .setMethodName(NEW_BUILDER_METHOD_NAME) + .setGenerics(Collections.singletonList(protoMethod.inputType().reference())) + .build(); + + TypeNode extractorVarType = + TypeNode.withReference( + ConcreteReference.builder() + .setClazz(Map.class) + .setGenerics(TypeNode.STRING.reference(), TypeNode.STRING.reference()) + .build()); + + String pathPattern = + prependPathPrefix(protoMethod.httpBindings().lowerCamelPattern(), pathPrefix); + + expr = + methodMaker + .apply( + "setPath", + Arrays.asList( + ValueExpr.withValue(StringObjectValue.withValue(pathPattern)), + createFieldsExtractorClassInstance( + protoMethod, + extractorVarType, + protoMethod.httpBindings().pathParameters(), + "putPathParam", + restNumericEnumsEnabled))) + .apply(expr); + + if (!protoMethod.httpBindings().lowerCamelAdditionalPatterns().isEmpty()) { + expr = + methodMaker + .apply( + "setAdditionalPaths", + protoMethod.httpBindings().lowerCamelAdditionalPatterns().stream() + .map( + a -> + ValueExpr.withValue( + StringObjectValue.withValue(prependPathPrefix(a, pathPrefix)))) + .collect(Collectors.toList())) + .apply(expr); + } + TypeNode fieldsVarGenericType = + TypeNode.withReference( + ConcreteReference.builder() + .setClazz(List.class) + .setGenerics(TypeNode.STRING.reference()) + .build()); + + extractorVarType = + TypeNode.withReference( + ConcreteReference.builder() + .setClazz(Map.class) + .setGenerics(TypeNode.STRING.reference(), fieldsVarGenericType.reference()) + .build()); + + expr = + methodMaker + .apply( + "setQueryParamsExtractor", + Arrays.asList( + createFieldsExtractorClassInstance( + protoMethod, + extractorVarType, + protoMethod.httpBindings().queryParameters(), + "putQueryParam", + restNumericEnumsEnabled))) + .apply(expr); + + extractorVarType = TypeNode.STRING; + boolean asteriskBody = protoMethod.httpBindings().isAsteriskBody(); + expr = + methodMaker + .apply( + "setRequestBodyExtractor", + Arrays.asList( + createBodyFieldsExtractorClassInstance( + protoMethod, + extractorVarType, + asteriskBody + ? protoMethod.httpBindings().pathParameters() + : protoMethod.httpBindings().bodyParameters(), + "toBody", + asteriskBody, + restNumericEnumsEnabled))) + .apply(expr); + + expr = methodMaker.apply(BUILD_METHOD_NAME, Collections.emptyList()).apply(expr); + + return Collections.singletonList(expr); + } + + static List setResponseParserExpr(Method protoMethod) { + BiFunction, Function> + methodMaker = getMethodMaker(); + + MethodInvocationExpr expr = + MethodInvocationExpr.builder() + .setStaticReferenceType( + FIXED_REST_TYPESTORE.get(ProtoMessageResponseParser.class.getSimpleName())) + .setMethodName(NEW_BUILDER_METHOD_NAME) + .setGenerics(Collections.singletonList(protoMethod.outputType().reference())) + .build(); + + expr = + methodMaker + .apply( + "setDefaultInstance", + Arrays.asList( + MethodInvocationExpr.builder() + .setStaticReferenceType(protoMethod.outputType()) + .setMethodName("getDefaultInstance") + .setReturnType(protoMethod.outputType()) + .build())) + .apply(expr); + + expr = + methodMaker + .apply("setDefaultTypeRegistry", Arrays.asList(TYPE_REGISTRY_VAR_EXPR)) + .apply(expr); + expr = methodMaker.apply(BUILD_METHOD_NAME, Collections.emptyList()).apply(expr); + + return Collections.singletonList(expr); + } + + static MethodInvocationExpr createMethodDescriptorExpr( + Service service, + Method protoMethod, + VariableExpr methodDescriptorVarExpr, + boolean restNumericEnumsEnabled, + @Nullable String pathPrefix) { + MethodInvocationExpr expr = + MethodInvocationExpr.builder() + .setMethodName(NEW_BUILDER_METHOD_NAME) + .setStaticReferenceType( + FIXED_REST_TYPESTORE.get(ApiMethodDescriptor.class.getSimpleName())) + .setGenerics(methodDescriptorVarExpr.variable().type().reference().generics()) + .build(); + + BiFunction, Function> + methodMaker = getMethodMaker(); + + String codeMethodArgName = getProtoRpcFullMethodName(service, protoMethod); + expr = + methodMaker + .apply( + "setFullMethodName", + Arrays.asList(ValueExpr.withValue(StringObjectValue.withValue(codeMethodArgName)))) + .apply(expr); + + expr = methodMaker.apply("setHttpMethod", getHttpMethodTypeExpr(protoMethod)).apply(expr); + expr = methodMaker.apply("setType", getMethodTypeExpr(protoMethod)).apply(expr); + expr = + methodMaker + .apply( + "setRequestFormatter", + getRequestFormatterExpr(protoMethod, restNumericEnumsEnabled, pathPrefix)) + .apply(expr); + expr = methodMaker.apply("setResponseParser", setResponseParserExpr(protoMethod)).apply(expr); + + return expr; + } + + static Statement createMethodDescriptorVariableDecl( + Service service, + Method protoMethod, + VariableExpr methodDescriptorVarExpr, + boolean restNumericEnumsEnabled, + @Nullable String pathPrefix) { + MethodInvocationExpr expr = + createMethodDescriptorExpr( + service, protoMethod, methodDescriptorVarExpr, restNumericEnumsEnabled, pathPrefix); + + expr = + MethodInvocationExpr.builder() + .setMethodName(BUILD_METHOD_NAME) + .setExprReferenceExpr(expr) + .setReturnType(methodDescriptorVarExpr.type()) + .build(); + + return ExprStatement.withExpr( + AssignmentExpr.builder() + .setVariableExpr( + methodDescriptorVarExpr.toBuilder() + .setIsDecl(true) + .setScope(ScopeNode.PRIVATE) + .setIsStatic(true) + .setIsFinal(true) + .build()) + .setValueExpr(expr) + .build()); + } + + static MethodDefinition createGetMethodDescriptorsMethod( + Collection methodDescriptorVarExprs) { + List bodyExprs = new ArrayList<>(); + + VariableExpr methodDescriptorsVarExpr = + VariableExpr.withVariable( + Variable.builder() + .setType( + TypeNode.withReference( + ConcreteReference.builder() + .setClazz(List.class) + .setGenerics( + Arrays.asList( + FIXED_REST_TYPESTORE.get("ApiMethodDescriptor").reference())) + .build())) + .setName("methodDescriptors") + .build()); + + bodyExprs.add( + AssignmentExpr.builder() + .setVariableExpr(methodDescriptorsVarExpr.toBuilder().setIsDecl(true).build()) + .setValueExpr( + NewObjectExpr.builder() + .setType(FIXED_REST_TYPESTORE.get("ArrayList")) + .setIsGeneric(true) + .build()) + .build()); + + for (VariableExpr methodDescriptorVarExpr : methodDescriptorVarExprs) { + bodyExprs.add( + MethodInvocationExpr.builder() + .setExprReferenceExpr(methodDescriptorsVarExpr) + .setMethodName("add") + .setArguments(methodDescriptorVarExpr) + .build()); + } + + return MethodDefinition.builder() + .setScope(ScopeNode.PUBLIC) + .setIsStatic(true) + .setReturnType(methodDescriptorsVarExpr.type()) + .setReturnExpr(methodDescriptorsVarExpr) + .setAnnotations( + Arrays.asList( + AnnotationNode.withType( + TypeNode.withReference(ConcreteReference.withClazz(InternalApi.class))))) + .setName("getMethodDescriptors") + .setBody(bodyExprs.stream().map(ExprStatement::withExpr).collect(Collectors.toList())) + .build(); + } + + private static Expr createBodyFieldsExtractorClassInstance( + Method method, + TypeNode extractorReturnType, + Set httpBindingFieldNames, + String serializerMethodName, + boolean asteriskBody, + boolean restNumericEnumEnabled) { + List bodyStatements = new ArrayList<>(); + + Expr returnExpr = null; + Expr serializerExpr = + MethodInvocationExpr.builder() + .setMethodName("create") + .setStaticReferenceType( + FIXED_REST_TYPESTORE.get(ProtoRestSerializer.class.getSimpleName())) + .build(); + + VariableExpr requestVarExpr = + VariableExpr.withVariable( + Variable.builder().setType(method.inputType()).setName("request").build()); + Expr bodyRequestExpr = requestVarExpr; + String requestMethodPrefix = "get"; + String bodyParamName = null; + + if (asteriskBody) { + bodyRequestExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(requestVarExpr) + .setMethodName("toBuilder") + .build(); + requestMethodPrefix = "clear"; + } + + Expr prevExpr = bodyRequestExpr; + for (HttpBinding httpBindingFieldName : httpBindingFieldNames) { + MethodInvocationExpr.Builder requestFieldMethodExprBuilder = + MethodInvocationExpr.builder().setExprReferenceExpr(prevExpr); + bodyParamName = + !Strings.isNullOrEmpty(httpBindingFieldName.jsonName()) + ? httpBindingFieldName.jsonName() + : JavaStyle.toLowerCamelCase(httpBindingFieldName.name()); + String[] descendantFields = httpBindingFieldName.name().split("\\."); + if (asteriskBody && descendantFields.length > 1) { + continue; + } + + for (int i = 0; i < descendantFields.length; i++) { + String currFieldName = descendantFields[i]; + String bindingFieldMethodName = + String.format("%s%s", requestMethodPrefix, JavaStyle.toUpperCamelCase(currFieldName)); + requestFieldMethodExprBuilder = + requestFieldMethodExprBuilder.setMethodName(bindingFieldMethodName); + + if (i < descendantFields.length - 1) { + requestFieldMethodExprBuilder = + MethodInvocationExpr.builder() + .setExprReferenceExpr(requestFieldMethodExprBuilder.build()); + } + } + prevExpr = requestFieldMethodExprBuilder.build(); + } + + if (httpBindingFieldNames.isEmpty() && !asteriskBody) { + returnExpr = ValueExpr.createNullExpr(); + } else { + ImmutableList.Builder paramsPutArgs = ImmutableList.builder(); + if (asteriskBody) { + prevExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(prevExpr) + .setMethodName(BUILD_METHOD_NAME) + .build(); + bodyParamName = "*"; + } + paramsPutArgs.add(ValueExpr.withValue(StringObjectValue.withValue(bodyParamName))); + paramsPutArgs.add(prevExpr); + + PrimitiveValue primitiveValue = + PrimitiveValue.builder() + .setType(TypeNode.BOOLEAN) + .setValue(String.valueOf(restNumericEnumEnabled)) + .build(); + paramsPutArgs.add(ValueExpr.withValue(primitiveValue)); + + returnExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(serializerExpr) + .setMethodName(serializerMethodName) + .setArguments(paramsPutArgs.build()) + .setReturnType(extractorReturnType) + .build(); + } + + return LambdaExpr.builder() + .setArguments(requestVarExpr.toBuilder().setIsDecl(true).build()) + .setBody(bodyStatements) + .setReturnExpr(returnExpr) + .build(); + } + + private static Expr createFieldsExtractorClassInstance( + Method method, + TypeNode extractorReturnType, + Set httpBindingFieldNames, + String serializerMethodName, + boolean restNumericEnumsEnabled) { + List bodyStatements = new ArrayList<>(); + + VariableExpr fieldsVarExpr = + VariableExpr.withVariable( + Variable.builder().setName("fields").setType(extractorReturnType).build()); + Expr fieldsAssignExpr = + AssignmentExpr.builder() + .setVariableExpr(fieldsVarExpr.toBuilder().setIsDecl(true).build()) + .setValueExpr( + NewObjectExpr.builder() + .setType(FIXED_REST_TYPESTORE.get(HashMap.class.getSimpleName())) + .setIsGeneric(true) + .build()) + .build(); + + bodyStatements.add(ExprStatement.withExpr(fieldsAssignExpr)); + + TypeNode serializerVarType = + TypeNode.withReference( + ConcreteReference.builder() + .setClazz(ProtoRestSerializer.class) + .setGenerics(method.inputType().reference()) + .build()); + + VariableExpr serializerVarExpr = + VariableExpr.withVariable( + Variable.builder().setName("serializer").setType(serializerVarType).build()); + + Expr serializerAssignExpr = + AssignmentExpr.builder() + .setVariableExpr(serializerVarExpr.toBuilder().setIsDecl(true).build()) + .setValueExpr( + MethodInvocationExpr.builder() + .setStaticReferenceType( + FIXED_REST_TYPESTORE.get(ProtoRestSerializer.class.getSimpleName())) + .setMethodName("create") + .setReturnType(serializerVarType) + .build()) + .build(); + + bodyStatements.add(ExprStatement.withExpr(serializerAssignExpr)); + + VariableExpr requestVarExpr = + VariableExpr.withVariable( + Variable.builder().setType(method.inputType()).setName("request").build()); + + for (HttpBinding httpBindingFieldName : httpBindingFieldNames) { + MethodInvocationExpr.Builder requestFieldGetterExprBuilder = + MethodInvocationExpr.builder().setExprReferenceExpr(requestVarExpr); + MethodInvocationExpr.Builder requestFieldHasExprBuilder = + MethodInvocationExpr.builder().setExprReferenceExpr(requestVarExpr); + String[] descendantFields = httpBindingFieldName.name().split("\\."); + for (int i = 0; i < descendantFields.length; i++) { + String currFieldName = descendantFields[i]; + String bindingFieldMethodName = + getBindingFieldMethodName( + httpBindingFieldName, + descendantFields.length, + i, + JavaStyle.toUpperCamelCase(currFieldName)); + requestFieldGetterExprBuilder = + requestFieldGetterExprBuilder.setMethodName(bindingFieldMethodName); + + String bindingFieldHasMethodName = + (i < descendantFields.length - 1) + ? bindingFieldMethodName + : String.format("has%s", JavaStyle.toUpperCamelCase(currFieldName)); + requestFieldHasExprBuilder = + requestFieldHasExprBuilder + .setMethodName(bindingFieldHasMethodName) + .setReturnType(TypeNode.BOOLEAN); + + if (i < descendantFields.length - 1) { + requestFieldGetterExprBuilder = + MethodInvocationExpr.builder() + .setExprReferenceExpr(requestFieldGetterExprBuilder.build()); + requestFieldHasExprBuilder = + MethodInvocationExpr.builder() + .setExprReferenceExpr(requestFieldHasExprBuilder.build()); + } + } + + MethodInvocationExpr requestBuilderExpr = requestFieldGetterExprBuilder.build(); + MethodInvocationExpr requestHasExpr = requestFieldHasExprBuilder.build(); + + ImmutableList.Builder paramsPutArgs = ImmutableList.builder(); + paramsPutArgs.add(fieldsVarExpr); + paramsPutArgs.add( + ValueExpr.withValue( + StringObjectValue.withValue( + (httpBindingFieldName.jsonName() != null) + ? httpBindingFieldName.jsonName() + : JavaStyle.toLowerCamelCase(httpBindingFieldName.name())))); + paramsPutArgs.add(requestBuilderExpr); + + Expr paramsPutExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(serializerVarExpr) + .setMethodName(serializerMethodName) + .setArguments(paramsPutArgs.build()) + .setReturnType(extractorReturnType) + .build(); + + if (httpBindingFieldName.isOptional()) { + bodyStatements.add( + IfStatement.builder() + .setConditionExpr(requestHasExpr) + .setBody(Arrays.asList(ExprStatement.withExpr(paramsPutExpr))) + .build()); + } else { + bodyStatements.add(ExprStatement.withExpr(paramsPutExpr)); + } + } + + if (restNumericEnumsEnabled && serializerMethodName.equals("putQueryParam")) { + ImmutableList.Builder paramsPutArgs = ImmutableList.builder(); + paramsPutArgs.add(fieldsVarExpr); + paramsPutArgs.add(ValueExpr.withValue(StringObjectValue.withValue("$alt"))); + paramsPutArgs.add(ValueExpr.withValue(StringObjectValue.withValue("json;enum-encoding=int"))); + + Expr paramsPutExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(serializerVarExpr) + .setMethodName(serializerMethodName) + .setArguments(paramsPutArgs.build()) + .setReturnType(extractorReturnType) + .build(); + bodyStatements.add(ExprStatement.withExpr(paramsPutExpr)); + } + + return LambdaExpr.builder() + .setArguments(requestVarExpr.toBuilder().setIsDecl(true).build()) + .setBody(bodyStatements) + .setReturnExpr(fieldsVarExpr) + .build(); + } + + static String getBindingFieldMethodName( + HttpBinding httpBindingField, int descendantFieldsLengths, int index, String currFieldName) { + if (index == descendantFieldsLengths - 1) { + if (httpBindingField.isRepeated()) { + return String.format("get%sList", currFieldName); + } + if (httpBindingField.isEnum()) { + return String.format("get%sValue", currFieldName); + } + } + return String.format("get%s", currFieldName); + } +} diff --git a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/rest/HttpJsonServiceStubClassComposer.java b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/rest/HttpJsonServiceStubClassComposer.java index f3ca00f9db0f..685f3784bc8d 100644 --- a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/rest/HttpJsonServiceStubClassComposer.java +++ b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/rest/HttpJsonServiceStubClassComposer.java @@ -17,7 +17,6 @@ import com.google.api.HttpRule; import com.google.api.core.InternalApi; import com.google.api.gax.httpjson.ApiMethodDescriptor; -import com.google.api.gax.httpjson.ApiMethodDescriptor.MethodType; import com.google.api.gax.httpjson.FieldsExtractor; import com.google.api.gax.httpjson.HttpJsonCallSettings; import com.google.api.gax.httpjson.HttpJsonLongRunningClient; @@ -30,13 +29,10 @@ import com.google.api.gax.longrunning.OperationSnapshot; import com.google.api.gax.rpc.LongRunningClient; import com.google.api.gax.rpc.UnaryCallable; -import com.google.api.generator.engine.ast.AnnotationNode; import com.google.api.generator.engine.ast.AssignmentExpr; import com.google.api.generator.engine.ast.ConcreteReference; -import com.google.api.generator.engine.ast.EnumRefExpr; import com.google.api.generator.engine.ast.Expr; import com.google.api.generator.engine.ast.ExprStatement; -import com.google.api.generator.engine.ast.IfStatement; import com.google.api.generator.engine.ast.LambdaExpr; import com.google.api.generator.engine.ast.MethodDefinition; import com.google.api.generator.engine.ast.MethodInvocationExpr; @@ -55,16 +51,12 @@ import com.google.api.generator.gapic.composer.common.AbstractTransportServiceStubClassComposer; import com.google.api.generator.gapic.composer.store.TypeStore; import com.google.api.generator.gapic.model.GapicContext; -import com.google.api.generator.gapic.model.HttpBindings.HttpBinding; import com.google.api.generator.gapic.model.Message; import com.google.api.generator.gapic.model.Method; import com.google.api.generator.gapic.model.OperationResponse; import com.google.api.generator.gapic.model.Service; import com.google.api.generator.gapic.utils.JavaStyle; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Strings; import com.google.common.collect.BiMap; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.protobuf.TypeRegistry; import java.util.ArrayList; @@ -85,8 +77,8 @@ public class HttpJsonServiceStubClassComposer extends AbstractTransportServiceSt private static final HttpJsonServiceStubClassComposer INSTANCE = new HttpJsonServiceStubClassComposer(); - private static final TypeStore FIXED_REST_TYPESTORE = createStaticTypes(); - private static final VariableExpr TYPE_REGISTRY_VAR_EXPR = + static final TypeStore FIXED_REST_TYPESTORE = createStaticTypes(); + static final VariableExpr TYPE_REGISTRY_VAR_EXPR = VariableExpr.builder() .setVariable( Variable.builder() @@ -137,33 +129,11 @@ protected Statement createMethodDescriptorVariableDecl( Map messageTypes, boolean restNumericEnumsEnabled) { MethodInvocationExpr expr = - MethodInvocationExpr.builder() - .setMethodName("newBuilder") - .setStaticReferenceType( - FIXED_REST_TYPESTORE.get(ApiMethodDescriptor.class.getSimpleName())) - .setGenerics(methodDescriptorVarExpr.variable().type().reference().generics()) - .build(); + HttpJsonDescriptorComposer.createMethodDescriptorExpr( + service, protoMethod, methodDescriptorVarExpr, restNumericEnumsEnabled, null); BiFunction, Function> - methodMaker = getMethodMaker(); - - String codeMethodArgName = getProtoRpcFullMethodName(service, protoMethod); - expr = - methodMaker - .apply( - "setFullMethodName", - Arrays.asList(ValueExpr.withValue(StringObjectValue.withValue(codeMethodArgName)))) - .apply(expr); - - expr = methodMaker.apply("setHttpMethod", getHttpMethodTypeExpr(protoMethod)).apply(expr); - expr = methodMaker.apply("setType", getMethodTypeExpr(protoMethod)).apply(expr); - expr = - methodMaker - .apply( - "setRequestFormatter", - getRequestFormatterExpr(protoMethod, restNumericEnumsEnabled)) - .apply(expr); - expr = methodMaker.apply("setResponseParser", setResponseParserExpr(protoMethod)).apply(expr); + methodMaker = HttpJsonDescriptorComposer.getMethodMaker(); if (protoMethod.isOperationPollingMethod() || protoMethod.hasLro()) { expr = @@ -217,192 +187,14 @@ protected List createGetMethodDescriptorsMethod( Service service, TypeStore typeStore, Map protoMethodNameToDescriptorVarExprs) { - - List bodyExprs = new ArrayList<>(); - - VariableExpr methodDescriptorsVarExpr = - VariableExpr.withVariable( - Variable.builder() - .setType( - TypeNode.withReference( - ConcreteReference.builder() - .setClazz(List.class) - .setGenerics( - Arrays.asList( - FIXED_REST_TYPESTORE.get("ApiMethodDescriptor").reference())) - .build())) - .setName("methodDescriptors") - .build()); - - bodyExprs.add( - AssignmentExpr.builder() - .setVariableExpr(methodDescriptorsVarExpr.toBuilder().setIsDecl(true).build()) - .setValueExpr( - NewObjectExpr.builder() - .setType(FIXED_REST_TYPESTORE.get("ArrayList")) - .setIsGeneric(true) - .build()) - .build()); - - for (VariableExpr methodDescriptorVarExpr : protoMethodNameToDescriptorVarExprs.values()) { - bodyExprs.add( - MethodInvocationExpr.builder() - .setExprReferenceExpr(methodDescriptorsVarExpr) - .setMethodName("add") - .setArguments(methodDescriptorVarExpr) - .build()); - } - - return Arrays.asList( - MethodDefinition.builder() - .setScope(ScopeNode.PUBLIC) - .setIsStatic(true) - .setReturnType(methodDescriptorsVarExpr.type()) - .setReturnExpr(methodDescriptorsVarExpr) - .setAnnotations( - Arrays.asList(AnnotationNode.withType(FIXED_REST_TYPESTORE.get("InternalApi")))) - .setName("getMethodDescriptors") - .setBody(bodyExprs.stream().map(ExprStatement::withExpr).collect(Collectors.toList())) - .build()); + return Collections.singletonList( + HttpJsonDescriptorComposer.createGetMethodDescriptorsMethod( + protoMethodNameToDescriptorVarExprs.values())); } - private BiFunction, Function> + static BiFunction, Function> getMethodMaker() { - return (mName, argExpr) -> - (m) -> - MethodInvocationExpr.builder() - .setMethodName(mName) - .setArguments(argExpr) - .setExprReferenceExpr(m) - .build(); - } - - private List getRequestFormatterExpr(Method protoMethod, boolean restNumericEnumsEnabled) { - BiFunction, Function> - methodMaker = getMethodMaker(); - - MethodInvocationExpr expr = - MethodInvocationExpr.builder() - .setStaticReferenceType( - FIXED_REST_TYPESTORE.get(ProtoMessageRequestFormatter.class.getSimpleName())) - .setMethodName("newBuilder") - .setGenerics(Collections.singletonList(protoMethod.inputType().reference())) - .build(); - - TypeNode extractorVarType = - TypeNode.withReference( - ConcreteReference.builder() - .setClazz(Map.class) - .setGenerics(TypeNode.STRING.reference(), TypeNode.STRING.reference()) - .build()); - - expr = - methodMaker - .apply( - "setPath", - Arrays.asList( - ValueExpr.withValue( - StringObjectValue.withValue( - protoMethod.httpBindings().lowerCamelPattern())), - createFieldsExtractorClassInstance( - protoMethod, - extractorVarType, - protoMethod.httpBindings().pathParameters(), - "putPathParam", - restNumericEnumsEnabled))) - .apply(expr); - - if (!protoMethod.httpBindings().lowerCamelAdditionalPatterns().isEmpty()) { - expr = - methodMaker - .apply( - "setAdditionalPaths", - protoMethod.httpBindings().lowerCamelAdditionalPatterns().stream() - .map(a -> ValueExpr.withValue(StringObjectValue.withValue(a))) - .collect(Collectors.toList())) - .apply(expr); - } - TypeNode fieldsVarGenericType = - TypeNode.withReference( - ConcreteReference.builder() - .setClazz(List.class) - .setGenerics(TypeNode.STRING.reference()) - .build()); - - extractorVarType = - TypeNode.withReference( - ConcreteReference.builder() - .setClazz(Map.class) - .setGenerics(TypeNode.STRING.reference(), fieldsVarGenericType.reference()) - .build()); - - expr = - methodMaker - .apply( - "setQueryParamsExtractor", - Arrays.asList( - createFieldsExtractorClassInstance( - protoMethod, - extractorVarType, - protoMethod.httpBindings().queryParameters(), - "putQueryParam", - restNumericEnumsEnabled))) - .apply(expr); - - extractorVarType = TypeNode.STRING; - boolean asteriskBody = protoMethod.httpBindings().isAsteriskBody(); - expr = - methodMaker - .apply( - "setRequestBodyExtractor", - Arrays.asList( - createBodyFieldsExtractorClassInstance( - protoMethod, - extractorVarType, - asteriskBody - ? protoMethod.httpBindings().pathParameters() - : protoMethod.httpBindings().bodyParameters(), - "toBody", - asteriskBody, - restNumericEnumsEnabled))) - .apply(expr); - expr = methodMaker.apply("build", Collections.emptyList()).apply(expr); - - return Collections.singletonList(expr); - } - - private List setResponseParserExpr(Method protoMethod) { - BiFunction, Function> - methodMaker = getMethodMaker(); - - MethodInvocationExpr expr = - MethodInvocationExpr.builder() - .setStaticReferenceType( - FIXED_REST_TYPESTORE.get(ProtoMessageResponseParser.class.getSimpleName())) - .setMethodName("newBuilder") - .setGenerics(Collections.singletonList(protoMethod.outputType().reference())) - // .setArguments(Arrays.asList(m)) - .build(); - - expr = - methodMaker - .apply( - "setDefaultInstance", - Arrays.asList( - MethodInvocationExpr.builder() - .setStaticReferenceType(protoMethod.outputType()) - .setMethodName("getDefaultInstance") - .setReturnType(protoMethod.outputType()) - .build())) - .apply(expr); - - expr = - methodMaker - .apply("setDefaultTypeRegistry", Arrays.asList(TYPE_REGISTRY_VAR_EXPR)) - .apply(expr); - expr = methodMaker.apply("build", Collections.emptyList()).apply(expr); - - return Collections.singletonList(expr); + return HttpJsonDescriptorComposer.getMethodMaker(); } // Generates get[camelCase(fieldName)] @@ -726,320 +518,8 @@ private List setPollingRequestFactoryExpr( .build()); } - private Expr createBodyFieldsExtractorClassInstance( - Method method, - TypeNode extractorReturnType, - Set httpBindingFieldNames, - String serializerMethodName, - boolean asteriskBody, - boolean restNumericEnumEnabled) { - List bodyStatements = new ArrayList<>(); - - Expr returnExpr = null; - Expr serializerExpr = - MethodInvocationExpr.builder() - .setMethodName("create") - .setStaticReferenceType( - FIXED_REST_TYPESTORE.get(ProtoRestSerializer.class.getSimpleName())) - .build(); - - VariableExpr requestVarExpr = - VariableExpr.withVariable( - Variable.builder().setType(method.inputType()).setName("request").build()); - Expr bodyRequestExpr = requestVarExpr; - String requestMethodPrefix = "get"; - String bodyParamName = null; - - if (asteriskBody) { - bodyRequestExpr = - MethodInvocationExpr.builder() - .setExprReferenceExpr(requestVarExpr) - .setMethodName("toBuilder") - .build(); - // In case of `body: "*"` case we send the whole request message as a body, minus the fields - // in the path, therefore the "clear" prefix here. - requestMethodPrefix = "clear"; - } - - Expr prevExpr = bodyRequestExpr; - for (HttpBinding httpBindingFieldName : httpBindingFieldNames) { - // Handle foo.bar cases by descending into the subfields. - MethodInvocationExpr.Builder requestFieldMethodExprBuilder = - MethodInvocationExpr.builder().setExprReferenceExpr(prevExpr); - // Use explicit json_name if defined in the proto, prioritizing the actual wire name - // over Java-escaped identifiers. Note that trailing underscores (e.g., 'case_') result from: - // 1. protoc-gen-java: - // https://github.com/protocolbuffers/protobuf/blob/cecbbf41e43634c7c5b940dd336aa81b31fd4e5d/src/google/protobuf/compiler/java/names.cc#L189-L195 - // 2. gapic-generator-java Keyword implementation: - // com/google/api/generator/engine/lexicon/Keyword.java#L92-L94 - bodyParamName = - !Strings.isNullOrEmpty(httpBindingFieldName.jsonName()) - ? httpBindingFieldName.jsonName() - : JavaStyle.toLowerCamelCase(httpBindingFieldName.name()); - String[] descendantFields = httpBindingFieldName.name().split("\\."); - if (asteriskBody && descendantFields.length > 1) { - // This is the `body: "*"` case, do not clean nested body fields as it a very rare, not - // well-defined case, and it is generally safer to send more than less in such case. - continue; - } - - for (int i = 0; i < descendantFields.length; i++) { - String currFieldName = descendantFields[i]; - String bindingFieldMethodName = - String.format("%s%s", requestMethodPrefix, JavaStyle.toUpperCamelCase(currFieldName)); - requestFieldMethodExprBuilder = - requestFieldMethodExprBuilder.setMethodName(bindingFieldMethodName); - - if (i < descendantFields.length - 1) { - requestFieldMethodExprBuilder = - MethodInvocationExpr.builder() - .setExprReferenceExpr(requestFieldMethodExprBuilder.build()); - } - } - prevExpr = requestFieldMethodExprBuilder.build(); - } - - if (httpBindingFieldNames.isEmpty() && !asteriskBody) { - returnExpr = ValueExpr.createNullExpr(); - } else { - ImmutableList.Builder paramsPutArgs = ImmutableList.builder(); - if (asteriskBody) { - prevExpr = - MethodInvocationExpr.builder() - .setExprReferenceExpr(prevExpr) - .setMethodName("build") - .build(); - bodyParamName = "*"; - } - paramsPutArgs.add(ValueExpr.withValue(StringObjectValue.withValue(bodyParamName))); - paramsPutArgs.add(prevExpr); - - PrimitiveValue primitiveValue = - PrimitiveValue.builder() - .setType(TypeNode.BOOLEAN) - .setValue(String.valueOf(restNumericEnumEnabled)) - .build(); - paramsPutArgs.add(ValueExpr.withValue(primitiveValue)); - - returnExpr = - MethodInvocationExpr.builder() - .setExprReferenceExpr(serializerExpr) - .setMethodName(serializerMethodName) - .setArguments(paramsPutArgs.build()) - .setReturnType(extractorReturnType) - .build(); - } - - // Overrides FieldsExtractor - // (https://github.com/googleapis/gax-java/blob/12b18ee255d3fabe13bb3969df40753b29f830d5/gax-httpjson/src/main/java/com/google/api/gax/httpjson/FieldsExtractor.java). - return LambdaExpr.builder() - .setArguments(requestVarExpr.toBuilder().setIsDecl(true).build()) - .setBody(bodyStatements) - .setReturnExpr(returnExpr) - .build(); - } - - private Expr createFieldsExtractorClassInstance( - Method method, - TypeNode extractorReturnType, - Set httpBindingFieldNames, - String serializerMethodName, - boolean restNumericEnumsEnabled) { - List bodyStatements = new ArrayList<>(); - - VariableExpr fieldsVarExpr = - VariableExpr.withVariable( - Variable.builder().setName("fields").setType(extractorReturnType).build()); - Expr fieldsAssignExpr = - AssignmentExpr.builder() - .setVariableExpr(fieldsVarExpr.toBuilder().setIsDecl(true).build()) - .setValueExpr( - NewObjectExpr.builder() - .setType(FIXED_REST_TYPESTORE.get(HashMap.class.getSimpleName())) - .setIsGeneric(true) - .build()) - .build(); - - bodyStatements.add(ExprStatement.withExpr(fieldsAssignExpr)); - - TypeNode serializerVarType = - TypeNode.withReference( - ConcreteReference.builder() - .setClazz(ProtoRestSerializer.class) - .setGenerics(method.inputType().reference()) - .build()); - - VariableExpr serializerVarExpr = - VariableExpr.withVariable( - Variable.builder().setName("serializer").setType(serializerVarType).build()); - - Expr serializerAssignExpr = - AssignmentExpr.builder() - .setVariableExpr(serializerVarExpr.toBuilder().setIsDecl(true).build()) - .setValueExpr( - MethodInvocationExpr.builder() - .setStaticReferenceType( - FIXED_REST_TYPESTORE.get(ProtoRestSerializer.class.getSimpleName())) - .setMethodName("create") - .setReturnType(serializerVarType) - .build()) - .build(); - - bodyStatements.add(ExprStatement.withExpr(serializerAssignExpr)); - - VariableExpr requestVarExpr = - VariableExpr.withVariable( - Variable.builder().setType(method.inputType()).setName("request").build()); - - for (HttpBinding httpBindingFieldName : httpBindingFieldNames) { - // Handle foo.bar cases by descending into the subfields. - MethodInvocationExpr.Builder requestFieldGetterExprBuilder = - MethodInvocationExpr.builder().setExprReferenceExpr(requestVarExpr); - MethodInvocationExpr.Builder requestFieldHasExprBuilder = - MethodInvocationExpr.builder().setExprReferenceExpr(requestVarExpr); - String[] descendantFields = httpBindingFieldName.name().split("\\."); - for (int i = 0; i < descendantFields.length; i++) { - String currFieldName = descendantFields[i]; - String bindingFieldMethodName = - getBindingFieldMethodName( - httpBindingFieldName, - descendantFields.length, - i, - JavaStyle.toUpperCamelCase(currFieldName)); - requestFieldGetterExprBuilder = - requestFieldGetterExprBuilder.setMethodName(bindingFieldMethodName); - - String bindingFieldHasMethodName = - (i < descendantFields.length - 1) - ? bindingFieldMethodName - : String.format("has%s", JavaStyle.toUpperCamelCase(currFieldName)); - requestFieldHasExprBuilder = - requestFieldHasExprBuilder - .setMethodName(bindingFieldHasMethodName) - .setReturnType(TypeNode.BOOLEAN); - - if (i < descendantFields.length - 1) { - requestFieldGetterExprBuilder = - MethodInvocationExpr.builder() - .setExprReferenceExpr(requestFieldGetterExprBuilder.build()); - requestFieldHasExprBuilder = - MethodInvocationExpr.builder() - .setExprReferenceExpr(requestFieldHasExprBuilder.build()); - } - } - - MethodInvocationExpr requestBuilderExpr = requestFieldGetterExprBuilder.build(); - MethodInvocationExpr requestHasExpr = requestFieldHasExprBuilder.build(); - - ImmutableList.Builder paramsPutArgs = ImmutableList.builder(); - - paramsPutArgs.add(fieldsVarExpr); - - paramsPutArgs.add( - ValueExpr.withValue( - StringObjectValue.withValue( - // Use explicit json_name if defined in the proto, prioritizing the actual wire - // name over Java-escaped identifiers (e.g., avoiding 'case_' generated to prevent - // keywords conflict). - (httpBindingFieldName.jsonName() != null) - ? httpBindingFieldName.jsonName() - : JavaStyle.toLowerCamelCase(httpBindingFieldName.name())))); - paramsPutArgs.add(requestBuilderExpr); - - Expr paramsPutExpr = - MethodInvocationExpr.builder() - .setExprReferenceExpr(serializerVarExpr) - .setMethodName(serializerMethodName) - .setArguments(paramsPutArgs.build()) - .setReturnType(extractorReturnType) - .build(); - - if (httpBindingFieldName.isOptional()) { - bodyStatements.add( - IfStatement.builder() - .setConditionExpr(requestHasExpr) - .setBody(Arrays.asList(ExprStatement.withExpr(paramsPutExpr))) - .build()); - } else { - bodyStatements.add(ExprStatement.withExpr(paramsPutExpr)); - } - } - - // Add a fixed query param for numeric enum, see b/232457244 for details - if (restNumericEnumsEnabled && serializerMethodName.equals("putQueryParam")) { - ImmutableList.Builder paramsPutArgs = ImmutableList.builder(); - - paramsPutArgs.add(fieldsVarExpr); - paramsPutArgs.add(ValueExpr.withValue(StringObjectValue.withValue("$alt"))); - paramsPutArgs.add(ValueExpr.withValue(StringObjectValue.withValue("json;enum-encoding=int"))); - - Expr paramsPutExpr = - MethodInvocationExpr.builder() - .setExprReferenceExpr(serializerVarExpr) - .setMethodName(serializerMethodName) - .setArguments(paramsPutArgs.build()) - .setReturnType(extractorReturnType) - .build(); - bodyStatements.add(ExprStatement.withExpr(paramsPutExpr)); - } - - // Overrides FieldsExtractor - // (https://github.com/googleapis/gax-java/blob/12b18ee255d3fabe13bb3969df40753b29f830d5/gax-httpjson/src/main/java/com/google/api/gax/httpjson/FieldsExtractor.java). - return LambdaExpr.builder() - .setArguments(requestVarExpr.toBuilder().setIsDecl(true).build()) - .setBody(bodyStatements) - .setReturnExpr(fieldsVarExpr) - .build(); - } - - @VisibleForTesting - String getBindingFieldMethodName( - HttpBinding httpBindingField, int descendantFieldsLengths, int index, String currFieldName) { - if (index == descendantFieldsLengths - 1) { - if (httpBindingField.isRepeated()) { - return String.format("get%sList", currFieldName); - } - if (httpBindingField.isEnum()) { - return String.format("get%sValue", currFieldName); - } - } - return String.format("get%s", currFieldName); - } - - private List getHttpMethodTypeExpr(Method protoMethod) { - return Collections.singletonList( - ValueExpr.withValue( - StringObjectValue.withValue(protoMethod.httpBindings().httpVerb().toString()))); - } - - private List getMethodTypeExpr(Method protoMethod) { - MethodType methodType; - switch (protoMethod.stream()) { - case NONE: - methodType = MethodType.UNARY; - break; - case SERVER: - methodType = MethodType.SERVER_STREAMING; - break; - case CLIENT: - // Not feasible to suppor in REST - case BIDI: - // Not feasible to suppor in REST - default: - throw new UnsupportedOperationException( - String.format( - "Methods of type %s are not supported by REST transport", protoMethod.stream())); - } - EnumRefExpr expr = - EnumRefExpr.builder() - .setName(methodType.toString()) - .setType( - TypeNode.withReference( - ConcreteReference.builder() - .setClazz(ApiMethodDescriptor.MethodType.class) - .build())) - .build(); - return Collections.singletonList(expr); + static String getProtoRpcName(Service protoService, Method protoMethod) { + return HttpJsonDescriptorComposer.getProtoRpcFullMethodName(protoService, protoMethod); } @Override @@ -1365,7 +845,8 @@ private List createInvalidClassMethods(Service service) { .setType(FIXED_TYPESTORE.get("UnsupportedOperationException")) .setMessageExpr( String.format( - "Not implemented: %s(). %s transport is not implemented for this method yet.", + "Not implemented: %s(). %s transport is not implemented for" + + " this method yet.", callableName, getTransportContext().transport())) .build()))) .build()); diff --git a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/samplecode/ServiceClientCallableMethodSampleComposer.java b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/samplecode/ServiceClientCallableMethodSampleComposer.java index bde75f09cedb..b8073a911ef8 100644 --- a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/samplecode/ServiceClientCallableMethodSampleComposer.java +++ b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/samplecode/ServiceClientCallableMethodSampleComposer.java @@ -57,6 +57,7 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; import org.jspecify.annotations.NullMarked; @@ -545,12 +546,16 @@ public static Sample composePagedCallableMethod( } // Compose sample code for the method where it is CallableMethodKind.REGULAR. - public static Sample composeRegularCallableMethod( + public static Optional composeRegularCallableMethod( Method method, TypeNode clientType, Map resourceNames, Map messageTypes, Service service) { + if (method.isResumableUpload()) { + return Optional.empty(); + } + VariableExpr clientVarExpr = VariableExpr.withVariable( Variable.builder() @@ -601,7 +606,7 @@ public static Sample composeRegularCallableMethod( .setTryBody(bodyStatements) .setIsSampleCode(true) .build()); - return Sample.builder().setBody(body).setRegionTag(regionTag).build(); + return Optional.of(Sample.builder().setBody(body).setRegionTag(regionTag).build()); } public static Sample composeStreamCallableMethod( diff --git a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/samplecode/ServiceClientHeaderSampleComposer.java b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/samplecode/ServiceClientHeaderSampleComposer.java index 8a99010ff102..1b454e0fcd25 100644 --- a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/samplecode/ServiceClientHeaderSampleComposer.java +++ b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/samplecode/ServiceClientHeaderSampleComposer.java @@ -56,10 +56,10 @@ public static Sample composeClassHeaderSample( Map messageTypes) { List publicMethods = service.methods().stream() - .filter(m -> m.isInternalApi() == false) + .filter(m -> !m.isInternalApi() && !m.isResumableUpload()) .collect(Collectors.toList()); - // If all generated methods are INTERNAL, generate an empty service sample. + // If all generated methods are INTERNAL or resumable upload, generate an empty service sample. if (publicMethods.isEmpty()) { return ServiceClientMethodSampleComposer.composeEmptyServiceSample(clientType, service); } @@ -75,7 +75,11 @@ public static Sample composeClassHeaderSample( if (method.stream() == Method.Stream.NONE) { if (method.methodSignatures().isEmpty()) { return ServiceClientMethodSampleComposer.composeCanonicalSample( - method, clientType, resourceNames, messageTypes, service); + method, clientType, resourceNames, messageTypes, service) + .orElseGet( + () -> + ServiceClientMethodSampleComposer.composeEmptyServiceSample( + clientType, service)); } return composeShowcaseMethodSample( method, diff --git a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/samplecode/ServiceClientMethodSampleComposer.java b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/samplecode/ServiceClientMethodSampleComposer.java index 998813c6b7a8..ca8e43f329a1 100644 --- a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/samplecode/ServiceClientMethodSampleComposer.java +++ b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/samplecode/ServiceClientMethodSampleComposer.java @@ -40,6 +40,7 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; import org.jspecify.annotations.NullMarked; @@ -72,12 +73,16 @@ public static Sample composeEmptyServiceSample(TypeNode clientType, Service serv return Sample.builder().setBody(body).setRegionTag(regionTag).setIsCanonical(true).build(); } - public static Sample composeCanonicalSample( + public static Optional composeCanonicalSample( Method method, TypeNode clientType, Map resourceNames, Map messageTypes, Service service) { + if (method.isResumableUpload()) { + return Optional.empty(); + } + VariableExpr clientVarExpr = VariableExpr.withVariable( Variable.builder() @@ -137,7 +142,8 @@ public static Sample composeCanonicalSample( .setTryBody(bodyStatements) .setIsSampleCode(true) .build()); - return Sample.builder().setBody(body).setRegionTag(regionTag).setIsCanonical(true).build(); + return Optional.of( + Sample.builder().setBody(body).setRegionTag(regionTag).setIsCanonical(true).build()); } static Sample composeSample( diff --git a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/samplecode/SettingsSampleComposer.java b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/samplecode/SettingsSampleComposer.java index d8c796c83be3..153f300c041e 100644 --- a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/samplecode/SettingsSampleComposer.java +++ b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/samplecode/SettingsSampleComposer.java @@ -25,6 +25,7 @@ import com.google.api.generator.engine.ast.VaporReference; import com.google.api.generator.engine.ast.Variable; import com.google.api.generator.engine.ast.VariableExpr; +import com.google.api.generator.gapic.model.Method; import com.google.api.generator.gapic.model.RegionTag; import com.google.api.generator.gapic.model.Sample; import com.google.api.generator.gapic.utils.JavaStyle; @@ -38,6 +39,14 @@ @NullMarked public final class SettingsSampleComposer { + public static Optional composeSettingsSample( + Method method, String settingsClassName, TypeNode classType) { + if (method.isResumableUpload()) { + return Optional.empty(); + } + return composeSettingsSample(Optional.of(method.name()), settingsClassName, classType); + } + public static Optional composeSettingsSample( Optional methodNameOpt, String settingsClassName, TypeNode classType) { if (!methodNameOpt.isPresent()) { diff --git a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/common/ResumableUploadServiceClientComposerTest.java b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/common/ResumableUploadServiceClientComposerTest.java new file mode 100644 index 000000000000..e15e6c533f02 --- /dev/null +++ b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/common/ResumableUploadServiceClientComposerTest.java @@ -0,0 +1,155 @@ +// Copyright 2026 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.composer.common; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.api.generator.engine.ast.ConcreteReference; +import com.google.api.generator.engine.ast.TypeNode; +import com.google.api.generator.engine.writer.JavaWriterVisitor; +import com.google.api.generator.gapic.composer.grpc.ServiceClientTestClassComposer; +import com.google.api.generator.gapic.composer.rest.ServiceClientClassComposer; +import com.google.api.generator.gapic.composer.rest.ServiceStubClassComposer; +import com.google.api.generator.gapic.model.GapicClass; +import com.google.api.generator.gapic.model.GapicContext; +import com.google.api.generator.gapic.model.Method; +import com.google.api.generator.gapic.model.Service; +import com.google.api.generator.test.protoloader.RestTestProtoLoader; +import com.google.protobuf.Empty; +import java.util.Arrays; +import org.junit.jupiter.api.Test; + +class ResumableUploadServiceClientComposerTest { + + @Test + void generateServiceStub_resumableUploadCallable() { + GapicContext context = RestTestProtoLoader.instance().parseShowcaseResumableUpload(); + Service service = context.services().get(0); + + GapicClass clazz = ServiceStubClassComposer.instance().generate(context, service); + JavaWriterVisitor visitor = new JavaWriterVisitor(); + clazz.classDefinition().accept(visitor); + String code = visitor.write(); + + // Verify import of ResumableUploadCallable + assertThat(code).contains("import com.google.api.gax.rpc.ResumableUploadCallable;"); + + // Verify abstract callable getter on stub + assertThat(code) + .contains( + "public ResumableUploadCallable" + + " uploadMediaCallable() {\n" + + " throw new UnsupportedOperationException(\"Not implemented:" + + " uploadMediaCallable()\");\n" + + " }"); + } + + @Test + void generateServiceClient_resumableUploadCallableAndConvenienceMethod() { + GapicContext context = RestTestProtoLoader.instance().parseShowcaseResumableUpload(); + Service service = context.services().get(0); + + GapicClass clazz = ServiceClientClassComposer.instance().generate(context, service); + JavaWriterVisitor visitor = new JavaWriterVisitor(); + clazz.classDefinition().accept(visitor); + String code = visitor.write(); + + // Verify imports + assertThat(code).contains("import com.google.api.gax.rpc.ApiExceptions;"); + assertThat(code).contains("import com.google.api.gax.rpc.ResumableUploadCallable;"); + assertThat(code).contains("import java.io.InputStream;"); + + // Verify callable getter on client delegating to stub + assertThat(code) + .contains( + "public final ResumableUploadCallable\n" + + " uploadMediaCallable() {\n" + + " return stub.uploadMediaCallable();\n" + + " }"); + + // Verify synchronous convenience method calling ApiExceptions.callAndTranslateApiException + assertThat(code) + .contains( + "public final UploadMediaResponse uploadMedia(UploadMediaRequest request, InputStream" + + " payload) {\n" + + " return ApiExceptions.callAndTranslateApiException(\n" + + " uploadMediaCallable().futureCall(request, payload," + + " ((ResumableUploadCallSettings) null)));\n" + + " }"); + + // Verify method variants (flattened methods) are suppressed + assertThat(code).doesNotContain("uploadMedia(String"); + + // Verify Javadoc warning on methods + assertThat(code) + .contains( + "Call context overrides (such as withTimeout, withRetrySettings, or credentials)"); + assertThat(code).contains("to the start request (session initiation)"); + assertThat(code).contains("Per-chunk PUT calls rely on the configured timeout"); + assertThat(code).contains("retry settings from ResumableUploadCallSettings."); + + // Verify @param request and @param payload in Javadoc + assertThat(code).contains("@param request The request object"); + assertThat(code).contains("@param payload The payload data stream to upload."); + } + + @Test + void generateServiceClientTest_resumableUploadTest() { + GapicContext context = RestTestProtoLoader.instance().parseShowcaseResumableUpload(); + Service service = context.services().get(0); + + GapicClass clazz = ServiceClientTestClassComposer.instance().generate(context, service); + JavaWriterVisitor visitor = new JavaWriterVisitor(); + clazz.classDefinition().accept(visitor); + String code = visitor.write(); + + // Verify test placeholder for resumable upload method + assertThat(code).contains("public void uploadMediaTest() throws Exception"); + assertThat(code) + .contains( + "@Ignore(\n" + + " \"Requires live HTTP/REST server supporting resumable upload protocol;" + + " tested in integration tests.\")"); + assertThat(code).contains("The uploadMedia() method requires a live HTTP/REST server"); + assertThat(code).contains("protocol and is tested in integration tests."); + + // Verify no exception test is generated + assertThat(code).doesNotContain("uploadMediaExceptionTest"); + } + + @Test + void generateServiceClient_resumableUploadVoidReturn() { + GapicContext context = RestTestProtoLoader.instance().parseShowcaseResumableUpload(); + Service service = context.services().get(0); + TypeNode emptyType = TypeNode.withReference(ConcreteReference.withClazz(Empty.class)); + Method emptyOutputMethod = + service.methods().get(0).toBuilder().setOutputType(emptyType).build(); + Service emptyOutputService = + service.toBuilder().setMethods(Arrays.asList(emptyOutputMethod)).build(); + + GapicClass clazz = ServiceClientClassComposer.instance().generate(context, emptyOutputService); + JavaWriterVisitor visitor = new JavaWriterVisitor(); + clazz.classDefinition().accept(visitor); + String code = visitor.write(); + + assertThat(code) + .contains( + "public final void uploadMedia(UploadMediaRequest request, InputStream payload) {\n" + + " ApiExceptions.callAndTranslateApiException(\n" + + " uploadMediaCallable().futureCall(request, payload," + + " ((ResumableUploadCallSettings) null)));\n" + + " }"); + } +} diff --git a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/common/ResumableUploadSettingsComposerTest.java b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/common/ResumableUploadSettingsComposerTest.java new file mode 100644 index 000000000000..d9f9675c4332 --- /dev/null +++ b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/common/ResumableUploadSettingsComposerTest.java @@ -0,0 +1,152 @@ +// Copyright 2026 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.composer.common; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.api.generator.engine.writer.JavaWriterVisitor; +import com.google.api.generator.gapic.composer.rest.ServiceSettingsClassComposer; +import com.google.api.generator.gapic.composer.rest.ServiceStubSettingsClassComposer; +import com.google.api.generator.gapic.model.GapicClass; +import com.google.api.generator.gapic.model.GapicContext; +import com.google.api.generator.gapic.model.GapicServiceConfig; +import com.google.api.generator.gapic.model.Service; +import com.google.api.generator.test.protoloader.RestTestProtoLoader; +import com.google.protobuf.Duration; +import io.grpc.serviceconfig.MethodConfig; +import io.grpc.serviceconfig.ServiceConfig; +import java.util.Optional; +import org.junit.jupiter.api.Test; + +class ResumableUploadSettingsComposerTest { + + @Test + void generateServiceStubSettings_resumableUploadCallSettings() { + GapicContext context = RestTestProtoLoader.instance().parseShowcaseResumableUpload(); + Service service = context.services().get(0); + + GapicClass clazz = ServiceStubSettingsClassComposer.instance().generate(context, service); + JavaWriterVisitor visitor = new JavaWriterVisitor(); + clazz.classDefinition().accept(visitor); + String code = visitor.write(); + + // Verify import of ResumableUploadCallSettings + assertThat(code).contains("import com.google.api.gax.rpc.ResumableUploadCallSettings;"); + + // Verify field in outer class + assertThat(code).contains("private final ResumableUploadCallSettings uploadMediaSettings;"); + + // Verify getter in outer class + assertThat(code) + .contains( + "public ResumableUploadCallSettings uploadMediaSettings() {\n" + + " return uploadMediaSettings;\n" + + " }"); + + // Verify field in Builder class + assertThat(code) + .contains("private final ResumableUploadCallSettings.Builder uploadMediaSettings;"); + + // Verify getter in Builder class + assertThat(code) + .contains( + "public ResumableUploadCallSettings.Builder uploadMediaSettings() {\n" + + " return uploadMediaSettings;\n" + + " }"); + + // Verify Builder constructor instantiates with ResumableUploadCallSettings.newBuilder() + assertThat(code).contains("uploadMediaSettings = ResumableUploadCallSettings.newBuilder();"); + + // Verify outer constructor builds the settings + assertThat(code) + .contains("uploadMediaSettings = settingsBuilder.uploadMediaSettings().build();"); + + // Verify unaryMethodSettingsBuilders does NOT contain uploadMediaSettings + assertThat(code).doesNotContain("unaryMethodSettingsBuilders.add(uploadMediaSettings);"); + assertThat(code) + .doesNotContain("unaryMethodSettingsBuilders.add(builder.uploadMediaSettings());"); + + // Verify Javadoc on settings getters + assertThat(code) + .contains("Note that custom retry settings and headers configured via ApiCallContext"); + assertThat(code).contains("the initial session initiation request."); + } + + @Test + void generateServiceSettings_resumableUploadCallSettings() { + GapicContext context = RestTestProtoLoader.instance().parseShowcaseResumableUpload(); + Service service = context.services().get(0); + + GapicClass clazz = ServiceSettingsClassComposer.instance().generate(context, service); + JavaWriterVisitor visitor = new JavaWriterVisitor(); + clazz.classDefinition().accept(visitor); + String code = visitor.write(); + + // Verify import of ResumableUploadCallSettings + assertThat(code).contains("import com.google.api.gax.rpc.ResumableUploadCallSettings;"); + + // Verify getter in outer ServiceSettings class delegating to stub settings + assertThat(code) + .contains( + "public ResumableUploadCallSettings uploadMediaSettings() {\n" + + " return ((ResumableUploadServiceStubSettings)" + + " getStubSettings()).uploadMediaSettings();\n" + + " }"); + + // Verify getter in Builder class delegating to stub settings builder + assertThat(code) + .contains( + "public ResumableUploadCallSettings.Builder uploadMediaSettings() {\n" + + " return getStubSettingsBuilder().uploadMediaSettings();\n" + + " }"); + + // Verify Javadoc on settings getters + assertThat(code) + .contains("Note that custom retry settings and headers configured via ApiCallContext"); + assertThat(code).contains("the initial session initiation request."); + } + + @Test + void generateServiceStubSettings_withTimeoutConfig() { + GapicContext baseContext = RestTestProtoLoader.instance().parseShowcaseResumableUpload(); + Service service = baseContext.services().get(0); + + ServiceConfig serviceConfig = + ServiceConfig.newBuilder() + .addMethodConfig( + MethodConfig.newBuilder() + .addName( + MethodConfig.Name.newBuilder() + .setService("google.showcase.v1beta1.ResumableUploadService") + .setMethod("UploadMedia")) + .setTimeout(Duration.newBuilder().setSeconds(300))) + .build(); + GapicServiceConfig gapicServiceConfig = GapicServiceConfig.create(Optional.of(serviceConfig)); + GapicContext context = baseContext.toBuilder().setServiceConfig(gapicServiceConfig).build(); + + GapicClass clazz = ServiceStubSettingsClassComposer.instance().generate(context, service); + JavaWriterVisitor visitor = new JavaWriterVisitor(); + clazz.classDefinition().accept(visitor); + String code = visitor.write(); + + // Verify global timeout is configured on builder.uploadMediaSettings() + assertThat(code) + .contains("builder.uploadMediaSettings().setGlobalTimeout(Duration.ofMillis(300000L));"); + + // Verify retryable codes and retry settings are NOT attempted on ResumableUploadCallSettings + assertThat(code).doesNotContain("builder.uploadMediaSettings().setRetryableCodes"); + assertThat(code).doesNotContain("builder.uploadMediaSettings().setRetrySettings"); + } +} diff --git a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/ServiceClientClassComposerTest.java b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/ServiceClientClassComposerTest.java index 2654eb601531..c679df2d30ea 100644 --- a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/ServiceClientClassComposerTest.java +++ b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/ServiceClientClassComposerTest.java @@ -64,7 +64,13 @@ private static Stream data() { GrpcTestProtoLoader.instance().parseSelectiveGenerationTesting(), "localhost:7469", "v1beta1", - 1)); + 1), + Arguments.of( + "ResumableUploadServiceClient", + GrpcTestProtoLoader.instance().parseShowcaseResumableUpload(), + "localhost:7469", + "v1beta1", + 0)); } @ParameterizedTest diff --git a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/ServiceSettingsClassComposerTest.java b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/ServiceSettingsClassComposerTest.java index da503cd09f01..d70b43bb3b9f 100644 --- a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/ServiceSettingsClassComposerTest.java +++ b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/ServiceSettingsClassComposerTest.java @@ -45,7 +45,13 @@ static Stream data() { TestProtoLoader.instance().parseSelectiveGenerationTesting(), "localhost:7469", "v1beta1", - 1)); + 1), + Arguments.of( + "ResumableUploadServiceSettings", + TestProtoLoader.instance().parseShowcaseResumableUpload(), + "localhost:7469", + "v1beta1", + 0)); } @ParameterizedTest diff --git a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/ServiceStubClassComposerTest.java b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/ServiceStubClassComposerTest.java index 21d57f02b8a3..5c8d522ec286 100644 --- a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/ServiceStubClassComposerTest.java +++ b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/ServiceStubClassComposerTest.java @@ -39,7 +39,13 @@ static Stream data() { TestProtoLoader.instance().parseSelectiveGenerationTesting(), "", "", - 1)); + 1), + Arguments.of( + "ResumableUploadServiceStub", + TestProtoLoader.instance().parseShowcaseResumableUpload(), + "", + "", + 0)); } @ParameterizedTest diff --git a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/ServiceStubSettingsClassComposerTest.java b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/ServiceStubSettingsClassComposerTest.java index 3287309ee457..3e7a017403f3 100644 --- a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/ServiceStubSettingsClassComposerTest.java +++ b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/ServiceStubSettingsClassComposerTest.java @@ -68,6 +68,12 @@ static Stream data() { GrpcTestProtoLoader.instance().parseBigqueryService(), "bigquery", "v2", + 0), + Arguments.of( + "ResumableUploadServiceStubSettings", + GrpcTestProtoLoader.instance().parseShowcaseResumableUpload(), + "localhost:7469", + "v1beta1", 0)); } diff --git a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/ResumableUploadServiceClient.golden b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/ResumableUploadServiceClient.golden new file mode 100644 index 000000000000..56c6f8a20dc5 --- /dev/null +++ b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/ResumableUploadServiceClient.golden @@ -0,0 +1,208 @@ +package com.google.showcase.v1beta1; + +import com.google.api.core.BetaApi; +import com.google.api.gax.core.BackgroundResource; +import com.google.api.gax.rpc.ApiExceptions; +import com.google.api.gax.rpc.ResumableUploadCallSettings; +import com.google.api.gax.rpc.ResumableUploadCallable; +import com.google.showcase.v1beta1.stub.ResumableUploadServiceStub; +import com.google.showcase.v1beta1.stub.ResumableUploadServiceStubSettings; +import java.io.IOException; +import java.io.InputStream; +import java.util.concurrent.TimeUnit; +import javax.annotation.Generated; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +// AUTO-GENERATED DOCUMENTATION AND CLASS. +/** + * This class provides the ability to make remote calls to the backing service through method calls + * that map to API methods. Sample code to get started: + * + *
{@code
+ * // This snippet has been automatically generated and should be regarded as a code template only.
+ * // It will require modifications to work:
+ * // - It may require correct/in-range values for request initialization.
+ * // - It may require specifying regional endpoints when creating the service client as shown in
+ * // https://cloud.google.com/java/docs/setup#configure_endpoints_for_the_client_library
+ * try (ResumableUploadServiceClient resumableUploadServiceClient =
+ *     ResumableUploadServiceClient.create()) {}
+ * }
+ * + *

Note: close() needs to be called on the ResumableUploadServiceClient object to clean up + * resources such as threads. In the example above, try-with-resources is used, which automatically + * calls close(). + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Methods
MethodDescriptionMethod Variants

UploadMedia

+ *

Request object method variants only take one parameter, a request object, which must be constructed before the call.

+ *
    + *
  • uploadMedia(UploadMediaRequest request, InputStream payload) + *

+ *

Callable method variants take no parameters and return an immutable API callable object, which can be used to initiate calls to the service.

+ *
    + *
  • uploadMediaCallable() + *

+ *
+ * + *

See the individual methods for example code. + * + *

Many parameters require resource names to be formatted in a particular way. To assist with + * these names, this class includes a format method for each type of name, and additionally a parse + * method to extract the individual identifiers contained within names that are returned. + * + *

This class can be customized by passing in a custom instance of ResumableUploadServiceSettings + * to create(). For example: + * + *

To customize credentials: + * + *

{@code
+ * // This snippet has been automatically generated and should be regarded as a code template only.
+ * // It will require modifications to work:
+ * // - It may require correct/in-range values for request initialization.
+ * // - It may require specifying regional endpoints when creating the service client as shown in
+ * // https://cloud.google.com/java/docs/setup#configure_endpoints_for_the_client_library
+ * ResumableUploadServiceSettings resumableUploadServiceSettings =
+ *     ResumableUploadServiceSettings.newBuilder()
+ *         .setCredentialsProvider(FixedCredentialsProvider.create(myCredentials))
+ *         .build();
+ * ResumableUploadServiceClient resumableUploadServiceClient =
+ *     ResumableUploadServiceClient.create(resumableUploadServiceSettings);
+ * }
+ * + *

To customize the endpoint: + * + *

{@code
+ * // This snippet has been automatically generated and should be regarded as a code template only.
+ * // It will require modifications to work:
+ * // - It may require correct/in-range values for request initialization.
+ * // - It may require specifying regional endpoints when creating the service client as shown in
+ * // https://cloud.google.com/java/docs/setup#configure_endpoints_for_the_client_library
+ * ResumableUploadServiceSettings resumableUploadServiceSettings =
+ *     ResumableUploadServiceSettings.newBuilder().setEndpoint(myEndpoint).build();
+ * ResumableUploadServiceClient resumableUploadServiceClient =
+ *     ResumableUploadServiceClient.create(resumableUploadServiceSettings);
+ * }
+ * + *

Please refer to the GitHub repository's samples for more quickstart code snippets. + */ +@NullMarked +@BetaApi +@Generated("by gapic-generator-java") +public class ResumableUploadServiceClient implements BackgroundResource { + private final @Nullable ResumableUploadServiceSettings settings; + private final ResumableUploadServiceStub stub; + + /** Constructs an instance of ResumableUploadServiceClient with default settings. */ + public static final ResumableUploadServiceClient create() throws IOException { + return create(ResumableUploadServiceSettings.newBuilder().build()); + } + + /** + * Constructs an instance of ResumableUploadServiceClient, using the given settings. The channels + * are created based on the settings passed in, or defaults for any settings that are not set. + */ + public static final ResumableUploadServiceClient create(ResumableUploadServiceSettings settings) + throws IOException { + return new ResumableUploadServiceClient(settings); + } + + /** + * Constructs an instance of ResumableUploadServiceClient, using the given stub for making calls. + * This is for advanced usage - prefer using create(ResumableUploadServiceSettings). + */ + public static final ResumableUploadServiceClient create(ResumableUploadServiceStub stub) { + return new ResumableUploadServiceClient(stub); + } + + /** + * Constructs an instance of ResumableUploadServiceClient, using the given settings. This is + * protected so that it is easy to make a subclass, but otherwise, the static factory methods + * should be preferred. + */ + protected ResumableUploadServiceClient(ResumableUploadServiceSettings settings) + throws IOException { + this.settings = settings; + this.stub = ((ResumableUploadServiceStubSettings) settings.getStubSettings()).createStub(); + } + + protected ResumableUploadServiceClient(ResumableUploadServiceStub stub) { + this.settings = null; + this.stub = stub; + } + + public final @Nullable ResumableUploadServiceSettings getSettings() { + return settings; + } + + public ResumableUploadServiceStub getStub() { + return stub; + } + + // AUTO-GENERATED DOCUMENTATION AND METHOD. + /** + * Call context overrides (such as withTimeout, withRetrySettings, or credentials) apply strictly + * to the start request (session initiation). Per-chunk PUT calls rely on the configured timeout + * and retry settings from ResumableUploadCallSettings. + * + * @param request The request object containing all of the parameters for the API call. + * @param payload The payload data stream to upload. + * @throws com.google.api.gax.rpc.ApiException if the remote call fails + */ + public final UploadMediaResponse uploadMedia(UploadMediaRequest request, InputStream payload) { + return ApiExceptions.callAndTranslateApiException( + uploadMediaCallable().futureCall(request, payload, ((ResumableUploadCallSettings) null))); + } + + // AUTO-GENERATED DOCUMENTATION AND METHOD. + /** + * Call context overrides (such as withTimeout, withRetrySettings, or credentials) apply strictly + * to the start request (session initiation). Per-chunk PUT calls rely on the configured timeout + * and retry settings from ResumableUploadCallSettings. + */ + public final ResumableUploadCallable + uploadMediaCallable() { + return stub.uploadMediaCallable(); + } + + @Override + public final void close() { + stub.close(); + } + + @Override + public void shutdown() { + stub.shutdown(); + } + + @Override + public boolean isShutdown() { + return stub.isShutdown(); + } + + @Override + public boolean isTerminated() { + return stub.isTerminated(); + } + + @Override + public void shutdownNow() { + stub.shutdownNow(); + } + + @Override + public boolean awaitTermination(long duration, TimeUnit unit) throws InterruptedException { + return stub.awaitTermination(duration, unit); + } +} diff --git a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/ResumableUploadServiceSettings.golden b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/ResumableUploadServiceSettings.golden new file mode 100644 index 000000000000..8d6fbc1100b2 --- /dev/null +++ b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/ResumableUploadServiceSettings.golden @@ -0,0 +1,164 @@ +package com.google.showcase.v1beta1; + +import com.google.api.core.ApiFunction; +import com.google.api.core.BetaApi; +import com.google.api.gax.core.GoogleCredentialsProvider; +import com.google.api.gax.core.InstantiatingExecutorProvider; +import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider; +import com.google.api.gax.rpc.ApiClientHeaderProvider; +import com.google.api.gax.rpc.ClientContext; +import com.google.api.gax.rpc.ClientSettings; +import com.google.api.gax.rpc.ResumableUploadCallSettings; +import com.google.api.gax.rpc.StubSettings; +import com.google.api.gax.rpc.TransportChannelProvider; +import com.google.api.gax.rpc.UnaryCallSettings; +import com.google.showcase.v1beta1.stub.ResumableUploadServiceStubSettings; +import java.io.IOException; +import java.util.List; +import javax.annotation.Generated; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +// AUTO-GENERATED DOCUMENTATION AND CLASS. +/** + * Settings class to configure an instance of {@link ResumableUploadServiceClient}. + * + *

The default instance has everything set to sensible defaults: + * + *

    + *
  • The default service address (localhost) and default port (7469) are used. + *
  • Credentials are acquired automatically through Application Default Credentials. + *
  • Retries are configured for idempotent methods but not for non-idempotent methods. + *
+ * + *

The builder of this class is recursive, so contained classes are themselves builders. When + * build() is called, the tree of builders is called to create the complete settings object. + */ +@NullMarked +@BetaApi +@Generated("by gapic-generator-java") +public class ResumableUploadServiceSettings extends ClientSettings { + + /** + * Returns the object with the settings used for calls to uploadMedia. + * + *

Note that custom retry settings and headers configured via ApiCallContext apply strictly to + * the initial session initiation request. + */ + public ResumableUploadCallSettings uploadMediaSettings() { + return ((ResumableUploadServiceStubSettings) getStubSettings()).uploadMediaSettings(); + } + + public static final ResumableUploadServiceSettings create(ResumableUploadServiceStubSettings stub) + throws IOException { + return new ResumableUploadServiceSettings.Builder(stub.toBuilder()).build(); + } + + /** Returns a builder for the default ExecutorProvider for this service. */ + public static InstantiatingExecutorProvider.Builder defaultExecutorProviderBuilder() { + return ResumableUploadServiceStubSettings.defaultExecutorProviderBuilder(); + } + + /** Returns the default service endpoint. */ + public static String getDefaultEndpoint() { + return ResumableUploadServiceStubSettings.getDefaultEndpoint(); + } + + /** Returns the default service scopes. */ + public static List getDefaultServiceScopes() { + return ResumableUploadServiceStubSettings.getDefaultServiceScopes(); + } + + /** Returns a builder for the default credentials for this service. */ + public static GoogleCredentialsProvider.Builder defaultCredentialsProviderBuilder() { + return ResumableUploadServiceStubSettings.defaultCredentialsProviderBuilder(); + } + + /** Returns a builder for the default ChannelProvider for this service. */ + public static InstantiatingGrpcChannelProvider.Builder defaultGrpcTransportProviderBuilder() { + return ResumableUploadServiceStubSettings.defaultGrpcTransportProviderBuilder(); + } + + public static TransportChannelProvider defaultTransportChannelProvider() { + return ResumableUploadServiceStubSettings.defaultTransportChannelProvider(); + } + + public static ApiClientHeaderProvider.Builder defaultApiClientHeaderProviderBuilder() { + return ResumableUploadServiceStubSettings.defaultApiClientHeaderProviderBuilder(); + } + + /** Returns a new builder for this class. */ + public static Builder newBuilder() { + return Builder.createDefault(); + } + + /** Returns a new builder for this class. */ + public static Builder newBuilder(@Nullable ClientContext clientContext) { + return new Builder(clientContext); + } + + /** Returns a builder containing all the values of this settings class. */ + public Builder toBuilder() { + return new Builder(this); + } + + protected ResumableUploadServiceSettings(Builder settingsBuilder) throws IOException { + super(settingsBuilder); + } + + /** Builder for ResumableUploadServiceSettings. */ + public static class Builder + extends ClientSettings.Builder { + + protected Builder() throws IOException { + this(((ClientContext) null)); + } + + protected Builder(@Nullable ClientContext clientContext) { + super(ResumableUploadServiceStubSettings.newBuilder(clientContext)); + } + + protected Builder(ResumableUploadServiceSettings settings) { + super(settings.getStubSettings().toBuilder()); + } + + protected Builder(ResumableUploadServiceStubSettings.Builder stubSettings) { + super(stubSettings); + } + + private static Builder createDefault() { + return new Builder(ResumableUploadServiceStubSettings.newBuilder()); + } + + public ResumableUploadServiceStubSettings.Builder getStubSettingsBuilder() { + return ((ResumableUploadServiceStubSettings.Builder) getStubSettings()); + } + + /** + * Applies the given settings updater function to all of the unary API methods in this service. + * + *

Note: This method does not support applying settings to streaming methods. + */ + public Builder applyToAllUnaryMethods( + ApiFunction, Void> settingsUpdater) { + super.applyToAllUnaryMethods( + getStubSettingsBuilder().unaryMethodSettingsBuilders(), settingsUpdater); + return this; + } + + /** + * Returns the builder for the settings used for calls to uploadMedia. + * + *

Note that custom retry settings and headers configured via ApiCallContext apply strictly + * to the initial session initiation request. + */ + public ResumableUploadCallSettings.Builder uploadMediaSettings() { + return getStubSettingsBuilder().uploadMediaSettings(); + } + + @Override + public ResumableUploadServiceSettings build() throws IOException { + return new ResumableUploadServiceSettings(this); + } + } +} diff --git a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/ResumableUploadServiceStub.golden b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/ResumableUploadServiceStub.golden new file mode 100644 index 000000000000..02423b36738e --- /dev/null +++ b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/ResumableUploadServiceStub.golden @@ -0,0 +1,28 @@ +package com.google.showcase.v1beta1.stub; + +import com.google.api.core.BetaApi; +import com.google.api.gax.core.BackgroundResource; +import com.google.api.gax.rpc.ResumableUploadCallable; +import com.google.showcase.v1beta1.UploadMediaRequest; +import com.google.showcase.v1beta1.UploadMediaResponse; +import javax.annotation.Generated; +import org.jspecify.annotations.NullMarked; + +// AUTO-GENERATED DOCUMENTATION AND CLASS. +/** + * Base stub class for the ResumableUploadService service API. + * + *

This class is for advanced usage and reflects the underlying API directly. + */ +@NullMarked +@BetaApi +@Generated("by gapic-generator-java") +public abstract class ResumableUploadServiceStub implements BackgroundResource { + + public ResumableUploadCallable uploadMediaCallable() { + throw new UnsupportedOperationException("Not implemented: uploadMediaCallable()"); + } + + @Override + public abstract void close(); +} diff --git a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/ResumableUploadServiceStubSettings.golden b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/ResumableUploadServiceStubSettings.golden new file mode 100644 index 000000000000..59ba0aead96c --- /dev/null +++ b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/ResumableUploadServiceStubSettings.golden @@ -0,0 +1,246 @@ +package com.google.showcase.v1beta1.stub; + +import com.google.api.core.ApiFunction; +import com.google.api.core.BetaApi; +import com.google.api.core.ObsoleteApi; +import com.google.api.gax.core.GaxProperties; +import com.google.api.gax.core.GoogleCredentialsProvider; +import com.google.api.gax.core.InstantiatingExecutorProvider; +import com.google.api.gax.grpc.GaxGrpcProperties; +import com.google.api.gax.grpc.GrpcTransportChannel; +import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider; +import com.google.api.gax.retrying.RetrySettings; +import com.google.api.gax.rpc.ApiClientHeaderProvider; +import com.google.api.gax.rpc.ClientContext; +import com.google.api.gax.rpc.LibraryMetadata; +import com.google.api.gax.rpc.ResumableUploadCallSettings; +import com.google.api.gax.rpc.StatusCode; +import com.google.api.gax.rpc.StubSettings; +import com.google.api.gax.rpc.TransportChannelProvider; +import com.google.api.gax.rpc.UnaryCallSettings; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import java.io.IOException; +import java.time.Duration; +import java.util.List; +import javax.annotation.Generated; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +// AUTO-GENERATED DOCUMENTATION AND CLASS. +/** + * Settings class to configure an instance of {@link ResumableUploadServiceStub}. + * + *

The default instance has everything set to sensible defaults: + * + *

    + *
  • The default service address (localhost) and default port (7469) are used. + *
  • Credentials are acquired automatically through Application Default Credentials. + *
  • Retries are configured for idempotent methods but not for non-idempotent methods. + *
+ * + *

The builder of this class is recursive, so contained classes are themselves builders. When + * build() is called, the tree of builders is called to create the complete settings object. + */ +@NullMarked +@BetaApi +@Generated("by gapic-generator-java") +@SuppressWarnings("CanonicalDuration") +public class ResumableUploadServiceStubSettings + extends StubSettings { + /** The default scopes of the service. */ + private static final ImmutableList DEFAULT_SERVICE_SCOPES = + ImmutableList.builder().build(); + + private final ResumableUploadCallSettings uploadMediaSettings; + + /** + * Returns the object with the settings used for calls to uploadMedia. + * + *

Note that custom retry settings and headers configured via ApiCallContext apply strictly to + * the initial session initiation request. + */ + public ResumableUploadCallSettings uploadMediaSettings() { + return uploadMediaSettings; + } + + public ResumableUploadServiceStub createStub() throws IOException { + if (getTransportChannelProvider() + .getTransportName() + .equals(GrpcTransportChannel.getGrpcTransportName())) { + return GrpcResumableUploadServiceStub.create(this); + } + throw new UnsupportedOperationException( + String.format( + "Transport not supported: %s", getTransportChannelProvider().getTransportName())); + } + + /** Returns a builder for the default ExecutorProvider for this service. */ + public static InstantiatingExecutorProvider.Builder defaultExecutorProviderBuilder() { + return InstantiatingExecutorProvider.newBuilder(); + } + + /** Returns the default service endpoint. */ + @ObsoleteApi("Use getEndpoint() instead") + public static String getDefaultEndpoint() { + return "localhost:7469"; + } + + /** Returns the default mTLS service endpoint. */ + public static String getDefaultMtlsEndpoint() { + return "localhost:7469"; + } + + /** Returns the default service scopes. */ + public static List getDefaultServiceScopes() { + return DEFAULT_SERVICE_SCOPES; + } + + /** Returns a builder for the default credentials for this service. */ + public static GoogleCredentialsProvider.Builder defaultCredentialsProviderBuilder() { + return GoogleCredentialsProvider.newBuilder() + .setScopesToApply(DEFAULT_SERVICE_SCOPES) + .setUseJwtAccessWithScope(true); + } + + /** Returns a builder for the default ChannelProvider for this service. */ + public static InstantiatingGrpcChannelProvider.Builder defaultGrpcTransportProviderBuilder() { + return InstantiatingGrpcChannelProvider.newBuilder() + .setMaxInboundMessageSize(Integer.MAX_VALUE); + } + + public static TransportChannelProvider defaultTransportChannelProvider() { + return defaultGrpcTransportProviderBuilder().build(); + } + + public static ApiClientHeaderProvider.Builder defaultApiClientHeaderProviderBuilder() { + return ApiClientHeaderProvider.newBuilder() + .setGeneratedLibToken( + "gapic", GaxProperties.getLibraryVersion(ResumableUploadServiceStubSettings.class)) + .setTransportToken( + GaxGrpcProperties.getGrpcTokenName(), GaxGrpcProperties.getGrpcVersion()); + } + + /** Returns a new builder for this class. */ + public static Builder newBuilder() { + return Builder.createDefault(); + } + + /** Returns a new builder for this class. */ + public static Builder newBuilder(@Nullable ClientContext clientContext) { + return new Builder(clientContext); + } + + /** Returns a builder containing all the values of this settings class. */ + public Builder toBuilder() { + return new Builder(this); + } + + protected ResumableUploadServiceStubSettings(Builder settingsBuilder) throws IOException { + super(settingsBuilder); + + uploadMediaSettings = settingsBuilder.uploadMediaSettings().build(); + } + + @Override + protected LibraryMetadata getLibraryMetadata() { + return LibraryMetadata.newBuilder().build(); + } + + /** Builder for ResumableUploadServiceStubSettings. */ + public static class Builder + extends StubSettings.Builder { + private final ImmutableList> unaryMethodSettingsBuilders; + private final ResumableUploadCallSettings.Builder uploadMediaSettings; + private static final ImmutableMap> + RETRYABLE_CODE_DEFINITIONS; + + static { + ImmutableMap.Builder> definitions = + ImmutableMap.builder(); + definitions.put("no_retry_codes", ImmutableSet.copyOf(Lists.newArrayList())); + RETRYABLE_CODE_DEFINITIONS = definitions.build(); + } + + private static final ImmutableMap RETRY_PARAM_DEFINITIONS; + + static { + ImmutableMap.Builder definitions = ImmutableMap.builder(); + RetrySettings settings = null; + settings = RetrySettings.newBuilder().setRpcTimeoutMultiplier(1.0).build(); + definitions.put("no_retry_params", settings); + RETRY_PARAM_DEFINITIONS = definitions.build(); + } + + protected Builder() { + this(((ClientContext) null)); + } + + protected Builder(@Nullable ClientContext clientContext) { + super(clientContext); + + uploadMediaSettings = ResumableUploadCallSettings.newBuilder(); + + unaryMethodSettingsBuilders = ImmutableList.>of(); + initDefaults(this); + } + + protected Builder(ResumableUploadServiceStubSettings settings) { + super(settings); + + uploadMediaSettings = settings.uploadMediaSettings.toBuilder(); + + unaryMethodSettingsBuilders = ImmutableList.>of(); + } + + private static Builder createDefault() { + Builder builder = new Builder(((ClientContext) null)); + + builder.setTransportChannelProvider(defaultTransportChannelProvider()); + builder.setCredentialsProvider(defaultCredentialsProviderBuilder().build()); + builder.setInternalHeaderProvider(defaultApiClientHeaderProviderBuilder().build()); + builder.setMtlsEndpoint(getDefaultMtlsEndpoint()); + builder.setSwitchToMtlsEndpointAllowed(true); + + return initDefaults(builder); + } + + private static Builder initDefaults(Builder builder) { + builder.uploadMediaSettings().setGlobalTimeout(Duration.ofMillis(5000L)); + + return builder; + } + + /** + * Applies the given settings updater function to all of the unary API methods in this service. + * + *

Note: This method does not support applying settings to streaming methods. + */ + public Builder applyToAllUnaryMethods( + ApiFunction, Void> settingsUpdater) { + super.applyToAllUnaryMethods(unaryMethodSettingsBuilders, settingsUpdater); + return this; + } + + public ImmutableList> unaryMethodSettingsBuilders() { + return unaryMethodSettingsBuilders; + } + + /** + * Returns the builder for the settings used for calls to uploadMedia. + * + *

Note that custom retry settings and headers configured via ApiCallContext apply strictly + * to the initial session initiation request. + */ + public ResumableUploadCallSettings.Builder uploadMediaSettings() { + return uploadMediaSettings; + } + + @Override + public ResumableUploadServiceStubSettings build() throws IOException { + return new ResumableUploadServiceStubSettings(this); + } + } +} diff --git a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/resumableuploadserviceclient/SyncCreateSetCredentialsProvider.golden b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/resumableuploadserviceclient/SyncCreateSetCredentialsProvider.golden new file mode 100644 index 000000000000..61f79a3354cf --- /dev/null +++ b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/resumableuploadserviceclient/SyncCreateSetCredentialsProvider.golden @@ -0,0 +1,45 @@ +/* + * Copyright 2025 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 + * + * https://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.showcase.v1beta1.samples; + +// [START goldensample_generated_ResumableUploadService_Create_SetCredentialsProvider_sync] +import com.google.api.gax.core.FixedCredentialsProvider; +import com.google.showcase.v1beta1.ResumableUploadServiceClient; +import com.google.showcase.v1beta1.ResumableUploadServiceSettings; +import com.google.showcase.v1beta1.myCredentials; + +public class SyncCreateSetCredentialsProvider { + + public static void main(String[] args) throws Exception { + syncCreateSetCredentialsProvider(); + } + + public static void syncCreateSetCredentialsProvider() throws Exception { + // This snippet has been automatically generated and should be regarded as a code template only. + // It will require modifications to work: + // - It may require correct/in-range values for request initialization. + // - It may require specifying regional endpoints when creating the service client as shown in + // https://cloud.google.com/java/docs/setup#configure_endpoints_for_the_client_library + ResumableUploadServiceSettings resumableUploadServiceSettings = + ResumableUploadServiceSettings.newBuilder() + .setCredentialsProvider(FixedCredentialsProvider.create(myCredentials)) + .build(); + ResumableUploadServiceClient resumableUploadServiceClient = + ResumableUploadServiceClient.create(resumableUploadServiceSettings); + } +} +// [END goldensample_generated_ResumableUploadService_Create_SetCredentialsProvider_sync] diff --git a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/resumableuploadserviceclient/SyncCreateSetEndpoint.golden b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/resumableuploadserviceclient/SyncCreateSetEndpoint.golden new file mode 100644 index 000000000000..a41fe16336d8 --- /dev/null +++ b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/resumableuploadserviceclient/SyncCreateSetEndpoint.golden @@ -0,0 +1,42 @@ +/* + * Copyright 2025 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 + * + * https://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.showcase.v1beta1.samples; + +// [START goldensample_generated_ResumableUploadService_Create_SetEndpoint_sync] +import com.google.showcase.v1beta1.ResumableUploadServiceClient; +import com.google.showcase.v1beta1.ResumableUploadServiceSettings; +import com.google.showcase.v1beta1.myEndpoint; + +public class SyncCreateSetEndpoint { + + public static void main(String[] args) throws Exception { + syncCreateSetEndpoint(); + } + + public static void syncCreateSetEndpoint() throws Exception { + // This snippet has been automatically generated and should be regarded as a code template only. + // It will require modifications to work: + // - It may require correct/in-range values for request initialization. + // - It may require specifying regional endpoints when creating the service client as shown in + // https://cloud.google.com/java/docs/setup#configure_endpoints_for_the_client_library + ResumableUploadServiceSettings resumableUploadServiceSettings = + ResumableUploadServiceSettings.newBuilder().setEndpoint(myEndpoint).build(); + ResumableUploadServiceClient resumableUploadServiceClient = + ResumableUploadServiceClient.create(resumableUploadServiceSettings); + } +} +// [END goldensample_generated_ResumableUploadService_Create_SetEndpoint_sync] diff --git a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/resumableuploadserviceclient/SyncEmtpy.golden b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/resumableuploadserviceclient/SyncEmtpy.golden new file mode 100644 index 000000000000..a3713f4609f3 --- /dev/null +++ b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/resumableuploadserviceclient/SyncEmtpy.golden @@ -0,0 +1,38 @@ +/* + * Copyright 2025 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 + * + * https://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.showcase.v1beta1.samples; + +// [START goldensample_generated_ResumableUploadService_Emtpy_sync] +import com.google.showcase.v1beta1.ResumableUploadServiceClient; + +public class SyncEmtpy { + + public static void main(String[] args) throws Exception { + syncEmtpy(); + } + + public static void syncEmtpy() throws Exception { + // This snippet has been automatically generated and should be regarded as a code template only. + // It will require modifications to work: + // - It may require correct/in-range values for request initialization. + // - It may require specifying regional endpoints when creating the service client as shown in + // https://cloud.google.com/java/docs/setup#configure_endpoints_for_the_client_library + try (ResumableUploadServiceClient resumableUploadServiceClient = + ResumableUploadServiceClient.create()) {} + } +} +// [END goldensample_generated_ResumableUploadService_Emtpy_sync] diff --git a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/HttpJsonDescriptorComposerTest.java b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/HttpJsonDescriptorComposerTest.java new file mode 100644 index 000000000000..8037b50d4cbf --- /dev/null +++ b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/HttpJsonDescriptorComposerTest.java @@ -0,0 +1,212 @@ +// Copyright 2026 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.composer.rest; + +import com.google.api.generator.engine.ast.Expr; +import com.google.api.generator.engine.ast.TypeNode; +import com.google.api.generator.engine.writer.JavaWriterVisitor; +import com.google.api.generator.gapic.model.Field; +import com.google.api.generator.gapic.model.GapicContext; +import com.google.api.generator.gapic.model.HttpBindings; +import com.google.api.generator.gapic.model.HttpBindings.HttpBinding; +import com.google.api.generator.gapic.model.Method; +import com.google.api.generator.gapic.model.Service; +import com.google.api.generator.test.protoloader.RestTestProtoLoader; +import static com.google.common.truth.Truth.assertThat; + +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; + +class HttpJsonDescriptorComposerTest { + + private static final String RESUMABLE_UPLOAD_PREFIX = "/resumable/upload"; + private static final String TEST_PATH = "/v1/foo"; + + @Test + void prependPathPrefix_nullPrefix_returnsOriginalPath() { + assertThat(HttpJsonDescriptorComposer.prependPathPrefix(TEST_PATH, null)).isEqualTo(TEST_PATH); + assertThat(HttpJsonDescriptorComposer.prependPathPrefix("v1/foo", null)).isEqualTo("v1/foo"); + } + + @Test + void prependPathPrefix_barePrefix_normalizesSlashes() { + assertThat(HttpJsonDescriptorComposer.prependPathPrefix("v1/foo", "resumable/upload")) + .isEqualTo("/resumable/upload/v1/foo"); + assertThat(HttpJsonDescriptorComposer.prependPathPrefix(TEST_PATH, "resumable/upload")) + .isEqualTo("/resumable/upload/v1/foo"); + } + + @Test + void prependPathPrefix_slashWrappedPrefix_normalizesSlashes() { + assertThat(HttpJsonDescriptorComposer.prependPathPrefix("v1/foo", "/resumable/upload/")) + .isEqualTo("/resumable/upload/v1/foo"); + assertThat(HttpJsonDescriptorComposer.prependPathPrefix(TEST_PATH, "/resumable/upload/")) + .isEqualTo("/resumable/upload/v1/foo"); + } + + @Test + void prependPathPrefix_leadingSlashOnlyPrefix_normalizesSlashes() { + assertThat(HttpJsonDescriptorComposer.prependPathPrefix("v1/foo", RESUMABLE_UPLOAD_PREFIX)) + .isEqualTo("/resumable/upload/v1/foo"); + assertThat(HttpJsonDescriptorComposer.prependPathPrefix(TEST_PATH, RESUMABLE_UPLOAD_PREFIX)) + .isEqualTo("/resumable/upload/v1/foo"); + } + + @Test + void prependPathPrefix_trailingSlashOnlyPrefix_normalizesSlashes() { + assertThat(HttpJsonDescriptorComposer.prependPathPrefix("v1/foo", "resumable/upload/")) + .isEqualTo("/resumable/upload/v1/foo"); + assertThat(HttpJsonDescriptorComposer.prependPathPrefix(TEST_PATH, "resumable/upload/")) + .isEqualTo("/resumable/upload/v1/foo"); + } + + @Test + void getRequestFormatterExpr_withPathPrefix_prependsPrefix() { + GapicContext context = RestTestProtoLoader.instance().parseShowcaseResumableUpload(); + Service service = context.services().get(0); + Method uploadMethod = + service.methods().stream().filter(Method::isResumableUpload).findFirst().get(); + + List exprs = + HttpJsonDescriptorComposer.getRequestFormatterExpr( + uploadMethod, context.restNumericEnumsEnabled(), RESUMABLE_UPLOAD_PREFIX); + + JavaWriterVisitor visitor = new JavaWriterVisitor(); + for (Expr expr : exprs) { + expr.accept(visitor); + } + String code = visitor.write(); + assertThat(code).contains("/resumable/upload/v1beta1/files:upload"); + } + + @Test + void getRequestFormatterExpr_withAdditionalPaths_prependsPrefix() { + GapicContext context = RestTestProtoLoader.instance().parseShowcaseResumableUpload(); + Service service = context.services().get(0); + Method uploadMethod = + service.methods().stream().filter(Method::isResumableUpload).findFirst().get(); + + HttpBindings bindingsWithAdditional = + HttpBindings.builder() + .setHttpVerb(uploadMethod.httpBindings().httpVerb()) + .setPattern(uploadMethod.httpBindings().pattern()) + .setAdditionalPatterns(Arrays.asList("/v1beta1/files:additionalUpload")) + .setPathParameters(uploadMethod.httpBindings().pathParameters()) + .setQueryParameters(uploadMethod.httpBindings().queryParameters()) + .setBodyParameters(uploadMethod.httpBindings().bodyParameters()) + .setIsAsteriskBody(uploadMethod.httpBindings().isAsteriskBody()) + .build(); + Method methodWithAdditional = + uploadMethod.toBuilder().setHttpBindings(bindingsWithAdditional).build(); + + List exprs = + HttpJsonDescriptorComposer.getRequestFormatterExpr( + methodWithAdditional, context.restNumericEnumsEnabled(), RESUMABLE_UPLOAD_PREFIX); + + JavaWriterVisitor visitor = new JavaWriterVisitor(); + for (Expr expr : exprs) { + expr.accept(visitor); + } + String code = visitor.write(); + assertThat(code).contains("/resumable/upload/v1beta1/files:upload"); + assertThat(code).contains("setAdditionalPaths"); + assertThat(code).contains("/resumable/upload/v1beta1/files:additionalUpload"); + } + + @Test + void getHttpMethodTypeExpr_returnsCorrectVerb() { + GapicContext context = RestTestProtoLoader.instance().parseShowcaseResumableUpload(); + Service service = context.services().get(0); + Method uploadMethod = + service.methods().stream().filter(Method::isResumableUpload).findFirst().get(); + + List exprs = HttpJsonDescriptorComposer.getHttpMethodTypeExpr(uploadMethod); + JavaWriterVisitor visitor = new JavaWriterVisitor(); + for (Expr expr : exprs) { + expr.accept(visitor); + } + assertThat(visitor.write()).contains("\"POST\""); + } + + @Test + void getMethodTypeExpr_unaryAndServerStreaming() { + GapicContext context = RestTestProtoLoader.instance().parseEcho(); + Service service = context.services().get(0); + Method echoMethod = + service.methods().stream().filter(m -> m.name().equals("Echo")).findFirst().get(); + Method expandMethod = + service.methods().stream().filter(m -> m.name().equals("Expand")).findFirst().get(); + + List unaryExprs = HttpJsonDescriptorComposer.getMethodTypeExpr(echoMethod); + JavaWriterVisitor unaryVisitor = new JavaWriterVisitor(); + for (Expr expr : unaryExprs) { + expr.accept(unaryVisitor); + } + assertThat(unaryVisitor.write()).contains("MethodType.UNARY"); + + List streamExprs = HttpJsonDescriptorComposer.getMethodTypeExpr(expandMethod); + JavaWriterVisitor streamVisitor = new JavaWriterVisitor(); + for (Expr expr : streamExprs) { + expr.accept(streamVisitor); + } + assertThat(streamVisitor.write()).contains("MethodType.SERVER_STREAMING"); + } + + @Test + void setResponseParserExpr_containsDefaultInstanceAndRegistry() { + GapicContext context = RestTestProtoLoader.instance().parseShowcaseResumableUpload(); + Service service = context.services().get(0); + Method uploadMethod = + service.methods().stream().filter(Method::isResumableUpload).findFirst().get(); + + List exprs = HttpJsonDescriptorComposer.setResponseParserExpr(uploadMethod); + JavaWriterVisitor visitor = new JavaWriterVisitor(); + for (Expr expr : exprs) { + expr.accept(visitor); + } + String code = visitor.write(); + assertThat(code).contains("getDefaultInstance()"); + assertThat(code).contains("setDefaultTypeRegistry(typeRegistry)"); + } + + @Test + void getBindingFieldMethodName_allCases() { + Field repeatedField = + Field.builder().setName("values").setIsRepeated(true).setType(TypeNode.STRING).build(); + HttpBinding repeatedBinding = + HttpBinding.builder().setField(repeatedField).setName("values").build(); + assertThat( + HttpJsonDescriptorComposer.getBindingFieldMethodName(repeatedBinding, 1, 0, "Values")) + .isEqualTo("getValuesList"); + + Field enumField = + Field.builder().setName("enumVal").setIsEnum(true).setType(TypeNode.OBJECT).build(); + HttpBinding enumBinding = HttpBinding.builder().setField(enumField).setName("enumVal").build(); + assertThat(HttpJsonDescriptorComposer.getBindingFieldMethodName(enumBinding, 1, 0, "EnumVal")) + .isEqualTo("getEnumValValue"); + + Field regularField = Field.builder().setName("regularVal").setType(TypeNode.STRING).build(); + HttpBinding regularBinding = + HttpBinding.builder().setField(regularField).setName("regularVal").build(); + assertThat( + HttpJsonDescriptorComposer.getBindingFieldMethodName(regularBinding, 1, 0, "RegularVal")) + .isEqualTo("getRegularVal"); + + assertThat( + HttpJsonDescriptorComposer.getBindingFieldMethodName(regularBinding, 3, 1, "RegularVal")) + .isEqualTo("getRegularVal"); + } +} diff --git a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/HttpJsonServiceStubClassComposerTest.java b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/HttpJsonServiceStubClassComposerTest.java index 0e78c76c3646..25ee5da79ed0 100644 --- a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/HttpJsonServiceStubClassComposerTest.java +++ b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/HttpJsonServiceStubClassComposerTest.java @@ -19,12 +19,9 @@ import com.google.api.CustomHttpPattern; import com.google.api.Http; import com.google.api.HttpRule; -import com.google.api.generator.engine.ast.TypeNode; import com.google.api.generator.engine.writer.JavaWriterVisitor; -import com.google.api.generator.gapic.model.Field; import com.google.api.generator.gapic.model.GapicClass; import com.google.api.generator.gapic.model.GapicContext; -import com.google.api.generator.gapic.model.HttpBindings.HttpBinding; import com.google.api.generator.gapic.model.Service; import com.google.api.generator.test.framework.Assert; import com.google.api.generator.test.framework.GoldenFileWriter; @@ -76,48 +73,6 @@ void generateEchoServiceClasses() { Assert.assertCodeEquals(goldenFilePath, visitor.write()); } - @Test - void getBindingFieldMethodName_shouldReturnGetFieldListIfTheFieldIsInLastPositionAndIsRepeated() { - Field field = - Field.builder() - .setIsRepeated(true) - .setName("doesNotMatter") - .setType(TypeNode.OBJECT) - .build(); - HttpBinding httpBinding = - HttpBinding.builder().setField(field).setName("doesNotMatter").build(); - String actual = composer.getBindingFieldMethodName(httpBinding, 4, 3, "Values"); - Truth.assertThat(actual).isEqualTo("getValuesList"); - } - - @Test - void getBindingFieldMethodName_shouldReturnGetFieldValueIfTheFieldIsInLastPositionAndIsEnum() { - Field field = - Field.builder().setIsEnum(true).setName("doesNotMatter").setType(TypeNode.OBJECT).build(); - HttpBinding httpBinding = - HttpBinding.builder().setField(field).setName("doesNotMatter").build(); - String actual = composer.getBindingFieldMethodName(httpBinding, 4, 3, "Enums"); - Truth.assertThat(actual).isEqualTo("getEnumsValue"); - } - - @Test - void - getBindingFieldMethodName_shouldReturnGetFieldIfTheFieldIsInLastPositionAndNotRepeatedOrEnum() { - Field field = Field.builder().setName("doesNotMatter").setType(TypeNode.OBJECT).build(); - HttpBinding httpBinding = - HttpBinding.builder().setField(field).setName("doesNotMatter").build(); - String actual = composer.getBindingFieldMethodName(httpBinding, 4, 3, "Value"); - Truth.assertThat(actual).isEqualTo("getValue"); - } - - @Test - void getBindingFieldMethodName_shouldReturnGetFieldIfTheFieldIsNotInLastPosition() { - Field field = Field.builder().setName("doesNotMatter").setType(TypeNode.OBJECT).build(); - HttpBinding httpBinding = - HttpBinding.builder().setField(field).setName("doesNotMatter").build(); - String actual = composer.getBindingFieldMethodName(httpBinding, 4, 1, "Value"); - Truth.assertThat(actual).isEqualTo("getValue"); - } @Test void parseOperationsCustomHttpRules_shouldReturnMapIfContextContainsValidServiceYaml() { @@ -224,3 +179,4 @@ void generateHttpJsonServiceStubClass_clientResourceNameExtractor() { Assert.assertEmptySamples(clazz.samples()); } } + diff --git a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/samplecode/ServiceClientCallableMethodSampleComposerTest.java b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/samplecode/ServiceClientCallableMethodSampleComposerTest.java index 2b058aca7c64..574920b031c5 100644 --- a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/samplecode/ServiceClientCallableMethodSampleComposerTest.java +++ b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/samplecode/ServiceClientCallableMethodSampleComposerTest.java @@ -27,9 +27,12 @@ import com.google.api.generator.test.utils.LineFormatter; import com.google.protobuf.Descriptors; import com.google.showcase.v1beta1.EchoOuterClass; +import static com.google.common.truth.Truth.assertThat; + import java.util.Arrays; import java.util.Collections; import java.util.Map; +import java.util.Optional; import org.junit.Assert; import org.junit.jupiter.api.Test; @@ -1184,6 +1187,58 @@ void invalid_composeRegularCallableMethod_noRepeatedResponsePagedRpc() { method, clientType, resourceNames, messageTypes, service)); } + @Test + void resumableUploadMethod_returnsEmpty() { + Descriptors.FileDescriptor echoFileDescriptor = EchoOuterClass.getDescriptor(); + Map resourceNames = Parser.parseResourceNames(echoFileDescriptor); + Map messageTypes = Parser.parseMessages(echoFileDescriptor); + TypeNode clientType = + TypeNode.withReference( + VaporReference.builder() + .setName("EchoClient") + .setPakkage(SHOWCASE_PACKAGE_NAME) + .build()); + TypeNode inputType = + TypeNode.withReference( + VaporReference.builder() + .setName("EchoRequest") + .setPakkage(SHOWCASE_PACKAGE_NAME) + .build()); + TypeNode outputType = + TypeNode.withReference( + VaporReference.builder() + .setName("EchoResponse") + .setPakkage(SHOWCASE_PACKAGE_NAME) + .build()); + Method method = + Method.builder() + .setName("Echo") + .setInputType(inputType) + .setOutputType(outputType) + .setIsResumableUpload(true) + .setMethodSignatures(Collections.emptyList()) + .build(); + Service service = + Service.builder() + .setName("Echo") + .setDefaultHost("localhost:7469") + .setOauthScopes(Arrays.asList("https://www.googleapis.com/auth/cloud-platform")) + .setPakkage(SHOWCASE_PACKAGE_NAME) + .setProtoPakkage(SHOWCASE_PACKAGE_NAME) + .setOriginalJavaPackage(SHOWCASE_PACKAGE_NAME) + .setMethods(Arrays.asList(method)) + .setOverriddenName("Echo") + .build(); + Optional sampleOpt = + ServiceClientCallableMethodSampleComposer.composeRegularCallableMethod( + method, clientType, resourceNames, messageTypes, service); + assertThat(sampleOpt).isEmpty(); + } + + private String writeStatements(Optional sampleOpt) { + return writeStatements(sampleOpt.get()); + } + private String writeStatements(Sample sample) { return SampleCodeWriter.write(sample.body()); } diff --git a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/samplecode/ServiceClientMethodSampleComposerTest.java b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/samplecode/ServiceClientMethodSampleComposerTest.java index d1b93a932ab3..6c23fe443c57 100644 --- a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/samplecode/ServiceClientMethodSampleComposerTest.java +++ b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/samplecode/ServiceClientMethodSampleComposerTest.java @@ -26,9 +26,12 @@ import com.google.api.generator.test.utils.LineFormatter; import com.google.protobuf.Descriptors.FileDescriptor; import com.google.showcase.v1beta1.EchoOuterClass; +import static com.google.common.truth.Truth.assertThat; + import java.util.Arrays; import java.util.Collections; import java.util.Map; +import java.util.Optional; import org.junit.Assert; import org.junit.jupiter.api.Test; @@ -405,6 +408,58 @@ void valid_composeDefaultSample_pureUnaryReturnResponse() { Assert.assertEquals(results, expected); } + @Test + void resumableUploadMethod_returnsEmpty() { + FileDescriptor echoFileDescriptor = EchoOuterClass.getDescriptor(); + Map resourceNames = Parser.parseResourceNames(echoFileDescriptor); + Map messageTypes = Parser.parseMessages(echoFileDescriptor); + TypeNode clientType = + TypeNode.withReference( + VaporReference.builder() + .setName("EchoClient") + .setPakkage(SHOWCASE_PACKAGE_NAME) + .build()); + TypeNode inputType = + TypeNode.withReference( + VaporReference.builder() + .setName("EchoRequest") + .setPakkage(SHOWCASE_PACKAGE_NAME) + .build()); + TypeNode outputType = + TypeNode.withReference( + VaporReference.builder() + .setName("EchoResponse") + .setPakkage(SHOWCASE_PACKAGE_NAME) + .build()); + Method method = + Method.builder() + .setName("Echo") + .setInputType(inputType) + .setOutputType(outputType) + .setIsResumableUpload(true) + .setMethodSignatures(Collections.emptyList()) + .build(); + Service service = + Service.builder() + .setName("Echo") + .setDefaultHost("localhost:7469") + .setOauthScopes(Arrays.asList("https://www.googleapis.com/auth/cloud-platform")) + .setPakkage(SHOWCASE_PACKAGE_NAME) + .setProtoPakkage(SHOWCASE_PACKAGE_NAME) + .setOriginalJavaPackage(SHOWCASE_PACKAGE_NAME) + .setMethods(Arrays.asList(method)) + .setOverriddenName("Echo") + .build(); + Optional sampleOpt = + ServiceClientMethodSampleComposer.composeCanonicalSample( + method, clientType, resourceNames, messageTypes, service); + assertThat(sampleOpt).isEmpty(); + } + + private String writeStatements(Optional sampleOpt) { + return writeStatements(sampleOpt.get()); + } + private String writeStatements(Sample sample) { return SampleCodeWriter.write(sample.body()); }