From c4b78da7f3d8f1edca338b917f43111ca09ab868 Mon Sep 17 00:00:00 2001 From: Armando Belardo <11140328+armandobelardo@users.noreply.github.com> Date: Wed, 20 Mar 2024 10:22:52 -0400 Subject: [PATCH] fix: java request timeout config now works with idempotency headers (#3231) --- generators/java/sdk/CHANGELOG.md | 4 ++ generators/java/sdk/VERSION | 2 +- .../generators/ClientOptionsGenerator.java | 23 +++++++++++ .../generators/RequestOptionsGenerator.java | 28 ++++++------- .../.github/workflows/ci.yml | 23 +++++++++++ .../.mock/definition/api.yml | 5 +++ .../.mock/definition/payment.yml | 27 +++++++++++++ .../.mock/fern.config.json | 1 + .../java-sdk/idempotency-headers/build.gradle | 20 ++++++++++ .../gradle/wrapper/gradle-wrapper.properties | 2 +- seed/java-sdk/idempotency-headers/gradlew.bat | 20 +++++----- .../core/ClientOptions.java | 27 +++++++++++++ .../core/IdempotentRequestOptions.java | 40 ++++++++++++++++++- .../core/RequestOptions.java | 35 +++++++++++++++- .../resources/payment/PaymentClient.java | 15 +++++-- seed/java-sdk/seed.yml | 1 - .../exhaustive/additional_init_exports | 2 +- 17 files changed, 239 insertions(+), 36 deletions(-) create mode 100644 seed/java-sdk/idempotency-headers/.mock/definition/api.yml create mode 100644 seed/java-sdk/idempotency-headers/.mock/definition/payment.yml create mode 100644 seed/java-sdk/idempotency-headers/.mock/fern.config.json diff --git a/generators/java/sdk/CHANGELOG.md b/generators/java/sdk/CHANGELOG.md index ad91fc410c7..ca1cf617945 100644 --- a/generators/java/sdk/CHANGELOG.md +++ b/generators/java/sdk/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.8.6] - 2024-03-20 + +- Fix: the SDK now generates RequestOptions functions for timeouts with IdempotentRequestOptions correctly, previously timeout functions were only taking in regular RequestOptions. This also addresses a JavaPoet issue where fields were being initialized twice across RequestOptions and IdempotentRequestOptions classes, preventing the SDK from generating at all. + ## [0.8.5] - 2024-03-18 - Feat: add in publishing config that allows for signing published artifacts, this is required for publishing to Maven Central. diff --git a/generators/java/sdk/VERSION b/generators/java/sdk/VERSION index bbde4bee23f..120f5321551 100644 --- a/generators/java/sdk/VERSION +++ b/generators/java/sdk/VERSION @@ -1 +1 @@ -0.8.5 \ No newline at end of file +0.8.6 \ No newline at end of file diff --git a/generators/java/sdk/src/main/java/com/fern/java/client/generators/ClientOptionsGenerator.java b/generators/java/sdk/src/main/java/com/fern/java/client/generators/ClientOptionsGenerator.java index 1f1cd01f24c..71dd9036810 100644 --- a/generators/java/sdk/src/main/java/com/fern/java/client/generators/ClientOptionsGenerator.java +++ b/generators/java/sdk/src/main/java/com/fern/java/client/generators/ClientOptionsGenerator.java @@ -148,8 +148,31 @@ public GeneratedClientOptions generateFile() { .addMethod(environmentGetter) .addMethod(headersFromRequestOptions); + TypeName requestOptionsClassName = clientGeneratorContext.getPoetClassNameFactory().getRequestOptionsClassName(); if (headersFromIdempotentRequestOptions.isPresent()) { clientOptionsBuilder.addMethod(headersFromIdempotentRequestOptions.get()); + MethodSpec httpClientWithTimeoutGetter = MethodSpec.methodBuilder("httpClientWithTimeout") + .addModifiers(Modifier.PUBLIC) + .addParameter( + clientGeneratorContext.getPoetClassNameFactory().getIdempotentRequestOptionsClassName(), + REQUEST_OPTIONS_PARAMETER_NAME) + .returns(OKHTTP_CLIENT_FIELD.type) + .beginControlFlow("if ($L == null)", REQUEST_OPTIONS_PARAMETER_NAME) + .addStatement("return this.$L", OKHTTP_CLIENT_FIELD.name) + .endControlFlow() + .addStatement( + "return this.$L.newBuilder().callTimeout($N.getTimeout().get(), $N.getTimeoutTimeUnit())" + + ".connectTimeout(0, $T.SECONDS)" + + ".writeTimeout(0, $T.SECONDS)" + + ".readTimeout(0, $T.SECONDS).build()", + OKHTTP_CLIENT_FIELD.name, + REQUEST_OPTIONS_PARAMETER_NAME, + REQUEST_OPTIONS_PARAMETER_NAME, + TimeUnit.class, + TimeUnit.class, + TimeUnit.class) + .build(); + clientOptionsBuilder.addMethod(httpClientWithTimeoutGetter); } MethodSpec httpClientWithTimeoutGetter = MethodSpec.methodBuilder("httpClientWithTimeout") diff --git a/generators/java/sdk/src/main/java/com/fern/java/client/generators/RequestOptionsGenerator.java b/generators/java/sdk/src/main/java/com/fern/java/client/generators/RequestOptionsGenerator.java index cf4c2d4be8a..f183151b061 100644 --- a/generators/java/sdk/src/main/java/com/fern/java/client/generators/RequestOptionsGenerator.java +++ b/generators/java/sdk/src/main/java/com/fern/java/client/generators/RequestOptionsGenerator.java @@ -57,16 +57,6 @@ public final class RequestOptionsGenerator extends AbstractFileGenerator { Modifier.FINAL) .build(); - private static final FieldSpec.Builder TIMEOUT_FIELD_BUILDER = FieldSpec.builder( - ParameterizedTypeName.get(ClassName.get(Optional.class), TypeName.get(Integer.class)), - "timeout", - Modifier.PRIVATE); - - private static final FieldSpec.Builder TIMEOUT_TIME_UNIT_FIELD_BUILDER = FieldSpec.builder( - ParameterizedTypeName.get(TimeUnit.class), - "timeoutTimeUnit", - Modifier.PRIVATE); - private final List additionalHeaders; private final ClassName builderClassName; @@ -116,10 +106,20 @@ public GeneratedJavaFile generateFile() { fields.add(headerHandler.visitHeader(httpHeader)); } + FieldSpec.Builder timeoutFieldBuilder = FieldSpec.builder( + ParameterizedTypeName.get(ClassName.get(Optional.class), TypeName.get(Integer.class)), + "timeout", + Modifier.PRIVATE); + + FieldSpec.Builder timeoutTimeUnitFieldBuilder = FieldSpec.builder( + ParameterizedTypeName.get(TimeUnit.class), + "timeoutTimeUnit", + Modifier.PRIVATE); + // Add in the other (static) fields for request options createRequestOptionField( "getTimeout", - TIMEOUT_FIELD_BUILDER, + timeoutFieldBuilder, "null", requestOptionsTypeSpec, builderTypeSpec, @@ -127,15 +127,15 @@ public GeneratedJavaFile generateFile() { ); createRequestOptionField( "getTimeoutTimeUnit", - TIMEOUT_TIME_UNIT_FIELD_BUILDER, + timeoutTimeUnitFieldBuilder, "TimeUnit.SECONDS", requestOptionsTypeSpec, builderTypeSpec, fields ); - FieldSpec timeoutField = TIMEOUT_FIELD_BUILDER.build(); - FieldSpec timeUnitField = TIMEOUT_TIME_UNIT_FIELD_BUILDER.build(); + FieldSpec timeoutField = timeoutFieldBuilder.build(); + FieldSpec timeUnitField = timeoutTimeUnitFieldBuilder.build(); builderTypeSpec.addMethod(MethodSpec.methodBuilder(timeoutField.name) .addModifiers(Modifier.PUBLIC) .addParameter(Integer.class, timeoutField.name) diff --git a/seed/java-sdk/idempotency-headers/.github/workflows/ci.yml b/seed/java-sdk/idempotency-headers/.github/workflows/ci.yml index 260533c2a98..8598a73092a 100644 --- a/seed/java-sdk/idempotency-headers/.github/workflows/ci.yml +++ b/seed/java-sdk/idempotency-headers/.github/workflows/ci.yml @@ -36,3 +36,26 @@ jobs: - name: Test run: ./gradlew test + publish: + needs: [ compile, test ] + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up Java + id: setup-jre + uses: actions/setup-java@v1 + with: + java-version: "11" + architecture: x64 + + - name: Publish to maven + run: | + ./gradlew publish + env: + MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} + MAVEN_PUBLISH_REGISTRY_URL: "" diff --git a/seed/java-sdk/idempotency-headers/.mock/definition/api.yml b/seed/java-sdk/idempotency-headers/.mock/definition/api.yml new file mode 100644 index 00000000000..7fbd5ce078b --- /dev/null +++ b/seed/java-sdk/idempotency-headers/.mock/definition/api.yml @@ -0,0 +1,5 @@ +name: idempotency-headers +auth: bearer +idempotency-headers: + Idempotency-Key: string + Idempotency-Expiration: integer diff --git a/seed/java-sdk/idempotency-headers/.mock/definition/payment.yml b/seed/java-sdk/idempotency-headers/.mock/definition/payment.yml new file mode 100644 index 00000000000..d8eb6950460 --- /dev/null +++ b/seed/java-sdk/idempotency-headers/.mock/definition/payment.yml @@ -0,0 +1,27 @@ +types: + Currency: + enum: + - USD + - YEN + +service: + auth: true + base-path: /payment + endpoints: + create: + method: POST + path: "" + idempotent: true + request: + name: CreatePaymentRequest + body: + properties: + amount: integer + currency: Currency + response: uuid + + delete: + method: DELETE + path: /{paymentId} + path-parameters: + paymentId: string diff --git a/seed/java-sdk/idempotency-headers/.mock/fern.config.json b/seed/java-sdk/idempotency-headers/.mock/fern.config.json new file mode 100644 index 00000000000..5c8eb23d7e1 --- /dev/null +++ b/seed/java-sdk/idempotency-headers/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "0.19.0"} \ No newline at end of file diff --git a/seed/java-sdk/idempotency-headers/build.gradle b/seed/java-sdk/idempotency-headers/build.gradle index 26e4017afc4..5a8d8896a28 100644 --- a/seed/java-sdk/idempotency-headers/build.gradle +++ b/seed/java-sdk/idempotency-headers/build.gradle @@ -41,3 +41,23 @@ test { showStandardStreams = true } } +publishing { + publications { + maven(MavenPublication) { + groupId = 'com.fern' + artifactId = 'idempotency-headers' + version = '0.0.1' + from components.java + } + } + repositories { + maven { + url "$System.env.MAVEN_PUBLISH_REGISTRY_URL" + credentials { + username "$System.env.MAVEN_USERNAME" + password "$System.env.MAVEN_PASSWORD" + } + } + } +} + diff --git a/seed/java-sdk/idempotency-headers/gradle/wrapper/gradle-wrapper.properties b/seed/java-sdk/idempotency-headers/gradle/wrapper/gradle-wrapper.properties index 1af9e0930b8..a80b22ce5cf 100644 --- a/seed/java-sdk/idempotency-headers/gradle/wrapper/gradle-wrapper.properties +++ b/seed/java-sdk/idempotency-headers/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/seed/java-sdk/idempotency-headers/gradlew.bat b/seed/java-sdk/idempotency-headers/gradlew.bat index 6689b85beec..7101f8e4676 100644 --- a/seed/java-sdk/idempotency-headers/gradlew.bat +++ b/seed/java-sdk/idempotency-headers/gradlew.bat @@ -43,11 +43,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/seed/java-sdk/idempotency-headers/src/main/java/com/seed/idempotencyHeaders/core/ClientOptions.java b/seed/java-sdk/idempotency-headers/src/main/java/com/seed/idempotencyHeaders/core/ClientOptions.java index b01b657e298..17d51417ac7 100644 --- a/seed/java-sdk/idempotency-headers/src/main/java/com/seed/idempotencyHeaders/core/ClientOptions.java +++ b/seed/java-sdk/idempotency-headers/src/main/java/com/seed/idempotencyHeaders/core/ClientOptions.java @@ -5,6 +5,7 @@ import java.util.HashMap; import java.util.Map; +import java.util.concurrent.TimeUnit; import java.util.function.Supplier; import okhttp3.OkHttpClient; @@ -57,10 +58,36 @@ public Map headers(IdempotentRequestOptions requestOptions) { return values; } + public OkHttpClient httpClientWithTimeout(IdempotentRequestOptions requestOptions) { + if (requestOptions == null) { + return this.httpClient; + } + return this.httpClient + .newBuilder() + .callTimeout(requestOptions.getTimeout().get(), requestOptions.getTimeoutTimeUnit()) + .connectTimeout(0, TimeUnit.SECONDS) + .writeTimeout(0, TimeUnit.SECONDS) + .readTimeout(0, TimeUnit.SECONDS) + .build(); + } + public OkHttpClient httpClient() { return this.httpClient; } + public OkHttpClient httpClientWithTimeout(RequestOptions requestOptions) { + if (requestOptions == null) { + return this.httpClient; + } + return this.httpClient + .newBuilder() + .callTimeout(requestOptions.getTimeout().get(), requestOptions.getTimeoutTimeUnit()) + .connectTimeout(0, TimeUnit.SECONDS) + .writeTimeout(0, TimeUnit.SECONDS) + .readTimeout(0, TimeUnit.SECONDS) + .build(); + } + public static Builder builder() { return new Builder(); } diff --git a/seed/java-sdk/idempotency-headers/src/main/java/com/seed/idempotencyHeaders/core/IdempotentRequestOptions.java b/seed/java-sdk/idempotency-headers/src/main/java/com/seed/idempotencyHeaders/core/IdempotentRequestOptions.java index 3e77681250d..db3374ae623 100644 --- a/seed/java-sdk/idempotency-headers/src/main/java/com/seed/idempotencyHeaders/core/IdempotentRequestOptions.java +++ b/seed/java-sdk/idempotency-headers/src/main/java/com/seed/idempotencyHeaders/core/IdempotentRequestOptions.java @@ -5,6 +5,8 @@ import java.util.HashMap; import java.util.Map; +import java.util.Optional; +import java.util.concurrent.TimeUnit; public final class IdempotentRequestOptions { private final String token; @@ -13,10 +15,29 @@ public final class IdempotentRequestOptions { private final String idempotencyExpiration; - private IdempotentRequestOptions(String token, String idempotencyKey, String idempotencyExpiration) { + private final Optional timeout; + + private final TimeUnit timeoutTimeUnit; + + private IdempotentRequestOptions( + String token, + String idempotencyKey, + String idempotencyExpiration, + Optional timeout, + TimeUnit timeoutTimeUnit) { this.token = token; this.idempotencyKey = idempotencyKey; this.idempotencyExpiration = idempotencyExpiration; + this.timeout = timeout; + this.timeoutTimeUnit = timeoutTimeUnit; + } + + public Optional getTimeout() { + return timeout; + } + + public TimeUnit getTimeoutTimeUnit() { + return timeoutTimeUnit; } public Map getHeaders() { @@ -44,6 +65,10 @@ public static final class Builder { private String idempotencyExpiration = null; + private Optional timeout = null; + + private TimeUnit timeoutTimeUnit = TimeUnit.SECONDS; + public Builder token(String token) { this.token = token; return this; @@ -59,8 +84,19 @@ public Builder idempotencyExpiration(String idempotencyExpiration) { return this; } + public Builder timeout(Integer timeout) { + this.timeout = Optional.of(timeout); + return this; + } + + public Builder timeout(Integer timeout, TimeUnit timeoutTimeUnit) { + this.timeout = Optional.of(timeout); + this.timeoutTimeUnit = timeoutTimeUnit; + return this; + } + public IdempotentRequestOptions build() { - return new IdempotentRequestOptions(token, idempotencyKey, idempotencyExpiration); + return new IdempotentRequestOptions(token, idempotencyKey, idempotencyExpiration, timeout, timeoutTimeUnit); } } } diff --git a/seed/java-sdk/idempotency-headers/src/main/java/com/seed/idempotencyHeaders/core/RequestOptions.java b/seed/java-sdk/idempotency-headers/src/main/java/com/seed/idempotencyHeaders/core/RequestOptions.java index a5ee8c8b5fa..641efd3e8d9 100644 --- a/seed/java-sdk/idempotency-headers/src/main/java/com/seed/idempotencyHeaders/core/RequestOptions.java +++ b/seed/java-sdk/idempotency-headers/src/main/java/com/seed/idempotencyHeaders/core/RequestOptions.java @@ -5,12 +5,28 @@ import java.util.HashMap; import java.util.Map; +import java.util.Optional; +import java.util.concurrent.TimeUnit; public final class RequestOptions { private final String token; - private RequestOptions(String token) { + private final Optional timeout; + + private final TimeUnit timeoutTimeUnit; + + private RequestOptions(String token, Optional timeout, TimeUnit timeoutTimeUnit) { this.token = token; + this.timeout = timeout; + this.timeoutTimeUnit = timeoutTimeUnit; + } + + public Optional getTimeout() { + return timeout; + } + + public TimeUnit getTimeoutTimeUnit() { + return timeoutTimeUnit; } public Map getHeaders() { @@ -28,13 +44,28 @@ public static Builder builder() { public static final class Builder { private String token = null; + private Optional timeout = null; + + private TimeUnit timeoutTimeUnit = TimeUnit.SECONDS; + public Builder token(String token) { this.token = token; return this; } + public Builder timeout(Integer timeout) { + this.timeout = Optional.of(timeout); + return this; + } + + public Builder timeout(Integer timeout, TimeUnit timeoutTimeUnit) { + this.timeout = Optional.of(timeout); + this.timeoutTimeUnit = timeoutTimeUnit; + return this; + } + public RequestOptions build() { - return new RequestOptions(token); + return new RequestOptions(token, timeout, timeoutTimeUnit); } } } diff --git a/seed/java-sdk/idempotency-headers/src/main/java/com/seed/idempotencyHeaders/resources/payment/PaymentClient.java b/seed/java-sdk/idempotency-headers/src/main/java/com/seed/idempotencyHeaders/resources/payment/PaymentClient.java index fc99956dc3c..bb4738e5114 100644 --- a/seed/java-sdk/idempotency-headers/src/main/java/com/seed/idempotencyHeaders/resources/payment/PaymentClient.java +++ b/seed/java-sdk/idempotency-headers/src/main/java/com/seed/idempotencyHeaders/resources/payment/PaymentClient.java @@ -14,6 +14,7 @@ import java.util.UUID; import okhttp3.Headers; import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; @@ -48,8 +49,11 @@ public UUID create(CreatePaymentRequest request, IdempotentRequestOptions reques .addHeader("Content-Type", "application/json") .build(); try { - Response response = - clientOptions.httpClient().newCall(okhttpRequest).execute(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + Response response = client.newCall(okhttpRequest).execute(); if (response.isSuccessful()) { return ObjectMappers.JSON_MAPPER.readValue(response.body().string(), UUID.class); } @@ -77,8 +81,11 @@ public void delete(String paymentId, RequestOptions requestOptions) { .headers(Headers.of(clientOptions.headers(requestOptions))) .build(); try { - Response response = - clientOptions.httpClient().newCall(okhttpRequest).execute(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + Response response = client.newCall(okhttpRequest).execute(); if (response.isSuccessful()) { return; } diff --git a/seed/java-sdk/seed.yml b/seed/java-sdk/seed.yml index 8efa9d501b0..a0c07ddc6d2 100644 --- a/seed/java-sdk/seed.yml +++ b/seed/java-sdk/seed.yml @@ -43,7 +43,6 @@ allowedFailures: - response-property - examples - exhaustive:local-files - - idempotency-headers - trace - undiscriminated-unions - unions diff --git a/seed/python-sdk/exhaustive/additional_init_exports b/seed/python-sdk/exhaustive/additional_init_exports index 39ada5cccb1..69dd8ade604 160000 --- a/seed/python-sdk/exhaustive/additional_init_exports +++ b/seed/python-sdk/exhaustive/additional_init_exports @@ -1 +1 @@ -Subproject commit 39ada5cccb116c211d76966344a2da18c944def8 +Subproject commit 69dd8ade604ae72365018f721c58dec78c1d6b89