retryJitterFactor() {
+ return this.retryJitterFactor;
+ }
+
+ /**
+ * Releases resources owned by this client. Only shuts down the underlying OkHttpClient's
+ * dispatcher executor and evicts its connection pool when this client created that
+ * OkHttpClient itself; an OkHttpClient supplied via httpClient is left running, since the
+ * caller owns its lifecycle.
+ *
+ * In-flight calls are not cancelled or awaited, and any request issued after this method
+ * returns fails with a {@code RejectedExecutionException}. Options derived from this one via
+ * {@code Builder.from(...)} share the same dispatcher and connection pool, so closing either
+ * releases them for both. Calling this method more than once has no further effect.
+ *
+ * WebSocket clients created from this client that are still connected are disconnected
+ * first (whether or not the OkHttpClient is owned), so they stop reconnecting before the
+ * dispatcher goes away. Any WebSocket connect or reconnect attempted afterwards fails with
+ * an {@code IllegalStateException} explaining that the client has been closed.
+ */
+ public void close() {
+ if (!this.closed.compareAndSet(false, true)) {
+ return;
+ }
+ for (AutoCloseable webSocket : new ArrayList<>(this.openWebSockets)) {
+ try {
+ webSocket.close();
+ } catch (Exception e) {
+ // best effort; keep closing the remaining sockets and the HTTP client
+ }
+ }
+ this.openWebSockets.clear();
+ if (!this.ownsHttpClient) {
+ return;
+ }
+ this.httpClient.dispatcher().executorService().shutdown();
+ this.httpClient.connectionPool().evictAll();
+ }
+
+ /**
+ * Returns whether {@link #close()} has been called on this client.
+ */
+ public boolean isClosed() {
+ return this.closed.get();
+ }
+
+ /**
+ * Tracks a connected WebSocket client so that {@link #close()} disconnects it.
+ *
+ * @throws IllegalStateException if this client has already been closed
+ */
+ public void registerWebSocket(AutoCloseable webSocket) {
+ this.openWebSockets.add(webSocket);
+ if (this.closed.get()) {
+ this.openWebSockets.remove(webSocket);
+ throw new IllegalStateException("root client has been closed");
+ }
+ }
+
+ /**
+ * Stops tracking a WebSocket client that has been disconnected.
+ */
+ public void unregisterWebSocket(AutoCloseable webSocket) {
+ this.openWebSockets.remove(webSocket);
+ }
+
public Optional logging() {
return this.logging;
}
@@ -111,10 +213,22 @@ public static class Builder {
private int maxRetries = 2;
+ private Optional initialRetryDelayMillis = Optional.empty();
+
+ private Optional maxRetryDelayMillis = Optional.empty();
+
+ private Optional retryJitterFactor = Optional.empty();
+
private Optional timeout = Optional.empty();
private OkHttpClient httpClient = null;
+ private boolean ownsHttpClient = true;
+
+ private AtomicBoolean closed;
+
+ private Set openWebSockets;
+
private Optional logging = Optional.empty();
public Builder environment(Environment environment) {
@@ -123,7 +237,9 @@ public Builder environment(Environment environment) {
}
public Builder addHeader(String key, String value) {
- this.headers.put(key, value);
+ if (value != null) {
+ this.headers.put(key, value);
+ }
return this;
}
@@ -156,8 +272,37 @@ public Builder maxRetries(int maxRetries) {
return this;
}
+ /**
+ * Override the initial delay (in milliseconds) used for exponential backoff between retries. Defaults to 1000 milliseconds.
+ */
+ public Builder initialRetryDelayMillis(long initialRetryDelayMillis) {
+ this.initialRetryDelayMillis = Optional.of(initialRetryDelayMillis);
+ return this;
+ }
+
+ /**
+ * Override the maximum delay (in milliseconds) between retries. Defaults to 60000 milliseconds.
+ */
+ public Builder maxRetryDelayMillis(long maxRetryDelayMillis) {
+ this.maxRetryDelayMillis = Optional.of(maxRetryDelayMillis);
+ return this;
+ }
+
+ /**
+ * Override the jitter factor (between 0 and 1) applied to retry delays. Defaults to 0.2.
+ */
+ public Builder retryJitterFactor(double retryJitterFactor) {
+ this.retryJitterFactor = Optional.of(retryJitterFactor);
+ return this;
+ }
+
+ /**
+ * Sets the underlying OkHttp client. The caller retains ownership of its lifecycle:
+ * close() will not shut down its dispatcher executor or evict its connection pool.
+ */
public Builder httpClient(OkHttpClient httpClient) {
this.httpClient = httpClient;
+ this.ownsHttpClient = httpClient == null;
return this;
}
@@ -185,22 +330,37 @@ public ClientOptions build() {
.connectTimeout(0, TimeUnit.SECONDS)
.writeTimeout(0, TimeUnit.SECONDS)
.readTimeout(0, TimeUnit.SECONDS)
- .addInterceptor(new RetryInterceptor(this.maxRetries));
+ .addInterceptor(new RetryInterceptor(
+ this.maxRetries,
+ this.initialRetryDelayMillis,
+ this.maxRetryDelayMillis,
+ this.retryJitterFactor));
}
Logger logger = Logger.from(this.logging);
httpClientBuilder.addInterceptor(new LoggingInterceptor(logger));
+ httpClientBuilder.addInterceptor(new ResponseDecompressionInterceptor());
this.httpClient = httpClientBuilder.build();
this.timeout = Optional.of(httpClient.callTimeoutMillis() / 1000);
+ AtomicBoolean closedToUse = this.closed != null ? this.closed : new AtomicBoolean(false);
+ Set openWebSocketsToUse =
+ this.openWebSockets != null ? this.openWebSockets : ConcurrentHashMap.newKeySet();
+
return new ClientOptions(
environment,
headers,
headerSuppliers,
httpClient,
+ this.ownsHttpClient,
+ closedToUse,
+ openWebSocketsToUse,
this.timeout.get(),
this.maxRetries,
+ this.initialRetryDelayMillis,
+ this.maxRetryDelayMillis,
+ this.retryJitterFactor,
this.logging);
}
@@ -212,9 +372,15 @@ public static Builder from(ClientOptions clientOptions) {
builder.environment = clientOptions.environment();
builder.timeout = Optional.of(clientOptions.timeout(null));
builder.httpClient = clientOptions.httpClient();
+ builder.ownsHttpClient = clientOptions.ownsHttpClient;
+ builder.closed = clientOptions.closed;
+ builder.openWebSockets = clientOptions.openWebSockets;
builder.headers.putAll(clientOptions.headers);
builder.headerSuppliers.putAll(clientOptions.headerSuppliers);
builder.maxRetries = clientOptions.maxRetries();
+ builder.initialRetryDelayMillis = clientOptions.initialRetryDelayMillis();
+ builder.maxRetryDelayMillis = clientOptions.maxRetryDelayMillis();
+ builder.retryJitterFactor = clientOptions.retryJitterFactor();
builder.logging = clientOptions.logging();
return builder;
}
diff --git a/src/main/java/com/newscatcher/api/core/ConsoleLogger.java b/src/main/java/com/newscatcher/api/core/ConsoleLogger.java
index b0c1252..6d7f416 100644
--- a/src/main/java/com/newscatcher/api/core/ConsoleLogger.java
+++ b/src/main/java/com/newscatcher/api/core/ConsoleLogger.java
@@ -23,6 +23,7 @@ public String format(java.util.logging.LogRecord record) {
return record.getLevel() + " - " + record.getMessage() + System.lineSeparator();
}
});
+ handler.setLevel(Level.ALL);
logger.addHandler(handler);
logger.setUseParentHandlers(false);
logger.setLevel(Level.ALL);
diff --git a/src/main/java/com/newscatcher/api/core/DateTimeDeserializer.java b/src/main/java/com/newscatcher/api/core/DateTimeDeserializer.java
index f9e016d..6c64f22 100644
--- a/src/main/java/com/newscatcher/api/core/DateTimeDeserializer.java
+++ b/src/main/java/com/newscatcher/api/core/DateTimeDeserializer.java
@@ -14,11 +14,13 @@
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalQueries;
/**
- * Custom deserializer that handles converting ISO8601 dates into {@link OffsetDateTime} objects.
+ * Custom deserializer that handles converting date-time strings into {@link OffsetDateTime} objects.
+ * Supports ISO 8601 format, space-separated variants, and RFC 1123 (RFC 2822) format.
*/
class DateTimeDeserializer extends JsonDeserializer {
private static final SimpleModule MODULE;
@@ -42,8 +44,21 @@ public OffsetDateTime deserialize(JsonParser parser, DeserializationContext cont
if (token == JsonToken.VALUE_NUMBER_INT) {
return OffsetDateTime.ofInstant(Instant.ofEpochSecond(parser.getValueAsLong()), ZoneOffset.UTC);
} else {
- TemporalAccessor temporal = DateTimeFormatter.ISO_DATE_TIME.parseBest(
- parser.getValueAsString(), OffsetDateTime::from, LocalDateTime::from);
+ String value = parser.getValueAsString();
+ TemporalAccessor temporal;
+ try {
+ temporal = DateTimeFormatter.ISO_DATE_TIME.parseBest(value, OffsetDateTime::from, LocalDateTime::from);
+ } catch (DateTimeParseException e) {
+ try {
+ // Fall back to space-separated format (e.g. "2025-02-15 10:30:00+00:00").
+ temporal = DateTimeFormatter.ISO_DATE_TIME.parseBest(
+ value.replace(' ', 'T'), OffsetDateTime::from, LocalDateTime::from);
+ } catch (DateTimeParseException e2) {
+ // Fall back to RFC 1123 format (e.g. "Thu, 07 May 2026 14:23:38 +0000").
+ temporal = DateTimeFormatter.RFC_1123_DATE_TIME.parseBest(
+ value, OffsetDateTime::from, LocalDateTime::from);
+ }
+ }
if (temporal.query(TemporalQueries.offset()) == null) {
return LocalDateTime.from(temporal).atOffset(ZoneOffset.UTC);
diff --git a/src/main/java/com/newscatcher/api/core/RequestOptions.java b/src/main/java/com/newscatcher/api/core/RequestOptions.java
index a6cb9f7..53e0980 100644
--- a/src/main/java/com/newscatcher/api/core/RequestOptions.java
+++ b/src/main/java/com/newscatcher/api/core/RequestOptions.java
@@ -16,6 +16,8 @@ public final class RequestOptions {
private final TimeUnit timeoutTimeUnit;
+ private final Optional maxRetries;
+
private final Map headers;
private final Map> headerSuppliers;
@@ -28,6 +30,7 @@ private RequestOptions(
String apiKey,
Optional timeout,
TimeUnit timeoutTimeUnit,
+ Optional maxRetries,
Map headers,
Map> headerSuppliers,
Map queryParameters,
@@ -35,6 +38,7 @@ private RequestOptions(
this.apiKey = apiKey;
this.timeout = timeout;
this.timeoutTimeUnit = timeoutTimeUnit;
+ this.maxRetries = maxRetries;
this.headers = headers;
this.headerSuppliers = headerSuppliers;
this.queryParameters = queryParameters;
@@ -49,6 +53,10 @@ public TimeUnit getTimeoutTimeUnit() {
return timeoutTimeUnit;
}
+ public Optional getMaxRetries() {
+ return maxRetries;
+ }
+
public Map getHeaders() {
Map headers = new HashMap<>();
if (this.apiKey != null) {
@@ -80,6 +88,8 @@ public static class Builder {
private TimeUnit timeoutTimeUnit = TimeUnit.SECONDS;
+ private Optional maxRetries = Optional.empty();
+
private final Map headers = new HashMap<>();
private final Map> headerSuppliers = new HashMap<>();
@@ -104,6 +114,11 @@ public Builder timeout(Integer timeout, TimeUnit timeoutTimeUnit) {
return this;
}
+ public Builder maxRetries(Integer maxRetries) {
+ this.maxRetries = Optional.of(maxRetries);
+ return this;
+ }
+
public Builder addHeader(String key, String value) {
this.headers.put(key, value);
return this;
@@ -129,6 +144,7 @@ public RequestOptions build() {
apiKey,
timeout,
timeoutTimeUnit,
+ maxRetries,
headers,
headerSuppliers,
queryParameters,
diff --git a/src/main/java/com/newscatcher/api/core/ResponseDecompressionInterceptor.java b/src/main/java/com/newscatcher/api/core/ResponseDecompressionInterceptor.java
new file mode 100644
index 0000000..0e75733
--- /dev/null
+++ b/src/main/java/com/newscatcher/api/core/ResponseDecompressionInterceptor.java
@@ -0,0 +1,56 @@
+/**
+ * This file was auto-generated by Fern from our API Definition.
+ */
+package com.newscatcher.api.core;
+
+import java.io.IOException;
+import java.util.zip.Inflater;
+import okhttp3.Interceptor;
+import okhttp3.Response;
+import okhttp3.ResponseBody;
+import okio.GzipSource;
+import okio.InflaterSource;
+import okio.Okio;
+import okio.Source;
+
+/**
+ * Decompresses gzip and deflate encoded response bodies. OkHttp only performs
+ * transparent decompression when it adds the Accept-Encoding header itself, so
+ * responses to requests with an explicitly set Accept-Encoding header would
+ * otherwise be returned as raw compressed bytes.
+ */
+public class ResponseDecompressionInterceptor implements Interceptor {
+
+ @Override
+ public Response intercept(Chain chain) throws IOException {
+ return decompress(chain.proceed(chain.request()));
+ }
+
+ private Response decompress(Response response) {
+ ResponseBody body = response.body();
+ if (body == null) {
+ return response;
+ }
+ String encoding = response.header("Content-Encoding");
+ if (encoding == null) {
+ return response;
+ }
+ Source decompressedSource;
+ if (encoding.equalsIgnoreCase("gzip") || encoding.equalsIgnoreCase("x-gzip")) {
+ decompressedSource = new GzipSource(body.source());
+ } else if (encoding.equalsIgnoreCase("deflate")) {
+ decompressedSource = new InflaterSource(body.source(), new Inflater());
+ } else {
+ return response;
+ }
+ ResponseBody decompressedBody = ResponseBody.create(body.contentType(), -1L, Okio.buffer(decompressedSource));
+ return response.newBuilder()
+ .headers(response.headers()
+ .newBuilder()
+ .removeAll("Content-Encoding")
+ .removeAll("Content-Length")
+ .build())
+ .body(decompressedBody)
+ .build();
+ }
+}
diff --git a/src/main/java/com/newscatcher/api/core/RetryInterceptor.java b/src/main/java/com/newscatcher/api/core/RetryInterceptor.java
index 6a782b7..3855dc7 100644
--- a/src/main/java/com/newscatcher/api/core/RetryInterceptor.java
+++ b/src/main/java/com/newscatcher/api/core/RetryInterceptor.java
@@ -10,44 +10,100 @@
import java.time.format.DateTimeParseException;
import java.util.Optional;
import java.util.Random;
+import okhttp3.Call;
import okhttp3.Interceptor;
+import okhttp3.Request;
import okhttp3.Response;
+import okhttp3.ResponseBody;
+import okio.AsyncTimeout;
+import okio.Timeout;
public class RetryInterceptor implements Interceptor {
- private static final Duration INITIAL_RETRY_DELAY = Duration.ofMillis(1000);
- private static final Duration MAX_RETRY_DELAY = Duration.ofMillis(60000);
- private static final double JITTER_FACTOR = 0.2;
+ private static final Duration DEFAULT_INITIAL_RETRY_DELAY = Duration.ofMillis(1000);
+ private static final Duration DEFAULT_MAX_RETRY_DELAY = Duration.ofMillis(60000);
+ private static final double DEFAULT_JITTER_FACTOR = 0.2;
private final int maxRetries;
+ private final Duration initialRetryDelay;
+ private final Duration maxRetryDelay;
+ private final double jitterFactor;
private final Random random = new Random();
public RetryInterceptor(int maxRetries) {
+ this(maxRetries, Optional.empty(), Optional.empty(), Optional.empty());
+ }
+
+ public RetryInterceptor(
+ int maxRetries,
+ Optional initialRetryDelayMillis,
+ Optional maxRetryDelayMillis,
+ Optional jitterFactor) {
+ initialRetryDelayMillis.ifPresent(delay -> {
+ if (delay < 0) {
+ throw new IllegalArgumentException("initialRetryDelayMillis must be non-negative");
+ }
+ });
+ maxRetryDelayMillis.ifPresent(delay -> {
+ if (delay < 0) {
+ throw new IllegalArgumentException("maxRetryDelayMillis must be non-negative");
+ }
+ });
+ jitterFactor.ifPresent(factor -> {
+ if (factor < 0 || factor > 1) {
+ throw new IllegalArgumentException("jitterFactor must be between 0 and 1");
+ }
+ });
this.maxRetries = maxRetries;
+ this.initialRetryDelay = initialRetryDelayMillis.map(Duration::ofMillis).orElse(DEFAULT_INITIAL_RETRY_DELAY);
+ this.maxRetryDelay = maxRetryDelayMillis.map(Duration::ofMillis).orElse(DEFAULT_MAX_RETRY_DELAY);
+ this.jitterFactor = jitterFactor.orElse(DEFAULT_JITTER_FACTOR);
}
@Override
public Response intercept(Chain chain) throws IOException {
- Response response = chain.proceed(chain.request());
+ Request request = chain.request();
+ int effectiveMaxRetries = resolveMaxRetries(request);
+ Response response = chain.proceed(request);
if (shouldRetry(response.code())) {
- return retryChain(response, chain);
+ return retryChain(response, chain, effectiveMaxRetries);
}
return response;
}
- private Response retryChain(Response response, Chain chain) throws IOException {
- ExponentialBackoff backoff = new ExponentialBackoff(this.maxRetries);
+ private int resolveMaxRetries(Request request) {
+ MaxRetriesOverride override = request.tag(MaxRetriesOverride.class);
+ return override != null ? override.getValue() : this.maxRetries;
+ }
+
+ private Response retryChain(Response response, Chain chain, int maxRetries) throws IOException {
+ ExponentialBackoff backoff = new ExponentialBackoff(maxRetries);
Optional nextBackoff = backoff.nextBackoff(response);
while (nextBackoff.isPresent()) {
+ response = bufferResponse(response);
+ Optional callTimeout = suspendCallTimeout(chain);
try {
Thread.sleep(nextBackoff.get().toMillis());
} catch (InterruptedException e) {
+ response.close();
throw new IOException("Interrupted while trying request", e);
+ } finally {
+ callTimeout.ifPresent(AsyncTimeout::enter);
+ }
+ Response nextResponse;
+ try {
+ nextResponse = chain.proceed(chain.request());
+ } catch (IOException e) {
+ // A retry attempt failed with a transport error (connection reset, unreachable host, ...). The
+ // error is intentionally dropped: the response already received from the API is more actionable.
+ // Note that a call-timeout expiry or an explicit cancel() also ends up here, but OkHttp then
+ // discards whatever the interceptor chain returns and reports the cancellation itself.
+ return response;
}
response.close();
- response = chain.proceed(chain.request());
+ response = nextResponse;
if (shouldRetry(response.code())) {
nextBackoff = backoff.nextBackoff(response);
} else {
@@ -58,6 +114,51 @@ private Response retryChain(Response response, Chain chain) throws IOException {
return response;
}
+ /**
+ * Pauses the OkHttp call timeout so that it applies to each attempt individually rather than to the whole
+ * retry loop: the backoff wait (e.g. a {@code Retry-After} the size of the call timeout) does not consume the
+ * budget of the next attempt, and each attempt starts with a fresh budget once {@link AsyncTimeout#enter()}
+ * is called again.
+ *
+ * {@link AsyncTimeout#exit()} and {@link AsyncTimeout#enter()} must stay strictly paired: {@code exit()} on
+ * a timeout that is not armed is a no-op, but {@code enter()} on one that is already armed throws
+ * {@code IllegalStateException}. Every caller must re-enter the returned timeout exactly once, on all paths.
+ */
+ private static Optional suspendCallTimeout(Chain chain) {
+ Call call = chain.call();
+ if (call == null) {
+ return Optional.empty();
+ }
+ Timeout timeout = call.timeout();
+ if (!(timeout instanceof AsyncTimeout)) {
+ return Optional.empty();
+ }
+ AsyncTimeout callTimeout = (AsyncTimeout) timeout;
+ callTimeout.exit();
+ return Optional.of(callTimeout);
+ }
+
+ /**
+ * Reads the body into memory so the response stays usable after the underlying connection
+ * is released or cancelled by a later attempt.
+ */
+ private static Response bufferResponse(Response response) {
+ ResponseBody body = response.body();
+ if (body == null) {
+ return response;
+ }
+ try {
+ byte[] bytes = body.bytes();
+ Response buffered = response.newBuilder()
+ .body(ResponseBody.create(bytes, body.contentType()))
+ .build();
+ response.close();
+ return buffered;
+ } catch (IOException e) {
+ return response;
+ }
+ }
+
/**
* Calculates the retry delay from response headers, with fallback to exponential backoff.
* Priority: Retry-After > X-RateLimit-Reset > Exponential Backoff
@@ -70,7 +171,7 @@ private Duration getRetryDelayFromHeaders(Response response, int retryAttempt) {
Optional secondsDelay = tryParseLong(retryAfter)
.map(seconds -> seconds * 1000)
.filter(delayMs -> delayMs > 0)
- .map(delayMs -> Math.min(delayMs, MAX_RETRY_DELAY.toMillis()))
+ .map(delayMs -> Math.min(delayMs, maxRetryDelay.toMillis()))
.map(Duration::ofMillis);
if (secondsDelay.isPresent()) {
return secondsDelay.get();
@@ -80,7 +181,7 @@ private Duration getRetryDelayFromHeaders(Response response, int retryAttempt) {
Optional dateDelay = tryParseHttpDate(retryAfter)
.map(resetTime -> resetTime.toInstant().toEpochMilli() - System.currentTimeMillis())
.filter(delayMs -> delayMs > 0)
- .map(delayMs -> Math.min(delayMs, MAX_RETRY_DELAY.toMillis()))
+ .map(delayMs -> Math.min(delayMs, maxRetryDelay.toMillis()))
.map(Duration::ofMillis);
if (dateDelay.isPresent()) {
return dateDelay.get();
@@ -94,7 +195,7 @@ private Duration getRetryDelayFromHeaders(Response response, int retryAttempt) {
Optional rateLimitDelay = tryParseLong(rateLimitReset)
.map(resetTimeSeconds -> (resetTimeSeconds * 1000) - System.currentTimeMillis())
.filter(delayMs -> delayMs > 0)
- .map(delayMs -> Math.min(delayMs, MAX_RETRY_DELAY.toMillis()))
+ .map(delayMs -> Math.min(delayMs, maxRetryDelay.toMillis()))
.map(this::addPositiveJitter)
.map(Duration::ofMillis);
if (rateLimitDelay.isPresent()) {
@@ -103,8 +204,15 @@ private Duration getRetryDelayFromHeaders(Response response, int retryAttempt) {
}
// Fall back to exponential backoff, with symmetric jitter
- long baseDelay = INITIAL_RETRY_DELAY.toMillis() * (1L << retryAttempt); // 2^retryAttempt
- long cappedDelay = Math.min(baseDelay, MAX_RETRY_DELAY.toMillis());
+ long initialDelayMillis = initialRetryDelay.toMillis();
+ long maxDelayMillis = maxRetryDelay.toMillis();
+ long cappedDelay;
+ if (retryAttempt >= Long.SIZE - 1 || initialDelayMillis > (maxDelayMillis >> retryAttempt)) {
+ // initialDelayMillis * 2^retryAttempt would exceed maxDelayMillis (or overflow)
+ cappedDelay = maxDelayMillis;
+ } else {
+ cappedDelay = Math.min(initialDelayMillis << retryAttempt, maxDelayMillis); // 2^retryAttempt
+ }
return Duration.ofMillis(addSymmetricJitter(cappedDelay));
}
@@ -141,7 +249,7 @@ private Optional tryParseHttpDate(String value) {
* Used for X-RateLimit-Reset header delays.
*/
private long addPositiveJitter(long delayMs) {
- double jitterMultiplier = 1.0 + (random.nextDouble() * JITTER_FACTOR);
+ double jitterMultiplier = 1.0 + (random.nextDouble() * jitterFactor);
return (long) (delayMs * jitterMultiplier);
}
@@ -150,7 +258,7 @@ private long addPositiveJitter(long delayMs) {
* Used for exponential backoff delays.
*/
private long addSymmetricJitter(long delayMs) {
- double jitterMultiplier = 1.0 + ((random.nextDouble() - 0.5) * JITTER_FACTOR);
+ double jitterMultiplier = 1.0 + ((random.nextDouble() - 0.5) * jitterFactor);
return (long) (delayMs * jitterMultiplier);
}
@@ -158,6 +266,23 @@ private static boolean shouldRetry(int statusCode) {
return statusCode == 408 || statusCode == 429 || statusCode >= 500;
}
+ /**
+ * Per-request override carried on the OkHttp {@link Request} as a tag.
+ * When present, the interceptor uses this value instead of the client-wide
+ * {@code maxRetries} configured at construction time.
+ */
+ public static final class MaxRetriesOverride {
+ private final int value;
+
+ public MaxRetriesOverride(int value) {
+ this.value = value;
+ }
+
+ public int getValue() {
+ return value;
+ }
+ }
+
private final class ExponentialBackoff {
private final int maxNumRetries;
diff --git a/src/main/java/com/newscatcher/api/resources/aggregationcount/AsyncRawAggregationCountClient.java b/src/main/java/com/newscatcher/api/resources/aggregationcount/AsyncRawAggregationCountClient.java
index ea9757f..3a649c6 100644
--- a/src/main/java/com/newscatcher/api/resources/aggregationcount/AsyncRawAggregationCountClient.java
+++ b/src/main/java/com/newscatcher/api/resources/aggregationcount/AsyncRawAggregationCountClient.java
@@ -12,6 +12,7 @@
import com.newscatcher.api.core.ObjectMappers;
import com.newscatcher.api.core.QueryStringMapper;
import com.newscatcher.api.core.RequestOptions;
+import com.newscatcher.api.core.RetryInterceptor;
import com.newscatcher.api.errors.BadRequestError;
import com.newscatcher.api.errors.ForbiddenError;
import com.newscatcher.api.errors.InternalServerError;
@@ -269,6 +270,15 @@ public CompletableFuture
if (requestOptions != null && requestOptions.getTimeout().isPresent()) {
client = clientOptions.httpClientWithTimeout(requestOptions);
}
+ if (requestOptions != null && requestOptions.getMaxRetries().isPresent()) {
+ okhttpRequest = okhttpRequest
+ .newBuilder()
+ .tag(
+ RetryInterceptor.MaxRetriesOverride.class,
+ new RetryInterceptor.MaxRetriesOverride(
+ requestOptions.getMaxRetries().get()))
+ .build();
+ }
CompletableFuture> future = new CompletableFuture<>();
client.newCall(okhttpRequest).enqueue(new Callback() {
@Override
@@ -327,6 +337,9 @@ public void onResponse(@NotNull Call call, @NotNull Response response) throws IO
future.completeExceptionally(new NewscatcherApiApiException(
"Error with status code " + response.code(), response.code(), errorBody, response));
return;
+ } catch (JsonProcessingException e) {
+ future.completeExceptionally(
+ new NewscatcherApiException("Failed to deserialize response: " + e.getMessage(), e));
} catch (IOException e) {
future.completeExceptionally(
new NewscatcherApiException("Network error executing HTTP request", e));
@@ -380,6 +393,15 @@ public CompletableFuture> future = new CompletableFuture<>();
client.newCall(okhttpRequest).enqueue(new Callback() {
@Override
@@ -438,6 +460,9 @@ public void onResponse(@NotNull Call call, @NotNull Response response) throws IO
future.completeExceptionally(new NewscatcherApiApiException(
"Error with status code " + response.code(), response.code(), errorBody, response));
return;
+ } catch (JsonProcessingException e) {
+ future.completeExceptionally(
+ new NewscatcherApiException("Failed to deserialize response: " + e.getMessage(), e));
} catch (IOException e) {
future.completeExceptionally(
new NewscatcherApiException("Network error executing HTTP request", e));
diff --git a/src/main/java/com/newscatcher/api/resources/aggregationcount/RawAggregationCountClient.java b/src/main/java/com/newscatcher/api/resources/aggregationcount/RawAggregationCountClient.java
index 90a62e0..8e9cbb9 100644
--- a/src/main/java/com/newscatcher/api/resources/aggregationcount/RawAggregationCountClient.java
+++ b/src/main/java/com/newscatcher/api/resources/aggregationcount/RawAggregationCountClient.java
@@ -12,6 +12,7 @@
import com.newscatcher.api.core.ObjectMappers;
import com.newscatcher.api.core.QueryStringMapper;
import com.newscatcher.api.core.RequestOptions;
+import com.newscatcher.api.core.RetryInterceptor;
import com.newscatcher.api.errors.BadRequestError;
import com.newscatcher.api.errors.ForbiddenError;
import com.newscatcher.api.errors.InternalServerError;
@@ -264,6 +265,15 @@ public NewscatcherApiHttpResponse get(
if (requestOptions != null && requestOptions.getTimeout().isPresent()) {
client = clientOptions.httpClientWithTimeout(requestOptions);
}
+ if (requestOptions != null && requestOptions.getMaxRetries().isPresent()) {
+ okhttpRequest = okhttpRequest
+ .newBuilder()
+ .tag(
+ RetryInterceptor.MaxRetriesOverride.class,
+ new RetryInterceptor.MaxRetriesOverride(
+ requestOptions.getMaxRetries().get()))
+ .build();
+ }
try (Response response = client.newCall(okhttpRequest).execute()) {
ResponseBody responseBody = response.body();
String responseBodyString = responseBody != null ? responseBody.string() : "{}";
@@ -302,6 +312,8 @@ public NewscatcherApiHttpResponse get(
Object errorBody = ObjectMappers.parseErrorBody(responseBodyString);
throw new NewscatcherApiApiException(
"Error with status code " + response.code(), response.code(), errorBody, response);
+ } catch (JsonProcessingException e) {
+ throw new NewscatcherApiException("Failed to deserialize response: " + e.getMessage(), e);
} catch (IOException e) {
throw new NewscatcherApiException("Network error executing HTTP request", e);
}
@@ -345,6 +357,15 @@ public NewscatcherApiHttpResponse post(
if (requestOptions != null && requestOptions.getTimeout().isPresent()) {
client = clientOptions.httpClientWithTimeout(requestOptions);
}
+ if (requestOptions != null && requestOptions.getMaxRetries().isPresent()) {
+ okhttpRequest = okhttpRequest
+ .newBuilder()
+ .tag(
+ RetryInterceptor.MaxRetriesOverride.class,
+ new RetryInterceptor.MaxRetriesOverride(
+ requestOptions.getMaxRetries().get()))
+ .build();
+ }
try (Response response = client.newCall(okhttpRequest).execute()) {
ResponseBody responseBody = response.body();
String responseBodyString = responseBody != null ? responseBody.string() : "{}";
@@ -383,6 +404,8 @@ public NewscatcherApiHttpResponse post(
Object errorBody = ObjectMappers.parseErrorBody(responseBodyString);
throw new NewscatcherApiApiException(
"Error with status code " + response.code(), response.code(), errorBody, response);
+ } catch (JsonProcessingException e) {
+ throw new NewscatcherApiException("Failed to deserialize response: " + e.getMessage(), e);
} catch (IOException e) {
throw new NewscatcherApiException("Network error executing HTTP request", e);
}
diff --git a/src/main/java/com/newscatcher/api/resources/authors/AsyncRawAuthorsClient.java b/src/main/java/com/newscatcher/api/resources/authors/AsyncRawAuthorsClient.java
index 45ac4e0..5d30d62 100644
--- a/src/main/java/com/newscatcher/api/resources/authors/AsyncRawAuthorsClient.java
+++ b/src/main/java/com/newscatcher/api/resources/authors/AsyncRawAuthorsClient.java
@@ -12,6 +12,7 @@
import com.newscatcher.api.core.ObjectMappers;
import com.newscatcher.api.core.QueryStringMapper;
import com.newscatcher.api.core.RequestOptions;
+import com.newscatcher.api.core.RetryInterceptor;
import com.newscatcher.api.errors.BadRequestError;
import com.newscatcher.api.errors.ForbiddenError;
import com.newscatcher.api.errors.InternalServerError;
@@ -267,6 +268,15 @@ public CompletableFuture> get(
if (requestOptions != null && requestOptions.getTimeout().isPresent()) {
client = clientOptions.httpClientWithTimeout(requestOptions);
}
+ if (requestOptions != null && requestOptions.getMaxRetries().isPresent()) {
+ okhttpRequest = okhttpRequest
+ .newBuilder()
+ .tag(
+ RetryInterceptor.MaxRetriesOverride.class,
+ new RetryInterceptor.MaxRetriesOverride(
+ requestOptions.getMaxRetries().get()))
+ .build();
+ }
CompletableFuture> future = new CompletableFuture<>();
client.newCall(okhttpRequest).enqueue(new Callback() {
@Override
@@ -324,6 +334,9 @@ public void onResponse(@NotNull Call call, @NotNull Response response) throws IO
future.completeExceptionally(new NewscatcherApiApiException(
"Error with status code " + response.code(), response.code(), errorBody, response));
return;
+ } catch (JsonProcessingException e) {
+ future.completeExceptionally(
+ new NewscatcherApiException("Failed to deserialize response: " + e.getMessage(), e));
} catch (IOException e) {
future.completeExceptionally(
new NewscatcherApiException("Network error executing HTTP request", e));
@@ -376,6 +389,15 @@ public CompletableFuture> post(
if (requestOptions != null && requestOptions.getTimeout().isPresent()) {
client = clientOptions.httpClientWithTimeout(requestOptions);
}
+ if (requestOptions != null && requestOptions.getMaxRetries().isPresent()) {
+ okhttpRequest = okhttpRequest
+ .newBuilder()
+ .tag(
+ RetryInterceptor.MaxRetriesOverride.class,
+ new RetryInterceptor.MaxRetriesOverride(
+ requestOptions.getMaxRetries().get()))
+ .build();
+ }
CompletableFuture> future = new CompletableFuture<>();
client.newCall(okhttpRequest).enqueue(new Callback() {
@Override
@@ -433,6 +455,9 @@ public void onResponse(@NotNull Call call, @NotNull Response response) throws IO
future.completeExceptionally(new NewscatcherApiApiException(
"Error with status code " + response.code(), response.code(), errorBody, response));
return;
+ } catch (JsonProcessingException e) {
+ future.completeExceptionally(
+ new NewscatcherApiException("Failed to deserialize response: " + e.getMessage(), e));
} catch (IOException e) {
future.completeExceptionally(
new NewscatcherApiException("Network error executing HTTP request", e));
diff --git a/src/main/java/com/newscatcher/api/resources/authors/RawAuthorsClient.java b/src/main/java/com/newscatcher/api/resources/authors/RawAuthorsClient.java
index 44c3ee6..607c8d3 100644
--- a/src/main/java/com/newscatcher/api/resources/authors/RawAuthorsClient.java
+++ b/src/main/java/com/newscatcher/api/resources/authors/RawAuthorsClient.java
@@ -12,6 +12,7 @@
import com.newscatcher.api.core.ObjectMappers;
import com.newscatcher.api.core.QueryStringMapper;
import com.newscatcher.api.core.RequestOptions;
+import com.newscatcher.api.core.RetryInterceptor;
import com.newscatcher.api.errors.BadRequestError;
import com.newscatcher.api.errors.ForbiddenError;
import com.newscatcher.api.errors.InternalServerError;
@@ -263,6 +264,15 @@ public NewscatcherApiHttpResponse get(
if (requestOptions != null && requestOptions.getTimeout().isPresent()) {
client = clientOptions.httpClientWithTimeout(requestOptions);
}
+ if (requestOptions != null && requestOptions.getMaxRetries().isPresent()) {
+ okhttpRequest = okhttpRequest
+ .newBuilder()
+ .tag(
+ RetryInterceptor.MaxRetriesOverride.class,
+ new RetryInterceptor.MaxRetriesOverride(
+ requestOptions.getMaxRetries().get()))
+ .build();
+ }
try (Response response = client.newCall(okhttpRequest).execute()) {
ResponseBody responseBody = response.body();
String responseBodyString = responseBody != null ? responseBody.string() : "{}";
@@ -300,6 +310,8 @@ public NewscatcherApiHttpResponse get(
Object errorBody = ObjectMappers.parseErrorBody(responseBodyString);
throw new NewscatcherApiApiException(
"Error with status code " + response.code(), response.code(), errorBody, response);
+ } catch (JsonProcessingException e) {
+ throw new NewscatcherApiException("Failed to deserialize response: " + e.getMessage(), e);
} catch (IOException e) {
throw new NewscatcherApiException("Network error executing HTTP request", e);
}
@@ -343,6 +355,15 @@ public NewscatcherApiHttpResponse post(
if (requestOptions != null && requestOptions.getTimeout().isPresent()) {
client = clientOptions.httpClientWithTimeout(requestOptions);
}
+ if (requestOptions != null && requestOptions.getMaxRetries().isPresent()) {
+ okhttpRequest = okhttpRequest
+ .newBuilder()
+ .tag(
+ RetryInterceptor.MaxRetriesOverride.class,
+ new RetryInterceptor.MaxRetriesOverride(
+ requestOptions.getMaxRetries().get()))
+ .build();
+ }
try (Response response = client.newCall(okhttpRequest).execute()) {
ResponseBody responseBody = response.body();
String responseBodyString = responseBody != null ? responseBody.string() : "{}";
@@ -380,6 +401,8 @@ public NewscatcherApiHttpResponse post(
Object errorBody = ObjectMappers.parseErrorBody(responseBodyString);
throw new NewscatcherApiApiException(
"Error with status code " + response.code(), response.code(), errorBody, response);
+ } catch (JsonProcessingException e) {
+ throw new NewscatcherApiException("Failed to deserialize response: " + e.getMessage(), e);
} catch (IOException e) {
throw new NewscatcherApiException("Network error executing HTTP request", e);
}
diff --git a/src/main/java/com/newscatcher/api/resources/breakingnews/AsyncRawBreakingNewsClient.java b/src/main/java/com/newscatcher/api/resources/breakingnews/AsyncRawBreakingNewsClient.java
index c649336..341ca89 100644
--- a/src/main/java/com/newscatcher/api/resources/breakingnews/AsyncRawBreakingNewsClient.java
+++ b/src/main/java/com/newscatcher/api/resources/breakingnews/AsyncRawBreakingNewsClient.java
@@ -12,6 +12,7 @@
import com.newscatcher.api.core.ObjectMappers;
import com.newscatcher.api.core.QueryStringMapper;
import com.newscatcher.api.core.RequestOptions;
+import com.newscatcher.api.core.RetryInterceptor;
import com.newscatcher.api.errors.BadRequestError;
import com.newscatcher.api.errors.ForbiddenError;
import com.newscatcher.api.errors.InternalServerError;
@@ -182,6 +183,15 @@ public CompletableFuture> ge
if (requestOptions != null && requestOptions.getTimeout().isPresent()) {
client = clientOptions.httpClientWithTimeout(requestOptions);
}
+ if (requestOptions != null && requestOptions.getMaxRetries().isPresent()) {
+ okhttpRequest = okhttpRequest
+ .newBuilder()
+ .tag(
+ RetryInterceptor.MaxRetriesOverride.class,
+ new RetryInterceptor.MaxRetriesOverride(
+ requestOptions.getMaxRetries().get()))
+ .build();
+ }
CompletableFuture> future = new CompletableFuture<>();
client.newCall(okhttpRequest).enqueue(new Callback() {
@Override
@@ -239,6 +249,9 @@ public void onResponse(@NotNull Call call, @NotNull Response response) throws IO
future.completeExceptionally(new NewscatcherApiApiException(
"Error with status code " + response.code(), response.code(), errorBody, response));
return;
+ } catch (JsonProcessingException e) {
+ future.completeExceptionally(
+ new NewscatcherApiException("Failed to deserialize response: " + e.getMessage(), e));
} catch (IOException e) {
future.completeExceptionally(
new NewscatcherApiException("Network error executing HTTP request", e));
@@ -306,6 +319,15 @@ public CompletableFuture> po
if (requestOptions != null && requestOptions.getTimeout().isPresent()) {
client = clientOptions.httpClientWithTimeout(requestOptions);
}
+ if (requestOptions != null && requestOptions.getMaxRetries().isPresent()) {
+ okhttpRequest = okhttpRequest
+ .newBuilder()
+ .tag(
+ RetryInterceptor.MaxRetriesOverride.class,
+ new RetryInterceptor.MaxRetriesOverride(
+ requestOptions.getMaxRetries().get()))
+ .build();
+ }
CompletableFuture> future = new CompletableFuture<>();
client.newCall(okhttpRequest).enqueue(new Callback() {
@Override
@@ -363,6 +385,9 @@ public void onResponse(@NotNull Call call, @NotNull Response response) throws IO
future.completeExceptionally(new NewscatcherApiApiException(
"Error with status code " + response.code(), response.code(), errorBody, response));
return;
+ } catch (JsonProcessingException e) {
+ future.completeExceptionally(
+ new NewscatcherApiException("Failed to deserialize response: " + e.getMessage(), e));
} catch (IOException e) {
future.completeExceptionally(
new NewscatcherApiException("Network error executing HTTP request", e));
diff --git a/src/main/java/com/newscatcher/api/resources/breakingnews/RawBreakingNewsClient.java b/src/main/java/com/newscatcher/api/resources/breakingnews/RawBreakingNewsClient.java
index 5f03a20..0fcd6b6 100644
--- a/src/main/java/com/newscatcher/api/resources/breakingnews/RawBreakingNewsClient.java
+++ b/src/main/java/com/newscatcher/api/resources/breakingnews/RawBreakingNewsClient.java
@@ -12,6 +12,7 @@
import com.newscatcher.api.core.ObjectMappers;
import com.newscatcher.api.core.QueryStringMapper;
import com.newscatcher.api.core.RequestOptions;
+import com.newscatcher.api.core.RetryInterceptor;
import com.newscatcher.api.errors.BadRequestError;
import com.newscatcher.api.errors.ForbiddenError;
import com.newscatcher.api.errors.InternalServerError;
@@ -178,6 +179,15 @@ public NewscatcherApiHttpResponse get(
if (requestOptions != null && requestOptions.getTimeout().isPresent()) {
client = clientOptions.httpClientWithTimeout(requestOptions);
}
+ if (requestOptions != null && requestOptions.getMaxRetries().isPresent()) {
+ okhttpRequest = okhttpRequest
+ .newBuilder()
+ .tag(
+ RetryInterceptor.MaxRetriesOverride.class,
+ new RetryInterceptor.MaxRetriesOverride(
+ requestOptions.getMaxRetries().get()))
+ .build();
+ }
try (Response response = client.newCall(okhttpRequest).execute()) {
ResponseBody responseBody = response.body();
String responseBodyString = responseBody != null ? responseBody.string() : "{}";
@@ -216,6 +226,8 @@ public NewscatcherApiHttpResponse get(
Object errorBody = ObjectMappers.parseErrorBody(responseBodyString);
throw new NewscatcherApiApiException(
"Error with status code " + response.code(), response.code(), errorBody, response);
+ } catch (JsonProcessingException e) {
+ throw new NewscatcherApiException("Failed to deserialize response: " + e.getMessage(), e);
} catch (IOException e) {
throw new NewscatcherApiException("Network error executing HTTP request", e);
}
@@ -273,6 +285,15 @@ public NewscatcherApiHttpResponse post(
if (requestOptions != null && requestOptions.getTimeout().isPresent()) {
client = clientOptions.httpClientWithTimeout(requestOptions);
}
+ if (requestOptions != null && requestOptions.getMaxRetries().isPresent()) {
+ okhttpRequest = okhttpRequest
+ .newBuilder()
+ .tag(
+ RetryInterceptor.MaxRetriesOverride.class,
+ new RetryInterceptor.MaxRetriesOverride(
+ requestOptions.getMaxRetries().get()))
+ .build();
+ }
try (Response response = client.newCall(okhttpRequest).execute()) {
ResponseBody responseBody = response.body();
String responseBodyString = responseBody != null ? responseBody.string() : "{}";
@@ -311,6 +332,8 @@ public NewscatcherApiHttpResponse post(
Object errorBody = ObjectMappers.parseErrorBody(responseBodyString);
throw new NewscatcherApiApiException(
"Error with status code " + response.code(), response.code(), errorBody, response);
+ } catch (JsonProcessingException e) {
+ throw new NewscatcherApiException("Failed to deserialize response: " + e.getMessage(), e);
} catch (IOException e) {
throw new NewscatcherApiException("Network error executing HTTP request", e);
}
diff --git a/src/main/java/com/newscatcher/api/resources/latestheadlines/AsyncRawLatestHeadlinesClient.java b/src/main/java/com/newscatcher/api/resources/latestheadlines/AsyncRawLatestHeadlinesClient.java
index 4e46f2b..12ae6a3 100644
--- a/src/main/java/com/newscatcher/api/resources/latestheadlines/AsyncRawLatestHeadlinesClient.java
+++ b/src/main/java/com/newscatcher/api/resources/latestheadlines/AsyncRawLatestHeadlinesClient.java
@@ -12,6 +12,7 @@
import com.newscatcher.api.core.ObjectMappers;
import com.newscatcher.api.core.QueryStringMapper;
import com.newscatcher.api.core.RequestOptions;
+import com.newscatcher.api.core.RetryInterceptor;
import com.newscatcher.api.errors.BadRequestError;
import com.newscatcher.api.errors.ForbiddenError;
import com.newscatcher.api.errors.InternalServerError;
@@ -297,6 +298,15 @@ public CompletableFuture>
if (requestOptions != null && requestOptions.getTimeout().isPresent()) {
client = clientOptions.httpClientWithTimeout(requestOptions);
}
+ if (requestOptions != null && requestOptions.getMaxRetries().isPresent()) {
+ okhttpRequest = okhttpRequest
+ .newBuilder()
+ .tag(
+ RetryInterceptor.MaxRetriesOverride.class,
+ new RetryInterceptor.MaxRetriesOverride(
+ requestOptions.getMaxRetries().get()))
+ .build();
+ }
CompletableFuture> future = new CompletableFuture<>();
client.newCall(okhttpRequest).enqueue(new Callback() {
@Override
@@ -355,6 +365,9 @@ public void onResponse(@NotNull Call call, @NotNull Response response) throws IO
future.completeExceptionally(new NewscatcherApiApiException(
"Error with status code " + response.code(), response.code(), errorBody, response));
return;
+ } catch (JsonProcessingException e) {
+ future.completeExceptionally(
+ new NewscatcherApiException("Failed to deserialize response: " + e.getMessage(), e));
} catch (IOException e) {
future.completeExceptionally(
new NewscatcherApiException("Network error executing HTTP request", e));
@@ -423,6 +436,15 @@ public CompletableFuture
if (requestOptions != null && requestOptions.getTimeout().isPresent()) {
client = clientOptions.httpClientWithTimeout(requestOptions);
}
+ if (requestOptions != null && requestOptions.getMaxRetries().isPresent()) {
+ okhttpRequest = okhttpRequest
+ .newBuilder()
+ .tag(
+ RetryInterceptor.MaxRetriesOverride.class,
+ new RetryInterceptor.MaxRetriesOverride(
+ requestOptions.getMaxRetries().get()))
+ .build();
+ }
CompletableFuture> future = new CompletableFuture<>();
client.newCall(okhttpRequest).enqueue(new Callback() {
@Override
@@ -481,6 +503,9 @@ public void onResponse(@NotNull Call call, @NotNull Response response) throws IO
future.completeExceptionally(new NewscatcherApiApiException(
"Error with status code " + response.code(), response.code(), errorBody, response));
return;
+ } catch (JsonProcessingException e) {
+ future.completeExceptionally(
+ new NewscatcherApiException("Failed to deserialize response: " + e.getMessage(), e));
} catch (IOException e) {
future.completeExceptionally(
new NewscatcherApiException("Network error executing HTTP request", e));
diff --git a/src/main/java/com/newscatcher/api/resources/latestheadlines/RawLatestHeadlinesClient.java b/src/main/java/com/newscatcher/api/resources/latestheadlines/RawLatestHeadlinesClient.java
index 48f3bcd..15f735e 100644
--- a/src/main/java/com/newscatcher/api/resources/latestheadlines/RawLatestHeadlinesClient.java
+++ b/src/main/java/com/newscatcher/api/resources/latestheadlines/RawLatestHeadlinesClient.java
@@ -12,6 +12,7 @@
import com.newscatcher.api.core.ObjectMappers;
import com.newscatcher.api.core.QueryStringMapper;
import com.newscatcher.api.core.RequestOptions;
+import com.newscatcher.api.core.RetryInterceptor;
import com.newscatcher.api.errors.BadRequestError;
import com.newscatcher.api.errors.ForbiddenError;
import com.newscatcher.api.errors.InternalServerError;
@@ -291,6 +292,15 @@ public NewscatcherApiHttpResponse get(
if (requestOptions != null && requestOptions.getTimeout().isPresent()) {
client = clientOptions.httpClientWithTimeout(requestOptions);
}
+ if (requestOptions != null && requestOptions.getMaxRetries().isPresent()) {
+ okhttpRequest = okhttpRequest
+ .newBuilder()
+ .tag(
+ RetryInterceptor.MaxRetriesOverride.class,
+ new RetryInterceptor.MaxRetriesOverride(
+ requestOptions.getMaxRetries().get()))
+ .build();
+ }
try (Response response = client.newCall(okhttpRequest).execute()) {
ResponseBody responseBody = response.body();
String responseBodyString = responseBody != null ? responseBody.string() : "{}";
@@ -329,6 +339,8 @@ public NewscatcherApiHttpResponse get(
Object errorBody = ObjectMappers.parseErrorBody(responseBodyString);
throw new NewscatcherApiApiException(
"Error with status code " + response.code(), response.code(), errorBody, response);
+ } catch (JsonProcessingException e) {
+ throw new NewscatcherApiException("Failed to deserialize response: " + e.getMessage(), e);
} catch (IOException e) {
throw new NewscatcherApiException("Network error executing HTTP request", e);
}
@@ -386,6 +398,15 @@ public NewscatcherApiHttpResponse post(
if (requestOptions != null && requestOptions.getTimeout().isPresent()) {
client = clientOptions.httpClientWithTimeout(requestOptions);
}
+ if (requestOptions != null && requestOptions.getMaxRetries().isPresent()) {
+ okhttpRequest = okhttpRequest
+ .newBuilder()
+ .tag(
+ RetryInterceptor.MaxRetriesOverride.class,
+ new RetryInterceptor.MaxRetriesOverride(
+ requestOptions.getMaxRetries().get()))
+ .build();
+ }
try (Response response = client.newCall(okhttpRequest).execute()) {
ResponseBody responseBody = response.body();
String responseBodyString = responseBody != null ? responseBody.string() : "{}";
@@ -424,6 +445,8 @@ public NewscatcherApiHttpResponse post(
Object errorBody = ObjectMappers.parseErrorBody(responseBodyString);
throw new NewscatcherApiApiException(
"Error with status code " + response.code(), response.code(), errorBody, response);
+ } catch (JsonProcessingException e) {
+ throw new NewscatcherApiException("Failed to deserialize response: " + e.getMessage(), e);
} catch (IOException e) {
throw new NewscatcherApiException("Network error executing HTTP request", e);
}
diff --git a/src/main/java/com/newscatcher/api/resources/search/AsyncRawSearchClient.java b/src/main/java/com/newscatcher/api/resources/search/AsyncRawSearchClient.java
index b1d4b03..732283b 100644
--- a/src/main/java/com/newscatcher/api/resources/search/AsyncRawSearchClient.java
+++ b/src/main/java/com/newscatcher/api/resources/search/AsyncRawSearchClient.java
@@ -12,6 +12,7 @@
import com.newscatcher.api.core.ObjectMappers;
import com.newscatcher.api.core.QueryStringMapper;
import com.newscatcher.api.core.RequestOptions;
+import com.newscatcher.api.core.RetryInterceptor;
import com.newscatcher.api.errors.BadRequestError;
import com.newscatcher.api.errors.ForbiddenError;
import com.newscatcher.api.errors.InternalServerError;
@@ -334,6 +335,15 @@ public CompletableFuture> get(
if (requestOptions != null && requestOptions.getTimeout().isPresent()) {
client = clientOptions.httpClientWithTimeout(requestOptions);
}
+ if (requestOptions != null && requestOptions.getMaxRetries().isPresent()) {
+ okhttpRequest = okhttpRequest
+ .newBuilder()
+ .tag(
+ RetryInterceptor.MaxRetriesOverride.class,
+ new RetryInterceptor.MaxRetriesOverride(
+ requestOptions.getMaxRetries().get()))
+ .build();
+ }
CompletableFuture> future = new CompletableFuture<>();
client.newCall(okhttpRequest).enqueue(new Callback() {
@Override
@@ -391,6 +401,9 @@ public void onResponse(@NotNull Call call, @NotNull Response response) throws IO
future.completeExceptionally(new NewscatcherApiApiException(
"Error with status code " + response.code(), response.code(), errorBody, response));
return;
+ } catch (JsonProcessingException e) {
+ future.completeExceptionally(
+ new NewscatcherApiException("Failed to deserialize response: " + e.getMessage(), e));
} catch (IOException e) {
future.completeExceptionally(
new NewscatcherApiException("Network error executing HTTP request", e));
@@ -443,6 +456,15 @@ public CompletableFuture> post(
if (requestOptions != null && requestOptions.getTimeout().isPresent()) {
client = clientOptions.httpClientWithTimeout(requestOptions);
}
+ if (requestOptions != null && requestOptions.getMaxRetries().isPresent()) {
+ okhttpRequest = okhttpRequest
+ .newBuilder()
+ .tag(
+ RetryInterceptor.MaxRetriesOverride.class,
+ new RetryInterceptor.MaxRetriesOverride(
+ requestOptions.getMaxRetries().get()))
+ .build();
+ }
CompletableFuture> future = new CompletableFuture<>();
client.newCall(okhttpRequest).enqueue(new Callback() {
@Override
@@ -500,6 +522,9 @@ public void onResponse(@NotNull Call call, @NotNull Response response) throws IO
future.completeExceptionally(new NewscatcherApiApiException(
"Error with status code " + response.code(), response.code(), errorBody, response));
return;
+ } catch (JsonProcessingException e) {
+ future.completeExceptionally(
+ new NewscatcherApiException("Failed to deserialize response: " + e.getMessage(), e));
} catch (IOException e) {
future.completeExceptionally(
new NewscatcherApiException("Network error executing HTTP request", e));
diff --git a/src/main/java/com/newscatcher/api/resources/search/RawSearchClient.java b/src/main/java/com/newscatcher/api/resources/search/RawSearchClient.java
index 615b8ba..b033684 100644
--- a/src/main/java/com/newscatcher/api/resources/search/RawSearchClient.java
+++ b/src/main/java/com/newscatcher/api/resources/search/RawSearchClient.java
@@ -12,6 +12,7 @@
import com.newscatcher.api.core.ObjectMappers;
import com.newscatcher.api.core.QueryStringMapper;
import com.newscatcher.api.core.RequestOptions;
+import com.newscatcher.api.core.RetryInterceptor;
import com.newscatcher.api.errors.BadRequestError;
import com.newscatcher.api.errors.ForbiddenError;
import com.newscatcher.api.errors.InternalServerError;
@@ -329,6 +330,15 @@ public NewscatcherApiHttpResponse get(GetSearchRequest reques
if (requestOptions != null && requestOptions.getTimeout().isPresent()) {
client = clientOptions.httpClientWithTimeout(requestOptions);
}
+ if (requestOptions != null && requestOptions.getMaxRetries().isPresent()) {
+ okhttpRequest = okhttpRequest
+ .newBuilder()
+ .tag(
+ RetryInterceptor.MaxRetriesOverride.class,
+ new RetryInterceptor.MaxRetriesOverride(
+ requestOptions.getMaxRetries().get()))
+ .build();
+ }
try (Response response = client.newCall(okhttpRequest).execute()) {
ResponseBody responseBody = response.body();
String responseBodyString = responseBody != null ? responseBody.string() : "{}";
@@ -366,6 +376,8 @@ public NewscatcherApiHttpResponse get(GetSearchRequest reques
Object errorBody = ObjectMappers.parseErrorBody(responseBodyString);
throw new NewscatcherApiApiException(
"Error with status code " + response.code(), response.code(), errorBody, response);
+ } catch (JsonProcessingException e) {
+ throw new NewscatcherApiException("Failed to deserialize response: " + e.getMessage(), e);
} catch (IOException e) {
throw new NewscatcherApiException("Network error executing HTTP request", e);
}
@@ -409,6 +421,15 @@ public NewscatcherApiHttpResponse post(
if (requestOptions != null && requestOptions.getTimeout().isPresent()) {
client = clientOptions.httpClientWithTimeout(requestOptions);
}
+ if (requestOptions != null && requestOptions.getMaxRetries().isPresent()) {
+ okhttpRequest = okhttpRequest
+ .newBuilder()
+ .tag(
+ RetryInterceptor.MaxRetriesOverride.class,
+ new RetryInterceptor.MaxRetriesOverride(
+ requestOptions.getMaxRetries().get()))
+ .build();
+ }
try (Response response = client.newCall(okhttpRequest).execute()) {
ResponseBody responseBody = response.body();
String responseBodyString = responseBody != null ? responseBody.string() : "{}";
@@ -446,6 +467,8 @@ public NewscatcherApiHttpResponse post(
Object errorBody = ObjectMappers.parseErrorBody(responseBodyString);
throw new NewscatcherApiApiException(
"Error with status code " + response.code(), response.code(), errorBody, response);
+ } catch (JsonProcessingException e) {
+ throw new NewscatcherApiException("Failed to deserialize response: " + e.getMessage(), e);
} catch (IOException e) {
throw new NewscatcherApiException("Network error executing HTTP request", e);
}
diff --git a/src/main/java/com/newscatcher/api/resources/searchbylink/AsyncRawSearchByLinkClient.java b/src/main/java/com/newscatcher/api/resources/searchbylink/AsyncRawSearchByLinkClient.java
index 275f1f3..38afea2 100644
--- a/src/main/java/com/newscatcher/api/resources/searchbylink/AsyncRawSearchByLinkClient.java
+++ b/src/main/java/com/newscatcher/api/resources/searchbylink/AsyncRawSearchByLinkClient.java
@@ -12,6 +12,7 @@
import com.newscatcher.api.core.ObjectMappers;
import com.newscatcher.api.core.QueryStringMapper;
import com.newscatcher.api.core.RequestOptions;
+import com.newscatcher.api.core.RetryInterceptor;
import com.newscatcher.api.errors.BadRequestError;
import com.newscatcher.api.errors.ForbiddenError;
import com.newscatcher.api.errors.InternalServerError;
@@ -113,6 +114,15 @@ public CompletableFuture> get(
if (requestOptions != null && requestOptions.getTimeout().isPresent()) {
client = clientOptions.httpClientWithTimeout(requestOptions);
}
+ if (requestOptions != null && requestOptions.getMaxRetries().isPresent()) {
+ okhttpRequest = okhttpRequest
+ .newBuilder()
+ .tag(
+ RetryInterceptor.MaxRetriesOverride.class,
+ new RetryInterceptor.MaxRetriesOverride(
+ requestOptions.getMaxRetries().get()))
+ .build();
+ }
CompletableFuture> future = new CompletableFuture<>();
client.newCall(okhttpRequest).enqueue(new Callback() {
@Override
@@ -170,6 +180,9 @@ public void onResponse(@NotNull Call call, @NotNull Response response) throws IO
future.completeExceptionally(new NewscatcherApiApiException(
"Error with status code " + response.code(), response.code(), errorBody, response));
return;
+ } catch (JsonProcessingException e) {
+ future.completeExceptionally(
+ new NewscatcherApiException("Failed to deserialize response: " + e.getMessage(), e));
} catch (IOException e) {
future.completeExceptionally(
new NewscatcherApiException("Network error executing HTTP request", e));
@@ -236,6 +249,15 @@ public CompletableFuture> post(
if (requestOptions != null && requestOptions.getTimeout().isPresent()) {
client = clientOptions.httpClientWithTimeout(requestOptions);
}
+ if (requestOptions != null && requestOptions.getMaxRetries().isPresent()) {
+ okhttpRequest = okhttpRequest
+ .newBuilder()
+ .tag(
+ RetryInterceptor.MaxRetriesOverride.class,
+ new RetryInterceptor.MaxRetriesOverride(
+ requestOptions.getMaxRetries().get()))
+ .build();
+ }
CompletableFuture> future = new CompletableFuture<>();
client.newCall(okhttpRequest).enqueue(new Callback() {
@Override
@@ -293,6 +315,9 @@ public void onResponse(@NotNull Call call, @NotNull Response response) throws IO
future.completeExceptionally(new NewscatcherApiApiException(
"Error with status code " + response.code(), response.code(), errorBody, response));
return;
+ } catch (JsonProcessingException e) {
+ future.completeExceptionally(
+ new NewscatcherApiException("Failed to deserialize response: " + e.getMessage(), e));
} catch (IOException e) {
future.completeExceptionally(
new NewscatcherApiException("Network error executing HTTP request", e));
diff --git a/src/main/java/com/newscatcher/api/resources/searchbylink/RawSearchByLinkClient.java b/src/main/java/com/newscatcher/api/resources/searchbylink/RawSearchByLinkClient.java
index f09ef72..395c2e4 100644
--- a/src/main/java/com/newscatcher/api/resources/searchbylink/RawSearchByLinkClient.java
+++ b/src/main/java/com/newscatcher/api/resources/searchbylink/RawSearchByLinkClient.java
@@ -12,6 +12,7 @@
import com.newscatcher.api.core.ObjectMappers;
import com.newscatcher.api.core.QueryStringMapper;
import com.newscatcher.api.core.RequestOptions;
+import com.newscatcher.api.core.RetryInterceptor;
import com.newscatcher.api.errors.BadRequestError;
import com.newscatcher.api.errors.ForbiddenError;
import com.newscatcher.api.errors.InternalServerError;
@@ -109,6 +110,15 @@ public NewscatcherApiHttpResponse get(
if (requestOptions != null && requestOptions.getTimeout().isPresent()) {
client = clientOptions.httpClientWithTimeout(requestOptions);
}
+ if (requestOptions != null && requestOptions.getMaxRetries().isPresent()) {
+ okhttpRequest = okhttpRequest
+ .newBuilder()
+ .tag(
+ RetryInterceptor.MaxRetriesOverride.class,
+ new RetryInterceptor.MaxRetriesOverride(
+ requestOptions.getMaxRetries().get()))
+ .build();
+ }
try (Response response = client.newCall(okhttpRequest).execute()) {
ResponseBody responseBody = response.body();
String responseBodyString = responseBody != null ? responseBody.string() : "{}";
@@ -146,6 +156,8 @@ public NewscatcherApiHttpResponse get(
Object errorBody = ObjectMappers.parseErrorBody(responseBodyString);
throw new NewscatcherApiApiException(
"Error with status code " + response.code(), response.code(), errorBody, response);
+ } catch (JsonProcessingException e) {
+ throw new NewscatcherApiException("Failed to deserialize response: " + e.getMessage(), e);
} catch (IOException e) {
throw new NewscatcherApiException("Network error executing HTTP request", e);
}
@@ -203,6 +215,15 @@ public NewscatcherApiHttpResponse post(
if (requestOptions != null && requestOptions.getTimeout().isPresent()) {
client = clientOptions.httpClientWithTimeout(requestOptions);
}
+ if (requestOptions != null && requestOptions.getMaxRetries().isPresent()) {
+ okhttpRequest = okhttpRequest
+ .newBuilder()
+ .tag(
+ RetryInterceptor.MaxRetriesOverride.class,
+ new RetryInterceptor.MaxRetriesOverride(
+ requestOptions.getMaxRetries().get()))
+ .build();
+ }
try (Response response = client.newCall(okhttpRequest).execute()) {
ResponseBody responseBody = response.body();
String responseBodyString = responseBody != null ? responseBody.string() : "{}";
@@ -240,6 +261,8 @@ public NewscatcherApiHttpResponse post(
Object errorBody = ObjectMappers.parseErrorBody(responseBodyString);
throw new NewscatcherApiApiException(
"Error with status code " + response.code(), response.code(), errorBody, response);
+ } catch (JsonProcessingException e) {
+ throw new NewscatcherApiException("Failed to deserialize response: " + e.getMessage(), e);
} catch (IOException e) {
throw new NewscatcherApiException("Network error executing HTTP request", e);
}
diff --git a/src/main/java/com/newscatcher/api/resources/sources/AsyncRawSourcesClient.java b/src/main/java/com/newscatcher/api/resources/sources/AsyncRawSourcesClient.java
index 0a06241..328855f 100644
--- a/src/main/java/com/newscatcher/api/resources/sources/AsyncRawSourcesClient.java
+++ b/src/main/java/com/newscatcher/api/resources/sources/AsyncRawSourcesClient.java
@@ -12,6 +12,7 @@
import com.newscatcher.api.core.ObjectMappers;
import com.newscatcher.api.core.QueryStringMapper;
import com.newscatcher.api.core.RequestOptions;
+import com.newscatcher.api.core.RetryInterceptor;
import com.newscatcher.api.errors.BadRequestError;
import com.newscatcher.api.errors.ForbiddenError;
import com.newscatcher.api.errors.InternalServerError;
@@ -137,6 +138,15 @@ public CompletableFuture> get(
if (requestOptions != null && requestOptions.getTimeout().isPresent()) {
client = clientOptions.httpClientWithTimeout(requestOptions);
}
+ if (requestOptions != null && requestOptions.getMaxRetries().isPresent()) {
+ okhttpRequest = okhttpRequest
+ .newBuilder()
+ .tag(
+ RetryInterceptor.MaxRetriesOverride.class,
+ new RetryInterceptor.MaxRetriesOverride(
+ requestOptions.getMaxRetries().get()))
+ .build();
+ }
CompletableFuture> future = new CompletableFuture<>();
client.newCall(okhttpRequest).enqueue(new Callback() {
@Override
@@ -194,6 +204,9 @@ public void onResponse(@NotNull Call call, @NotNull Response response) throws IO
future.completeExceptionally(new NewscatcherApiApiException(
"Error with status code " + response.code(), response.code(), errorBody, response));
return;
+ } catch (JsonProcessingException e) {
+ future.completeExceptionally(
+ new NewscatcherApiException("Failed to deserialize response: " + e.getMessage(), e));
} catch (IOException e) {
future.completeExceptionally(
new NewscatcherApiException("Network error executing HTTP request", e));
@@ -260,6 +273,15 @@ public CompletableFuture> post(
if (requestOptions != null && requestOptions.getTimeout().isPresent()) {
client = clientOptions.httpClientWithTimeout(requestOptions);
}
+ if (requestOptions != null && requestOptions.getMaxRetries().isPresent()) {
+ okhttpRequest = okhttpRequest
+ .newBuilder()
+ .tag(
+ RetryInterceptor.MaxRetriesOverride.class,
+ new RetryInterceptor.MaxRetriesOverride(
+ requestOptions.getMaxRetries().get()))
+ .build();
+ }
CompletableFuture> future = new CompletableFuture<>();
client.newCall(okhttpRequest).enqueue(new Callback() {
@Override
@@ -317,6 +339,9 @@ public void onResponse(@NotNull Call call, @NotNull Response response) throws IO
future.completeExceptionally(new NewscatcherApiApiException(
"Error with status code " + response.code(), response.code(), errorBody, response));
return;
+ } catch (JsonProcessingException e) {
+ future.completeExceptionally(
+ new NewscatcherApiException("Failed to deserialize response: " + e.getMessage(), e));
} catch (IOException e) {
future.completeExceptionally(
new NewscatcherApiException("Network error executing HTTP request", e));
diff --git a/src/main/java/com/newscatcher/api/resources/sources/RawSourcesClient.java b/src/main/java/com/newscatcher/api/resources/sources/RawSourcesClient.java
index a553cc3..32c68bf 100644
--- a/src/main/java/com/newscatcher/api/resources/sources/RawSourcesClient.java
+++ b/src/main/java/com/newscatcher/api/resources/sources/RawSourcesClient.java
@@ -12,6 +12,7 @@
import com.newscatcher.api.core.ObjectMappers;
import com.newscatcher.api.core.QueryStringMapper;
import com.newscatcher.api.core.RequestOptions;
+import com.newscatcher.api.core.RetryInterceptor;
import com.newscatcher.api.errors.BadRequestError;
import com.newscatcher.api.errors.ForbiddenError;
import com.newscatcher.api.errors.InternalServerError;
@@ -133,6 +134,15 @@ public NewscatcherApiHttpResponse get(
if (requestOptions != null && requestOptions.getTimeout().isPresent()) {
client = clientOptions.httpClientWithTimeout(requestOptions);
}
+ if (requestOptions != null && requestOptions.getMaxRetries().isPresent()) {
+ okhttpRequest = okhttpRequest
+ .newBuilder()
+ .tag(
+ RetryInterceptor.MaxRetriesOverride.class,
+ new RetryInterceptor.MaxRetriesOverride(
+ requestOptions.getMaxRetries().get()))
+ .build();
+ }
try (Response response = client.newCall(okhttpRequest).execute()) {
ResponseBody responseBody = response.body();
String responseBodyString = responseBody != null ? responseBody.string() : "{}";
@@ -170,6 +180,8 @@ public NewscatcherApiHttpResponse get(
Object errorBody = ObjectMappers.parseErrorBody(responseBodyString);
throw new NewscatcherApiApiException(
"Error with status code " + response.code(), response.code(), errorBody, response);
+ } catch (JsonProcessingException e) {
+ throw new NewscatcherApiException("Failed to deserialize response: " + e.getMessage(), e);
} catch (IOException e) {
throw new NewscatcherApiException("Network error executing HTTP request", e);
}
@@ -227,6 +239,15 @@ public NewscatcherApiHttpResponse post(
if (requestOptions != null && requestOptions.getTimeout().isPresent()) {
client = clientOptions.httpClientWithTimeout(requestOptions);
}
+ if (requestOptions != null && requestOptions.getMaxRetries().isPresent()) {
+ okhttpRequest = okhttpRequest
+ .newBuilder()
+ .tag(
+ RetryInterceptor.MaxRetriesOverride.class,
+ new RetryInterceptor.MaxRetriesOverride(
+ requestOptions.getMaxRetries().get()))
+ .build();
+ }
try (Response response = client.newCall(okhttpRequest).execute()) {
ResponseBody responseBody = response.body();
String responseBodyString = responseBody != null ? responseBody.string() : "{}";
@@ -264,6 +285,8 @@ public NewscatcherApiHttpResponse post(
Object errorBody = ObjectMappers.parseErrorBody(responseBodyString);
throw new NewscatcherApiApiException(
"Error with status code " + response.code(), response.code(), errorBody, response);
+ } catch (JsonProcessingException e) {
+ throw new NewscatcherApiException("Failed to deserialize response: " + e.getMessage(), e);
} catch (IOException e) {
throw new NewscatcherApiException("Network error executing HTTP request", e);
}
diff --git a/src/main/java/com/newscatcher/api/resources/subscription/AsyncRawSubscriptionClient.java b/src/main/java/com/newscatcher/api/resources/subscription/AsyncRawSubscriptionClient.java
index 10a59c2..be1ef33 100644
--- a/src/main/java/com/newscatcher/api/resources/subscription/AsyncRawSubscriptionClient.java
+++ b/src/main/java/com/newscatcher/api/resources/subscription/AsyncRawSubscriptionClient.java
@@ -10,6 +10,7 @@
import com.newscatcher.api.core.NewscatcherApiHttpResponse;
import com.newscatcher.api.core.ObjectMappers;
import com.newscatcher.api.core.RequestOptions;
+import com.newscatcher.api.core.RetryInterceptor;
import com.newscatcher.api.errors.BadRequestError;
import com.newscatcher.api.errors.ForbiddenError;
import com.newscatcher.api.errors.InternalServerError;
@@ -68,6 +69,15 @@ public CompletableFuture> ge
if (requestOptions != null && requestOptions.getTimeout().isPresent()) {
client = clientOptions.httpClientWithTimeout(requestOptions);
}
+ if (requestOptions != null && requestOptions.getMaxRetries().isPresent()) {
+ okhttpRequest = okhttpRequest
+ .newBuilder()
+ .tag(
+ RetryInterceptor.MaxRetriesOverride.class,
+ new RetryInterceptor.MaxRetriesOverride(
+ requestOptions.getMaxRetries().get()))
+ .build();
+ }
CompletableFuture> future = new CompletableFuture<>();
client.newCall(okhttpRequest).enqueue(new Callback() {
@Override
@@ -125,6 +135,9 @@ public void onResponse(@NotNull Call call, @NotNull Response response) throws IO
future.completeExceptionally(new NewscatcherApiApiException(
"Error with status code " + response.code(), response.code(), errorBody, response));
return;
+ } catch (JsonProcessingException e) {
+ future.completeExceptionally(
+ new NewscatcherApiException("Failed to deserialize response: " + e.getMessage(), e));
} catch (IOException e) {
future.completeExceptionally(
new NewscatcherApiException("Network error executing HTTP request", e));
@@ -168,6 +181,15 @@ public CompletableFuture> po
if (requestOptions != null && requestOptions.getTimeout().isPresent()) {
client = clientOptions.httpClientWithTimeout(requestOptions);
}
+ if (requestOptions != null && requestOptions.getMaxRetries().isPresent()) {
+ okhttpRequest = okhttpRequest
+ .newBuilder()
+ .tag(
+ RetryInterceptor.MaxRetriesOverride.class,
+ new RetryInterceptor.MaxRetriesOverride(
+ requestOptions.getMaxRetries().get()))
+ .build();
+ }
CompletableFuture> future = new CompletableFuture<>();
client.newCall(okhttpRequest).enqueue(new Callback() {
@Override
@@ -225,6 +247,9 @@ public void onResponse(@NotNull Call call, @NotNull Response response) throws IO
future.completeExceptionally(new NewscatcherApiApiException(
"Error with status code " + response.code(), response.code(), errorBody, response));
return;
+ } catch (JsonProcessingException e) {
+ future.completeExceptionally(
+ new NewscatcherApiException("Failed to deserialize response: " + e.getMessage(), e));
} catch (IOException e) {
future.completeExceptionally(
new NewscatcherApiException("Network error executing HTTP request", e));
diff --git a/src/main/java/com/newscatcher/api/resources/subscription/RawSubscriptionClient.java b/src/main/java/com/newscatcher/api/resources/subscription/RawSubscriptionClient.java
index f705b64..913b9e3 100644
--- a/src/main/java/com/newscatcher/api/resources/subscription/RawSubscriptionClient.java
+++ b/src/main/java/com/newscatcher/api/resources/subscription/RawSubscriptionClient.java
@@ -10,6 +10,7 @@
import com.newscatcher.api.core.NewscatcherApiHttpResponse;
import com.newscatcher.api.core.ObjectMappers;
import com.newscatcher.api.core.RequestOptions;
+import com.newscatcher.api.core.RetryInterceptor;
import com.newscatcher.api.errors.BadRequestError;
import com.newscatcher.api.errors.ForbiddenError;
import com.newscatcher.api.errors.InternalServerError;
@@ -64,6 +65,15 @@ public NewscatcherApiHttpResponse get(RequestOptions re
if (requestOptions != null && requestOptions.getTimeout().isPresent()) {
client = clientOptions.httpClientWithTimeout(requestOptions);
}
+ if (requestOptions != null && requestOptions.getMaxRetries().isPresent()) {
+ okhttpRequest = okhttpRequest
+ .newBuilder()
+ .tag(
+ RetryInterceptor.MaxRetriesOverride.class,
+ new RetryInterceptor.MaxRetriesOverride(
+ requestOptions.getMaxRetries().get()))
+ .build();
+ }
try (Response response = client.newCall(okhttpRequest).execute()) {
ResponseBody responseBody = response.body();
String responseBodyString = responseBody != null ? responseBody.string() : "{}";
@@ -102,6 +112,8 @@ public NewscatcherApiHttpResponse get(RequestOptions re
Object errorBody = ObjectMappers.parseErrorBody(responseBodyString);
throw new NewscatcherApiApiException(
"Error with status code " + response.code(), response.code(), errorBody, response);
+ } catch (JsonProcessingException e) {
+ throw new NewscatcherApiException("Failed to deserialize response: " + e.getMessage(), e);
} catch (IOException e) {
throw new NewscatcherApiException("Network error executing HTTP request", e);
}
@@ -136,6 +148,15 @@ public NewscatcherApiHttpResponse post(RequestOptions r
if (requestOptions != null && requestOptions.getTimeout().isPresent()) {
client = clientOptions.httpClientWithTimeout(requestOptions);
}
+ if (requestOptions != null && requestOptions.getMaxRetries().isPresent()) {
+ okhttpRequest = okhttpRequest
+ .newBuilder()
+ .tag(
+ RetryInterceptor.MaxRetriesOverride.class,
+ new RetryInterceptor.MaxRetriesOverride(
+ requestOptions.getMaxRetries().get()))
+ .build();
+ }
try (Response response = client.newCall(okhttpRequest).execute()) {
ResponseBody responseBody = response.body();
String responseBodyString = responseBody != null ? responseBody.string() : "{}";
@@ -174,6 +195,8 @@ public NewscatcherApiHttpResponse post(RequestOptions r
Object errorBody = ObjectMappers.parseErrorBody(responseBodyString);
throw new NewscatcherApiApiException(
"Error with status code " + response.code(), response.code(), errorBody, response);
+ } catch (JsonProcessingException e) {
+ throw new NewscatcherApiException("Failed to deserialize response: " + e.getMessage(), e);
} catch (IOException e) {
throw new NewscatcherApiException("Network error executing HTTP request", e);
}
diff --git a/src/main/java/com/newscatcher/api/types/AggregationCountResponseDto.java b/src/main/java/com/newscatcher/api/types/AggregationCountResponseDto.java
index 0a7c5d9..5f05e48 100644
--- a/src/main/java/com/newscatcher/api/types/AggregationCountResponseDto.java
+++ b/src/main/java/com/newscatcher/api/types/AggregationCountResponseDto.java
@@ -247,7 +247,6 @@ public Builder from(AggregationCountResponseDto other) {
}
/**
- * The status of the response.
* The status of the response.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -259,7 +258,6 @@ public TotalHitsStage status(@NotNull String status) {
}
/**
- * The total number of articles matching the search criteria.
* The total number of articles matching the search criteria.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -271,7 +269,6 @@ public PageStage totalHits(int totalHits) {
}
/**
- * The current page number of the results.
* The current page number of the results.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -283,7 +280,6 @@ public TotalPagesStage page(int page) {
}
/**
- * The total number of pages available for the given search criteria.
* The total number of pages available for the given search criteria.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -295,7 +291,6 @@ public PageSizeStage totalPages(int totalPages) {
}
/**
- * The number of articles per page.
* The number of articles per page.
* @return Reference to {@code this} so that method calls can be chained together.
*/
diff --git a/src/main/java/com/newscatcher/api/types/AllLinksDataItem.java b/src/main/java/com/newscatcher/api/types/AllLinksDataItem.java
index f514954..97abc19 100644
--- a/src/main/java/com/newscatcher/api/types/AllLinksDataItem.java
+++ b/src/main/java/com/newscatcher/api/types/AllLinksDataItem.java
@@ -140,7 +140,6 @@ public Builder from(AllLinksDataItem other) {
}
/**
- * The domain of the linked URL.
* The domain of the linked URL.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -152,7 +151,6 @@ public LinkStage domainUrl(@NotNull String domainUrl) {
}
/**
- * The complete URL of the link.
* The complete URL of the link.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -164,7 +162,6 @@ public TextStage link(@NotNull String link) {
}
/**
- * The anchor text of the link.
* The anchor text of the link.
* @return Reference to {@code this} so that method calls can be chained together.
*/
diff --git a/src/main/java/com/newscatcher/api/types/ArticleEntity.java b/src/main/java/com/newscatcher/api/types/ArticleEntity.java
index ab7d4ee..6e48e58 100644
--- a/src/main/java/com/newscatcher/api/types/ArticleEntity.java
+++ b/src/main/java/com/newscatcher/api/types/ArticleEntity.java
@@ -1045,7 +1045,6 @@ public Builder from(ArticleEntity other) {
}
/**
- * The title of the article.
* The title of the article.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -1057,7 +1056,6 @@ public LinkStage title(@NotNull String title) {
}
/**
- * The URL link to the article.
* The URL link to the article.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -1069,7 +1067,6 @@ public DomainUrlStage link(@NotNull String link) {
}
/**
- * The domain URL of the article.
* The domain URL of the article.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -1081,7 +1078,6 @@ public FullDomainUrlStage domainUrl(@NotNull String domainUrl) {
}
/**
- * The full domain URL of the article.
* The full domain URL of the article.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -1093,7 +1089,6 @@ public ParentUrlStage fullDomainUrl(@NotNull String fullDomainUrl) {
}
/**
- * The categorical URL of the article.
* The categorical URL of the article.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -1105,7 +1100,6 @@ public RankStage parentUrl(@NotNull String parentUrl) {
}
/**
- * The rank of the article's source.
* The rank of the article's source.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -1117,7 +1111,6 @@ public IdStage rank(int rank) {
}
/**
- * The unique identifier for the article.
* The unique identifier for the article.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -1129,7 +1122,6 @@ public ScoreStage id(@NotNull String id) {
}
/**
- * The relevance score of the article.
* The relevance score of the article.
* @return Reference to {@code this} so that method calls can be chained together.
*/
diff --git a/src/main/java/com/newscatcher/api/types/BaseSearchResponseDto.java b/src/main/java/com/newscatcher/api/types/BaseSearchResponseDto.java
index 634553c..6ff57d1 100644
--- a/src/main/java/com/newscatcher/api/types/BaseSearchResponseDto.java
+++ b/src/main/java/com/newscatcher/api/types/BaseSearchResponseDto.java
@@ -198,7 +198,6 @@ public Builder from(BaseSearchResponseDto other) {
}
/**
- * The status of the response.
* The status of the response.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -210,7 +209,6 @@ public TotalHitsStage status(@NotNull String status) {
}
/**
- * The total number of articles matching the search criteria.
* The total number of articles matching the search criteria.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -222,7 +220,6 @@ public PageStage totalHits(int totalHits) {
}
/**
- * The current page number of the results.
* The current page number of the results.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -234,7 +231,6 @@ public TotalPagesStage page(int page) {
}
/**
- * The total number of pages available for the given search criteria.
* The total number of pages available for the given search criteria.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -246,7 +242,6 @@ public PageSizeStage totalPages(int totalPages) {
}
/**
- * The number of articles per page.
* The number of articles per page.
* @return Reference to {@code this} so that method calls can be chained together.
*/
diff --git a/src/main/java/com/newscatcher/api/types/BreakingNewsArticleEntity.java b/src/main/java/com/newscatcher/api/types/BreakingNewsArticleEntity.java
index 27b6cdb..4f65bc4 100644
--- a/src/main/java/com/newscatcher/api/types/BreakingNewsArticleEntity.java
+++ b/src/main/java/com/newscatcher/api/types/BreakingNewsArticleEntity.java
@@ -954,7 +954,6 @@ public Builder from(BreakingNewsArticleEntity other) {
}
/**
- * The title of the article.
* The title of the article.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -966,7 +965,6 @@ public LinkStage title(@NotNull String title) {
}
/**
- * The URL link to the article.
* The URL link to the article.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -978,7 +976,6 @@ public DomainUrlStage link(@NotNull String link) {
}
/**
- * The domain URL of the article.
* The domain URL of the article.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -990,7 +987,6 @@ public FullDomainUrlStage domainUrl(@NotNull String domainUrl) {
}
/**
- * The full domain URL of the article.
* The full domain URL of the article.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -1002,7 +998,6 @@ public ParentUrlStage fullDomainUrl(@NotNull String fullDomainUrl) {
}
/**
- * The categorical URL of the article.
* The categorical URL of the article.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -1014,7 +1009,6 @@ public RankStage parentUrl(@NotNull String parentUrl) {
}
/**
- * The rank of the article's source.
* The rank of the article's source.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -1026,7 +1020,6 @@ public IdStage rank(int rank) {
}
/**
- * The unique identifier for the article.
* The unique identifier for the article.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -1038,7 +1031,6 @@ public ScoreStage id(@NotNull String id) {
}
/**
- * The relevance score of the article.
* The relevance score of the article.
* @return Reference to {@code this} so that method calls can be chained together.
*/
diff --git a/src/main/java/com/newscatcher/api/types/BreakingNewsEventEntity.java b/src/main/java/com/newscatcher/api/types/BreakingNewsEventEntity.java
index 595c78b..8105aa3 100644
--- a/src/main/java/com/newscatcher/api/types/BreakingNewsEventEntity.java
+++ b/src/main/java/com/newscatcher/api/types/BreakingNewsEventEntity.java
@@ -149,7 +149,6 @@ public Builder from(BreakingNewsEventEntity other) {
}
/**
- * Unique identifier for the breaking news event/cluster.
* Unique identifier for the breaking news event/cluster.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -161,7 +160,6 @@ public ArticlesCountStage eventId(@NotNull String eventId) {
}
/**
- * Number of articles in this breaking news cluster.
* Number of articles in this breaking news cluster.
* @return Reference to {@code this} so that method calls can be chained together.
*/
diff --git a/src/main/java/com/newscatcher/api/types/BreakingNewsResponseDto.java b/src/main/java/com/newscatcher/api/types/BreakingNewsResponseDto.java
index 27ca1b8..fc7041e 100644
--- a/src/main/java/com/newscatcher/api/types/BreakingNewsResponseDto.java
+++ b/src/main/java/com/newscatcher/api/types/BreakingNewsResponseDto.java
@@ -248,7 +248,6 @@ public Builder from(BreakingNewsResponseDto other) {
}
/**
- * The status of the response.
* The status of the response.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -260,7 +259,6 @@ public TotalHitsStage status(@NotNull String status) {
}
/**
- * The total number of articles matching the search criteria.
* The total number of articles matching the search criteria.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -272,7 +270,6 @@ public PageStage totalHits(int totalHits) {
}
/**
- * The current page number of the results.
* The current page number of the results.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -284,7 +281,6 @@ public TotalPagesStage page(int page) {
}
/**
- * The total number of pages available for the given search criteria.
* The total number of pages available for the given search criteria.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -296,7 +292,6 @@ public PageSizeStage totalPages(int totalPages) {
}
/**
- * The number of articles per page.
* The number of articles per page.
* @return Reference to {@code this} so that method calls can be chained together.
*/
diff --git a/src/main/java/com/newscatcher/api/types/ClusterEntity.java b/src/main/java/com/newscatcher/api/types/ClusterEntity.java
index a647b16..a80357f 100644
--- a/src/main/java/com/newscatcher/api/types/ClusterEntity.java
+++ b/src/main/java/com/newscatcher/api/types/ClusterEntity.java
@@ -146,7 +146,6 @@ public Builder from(ClusterEntity other) {
}
/**
- * The unique identifier for the cluster.
* The unique identifier for the cluster.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -158,7 +157,6 @@ public ClusterSizeStage clusterId(@NotNull String clusterId) {
}
/**
- * The number of articles in the cluster.
* The number of articles in the cluster.
* @return Reference to {@code this} so that method calls can be chained together.
*/
diff --git a/src/main/java/com/newscatcher/api/types/ClusteredArticlesDto.java b/src/main/java/com/newscatcher/api/types/ClusteredArticlesDto.java
index a3c40d2..2c77eaa 100644
--- a/src/main/java/com/newscatcher/api/types/ClusteredArticlesDto.java
+++ b/src/main/java/com/newscatcher/api/types/ClusteredArticlesDto.java
@@ -126,7 +126,6 @@ public Builder from(ClusteredArticlesDto other) {
}
/**
- * The number of clusters in the search results.
* The number of clusters in the search results.
* @return Reference to {@code this} so that method calls can be chained together.
*/
diff --git a/src/main/java/com/newscatcher/api/types/ClusteredSearchResponseDto.java b/src/main/java/com/newscatcher/api/types/ClusteredSearchResponseDto.java
index 3bae523..8ae6e11 100644
--- a/src/main/java/com/newscatcher/api/types/ClusteredSearchResponseDto.java
+++ b/src/main/java/com/newscatcher/api/types/ClusteredSearchResponseDto.java
@@ -283,7 +283,6 @@ public Builder from(ClusteredSearchResponseDto other) {
}
/**
- * The status of the response.
* The status of the response.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -295,7 +294,6 @@ public TotalHitsStage status(@NotNull String status) {
}
/**
- * The total number of articles matching the search criteria.
* The total number of articles matching the search criteria.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -307,7 +305,6 @@ public PageStage totalHits(int totalHits) {
}
/**
- * The current page number of the results.
* The current page number of the results.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -319,7 +316,6 @@ public TotalPagesStage page(int page) {
}
/**
- * The total number of pages available for the given search criteria.
* The total number of pages available for the given search criteria.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -331,7 +327,6 @@ public PageSizeStage totalPages(int totalPages) {
}
/**
- * The number of articles per page.
* The number of articles per page.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -343,7 +338,6 @@ public ClustersCountStage pageSize(int pageSize) {
}
/**
- * The number of clusters in the search results.
* The number of clusters in the search results.
* @return Reference to {@code this} so that method calls can be chained together.
*/
diff --git a/src/main/java/com/newscatcher/api/types/Error.java b/src/main/java/com/newscatcher/api/types/Error.java
index 0681e39..86439d5 100644
--- a/src/main/java/com/newscatcher/api/types/Error.java
+++ b/src/main/java/com/newscatcher/api/types/Error.java
@@ -140,7 +140,6 @@ public Builder from(Error other) {
}
/**
- * A detailed description of the error.
* A detailed description of the error.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -152,7 +151,6 @@ public StatusCodeStage message(@NotNull String message) {
}
/**
- * The HTTP status code of the error.
* The HTTP status code of the error.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -164,7 +162,6 @@ public StatusStage statusCode(int statusCode) {
}
/**
- * A short description of the status code.
* A short description of the status code.
* @return Reference to {@code this} so that method calls can be chained together.
*/
diff --git a/src/main/java/com/newscatcher/api/types/FailedAggregationCountResponseDto.java b/src/main/java/com/newscatcher/api/types/FailedAggregationCountResponseDto.java
index f9aa009..956735d 100644
--- a/src/main/java/com/newscatcher/api/types/FailedAggregationCountResponseDto.java
+++ b/src/main/java/com/newscatcher/api/types/FailedAggregationCountResponseDto.java
@@ -217,7 +217,6 @@ public Builder from(FailedAggregationCountResponseDto other) {
}
/**
- * The status of the response.
* The status of the response.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -229,7 +228,6 @@ public TotalHitsStage status(@NotNull String status) {
}
/**
- * The total number of articles matching the search criteria.
* The total number of articles matching the search criteria.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -241,7 +239,6 @@ public PageStage totalHits(int totalHits) {
}
/**
- * The current page number of the results.
* The current page number of the results.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -253,7 +250,6 @@ public TotalPagesStage page(int page) {
}
/**
- * The total number of pages available for the given search criteria.
* The total number of pages available for the given search criteria.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -265,7 +261,6 @@ public PageSizeStage totalPages(int totalPages) {
}
/**
- * The number of articles per page.
* The number of articles per page.
* @return Reference to {@code this} so that method calls can be chained together.
*/
diff --git a/src/main/java/com/newscatcher/api/types/FailedAuthorsResponseDto.java b/src/main/java/com/newscatcher/api/types/FailedAuthorsResponseDto.java
index 29ebf63..070310a 100644
--- a/src/main/java/com/newscatcher/api/types/FailedAuthorsResponseDto.java
+++ b/src/main/java/com/newscatcher/api/types/FailedAuthorsResponseDto.java
@@ -242,7 +242,6 @@ public Builder from(FailedAuthorsResponseDto other) {
}
/**
- * The status of the response.
* The status of the response.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -254,7 +253,6 @@ public TotalHitsStage status(@NotNull String status) {
}
/**
- * The total number of articles matching the search criteria.
* The total number of articles matching the search criteria.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -266,7 +264,6 @@ public PageStage totalHits(int totalHits) {
}
/**
- * The current page number of the results.
* The current page number of the results.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -278,7 +275,6 @@ public TotalPagesStage page(int page) {
}
/**
- * The total number of pages available for the given search criteria.
* The total number of pages available for the given search criteria.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -290,7 +286,6 @@ public PageSizeStage totalPages(int totalPages) {
}
/**
- * The number of articles per page.
* The number of articles per page.
* @return Reference to {@code this} so that method calls can be chained together.
*/
diff --git a/src/main/java/com/newscatcher/api/types/SearchResponseDto.java b/src/main/java/com/newscatcher/api/types/SearchResponseDto.java
index c9450e2..1cd8280 100644
--- a/src/main/java/com/newscatcher/api/types/SearchResponseDto.java
+++ b/src/main/java/com/newscatcher/api/types/SearchResponseDto.java
@@ -236,7 +236,6 @@ public Builder from(SearchResponseDto other) {
}
/**
- * The status of the response.
* The status of the response.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -248,7 +247,6 @@ public TotalHitsStage status(@NotNull String status) {
}
/**
- * The total number of articles matching the search criteria.
* The total number of articles matching the search criteria.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -260,7 +258,6 @@ public PageStage totalHits(int totalHits) {
}
/**
- * The current page number of the results.
* The current page number of the results.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -272,7 +269,6 @@ public TotalPagesStage page(int page) {
}
/**
- * The total number of pages available for the given search criteria.
* The total number of pages available for the given search criteria.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -284,7 +280,6 @@ public PageSizeStage totalPages(int totalPages) {
}
/**
- * The number of articles per page.
* The number of articles per page.
* @return Reference to {@code this} so that method calls can be chained together.
*/
diff --git a/src/main/java/com/newscatcher/api/types/SourceInfo.java b/src/main/java/com/newscatcher/api/types/SourceInfo.java
index 0e8b869..ebc33f8 100644
--- a/src/main/java/com/newscatcher/api/types/SourceInfo.java
+++ b/src/main/java/com/newscatcher/api/types/SourceInfo.java
@@ -165,7 +165,6 @@ public Builder from(SourceInfo other) {
}
/**
- * The domain URL of the news source.
* The domain URL of the news source.
* @return Reference to {@code this} so that method calls can be chained together.
*/
diff --git a/src/main/java/com/newscatcher/api/types/SourcesResponseDto.java b/src/main/java/com/newscatcher/api/types/SourcesResponseDto.java
index c079f71..3e336f2 100644
--- a/src/main/java/com/newscatcher/api/types/SourcesResponseDto.java
+++ b/src/main/java/com/newscatcher/api/types/SourcesResponseDto.java
@@ -152,7 +152,6 @@ public Builder from(SourcesResponseDto other) {
}
/**
- * A message indicating the result of the request.
* A message indicating the result of the request.
* @return Reference to {@code this} so that method calls can be chained together.
*/
diff --git a/src/main/java/com/newscatcher/api/types/SubscriptionResponseDto.java b/src/main/java/com/newscatcher/api/types/SubscriptionResponseDto.java
index e1c1e02..a72502a 100644
--- a/src/main/java/com/newscatcher/api/types/SubscriptionResponseDto.java
+++ b/src/main/java/com/newscatcher/api/types/SubscriptionResponseDto.java
@@ -223,7 +223,6 @@ public Builder from(SubscriptionResponseDto other) {
}
/**
- * Indicates whether the subscription is currently active.
* Indicates whether the subscription is currently active.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -235,7 +234,6 @@ public ConcurrentCallsStage active(boolean active) {
}
/**
- * The number of API calls allowed per second allowed in the current plan.
* The number of API calls allowed per second allowed in the current plan.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -247,7 +245,6 @@ public PlanStage concurrentCalls(int concurrentCalls) {
}
/**
- * The name of the subscription plan.
* The name of the subscription plan.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -259,7 +256,6 @@ public PlanCallsStage plan(@NotNull String plan) {
}
/**
- * The total number of API calls assigned to the current subscription.
* The total number of API calls assigned to the current subscription.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -271,7 +267,6 @@ public RemainingCallsStage planCalls(int planCalls) {
}
/**
- * The number of API calls remaining for the current subscription period.
* The number of API calls remaining for the current subscription period.
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -283,7 +278,6 @@ public HistoricalDaysStage remainingCalls(int remainingCalls) {
}
/**
- * The number of historical days accessible under the current subscription plan.
* The number of historical days accessible under the current subscription plan.
* @return Reference to {@code this} so that method calls can be chained together.
*/
diff --git a/src/main/java/com/newscatcher/api/types/TimeFrameCount.java b/src/main/java/com/newscatcher/api/types/TimeFrameCount.java
index 5646c72..e25644c 100644
--- a/src/main/java/com/newscatcher/api/types/TimeFrameCount.java
+++ b/src/main/java/com/newscatcher/api/types/TimeFrameCount.java
@@ -120,7 +120,6 @@ public Builder from(TimeFrameCount other) {
}
/**
- * The timestamp for the aggregation period in format "YYYY-MM-DD HH:mm:ss"
* The timestamp for the aggregation period in format "YYYY-MM-DD HH:mm:ss"
* @return Reference to {@code this} so that method calls can be chained together.
*/
@@ -132,7 +131,6 @@ public ArticleCountStage timeFrame(@NotNull OffsetDateTime timeFrame) {
}
/**
- * The number of articles published during this time frame
* The number of articles published during this time frame
* @return Reference to {@code this} so that method calls can be chained together.
*/
From becb97dc1d34ea3b222fc9bb8cabf666a4d6b5fb Mon Sep 17 00:00:00 2001
From: "fern-api[bot]" <115122769+fern-api[bot]@users.noreply.github.com>
Date: Wed, 30 Sep 2026 10:10:41 +0000
Subject: [PATCH 2/3] [fern-autoversion] feat: add AutoCloseable support and
configurable retry backoff to clients
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Both `NewscatcherApiClient` and `AsyncNewscatcherApiClient` now implement
`AutoCloseable`, enabling use in try-with-resources blocks for proper
resource cleanup. The builder classes gain three new optional methods for
fine-grained control over exponential backoff retry behavior. Javadoc
deduplication across all builder types is also included.
Key changes:
- `NewscatcherApiClient` and `AsyncNewscatcherApiClient` implement `AutoCloseable` with a `close()` method that shuts down the SDK-owned HTTP client
- `NewscatcherApiClientBuilder` and `AsyncNewscatcherApiClientBuilder` add `initialRetryDelayMillis()`, `maxRetryDelayMillis()`, and `retryJitterFactor()` builder methods
- `setAuthentication()` now guards against a null `apiKey` before adding the `x-api-token` header
- Duplicate Javadoc `` tags removed from builder methods across all response/entity types
🌿 Generated with Fern
---
.fern/metadata.json | 2 +-
README.md | 4 ++--
build.gradle | 4 ++--
changelog.md | 14 ++++++++++++++
.../com/newscatcher/api/core/ClientOptions.java | 4 ++--
5 files changed, 21 insertions(+), 7 deletions(-)
diff --git a/.fern/metadata.json b/.fern/metadata.json
index 668cdda..5320c98 100644
--- a/.fern/metadata.json
+++ b/.fern/metadata.json
@@ -10,5 +10,5 @@
"invokedBy": "ci",
"requestedVersion": "AUTO",
"ciProvider": "unknown",
- "sdkVersion": "0.0.0-fern-placeholder"
+ "sdkVersion": "3.1.0"
}
\ No newline at end of file
diff --git a/README.md b/README.md
index 9aadfe7..c90bb3f 100644
--- a/README.md
+++ b/README.md
@@ -43,7 +43,7 @@ Add the dependency in your `build.gradle` file:
```groovy
dependencies {
- implementation 'com.newscatcherapi:newscatcher-sdk:0.0.0-fern-placeholder'
+ implementation 'com.newscatcherapi:newscatcher-sdk:3.1.0'
}
```
@@ -55,7 +55,7 @@ Add the dependency in your `pom.xml` file:
com.newscatcherapi
newscatcher-sdk
- 0.0.0-fern-placeholder
+ 3.1.0
```
diff --git a/build.gradle b/build.gradle
index e4dc06e..1da1926 100644
--- a/build.gradle
+++ b/build.gradle
@@ -57,7 +57,7 @@ java {
group = 'com.newscatcherapi'
-version = '0.0.0-fern-placeholder'
+version = '3.1.0'
jar {
dependsOn(":generatePomFileForMavenPublication")
@@ -88,7 +88,7 @@ publishing {
maven(MavenPublication) {
groupId = 'com.newscatcherapi'
artifactId = 'newscatcher-sdk'
- version = '0.0.0-fern-placeholder'
+ version = '3.1.0'
from components.java
pom {
name = 'newscatcher'
diff --git a/changelog.md b/changelog.md
index b528e80..14517d2 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,3 +1,17 @@
+## [3.1.0] - 2026-09-30
+### Added
+- **`NewscatcherApiClient` / `AsyncNewscatcherApiClient`** — both now implement `AutoCloseable`, enabling safe use in try-with-resources blocks.
+- **`ClientOptions.close()` and `ClientOptions.isClosed()`** — explicit lifecycle management that shuts down the OkHttpClient dispatcher, connection pool, and any tracked WebSocket clients.
+- **`RequestOptions.Builder.maxRetries(Integer)`** — overrides the client-wide retry count for a single request across all resource clients (sync and async).
+- **`initialRetryDelayMillis()`, `maxRetryDelayMillis()`, and `retryJitterFactor()`** — new builder methods on `NewscatcherApiClientBuilder`, `AsyncNewscatcherApiClientBuilder`, and `ClientOptions.Builder` for fine-grained exponential backoff control.
+- **`ResponseDecompressionInterceptor`** — transparently decompresses gzip- and deflate-encoded response bodies, including responses to requests with an explicit `Accept-Encoding` header.
+
+### Fixed
+- **`setAuthentication()`** — no longer sends a null `x-api-token` header when `apiKey` is `null` in both builder classes.
+- **`DateTimeDeserializer`** — now falls back to space-separated ISO 8601 and RFC 1123 date formats when standard ISO 8601 parsing fails.
+- **`RetryInterceptor`** — response bodies are buffered before retry, call timeouts are suspended during backoff sleeps, and transport errors during retry no longer discard the last successful response.
+- **Deserialization error surfacing** — all sync and async resource clients now catch `JsonProcessingException` and throw a descriptive `NewscatcherApiException` instead of routing failures through the generic error handler.
+
## 3.0.0 - 2026-05-19
### Breaking Changes
* **`NlpDataEntity.getSummaryTranslated()`** — renamed to `getTranslationSummary()`; the underlying JSON field also changed from `summary_translated` to `translation_summary`. Update all call sites to use `getTranslationSummary()` and `Builder.translationSummary(...)`.
diff --git a/src/main/java/com/newscatcher/api/core/ClientOptions.java b/src/main/java/com/newscatcher/api/core/ClientOptions.java
index 9d4a9f5..7dde855 100644
--- a/src/main/java/com/newscatcher/api/core/ClientOptions.java
+++ b/src/main/java/com/newscatcher/api/core/ClientOptions.java
@@ -60,10 +60,10 @@ private ClientOptions(
this.headers.putAll(headers);
this.headers.putAll(new HashMap() {
{
- put("User-Agent", "com.newscatcherapi.newscatcher-sdk/0.0.0-fern-placeholder");
+ put("User-Agent", "com.newscatcherapi.newscatcher-sdk/3.1.0");
put("X-Fern-Language", "JAVA");
put("X-Fern-SDK-Name", "com.newscatcherapi:newscatcher-sdk");
- put("X-Fern-SDK-Version", "0.0.0-fern-placeholder");
+ put("X-Fern-SDK-Version", "3.1.0");
}
});
this.headerSuppliers = headerSuppliers;
From 6336d46c9bfc3ba5f7e709f08dbd7a8317d2bb67 Mon Sep 17 00:00:00 2001
From: "fern-api[bot]" <115122769+fern-api[bot]@users.noreply.github.com>
Date: Wed, 30 Sep 2026 10:10:42 +0000
Subject: [PATCH 3/3] [fern-replay] advance lockfile
---
.fern/replay.lock | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/.fern/replay.lock b/.fern/replay.lock
index 4ad9ddd..3966eba 100644
--- a/.fern/replay.lock
+++ b/.fern/replay.lock
@@ -6,5 +6,11 @@ generations:
timestamp: 2026-09-30T10:10:01.906Z
cli_version: unknown
generator_versions: {}
-current_generation: 75f537a2fa3ca2e3708cc98bdc39c5adf9517c58
+ - commit_sha: 0777c023fb0589f1b7e9651db92b05846a020f2b
+ tree_hash: 6e94e97ce8f556e90f099649e62c3ba6194f75a2
+ timestamp: 2026-09-30T10:10:02.351Z
+ cli_version: unknown
+ generator_versions:
+ fernapi/fern-java-sdk: 4.23.1
+current_generation: 0777c023fb0589f1b7e9651db92b05846a020f2b
patches: []