Skip to content

Commit ba580d9

Browse files
SDK regeneration
1 parent 5606dca commit ba580d9

File tree

131 files changed

+9673
-1727
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

131 files changed

+9673
-1727
lines changed

.fern/metadata.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"cliVersion": "1.0.4",
3+
"generatorName": "fernapi/fern-java-sdk",
4+
"generatorVersion": "3.15.1",
5+
"generatorConfig": {
6+
"publish-to": "central",
7+
"client-class-name": "BaseClient",
8+
"custom-dependencies": [
9+
"api org.apache.commons:commons-text:1.13.1"
10+
]
11+
}
12+
}

README.md

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,22 @@
55

66
The Pipedream Java library provides convenient access to the Pipedream APIs from Java.
77

8+
## Table of Contents
9+
10+
- [Installation](#installation)
11+
- [Usage](#usage)
12+
- [Environments](#environments)
13+
- [Base Url](#base-url)
14+
- [Exception Handling](#exception-handling)
15+
- [Advanced](#advanced)
16+
- [Custom Client](#custom-client)
17+
- [Retries](#retries)
18+
- [Timeouts](#timeouts)
19+
- [Custom Headers](#custom-headers)
20+
- [Access Raw Response Data](#access-raw-response-data)
21+
- [Contributing](#contributing)
22+
- [Reference](#reference)
23+
824
## Installation
925

1026
### Gradle
@@ -25,7 +41,7 @@ Add the dependency in your `pom.xml` file:
2541
<dependency>
2642
<groupId>com.pipedream</groupId>
2743
<artifactId>pipedream</artifactId>
28-
<version>1.1.0</version>
44+
<version>1.1.1</version>
2945
</dependency>
3046
```
3147

@@ -104,7 +120,7 @@ try{
104120

105121
### Custom Client
106122

107-
This SDK is built to work with any instance of `OkHttpClient`. By default, if no client is provided, the SDK will construct one.
123+
This SDK is built to work with any instance of `OkHttpClient`. By default, if no client is provided, the SDK will construct one.
108124
However, you can pass your own client like so:
109125

110126
```java
@@ -123,7 +139,9 @@ BaseClient client = BaseClient
123139

124140
The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long
125141
as the request is deemed retryable and the number of retry attempts has not grown larger than the configured
126-
retry limit (default: 2).
142+
retry limit (default: 2). Before defaulting to exponential backoff, the SDK will first attempt to respect
143+
the `Retry-After` header (as either in seconds or as an HTTP date), and then the `X-RateLimit-Reset` header
144+
(as a Unix timestamp in epoch seconds); failing both of those, it will fall back to exponential backoff.
127145

128146
A request is deemed retryable when any of the following HTTP status codes is returned:
129147

@@ -192,6 +210,19 @@ client.actions().run(
192210
);
193211
```
194212

213+
### Access Raw Response Data
214+
215+
The SDK provides access to raw response data, including headers, through the `withRawResponse()` method.
216+
The `withRawResponse()` method returns a raw client that wraps all responses with `body()` and `headers()` methods.
217+
(A normal client's `response` is identical to a raw client's `response.body()`.)
218+
219+
```java
220+
RunHttpResponse response = client.actions().withRawResponse().run(...);
221+
222+
System.out.println(response.body());
223+
System.out.println(response.headers().get("X-My-Header"));
224+
```
225+
195226
## Contributing
196227

197228
While we value open-source contributions to this SDK, this library is generated programmatically.

build.gradle

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,14 @@ repositories {
1414
}
1515

1616
dependencies {
17-
api 'com.squareup.okhttp3:okhttp:4.12.0'
18-
api 'com.fasterxml.jackson.core:jackson-databind:2.17.2'
19-
api 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.17.2'
20-
api 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.17.2'
17+
api 'com.squareup.okhttp3:okhttp:5.2.1'
18+
api 'com.fasterxml.jackson.core:jackson-databind:2.18.2'
19+
api 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.18.2'
20+
api 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.2'
2121
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
2222
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
2323
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.8.2'
2424
api 'org.apache.commons:commons-text:1.13.1'
25-
testImplementation 'com.squareup.okhttp3:mockwebserver:4.12.0'
2625
}
2726

2827

@@ -49,7 +48,7 @@ java {
4948

5049
group = 'com.pipedream'
5150

52-
version = '1.1.0'
51+
version = '1.1.1'
5352

5453
jar {
5554
dependsOn(":generatePomFileForMavenPublication")
@@ -80,7 +79,7 @@ publishing {
8079
maven(MavenPublication) {
8180
groupId = 'com.pipedream'
8281
artifactId = 'pipedream'
83-
version = '1.1.0'
82+
version = '1.1.1'
8483
from components.java
8584
pom {
8685
name = 'pipedream'

src/main/java/com/pipedream/api/core/BaseClientApiException.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,9 @@ public Map<String, List<String>> headers() {
6565
return this.headers;
6666
}
6767

68-
@java.lang.Override
68+
@Override
6969
public String toString() {
7070
return "BaseClientApiException{" + "message: " + getMessage() + ", statusCode: " + statusCode + ", body: "
71-
+ body + "}";
71+
+ ObjectMappers.stringify(body) + "}";
7272
}
7373
}

src/main/java/com/pipedream/api/core/ClientOptions.java

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ public final class ClientOptions {
2121

2222
private final int timeout;
2323

24+
private final int maxRetries;
25+
2426
private String projectId;
2527

2628
private ClientOptions(
@@ -29,21 +31,23 @@ private ClientOptions(
2931
Map<String, Supplier<String>> headerSuppliers,
3032
OkHttpClient httpClient,
3133
int timeout,
34+
int maxRetries,
3235
String projectId) {
3336
this.environment = environment;
3437
this.headers = new HashMap<>();
3538
this.headers.putAll(headers);
3639
this.headers.putAll(new HashMap<String, String>() {
3740
{
38-
put("User-Agent", "com.pipedream:pipedream/1.1.0");
41+
put("User-Agent", "com.pipedream:pipedream/1.1.1");
3942
put("X-Fern-Language", "JAVA");
4043
put("X-Fern-SDK-Name", "com.pipedream.fern:api-sdk");
41-
put("X-Fern-SDK-Version", "1.1.0");
44+
put("X-Fern-SDK-Version", "1.1.1");
4245
}
4346
});
4447
this.headerSuppliers = headerSuppliers;
4548
this.httpClient = httpClient;
4649
this.timeout = timeout;
50+
this.maxRetries = maxRetries;
4751
this.projectId = projectId;
4852
}
4953

@@ -86,6 +90,10 @@ public OkHttpClient httpClientWithTimeout(RequestOptions requestOptions) {
8690
.build();
8791
}
8892

93+
public int maxRetries() {
94+
return this.maxRetries;
95+
}
96+
8997
public String projectId() {
9098
return this.projectId;
9199
}
@@ -181,7 +189,13 @@ public ClientOptions build() {
181189
this.timeout = Optional.of(httpClient.callTimeoutMillis() / 1000);
182190

183191
return new ClientOptions(
184-
environment, headers, headerSuppliers, httpClient, this.timeout.get(), this.projectId);
192+
environment,
193+
headers,
194+
headerSuppliers,
195+
httpClient,
196+
this.timeout.get(),
197+
this.maxRetries,
198+
this.projectId);
185199
}
186200

187201
/**

src/main/java/com/pipedream/api/core/NullableNonemptyFilter.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ public boolean equals(Object o) {
1414
}
1515

1616
private boolean isOptionalEmpty(Object o) {
17-
return o instanceof Optional && !((Optional<?>) o).isPresent();
17+
if (o instanceof Optional) {
18+
return !((Optional<?>) o).isPresent();
19+
}
20+
return false;
1821
}
1922
}

src/main/java/com/pipedream/api/core/ObjectMappers.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package com.pipedream.api.core;
55

66
import com.fasterxml.jackson.annotation.JsonInclude;
7+
import com.fasterxml.jackson.core.JsonProcessingException;
78
import com.fasterxml.jackson.databind.DeserializationFeature;
89
import com.fasterxml.jackson.databind.ObjectMapper;
910
import com.fasterxml.jackson.databind.SerializationFeature;
@@ -33,4 +34,12 @@ public static String stringify(Object o) {
3334
return o.getClass().getName() + "@" + Integer.toHexString(o.hashCode());
3435
}
3536
}
37+
38+
public static Object parseErrorBody(String responseBodyString) {
39+
try {
40+
return JSON_MAPPER.readValue(responseBodyString, Object.class);
41+
} catch (JsonProcessingException ignored) {
42+
return responseBodyString;
43+
}
44+
}
3645
}

src/main/java/com/pipedream/api/core/RetryInterceptor.java

Lines changed: 110 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,20 @@
55

66
import java.io.IOException;
77
import java.time.Duration;
8+
import java.time.ZonedDateTime;
9+
import java.time.format.DateTimeFormatter;
10+
import java.time.format.DateTimeParseException;
811
import java.util.Optional;
912
import java.util.Random;
1013
import okhttp3.Interceptor;
1114
import okhttp3.Response;
1215

1316
public class RetryInterceptor implements Interceptor {
1417

15-
private static final Duration ONE_SECOND = Duration.ofSeconds(1);
18+
private static final Duration INITIAL_RETRY_DELAY = Duration.ofMillis(1000);
19+
private static final Duration MAX_RETRY_DELAY = Duration.ofMillis(60000);
20+
private static final double JITTER_FACTOR = 0.2;
21+
1622
private final ExponentialBackoff backoff;
1723
private final Random random = new Random();
1824

@@ -32,7 +38,7 @@ public Response intercept(Chain chain) throws IOException {
3238
}
3339

3440
private Response retryChain(Response response, Chain chain) throws IOException {
35-
Optional<Duration> nextBackoff = this.backoff.nextBackoff();
41+
Optional<Duration> nextBackoff = this.backoff.nextBackoff(response);
3642
while (nextBackoff.isPresent()) {
3743
try {
3844
Thread.sleep(nextBackoff.get().toMillis());
@@ -42,7 +48,7 @@ private Response retryChain(Response response, Chain chain) throws IOException {
4248
response.close();
4349
response = chain.proceed(chain.request());
4450
if (shouldRetry(response.code())) {
45-
nextBackoff = this.backoff.nextBackoff();
51+
nextBackoff = this.backoff.nextBackoff(response);
4652
} else {
4753
return response;
4854
}
@@ -51,6 +57,102 @@ private Response retryChain(Response response, Chain chain) throws IOException {
5157
return response;
5258
}
5359

60+
/**
61+
* Calculates the retry delay from response headers, with fallback to exponential backoff.
62+
* Priority: Retry-After > X-RateLimit-Reset > Exponential Backoff
63+
*/
64+
private Duration getRetryDelayFromHeaders(Response response, int retryAttempt) {
65+
// Check for Retry-After header first (RFC 7231), with no jitter
66+
String retryAfter = response.header("Retry-After");
67+
if (retryAfter != null) {
68+
// Parse as number of seconds...
69+
Optional<Duration> secondsDelay = tryParseLong(retryAfter)
70+
.map(seconds -> seconds * 1000)
71+
.filter(delayMs -> delayMs > 0)
72+
.map(delayMs -> Math.min(delayMs, MAX_RETRY_DELAY.toMillis()))
73+
.map(Duration::ofMillis);
74+
if (secondsDelay.isPresent()) {
75+
return secondsDelay.get();
76+
}
77+
78+
// ...or as an HTTP date; both are valid
79+
Optional<Duration> dateDelay = tryParseHttpDate(retryAfter)
80+
.map(resetTime -> resetTime.toInstant().toEpochMilli() - System.currentTimeMillis())
81+
.filter(delayMs -> delayMs > 0)
82+
.map(delayMs -> Math.min(delayMs, MAX_RETRY_DELAY.toMillis()))
83+
.map(Duration::ofMillis);
84+
if (dateDelay.isPresent()) {
85+
return dateDelay.get();
86+
}
87+
}
88+
89+
// Then check for industry-standard X-RateLimit-Reset header, with positive jitter
90+
String rateLimitReset = response.header("X-RateLimit-Reset");
91+
if (rateLimitReset != null) {
92+
// Assume Unix timestamp in epoch seconds
93+
Optional<Duration> rateLimitDelay = tryParseLong(rateLimitReset)
94+
.map(resetTimeSeconds -> (resetTimeSeconds * 1000) - System.currentTimeMillis())
95+
.filter(delayMs -> delayMs > 0)
96+
.map(delayMs -> Math.min(delayMs, MAX_RETRY_DELAY.toMillis()))
97+
.map(this::addPositiveJitter)
98+
.map(Duration::ofMillis);
99+
if (rateLimitDelay.isPresent()) {
100+
return rateLimitDelay.get();
101+
}
102+
}
103+
104+
// Fall back to exponential backoff, with symmetric jitter
105+
long baseDelay = INITIAL_RETRY_DELAY.toMillis() * (1L << retryAttempt); // 2^retryAttempt
106+
long cappedDelay = Math.min(baseDelay, MAX_RETRY_DELAY.toMillis());
107+
return Duration.ofMillis(addSymmetricJitter(cappedDelay));
108+
}
109+
110+
/**
111+
* Attempts to parse a string as a long, returning empty Optional on failure.
112+
*/
113+
private Optional<Long> tryParseLong(String value) {
114+
if (value == null) {
115+
return Optional.empty();
116+
}
117+
try {
118+
return Optional.of(Long.parseLong(value));
119+
} catch (NumberFormatException e) {
120+
return Optional.empty();
121+
}
122+
}
123+
124+
/**
125+
* Attempts to parse a string as an HTTP date (RFC 1123), returning empty Optional on failure.
126+
*/
127+
private Optional<ZonedDateTime> tryParseHttpDate(String value) {
128+
if (value == null) {
129+
return Optional.empty();
130+
}
131+
try {
132+
return Optional.of(ZonedDateTime.parse(value, DateTimeFormatter.RFC_1123_DATE_TIME));
133+
} catch (DateTimeParseException e) {
134+
return Optional.empty();
135+
}
136+
}
137+
138+
/**
139+
* Adds positive jitter (100-120% of original value) to prevent thundering herd.
140+
* Used for X-RateLimit-Reset header delays.
141+
*/
142+
private long addPositiveJitter(long delayMs) {
143+
double jitterMultiplier = 1.0 + (random.nextDouble() * JITTER_FACTOR);
144+
return (long) (delayMs * jitterMultiplier);
145+
}
146+
147+
/**
148+
* Adds symmetric jitter (90-110% of original value) to prevent thundering herd.
149+
* Used for exponential backoff delays.
150+
*/
151+
private long addSymmetricJitter(long delayMs) {
152+
double jitterMultiplier = 1.0 + ((random.nextDouble() - 0.5) * JITTER_FACTOR);
153+
return (long) (delayMs * jitterMultiplier);
154+
}
155+
54156
private static boolean shouldRetry(int statusCode) {
55157
return statusCode == 408 || statusCode == 429 || statusCode >= 500;
56158
}
@@ -65,14 +167,14 @@ private final class ExponentialBackoff {
65167
this.maxNumRetries = maxNumRetries;
66168
}
67169

68-
public Optional<Duration> nextBackoff() {
69-
retryNumber += 1;
70-
if (retryNumber > maxNumRetries) {
170+
public Optional<Duration> nextBackoff(Response response) {
171+
if (retryNumber >= maxNumRetries) {
71172
return Optional.empty();
72173
}
73174

74-
int upperBound = (int) Math.pow(2, retryNumber);
75-
return Optional.of(ONE_SECOND.multipliedBy(random.nextInt(upperBound)));
175+
Duration delay = getRetryDelayFromHeaders(response, retryNumber);
176+
retryNumber += 1;
177+
return Optional.of(delay);
76178
}
77179
}
78180
}

0 commit comments

Comments
 (0)