Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .fern/metadata.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
{
"cliVersion": "5.143.0",
"cliVersion": "5.170.5",
"generatorName": "fernapi/fern-java-sdk",
"generatorVersion": "4.23.1",
"generatorVersion": "4.32.4",
"generatorConfig": {
"publish-to": "central",
"client-class-name": "CatchAllApi",
"package-prefix": "com.newscatcher.catchall"
},
"originGitCommit": "6ef7448ab601e5b372caec45717b0390a4150d2c",
"originGitCommit": "b7764dca3eb740a480b120edb87932f928b9210e",
"originGitCommitIsDirty": true,
"invokedBy": "ci",
"requestedVersion": "AUTO",
"ciProvider": "unknown",
"sdkVersion": "3.0.5"
"sdkVersion": "4.0.0"
}
8 changes: 7 additions & 1 deletion .fern/replay.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ jobs:
env:
MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
MAVEN_PUBLISH_REGISTRY_URL: "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
MAVEN_PUBLISH_REGISTRY_URL: "https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/"
MAVEN_SIGNATURE_KID: ${{ secrets.MAVEN_SIGNATURE_KID }}
MAVEN_SIGNATURE_SECRET_KEY: ${{ secrets.MAVEN_SIGNATURE_SECRET_KEY }}
MAVEN_SIGNATURE_PASSWORD: ${{ secrets.MAVEN_SIGNATURE_PASSWORD }}
27 changes: 24 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ The Newscatcher Java library provides convenient access to the Newscatcher APIs
- [Retries](#retries)
- [Timeouts](#timeouts)
- [Custom Headers](#custom-headers)
- [Additional Body Properties](#additional-body-properties)
- [Access Raw Response Data](#access-raw-response-data)
- [Contributing](#contributing)
- [Support](#support)
Expand All @@ -35,7 +36,7 @@ Add the dependency in your `build.gradle` file:

```groovy
dependencies {
implementation 'com.newscatcherapi:newscatcher-catchall-sdk:3.0.5'
implementation 'com.newscatcherapi:newscatcher-catchall-sdk:4.0.0'
}
```

Expand All @@ -47,7 +48,7 @@ Add the dependency in your `pom.xml` file:
<dependency>
<groupId>com.newscatcherapi</groupId>
<artifactId>newscatcher-catchall-sdk</artifactId>
<version>3.0.5</version>
<version>4.0.0</version>
</dependency>
```

Expand Down Expand Up @@ -99,7 +100,7 @@ import com.newscatcher.catchall.core.Environment;

CatchAllApi client = CatchAllApi
.builder()
.environment(Environment.Default)
.environment(Environment.DEFAULT)
.build();
```

Expand Down Expand Up @@ -231,6 +232,25 @@ client.jobs().createJob(
);
```

### Additional Body Properties

If you need to send a request body property that is not yet part of the SDK (for example, a beta field),
use the `addBodyProperty` request option. Properties are sent with the key exactly as given and override
any property with the same key; for endpoints without a request body, they are sent as a JSON body.
This is supported for JSON and form-urlencoded request bodies, but not for multipart (file upload) requests.

```java
import com.newscatcher.catchall.core.RequestOptions;

client.jobs().createJob(
...,
RequestOptions
.builder()
.addBodyProperty("extra_field", "extra-value")
.build()
);
```

### Access Raw Response Data

The SDK provides access to raw response data, including headers, through the `withRawResponse()` method.
Expand Down Expand Up @@ -268,3 +288,4 @@ On the other hand, contributions to the README are always very welcome!




4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ java {

group = 'com.newscatcherapi'

version = '3.0.5'
version = '4.0.0'

jar {
dependsOn(":generatePomFileForMavenPublication")
Expand Down Expand Up @@ -88,7 +88,7 @@ publishing {
maven(MavenPublication) {
groupId = 'com.newscatcherapi'
artifactId = 'newscatcher-catchall-sdk'
version = '3.0.5'
version = '4.0.0'
from components.java
pom {
name = 'newscatcher'
Expand Down
18 changes: 18 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
## [4.0.0] - 2026-10-22
### Breaking Changes
- **`DatasetsClient.createDatasetFromCsv()`** — all InputStream overloads now require a `CreateDatasetFromCsvRequest` as the first argument; replace `createDatasetFromCsv(stream, filename, ...)` with `createDatasetFromCsv(request, stream, filename, ...)`.
- **`AsyncDatasetsClient.createDatasetFromCsv()`** — same signature change as the synchronous client; prepend a `CreateDatasetFromCsvRequest` argument before `stream` and `filename` at every call site.
- **`RawDatasetsClient.createDatasetFromCsv()`** — all four overloads now require a leading `CreateDatasetFromCsvRequest` parameter so that `name` and `description` are sent as multipart form fields; add a `CreateDatasetFromCsvRequest` as the first argument to every call site.
- **`AsyncRawDatasetsClient.createDatasetFromCsv()`** — all four overloads now require a leading `CreateDatasetFromCsvRequest` parameter; update every call site to prepend a `CreateDatasetFromCsvRequest` argument.

### Added
- **`RequestOptions.Builder.addBodyProperty()` / `bodyProperties()`** — new builder methods that inject arbitrary key/value pairs into the JSON or form-urlencoded request body, overriding any existing property with the same key.
- **`RetryInterceptor.newAsyncCall()`** — new static factory that creates a non-blocking async call with built-in retry support, preventing OkHttp dispatcher slot exhaustion and enabling immediate cancellation.
- **`Stream.fromSseWithEventDiscrimination()`** — new static factory for SSE streams that supports envelope-level event discrimination, deserializing specified event names from the full SSE envelope rather than just the `data` payload.
- **`PathSegments.validate()`** — new core utility that rejects `"."` and `".."` as path parameter values, preventing silent URL path traversal.

### Changed
- **`AsyncDatasetsClient`** — all async methods now propagate `CompletableFuture` cancellation to the underlying HTTP call, ensuring in-flight requests are properly cancelled.
- **Request builder classes** — `@JsonAnySetter` now filters out known property keys on all request types (e.g., `CreateDatasetRequest`, `UpdateDatasetRequest`, `ListDatasetsRequest`, and others), preventing typed fields from being silently overwritten during deserialization.
- **`RawDatasetsClient`** — path segment values for `datasetId` are now validated via `PathSegments.validate()` and extra body properties from `RequestOptions` are merged into every JSON request body.

## [3.0.5] - 2026-09-30

## [3.0.4] - 2026-08-04
Expand Down
89 changes: 89 additions & 0 deletions src/main/java/com/newscatcher/catchall/core/BodyProperties.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/**
* This file was auto-generated by Fern from our API Definition.
*/
package com.newscatcher.catchall.core;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.util.LinkedHashMap;
import java.util.Map;
import okhttp3.RequestBody;

/**
* Merges the additional body properties set through {@code RequestOptions.Builder#addBodyProperty} into request
* bodies. Properties are sent with the keys exactly as given (no casing transform) and override any generated body
* property with the same key.
*
* <p>Supported for JSON and {@code application/x-www-form-urlencoded} bodies. Multipart (file upload) and raw bytes
* bodies are sent unchanged.
*/
public final class BodyProperties {

private BodyProperties() {}

public static boolean isPresent(Map<String, Object> bodyProperties) {
return bodyProperties != null && !bodyProperties.isEmpty();
}

/**
* Returns the value to serialize as the JSON request body. Without body properties the body is returned unchanged.
* Otherwise the body is converted to a JSON object (an empty one when the body is null or absent) and the body
* properties are set on it, overriding existing keys. Bodies that are not JSON objects (arrays, primitives) are
* returned unchanged.
*/
public static Object merge(Object body, Map<String, Object> bodyProperties) throws JsonProcessingException {
if (!isPresent(bodyProperties)) {
return body;
}
JsonNode node;
try {
node = ObjectMappers.JSON_MAPPER.valueToTree(body);
} catch (IllegalArgumentException e) {
throw JsonMappingException.from((com.fasterxml.jackson.core.JsonParser) null, e.getMessage(), e);
}
ObjectNode merged;
if (node == null || node.isNull() || node.isMissingNode()) {
merged = ObjectMappers.JSON_MAPPER.createObjectNode();
} else if (node.isObject()) {
merged = (ObjectNode) node;
} else {
return body;
}
for (Map.Entry<String, Object> entry : bodyProperties.entrySet()) {
merged.putPOJO(entry.getKey(), entry.getValue());
}
return merged;
}

/**
* Returns a JSON request body made of the body properties for an endpoint without a request body, or the fallback
* when there are no body properties.
*/
public static RequestBody toRequestBody(Map<String, Object> bodyProperties, RequestBody fallback) {
if (!isPresent(bodyProperties)) {
return fallback;
}
try {
return RequestBody.create(
ObjectMappers.JSON_MAPPER.writeValueAsBytes(merge(null, bodyProperties)),
MediaTypes.APPLICATION_JSON);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("Failed to serialize body properties", e);
}
}

/** Returns the form parameters with the body properties added, overriding existing keys. */
public static Map<String, Object> mergeFormParams(
Map<String, Object> formParams, Map<String, Object> bodyProperties) {
Map<String, Object> merged = new LinkedHashMap<>();
if (formParams != null) {
merged.putAll(formParams);
}
if (isPresent(bodyProperties)) {
merged.putAll(bodyProperties);
}
return merged;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,10 @@ private ClientOptions(
this.headers.putAll(headers);
this.headers.putAll(new HashMap<String, String>() {
{
put("User-Agent", "com.newscatcherapi.newscatcher-catchall-sdk/3.0.5");
put("User-Agent", "com.newscatcherapi.newscatcher-catchall-sdk/4.0.0");
put("X-Fern-Language", "JAVA");
put("X-Fern-SDK-Name", "com.newscatcherapi:newscatcher-catchall-sdk");
put("X-Fern-SDK-Version", "3.0.5");
put("X-Fern-SDK-Version", "4.0.0");
}
});
this.headerSuppliers = headerSuppliers;
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/com/newscatcher/catchall/core/LogLevel.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
*/
package com.newscatcher.catchall.core;

import java.util.Locale;

/**
* Log levels for SDK logging configuration.
* Silent by default — no log output unless explicitly configured.
Expand Down Expand Up @@ -31,6 +33,6 @@ public int getValue() {
* @throws IllegalArgumentException if the string does not match any level
*/
public static LogLevel fromString(String level) {
return LogLevel.valueOf(level.toUpperCase());
return LogLevel.valueOf(level.toUpperCase(Locale.ROOT));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import okhttp3.Interceptor;
import okhttp3.Request;
Expand Down Expand Up @@ -58,7 +59,7 @@ public Response intercept(Chain chain) throws IOException {
sb.append(", ");
}
sb.append(name).append("=");
if (SENSITIVE_HEADERS.contains(name.toLowerCase())) {
if (SENSITIVE_HEADERS.contains(name.toLowerCase(Locale.ROOT))) {
sb.append("[REDACTED]");
} else {
sb.append(request.header(name));
Expand All @@ -83,7 +84,7 @@ public Response intercept(Chain chain) throws IOException {
sb.append(", ");
}
sb.append(name).append("=");
if (SENSITIVE_HEADERS.contains(name.toLowerCase())) {
if (SENSITIVE_HEADERS.contains(name.toLowerCase(Locale.ROOT))) {
sb.append("[REDACTED]");
} else {
sb.append(response.header(name));
Expand Down
21 changes: 21 additions & 0 deletions src/main/java/com/newscatcher/catchall/core/PathSegments.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* This file was auto-generated by Fern from our API Definition.
*/
package com.newscatcher.catchall.core;

public final class PathSegments {

private PathSegments() {}

/**
* Returns the value unchanged, rejecting "." and "..". These are dot-segments that URL builders resolve,
* which would otherwise change the endpoint a request is sent to.
*/
public static String validate(String value) {
if (".".equals(value) || "..".equals(value)) {
throw new IllegalArgumentException(
"Invalid path parameter value \"" + value + "\": \".\" and \"..\" are not allowed.");
}
return value;
}
}
Loading
Loading