Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(java): allow streaming byte requests #4765

Merged
merged 21 commits into from
Sep 27, 2024
6 changes: 5 additions & 1 deletion fern/pages/changelogs/java-sdk/2024-09-26.mdx
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
## 1.2.0
## 2.2.0
**`(feat):`** We now provide endpoint methods for streaming byte array requests in addition to the previous methods accepting
byte array directly.


**`(chore):`** Bump Jackson version to latest (2.17.2)


Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import com.fern.java.client.generators.CoreMediaTypesGenerator;
import com.fern.java.client.generators.EnvironmentGenerator;
import com.fern.java.client.generators.ErrorGenerator;
import com.fern.java.client.generators.FileStreamGenerator;
import com.fern.java.client.generators.InputStreamRequestBodyGenerator;
import com.fern.java.client.generators.OAuthTokenSupplierGenerator;
import com.fern.java.client.generators.RequestOptionsGenerator;
import com.fern.java.client.generators.ResponseBodyInputStreamGenerator;
Expand Down Expand Up @@ -198,6 +200,12 @@ public GeneratedRootClient generateClient(
new ResponseBodyInputStreamGenerator(context);
this.addGeneratedFile(responseBodyInputStreamGenerator.generateFile());

InputStreamRequestBodyGenerator inputStreamRequestBodyGenerator = new InputStreamRequestBodyGenerator(context);
this.addGeneratedFile(inputStreamRequestBodyGenerator.generateFile());

FileStreamGenerator fileStreamGenerator = new FileStreamGenerator(context);
this.addGeneratedFile(fileStreamGenerator.generateFile());

ResponseBodyReaderGenerator responseBodyReaderGenerator = new ResponseBodyReaderGenerator(context);
this.addGeneratedFile(responseBodyReaderGenerator.generateFile());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ public ClassName getErrorClassName(ErrorDeclaration errorDeclaration) {
errorDeclaration.getName().getName().getPascalCase().getSafeName());
}

public ClassName getInputStreamRequestBodyClassName() {
return ClassName.get(getCorePackage(), "InputStreamRequestBody");
}

public ClassName getFileStreamClassName() {
return ClassName.get(getCorePackage(), "FileStream");
}

public ClassName getRetryInterceptorClassName() {
return ClassName.get(getCorePackage(), "RetryInterceptor");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,17 @@ public Result buildClients() {
}
implBuilder.addMethod(httpEndpointMethodSpecs.getNonRequestOptionsMethodSpec());
implBuilder.addMethod(httpEndpointMethodSpecs.getRequestOptionsMethodSpec());
if (httpEndpointMethodSpecs
.getNonRequestOptionsByteArrayMethodSpec()
.isPresent()) {
implBuilder.addMethod(httpEndpointMethodSpecs
.getNonRequestOptionsByteArrayMethodSpec()
.get());
}
if (httpEndpointMethodSpecs.getByteArrayMethodSpec().isPresent()) {
implBuilder.addMethod(
httpEndpointMethodSpecs.getByteArrayMethodSpec().get());
}
generatedWrappedRequests.addAll(httpEndpointMethodSpecFactory.getGeneratedWrappedRequests());
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* (c) Copyright 2023 Birch Solutions Inc. All rights reserved.
*
* 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.fern.java.client.generators;

import com.fern.java.client.ClientGeneratorContext;
import com.fern.java.generators.AbstractFileGenerator;
import com.fern.java.output.GeneratedResourcesJavaFile;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;

public final class FileStreamGenerator extends AbstractFileGenerator {

public FileStreamGenerator(ClientGeneratorContext clientGeneratorContext) {
super(clientGeneratorContext.getPoetClassNameFactory().getFileStreamClassName(), clientGeneratorContext);
}

@Override
public GeneratedResourcesJavaFile generateFile() {
try (InputStream is = FileStreamGenerator.class.getResourceAsStream("/FileStream.java")) {
String contents = new String(is.readAllBytes(), StandardCharsets.UTF_8);
return GeneratedResourcesJavaFile.builder()
.className(className)
.contents(contents)
.build();
} catch (IOException e) {
throw new RuntimeException("Failed to read FileStream.java");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* (c) Copyright 2023 Birch Solutions Inc. All rights reserved.
*
* 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.fern.java.client.generators;

import com.fern.java.client.ClientGeneratorContext;
import com.fern.java.generators.AbstractFileGenerator;
import com.fern.java.output.GeneratedResourcesJavaFile;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;

public final class InputStreamRequestBodyGenerator extends AbstractFileGenerator {

public InputStreamRequestBodyGenerator(ClientGeneratorContext clientGeneratorContext) {
super(
clientGeneratorContext.getPoetClassNameFactory().getInputStreamRequestBodyClassName(),
clientGeneratorContext);
}

@Override
public GeneratedResourcesJavaFile generateFile() {
try (InputStream is =
InputStreamRequestBodyGenerator.class.getResourceAsStream("/InputStreamRequestBody.java")) {
String contents = new String(is.readAllBytes(), StandardCharsets.UTF_8);
return GeneratedResourcesJavaFile.builder()
.className(className)
.contents(contents)
.build();
} catch (IOException e) {
throw new RuntimeException("Failed to read InputStreamRequestBody.java");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
import com.fern.java.output.GeneratedObjectMapper;
import com.fern.java.utils.JavaDocUtils;
import com.fern.java.utils.TypeReferenceUtils.ContainerTypeToUnderlyingType;
import com.squareup.javapoet.ArrayTypeName;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.CodeBlock.Builder;
Expand All @@ -81,6 +82,7 @@
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
Expand Down Expand Up @@ -194,7 +196,7 @@ public final HttpEndpointMethodSpecs generate() {
// Step 2: Add additional parameters
List<ParameterSpec> additionalParameters = additionalParameters();

// Step 3: Add path parameters
// Step 3: Add parameters
endpointMethodBuilder.addParameters(pathParameters);
endpointMethodBuilder.addParameters(additionalParameters);
if (httpEndpoint.getIdempotent()) {
Expand All @@ -203,10 +205,7 @@ public final HttpEndpointMethodSpecs generate() {
REQUEST_OPTIONS_PARAMETER_NAME)
.build());
} else {
endpointMethodBuilder.addParameter(ParameterSpec.builder(
clientGeneratorContext.getPoetClassNameFactory().getRequestOptionsClassName(),
REQUEST_OPTIONS_PARAMETER_NAME)
.build());
endpointMethodBuilder.addParameter(requestOptionsParameterSpec());
}

// Step 4: Get http client initializer
Expand Down Expand Up @@ -309,9 +308,53 @@ public final HttpEndpointMethodSpecs generate() {
bodyParameterSpec.type)
.build();
}
Optional<BytesRequest> maybeBytes = httpEndpoint
.getSdkRequest()
.flatMap(
sdkRequest -> sdkRequest.getShape().getJustRequestBody().flatMap(SdkRequestBodyType::getBytes));
MethodSpec nonRequestOptionsByteArrayMethodSpec = null;
MethodSpec byteArrayMethodSpec = null;

// add direct byte array endpoints for backwards compatibility
if (maybeBytes.isPresent()) {
BytesRequest bytes = maybeBytes.get();
ParameterSpec requestParameterSpec =
getBytesRequestParameterSpec(bytes, sdkRequest().get(), ArrayTypeName.of(byte.class));
MethodSpec byteArrayBaseMethodSpec = MethodSpec.methodBuilder(endpointWithRequestOptions.name)
.addModifiers(Modifier.PUBLIC)
.addParameters(pathParameters)
.addParameters(List.of(requestParameterSpec))
.addJavadoc(endpointWithRequestOptions.javadoc)
.returns(endpointWithRequestOptions.returnType)
.build();
Builder methodBodyBuilder = CodeBlock.builder();
if (!byteArrayBaseMethodSpec.returnType.equals(TypeName.VOID)) {
methodBodyBuilder.add("return ");
}
CodeBlock baseMethodBody = methodBodyBuilder
.add(
"$L(new $T($L)",
endpointWithRequestOptions.name,
ByteArrayInputStream.class,
requestParameterSpec.name)
.build();
nonRequestOptionsByteArrayMethodSpec = byteArrayBaseMethodSpec.toBuilder()
.addStatement(baseMethodBody.toBuilder().add(")").build())
.build();
byteArrayMethodSpec = byteArrayBaseMethodSpec.toBuilder()
.addParameter(requestOptionsParameterSpec())
.addStatement(baseMethodBody.toBuilder()
.add(", $L)", REQUEST_OPTIONS_PARAMETER_NAME)
.build())
.build();
}

return new HttpEndpointMethodSpecs(
endpointWithRequestOptions, endpointWithoutRequestOptions, endpointWithoutRequest);
endpointWithRequestOptions,
endpointWithoutRequestOptions,
endpointWithoutRequest,
byteArrayMethodSpec,
nonRequestOptionsByteArrayMethodSpec);
}

public abstract Optional<SdkRequest> sdkRequest();
Expand All @@ -330,6 +373,24 @@ public abstract CodeBlock getInitializeRequestCodeBlock(
CodeBlock inlineableHttpUrl,
boolean sendContentType);

public final ParameterSpec requestOptionsParameterSpec() {
return ParameterSpec.builder(
clientGeneratorContext.getPoetClassNameFactory().getRequestOptionsClassName(),
REQUEST_OPTIONS_PARAMETER_NAME)
.build();
}

protected final ParameterSpec getBytesRequestParameterSpec(
BytesRequest bytes, SdkRequest sdkRequest, TypeName typeName) {
if (bytes.getIsOptional()) {
typeName = ParameterizedTypeName.get(ClassName.get(Optional.class), typeName);
}
return ParameterSpec.builder(
typeName,
sdkRequest.getRequestParameterName().getCamelCase().getSafeName())
.build();
}

public final CodeBlock getResponseParserCodeBlock() {
CodeBlock.Builder httpResponseBuilder = CodeBlock.builder()
// Default the request client
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,20 @@ public final class HttpEndpointMethodSpecs {
private final MethodSpec nonRequestOptionsMethodSpec;
private final MethodSpec requestOptionsMethodSpec;
private final MethodSpec noRequestBodyMethodSpec;
private final MethodSpec byteArrayMethodSpec;
private final MethodSpec nonRequestOptionsByteArrayMethodSpec;

public HttpEndpointMethodSpecs(
MethodSpec requestOptionsMethodSpec,
MethodSpec nonRequestOptionsMethodSpec,
MethodSpec noRequestBodyMethodSpec) {
MethodSpec noRequestBodyMethodSpec,
MethodSpec byteArrayMethodSpec,
MethodSpec nonRequestOptionsByteArrayMethodSpec) {
this.nonRequestOptionsMethodSpec = nonRequestOptionsMethodSpec;
this.requestOptionsMethodSpec = requestOptionsMethodSpec;
this.noRequestBodyMethodSpec = noRequestBodyMethodSpec;
this.byteArrayMethodSpec = byteArrayMethodSpec;
this.nonRequestOptionsByteArrayMethodSpec = nonRequestOptionsByteArrayMethodSpec;
}

public MethodSpec getNonRequestOptionsMethodSpec() {
Expand All @@ -45,4 +51,12 @@ public MethodSpec getRequestOptionsMethodSpec() {
public Optional<MethodSpec> getNoRequestBodyMethodSpec() {
return Optional.ofNullable(noRequestBodyMethodSpec);
}

public Optional<MethodSpec> getByteArrayMethodSpec() {
return Optional.ofNullable(byteArrayMethodSpec);
}

public Optional<MethodSpec> getNonRequestOptionsByteArrayMethodSpec() {
return Optional.ofNullable(nonRequestOptionsByteArrayMethodSpec);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,17 @@
import com.fern.java.generators.object.EnrichedObjectProperty;
import com.fern.java.output.GeneratedJavaFile;
import com.fern.java.output.GeneratedObjectMapper;
import com.squareup.javapoet.ArrayTypeName;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import java.io.InputStream;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import okhttp3.Headers;
import okhttp3.MediaType;
import okhttp3.Request;
import okhttp3.RequestBody;

Expand Down Expand Up @@ -149,17 +148,7 @@ public ParameterSpec visitTypeReference(HttpRequestBodyReference typeReference)

@Override
public ParameterSpec visitBytes(BytesRequest bytes) {
TypeName typeName = ArrayTypeName.of(byte.class);
if (bytes.getIsOptional()) {
typeName = ParameterizedTypeName.get(ClassName.get(Optional.class), typeName);
}
return ParameterSpec.builder(
typeName,
sdkRequest
.getRequestParameterName()
.getCamelCase()
.getSafeName())
.build();
return getBytesRequestParameterSpec(bytes, sdkRequest, TypeName.get(InputStream.class));
}

@Override
Expand Down Expand Up @@ -212,10 +201,6 @@ public Void visitTypeReference(HttpRequestBodyReference typeReference) {

@Override
public Void visitBytes(BytesRequest bytes) {
builder.add(
".addHeader($S, $S)\n",
AbstractEndpointWriter.CONTENT_TYPE_HEADER,
bytes.getContentType().orElse(AbstractEndpointWriter.APPLICATION_OCTET_STREAM));
return null;
}

Expand Down Expand Up @@ -304,10 +289,12 @@ public Void visitTypeReference(HttpRequestBodyReference _typeReference) {
@Override
public Void visitBytes(BytesRequest bytes) {
codeBlock.addStatement(
"$T $L = $T.create($L)",
"$T $L = new $T($T.parse($S), $L)",
RequestBody.class,
getOkhttpRequestBodyName(),
RequestBody.class,
clientGeneratorContext.getPoetClassNameFactory().getInputStreamRequestBodyClassName(),
MediaType.class,
bytes.getContentType().orElse(APPLICATION_OCTET_STREAM),
sdkRequest.getRequestParameterName().getCamelCase().getSafeName());
return null;
}
Expand Down
Loading
Loading