From 964e73bb1973967f7ea199b4fd76d496b87a1c3f Mon Sep 17 00:00:00 2001 From: Orkun Date: Tue, 22 Sep 2026 14:13:25 +0300 Subject: [PATCH 1/7] fix: reject invalid event and visitor ID path parameter values An ID given to `getEvent`, `updateEvent` or `deleteVisitorData` is percent-encoded, so slashes cannot inject extra path segments. A value that is not a valid identifier is now rejected before a request is sent. The check lives in the api.mustache template and is applied to every path parameter on regeneration. The thrown message stays generic, since it can surface to end users. INTER-2503 --- .changeset/validate-id-path-parameters.md | 5 + docs/FingerprintApi.md | 6 +- .../fingerprint/v4/api/FingerprintApi.java | 41 ++- .../fingerprint/v4/api/PathParameterTest.java | 256 ++++++++++++++++++ template/libraries/jersey3/api.mustache | 22 +- template/libraries/jersey3/api_doc.mustache | 4 +- 6 files changed, 312 insertions(+), 22 deletions(-) create mode 100644 .changeset/validate-id-path-parameters.md create mode 100644 sdk/src/test/java/com/fingerprint/v4/api/PathParameterTest.java diff --git a/.changeset/validate-id-path-parameters.md b/.changeset/validate-id-path-parameters.md new file mode 100644 index 00000000..8576e9f4 --- /dev/null +++ b/.changeset/validate-id-path-parameters.md @@ -0,0 +1,5 @@ +--- +'java-sdk': patch +--- + +**sdk**: `getEvent`, `updateEvent`, and `deleteVisitorData` now validate their ID path parameter and throw an `ApiException` without sending a request when the value is not a valid identifier. diff --git a/docs/FingerprintApi.md b/docs/FingerprintApi.md index 74968f9d..325b6950 100644 --- a/docs/FingerprintApi.md +++ b/docs/FingerprintApi.md @@ -89,7 +89,7 @@ public class FingerprintApiExample { | Name | Type | Description | Notes | |------------- | ------------- | ------------- | -------------| -| **visitorId** | **String**| The [visitor ID](https://docs.fingerprint.com/reference/js-agent-v4-get-function#visitor_id) you want to delete. | | +| **visitorId** | **String**| The [visitor ID](https://docs.fingerprint.com/reference/js-agent-v4-get-function#visitor_id) you want to delete. A value that is not a valid identifier is rejected before a request is sent. | | ### Return type @@ -171,7 +171,7 @@ public class FingerprintApiExample { | Name | Type | Description | Notes | |------------- | ------------- | ------------- | -------------| -| **eventId** | **String**| The unique [identifier](https://docs.fingerprint.com/reference/js-agent-v4-get-function#event_id) of each identification request (`requestId` can be used in its place). | | +| **eventId** | **String**| The unique [identifier](https://docs.fingerprint.com/reference/js-agent-v4-get-function#event_id) of each identification request (`requestId` can be used in its place). A value that is not a valid identifier is rejected before a request is sent. | | | **getEventOptionalParams** | [**FingerprintApi.GetEventOptionalParams**](#fingerprintapigeteventoptionalparams) | | [optional] | #### FingerprintApi.GetEventOptionalParams @@ -541,7 +541,7 @@ public class FingerprintApiExample { | Name | Type | Description | Notes | |------------- | ------------- | ------------- | -------------| -| **eventId** | **String**| The unique event [identifier](https://docs.fingerprint.com/reference/js-agent-v4-get-function#event_id). | | +| **eventId** | **String**| The unique event [identifier](https://docs.fingerprint.com/reference/js-agent-v4-get-function#event_id). A value that is not a valid identifier is rejected before a request is sent. | | | **eventUpdate** | [**EventUpdate**](EventUpdate.md)| | | ### Return type diff --git a/sdk/src/main/java/com/fingerprint/v4/api/FingerprintApi.java b/sdk/src/main/java/com/fingerprint/v4/api/FingerprintApi.java index e335038b..64f62bd3 100644 --- a/sdk/src/main/java/com/fingerprint/v4/api/FingerprintApi.java +++ b/sdk/src/main/java/com/fingerprint/v4/api/FingerprintApi.java @@ -70,7 +70,7 @@ public void setApiClient(ApiClient apiClient) { /** * Delete a visitor ID * Use this API to request the deletion of all data associated with a specific visitor ID. Upon a request to delete data for a visitor ID, - The data collected from the corresponding browser (or device) will be deleted asynchronously, typically within a few minutes. This data will no longer be available to identify this browser (or device). When the same browser (or device) revisits, it will receive a new visitor ID. - The identification events made from this browser (or device) in the past 10 days are typically deleted within 24 hrs. - The identification events made from this browser (or device) outside of the 10 days will be purged as per your [data retention period](https://docs.fingerprint.com/docs/regions#data-retention). The following timeline illustrates which events are deleted and which remain after a DELETE API request: ``` Day 1: First visit from browser A. (Assigned visitor ID: VID1000) Day 2: Browser A revisits. (Assigned the same visitor ID: VID1000) Day 13: Browser A revisits. (Assigned the same visitor ID: VID1000) Day 14: Delete VID1000 Day 15: Browser A re-visits. (Assigned a different visitor ID: VID9999) Day 15: GET /events/day-13 (Returns 404. The event is within the 10 days of deleting VID1000 and will have been deleted) Day 16: GET /events/day-2 (Returns 200. The event is outside of the 10 days of deleting VID1000 and is still available) ``` ### Availability This API is available only for Enterprise plans **upon request**. If you are interested, please [contact our support team](https://fingerprint.com/support/). ### Rate limits and daily quota The rate limits and daily quota for this API **differ** from those for our other API. The maximum number of DELETE requests that can be made in an hour cannot exceed 30 RPH, and the maximum number that can be made in a day cannot exceed 500 RPD. You can request an increase to these limits by contacting [our support team](https://fingerprint.com/support/). - * @param visitorId The [visitor ID](https://docs.fingerprint.com/reference/js-agent-v4-get-function#visitor_id) you want to delete. (required) + * @param visitorId The [visitor ID](https://docs.fingerprint.com/reference/js-agent-v4-get-function#visitor_id) you want to delete. (required) A value that is not a valid identifier is rejected with an {@link ApiException} before a request is sent. * @throws ApiException if fails to make API call * @http.response.details * @@ -89,7 +89,7 @@ public void deleteVisitorData(String visitorId) throws ApiException { /** * Delete a visitor ID * Use this API to request the deletion of all data associated with a specific visitor ID. Upon a request to delete data for a visitor ID, - The data collected from the corresponding browser (or device) will be deleted asynchronously, typically within a few minutes. This data will no longer be available to identify this browser (or device). When the same browser (or device) revisits, it will receive a new visitor ID. - The identification events made from this browser (or device) in the past 10 days are typically deleted within 24 hrs. - The identification events made from this browser (or device) outside of the 10 days will be purged as per your [data retention period](https://docs.fingerprint.com/docs/regions#data-retention). The following timeline illustrates which events are deleted and which remain after a DELETE API request: ``` Day 1: First visit from browser A. (Assigned visitor ID: VID1000) Day 2: Browser A revisits. (Assigned the same visitor ID: VID1000) Day 13: Browser A revisits. (Assigned the same visitor ID: VID1000) Day 14: Delete VID1000 Day 15: Browser A re-visits. (Assigned a different visitor ID: VID9999) Day 15: GET /events/day-13 (Returns 404. The event is within the 10 days of deleting VID1000 and will have been deleted) Day 16: GET /events/day-2 (Returns 200. The event is outside of the 10 days of deleting VID1000 and is still available) ``` ### Availability This API is available only for Enterprise plans **upon request**. If you are interested, please [contact our support team](https://fingerprint.com/support/). ### Rate limits and daily quota The rate limits and daily quota for this API **differ** from those for our other API. The maximum number of DELETE requests that can be made in an hour cannot exceed 30 RPH, and the maximum number that can be made in a day cannot exceed 500 RPD. You can request an increase to these limits by contacting [our support team](https://fingerprint.com/support/). - * @param visitorId The [visitor ID](https://docs.fingerprint.com/reference/js-agent-v4-get-function#visitor_id) you want to delete. (required) + * @param visitorId The [visitor ID](https://docs.fingerprint.com/reference/js-agent-v4-get-function#visitor_id) you want to delete. (required) A value that is not a valid identifier is rejected with an {@link ApiException} before a request is sent. * @return ApiResponse * @throws ApiException if fails to make API call * @http.response.details @@ -111,10 +111,15 @@ public ApiResponse deleteVisitorDataWithHttpInfo(String visitorId) throws 400, "Missing the required parameter 'visitorId' when calling deleteVisitorData"); } + // verify the path parameter 'visitorId' is a valid identifier + String escapedVisitorId = apiClient.escapeString(visitorId.toString()); + if (escapedVisitorId.equals(".") || escapedVisitorId.equals("..")) { + throw new ApiException(400, "invalid value for path parameter visitorId"); + } + // create path and map variables String localVarPath = - "/visitors/{visitor_id}" - .replaceAll("\\{" + "visitor_id" + "\\}", apiClient.escapeString(visitorId.toString())); + "/visitors/{visitor_id}".replaceAll("\\{" + "visitor_id" + "\\}", escapedVisitorId); // query params List localVarQueryParams = new ArrayList(); @@ -167,7 +172,7 @@ public GetEventOptionalParams setRulesetId(String rulesetId) { /** * Get an event by event ID * Get a detailed analysis of an individual identification event, including Smart Signals. Use `event_id` as the URL path parameter. This API method is scoped to a request, i.e. all returned information is by `event_id`. - * @param eventId The unique [identifier](https://docs.fingerprint.com/reference/js-agent-v4-get-function#event_id) of each identification request (`requestId` can be used in its place). (required) + * @param eventId The unique [identifier](https://docs.fingerprint.com/reference/js-agent-v4-get-function#event_id) of each identification request (`requestId` can be used in its place). (required) A value that is not a valid identifier is rejected with an {@link ApiException} before a request is sent. * @return Event * @throws ApiException if fails to make API call * @http.response.details @@ -189,7 +194,7 @@ public Event getEvent(String eventId) throws ApiException { /** * Get an event by event ID * Get a detailed analysis of an individual identification event, including Smart Signals. Use `event_id` as the URL path parameter. This API method is scoped to a request, i.e. all returned information is by `event_id`. - * @param eventId The unique [identifier](https://docs.fingerprint.com/reference/js-agent-v4-get-function#event_id) of each identification request (`requestId` can be used in its place). (required) + * @param eventId The unique [identifier](https://docs.fingerprint.com/reference/js-agent-v4-get-function#event_id) of each identification request (`requestId` can be used in its place). (required) A value that is not a valid identifier is rejected with an {@link ApiException} before a request is sent. * @param getEventOptionalParams Object containing optional parameters for API method. (optional) * @return Event * @throws ApiException if fails to make API call @@ -213,7 +218,7 @@ public Event getEvent(String eventId, GetEventOptionalParams getEventOptionalPar /** * Get an event by event ID * Get a detailed analysis of an individual identification event, including Smart Signals. Use `event_id` as the URL path parameter. This API method is scoped to a request, i.e. all returned information is by `event_id`. - * @param eventId The unique [identifier](https://docs.fingerprint.com/reference/js-agent-v4-get-function#event_id) of each identification request (`requestId` can be used in its place). (required) + * @param eventId The unique [identifier](https://docs.fingerprint.com/reference/js-agent-v4-get-function#event_id) of each identification request (`requestId` can be used in its place). (required) A value that is not a valid identifier is rejected with an {@link ApiException} before a request is sent. * @param getEventOptionalParams Object containing optional parameters for API method. (optional) * @return ApiResponse * @throws ApiException if fails to make API call @@ -238,10 +243,15 @@ public ApiResponse getEventWithHttpInfo( throw new ApiException(400, "Missing the required parameter 'eventId' when calling getEvent"); } + // verify the path parameter 'eventId' is a valid identifier + String escapedEventId = apiClient.escapeString(eventId.toString()); + if (escapedEventId.equals(".") || escapedEventId.equals("..")) { + throw new ApiException(400, "invalid value for path parameter eventId"); + } + // create path and map variables String localVarPath = - "/events/{event_id}" - .replaceAll("\\{" + "event_id" + "\\}", apiClient.escapeString(eventId.toString())); + "/events/{event_id}".replaceAll("\\{" + "event_id" + "\\}", escapedEventId); // query params List localVarQueryParams = new ArrayList(); @@ -1395,7 +1405,7 @@ public ApiResponse searchEventsWithHttpInfo( /** * Update an event * Change information in existing events specified by `event_id` or *flag suspicious events*. When an event is created, it can be assigned `linked_id` and `tags` submitted through the JS agent parameters. This information might not have been available on the client initially, so the Server API permits updating these attributes after the fact. **Warning** It's not possible to update events older than one month. **Warning** Trying to update an event immediately after creation may temporarily result in an error (HTTP 409 Conflict. The event is not mutable yet.) as the event is fully propagated across our systems. In such a case, simply retry the request. - * @param eventId The unique event [identifier](https://docs.fingerprint.com/reference/js-agent-v4-get-function#event_id). (required) + * @param eventId The unique event [identifier](https://docs.fingerprint.com/reference/js-agent-v4-get-function#event_id). (required) A value that is not a valid identifier is rejected with an {@link ApiException} before a request is sent. * @param eventUpdate (required) * @throws ApiException if fails to make API call * @http.response.details @@ -1415,7 +1425,7 @@ public void updateEvent(String eventId, EventUpdate eventUpdate) throws ApiExcep /** * Update an event * Change information in existing events specified by `event_id` or *flag suspicious events*. When an event is created, it can be assigned `linked_id` and `tags` submitted through the JS agent parameters. This information might not have been available on the client initially, so the Server API permits updating these attributes after the fact. **Warning** It's not possible to update events older than one month. **Warning** Trying to update an event immediately after creation may temporarily result in an error (HTTP 409 Conflict. The event is not mutable yet.) as the event is fully propagated across our systems. In such a case, simply retry the request. - * @param eventId The unique event [identifier](https://docs.fingerprint.com/reference/js-agent-v4-get-function#event_id). (required) + * @param eventId The unique event [identifier](https://docs.fingerprint.com/reference/js-agent-v4-get-function#event_id). (required) A value that is not a valid identifier is rejected with an {@link ApiException} before a request is sent. * @param eventUpdate (required) * @return ApiResponse * @throws ApiException if fails to make API call @@ -1445,10 +1455,15 @@ public ApiResponse updateEventWithHttpInfo(String eventId, EventUpdate eve 400, "Missing the required parameter 'eventUpdate' when calling updateEvent"); } + // verify the path parameter 'eventId' is a valid identifier + String escapedEventId = apiClient.escapeString(eventId.toString()); + if (escapedEventId.equals(".") || escapedEventId.equals("..")) { + throw new ApiException(400, "invalid value for path parameter eventId"); + } + // create path and map variables String localVarPath = - "/events/{event_id}" - .replaceAll("\\{" + "event_id" + "\\}", apiClient.escapeString(eventId.toString())); + "/events/{event_id}".replaceAll("\\{" + "event_id" + "\\}", escapedEventId); // query params List localVarQueryParams = new ArrayList(); diff --git a/sdk/src/test/java/com/fingerprint/v4/api/PathParameterTest.java b/sdk/src/test/java/com/fingerprint/v4/api/PathParameterTest.java new file mode 100644 index 00000000..a6e3219f --- /dev/null +++ b/sdk/src/test/java/com/fingerprint/v4/api/PathParameterTest.java @@ -0,0 +1,256 @@ +package com.fingerprint.v4.api; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.fingerprint.v4.model.EventUpdate; +import com.fingerprint.v4.sdk.ApiClient; +import com.fingerprint.v4.sdk.ApiException; +import com.sun.net.httpserver.HttpServer; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.TestFactory; + +/** + * Tests for how an ID given to a public API method travels into the request URL. + */ +public class PathParameterTest { + + @FunctionalInterface + private interface ApiCall { + void call(FingerprintApi api, String id) throws ApiException; + } + + /** An operation that takes an ID in the path. */ + private static final class Endpoint { + final String name; + + /** The parameter name as it appears in the rejection message. */ + final String param; + + /** The path the ID is appended to. */ + final String prefix; + + /** + * The stub response body, so a call that reaches the server can be asserted to succeed. Empty + * for operations that return nothing. + */ + final String response; + + final ApiCall call; + + Endpoint(String name, String param, String prefix, String response, ApiCall call) { + this.name = name; + this.param = param; + this.prefix = prefix; + this.response = response; + this.call = call; + } + } + + /** Returns every operation that takes an ID in the path. */ + private static List endpoints() throws IOException { + return Arrays.asList( + new Endpoint( + "getEvent", + "eventId", + "/events/", + readResource("mocks/events/get_event_200.json"), + (api, id) -> api.getEvent(id)), + new Endpoint( + "updateEvent", + "eventId", + "/events/", + "", + (api, id) -> api.updateEvent(id, new EventUpdate())), + new Endpoint( + "deleteVisitorData", + "visitorId", + "/visitors/", + "", + (api, id) -> api.deleteVisitorData(id))); + } + + private static String readResource(String name) throws IOException { + try (InputStream stream = PathParameterTest.class.getClassLoader().getResourceAsStream(name)) { + if (stream == null) { + throw new IllegalArgumentException(name + " is not found"); + } + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + byte[] chunk = new byte[8192]; + int read; + while ((read = stream.read(chunk)) != -1) { + buffer.write(chunk, 0, read); + } + return buffer.toString(StandardCharsets.UTF_8.name()); + } + } + + /** A stub API that records the request it was asked for. */ + private static final class StubApi implements AutoCloseable { + private final HttpServer server; + private final AtomicReference requestUri = new AtomicReference<>(); + + StubApi(String response) throws IOException { + byte[] body = response.getBytes(StandardCharsets.UTF_8); + + server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0); + server.createContext( + "/", + exchange -> { + requestUri.set(exchange.getRequestURI()); + + exchange.getResponseHeaders().set("Content-Type", "application/json"); + exchange.sendResponseHeaders(200, body.length == 0 ? -1 : body.length); + + try (OutputStream out = exchange.getResponseBody()) { + out.write(body); + } + }); + server.start(); + } + + FingerprintApi client() { + ApiClient apiClient = new ApiClient(); + apiClient.setBasePath("http://127.0.0.1:" + server.getAddress().getPort()); + apiClient.setBearerToken("api_key"); + + return new FingerprintApi(apiClient); + } + + /** The literal request target the server received, without the query string. */ + String requestTarget() { + URI uri = requestUri.get(); + + return uri == null ? null : uri.getRawPath(); + } + + /** The request target the server received, percent-decoded. */ + String decodedPath() { + URI uri = requestUri.get(); + + return uri == null ? null : uri.getPath(); + } + + boolean wasRequested() { + return requestUri.get() != null; + } + + @Override + public void close() { + server.stop(0); + } + } + + private static final class Id { + final String name; + final String id; + final String encoded; + + Id(String name, String id, String encoded) { + this.name = name; + this.id = id; + this.encoded = encoded; + } + } + + /** + * Asserts that an ID travels as a single, opaque path segment, so a value containing slashes + * cannot inject extra segments. + * + *

Assertions are made against the literal request target the server received, since only the + * bytes on the wire show what the API would actually be asked for. + */ + @TestFactory + Stream pathParameterEncoding() throws IOException { + List ids = + Arrays.asList( + new Id("Path traversal", "../events", "..%2Fevents"), + new Id("Nested path traversal", "../../events", "..%2F..%2Fevents"), + new Id("Leading slash", "/events/123", "%2Fevents%2F123"), + new Id("Absolute URL", "https://domain.tld/evil", "https%3A%2F%2Fdomain.tld%2Fevil"), + new Id("Query injection", "123?limit=1", "123%3Flimit%3D1"), + new Id("Fragment injection", "123#fragment", "123%23fragment"), + new Id("Whitespace", "hello world", "hello%20world"), + new Id("Empty", "", ""), + // Only a segment made up solely of dots is a dot-segment, so anything else containing a + // dot keeps passing through untouched. + new Id("Regular ID with a dot", "1708102555327.NLOjmg", "1708102555327.NLOjmg"), + new Id("Three dots", "...", "...")); + + List tests = new ArrayList<>(); + + for (Endpoint endpoint : endpoints()) { + for (Id id : ids) { + tests.add( + DynamicTest.dynamicTest( + endpoint.name + ": " + id.name, + () -> { + try (StubApi stub = new StubApi(endpoint.response)) { + endpoint.call.call(stub.client(), id.id); + + // The ID stays inside a single, opaque segment on the wire... + assertEquals(endpoint.prefix + id.encoded, stub.requestTarget()); + // ...and decodes back to exactly the ID the caller asked for. + assertEquals(endpoint.prefix + id.id, stub.decodedPath()); + } + })); + } + } + + return tests.stream(); + } + + /** + * Asserts that an ID of exactly "." or ".." is refused before any request is made. + * + *

Percent-encoding cannot help here: both stay literal dots once escaped, so a URL normalizer + * between the SDK and the API would collapse the segment and address a different endpoint than + * the caller asked for. No resource can be identified by such a value in the first place. + */ + @TestFactory + Stream pathParameterDotSegmentIsRejected() throws IOException { + List ids = + Arrays.asList(new Id("Dot segment", ".", "."), new Id("Parent dot segment", "..", "..")); + + List tests = new ArrayList<>(); + + for (Endpoint endpoint : endpoints()) { + for (Id id : ids) { + tests.add( + DynamicTest.dynamicTest( + endpoint.name + ": " + id.name, + () -> { + try (StubApi stub = new StubApi(endpoint.response)) { + FingerprintApi api = stub.client(); + + ApiException exception = + assertThrows(ApiException.class, () -> endpoint.call.call(api, id.id)); + + assertEquals(400, exception.getCode()); + // The message stays generic on purpose: it must not tell a caller which + // values are rejected, since it can surface to end users. + assertEquals( + "invalid value for path parameter " + endpoint.param, + exception.getMessage()); + assertFalse(stub.wasRequested(), "no request should have been sent"); + } + })); + } + } + + return tests.stream(); + } +} diff --git a/template/libraries/jersey3/api.mustache b/template/libraries/jersey3/api.mustache index 6bdbc8a6..ce8ca887 100644 --- a/template/libraries/jersey3/api.mustache +++ b/template/libraries/jersey3/api.mustache @@ -121,7 +121,7 @@ public class {{classname}} { * {{{notes}}} {{#allParams}} {{#required}} - * @param {{paramName}} {{{description}}} (required) + * @param {{paramName}} {{{description}}} (required){{#isPathParam}} A value that is not a valid identifier is rejected with an {@link ApiException} before a request is sent.{{/isPathParam}} {{/required}} {{/allParams}} {{#returnType}} @@ -159,7 +159,7 @@ public class {{classname}} { * {{{notes}}} {{#allParams}} {{#required}} - * @param {{paramName}} {{{description}}} (required) + * @param {{paramName}} {{{description}}} (required){{#isPathParam}} A value that is not a valid identifier is rejected with an {@link ApiException} before a request is sent.{{/isPathParam}} {{/required}} {{/allParams}} {{#hasOptionalParams}} @@ -198,7 +198,7 @@ public class {{classname}} { * {{{notes}}} {{#allParams}} {{#required}} - * @param {{paramName}} {{{description}}} (required) + * @param {{paramName}} {{{description}}} (required){{#isPathParam}} A value that is not a valid identifier is rejected with an {@link ApiException} before a request is sent.{{/isPathParam}} {{/required}} {{/allParams}} {{#hasOptionalParams}} @@ -234,9 +234,23 @@ public class {{classname}} { throw new ApiException(400, "Missing the required parameter '{{paramName}}' when calling {{operationId}}"); } {{/required}}{{/allParams}} + {{! A path parameter has to survive as a single, opaque path segment. Percent-encoding keeps }} + {{! slashes from injecting extra segments, but "." and ".." stay literal dots once escaped, so }} + {{! a URL normalizer between the SDK and the API would collapse the segment and address a }} + {{! different endpoint than the caller asked for. No resource is identified by such a value. }} + {{! The thrown message stays generic on purpose: it must not tell a caller which values are }} + {{! rejected, since it can surface to end users. }} + {{#pathParams}} + // verify the path parameter '{{paramName}}' is a valid identifier + String escaped{{#lambda.titlecase}}{{paramName}}{{/lambda.titlecase}} = apiClient.escapeString({{{paramName}}}.toString()); + if (escaped{{#lambda.titlecase}}{{paramName}}{{/lambda.titlecase}}.equals(".") || escaped{{#lambda.titlecase}}{{paramName}}{{/lambda.titlecase}}.equals("..")) { + throw new ApiException(400, "invalid value for path parameter {{paramName}}"); + } + + {{/pathParams}} // create path and map variables String localVarPath = "{{{path}}}"{{#pathParams}} - .replaceAll("\\{" + "{{baseName}}" + "\\}", apiClient.escapeString({{{paramName}}}.toString())){{/pathParams}}; + .replaceAll("\\{" + "{{baseName}}" + "\\}", escaped{{#lambda.titlecase}}{{paramName}}{{/lambda.titlecase}}){{/pathParams}}; // query params {{javaUtilPrefix}}List localVarQueryParams = new {{javaUtilPrefix}}ArrayList(); diff --git a/template/libraries/jersey3/api_doc.mustache b/template/libraries/jersey3/api_doc.mustache index 6a770e34..1f41ce1b 100644 --- a/template/libraries/jersey3/api_doc.mustache +++ b/template/libraries/jersey3/api_doc.mustache @@ -69,7 +69,7 @@ public class FingerprintApiExample { {{^allParams}}This endpoint does not need any parameter.{{/allParams}}{{#allParams}}{{#-last}} | Name | Type | Description | Notes | |------------- | ------------- | ------------- | -------------|{{/-last}}{{/allParams}} -{{#allParams}}{{#required}}| **{{paramName}}** | {{#isContainer}}{{#isArray}}{{#items}}{{#isModel}}{{^isFile}}[{{/isFile}}{{/isModel}}**List<{{dataType}}>**{{#isModel}}{{^isFile}}]({{^baseType}}{{dataType}}{{/baseType}}{{baseType}}.md){{/isFile}}{{/isModel}}{{/items}}{{/isArray}}{{#isMap}}{{#items}}{{#isModel}}{{^isFile}}[{{/isFile}}{{/isModel}}**Map<String,{{dataType}}>**{{#isModel}}{{^isFile}}]({{^baseType}}{{dataType}}{{/baseType}}{{baseType}}.md){{/isFile}}{{/isModel}}{{/items}}{{/isMap}}{{/isContainer}}{{^isContainer}}{{#isModel}}{{^isFile}}[{{/isFile}}{{/isModel}}**{{dataType}}**{{#isModel}}{{^isFile}}]({{^baseType}}{{dataType}}{{/baseType}}{{baseType}}.md){{/isFile}}{{/isModel}}{{/isContainer}}| {{{description}}} |{{^required}} [optional]{{/required}}{{^isContainer}}{{#defaultValue}} [default to {{.}}]{{/defaultValue}}{{/isContainer}}{{#allowableValues}} [enum: {{#values}}{{{.}}}{{^-last}}, {{/-last}}{{/values}}]{{/allowableValues}} | +{{#allParams}}{{#required}}| **{{paramName}}** | {{#isContainer}}{{#isArray}}{{#items}}{{#isModel}}{{^isFile}}[{{/isFile}}{{/isModel}}**List<{{dataType}}>**{{#isModel}}{{^isFile}}]({{^baseType}}{{dataType}}{{/baseType}}{{baseType}}.md){{/isFile}}{{/isModel}}{{/items}}{{/isArray}}{{#isMap}}{{#items}}{{#isModel}}{{^isFile}}[{{/isFile}}{{/isModel}}**Map<String,{{dataType}}>**{{#isModel}}{{^isFile}}]({{^baseType}}{{dataType}}{{/baseType}}{{baseType}}.md){{/isFile}}{{/isModel}}{{/items}}{{/isMap}}{{/isContainer}}{{^isContainer}}{{#isModel}}{{^isFile}}[{{/isFile}}{{/isModel}}**{{dataType}}**{{#isModel}}{{^isFile}}]({{^baseType}}{{dataType}}{{/baseType}}{{baseType}}.md){{/isFile}}{{/isModel}}{{/isContainer}}| {{{description}}}{{#isPathParam}} A value that is not a valid identifier is rejected before a request is sent.{{/isPathParam}} |{{^required}} [optional]{{/required}}{{^isContainer}}{{#defaultValue}} [default to {{.}}]{{/defaultValue}}{{/isContainer}}{{#allowableValues}} [enum: {{#values}}{{{.}}}{{^-last}}, {{/-last}}{{/values}}]{{/allowableValues}} | {{/required}}{{/allParams}}{{#hasOptionalParams}}| **{{operationId}}OptionalParams** | [**{{classname}}.{{operationIdCamelCase}}OptionalParams**](#{{#lambda.lowercase}}{{classname}}{{operationId}}OptionalParams{{/lambda.lowercase}}) | | [optional] | #### {{classname}}.{{operationIdCamelCase}}OptionalParams @@ -78,7 +78,7 @@ Object containing optional parameters for API method. Supports a fluent interfac | Name | Type | Description | Notes | |------------- | ------------- | ------------- | -------------| -{{#allParams}}{{^required}}| **{{paramName}}** | {{#isContainer}}{{#isArray}}{{#items}}{{#isModel}}{{^isFile}}[{{/isFile}}{{/isModel}}**List<{{dataType}}>**{{#isModel}}{{^isFile}}]({{^baseType}}{{dataType}}{{/baseType}}{{baseType}}.md){{/isFile}}{{/isModel}}{{/items}}{{/isArray}}{{#isMap}}{{#items}}{{#isModel}}{{^isFile}}[{{/isFile}}{{/isModel}}**Map<String,{{dataType}}>**{{#isModel}}{{^isFile}}]({{^baseType}}{{dataType}}{{/baseType}}{{baseType}}.md){{/isFile}}{{/isModel}}{{/items}}{{/isMap}}{{/isContainer}}{{^isContainer}}{{#isModel}}{{^isFile}}[{{/isFile}}{{/isModel}}**{{dataType}}**{{#isModel}}{{^isFile}}]({{^baseType}}{{dataType}}{{/baseType}}{{baseType}}.md){{/isFile}}{{/isModel}}{{/isContainer}}| {{{description}}} |{{^required}} [optional]{{/required}}{{^isContainer}}{{#defaultValue}} [default to {{.}}]{{/defaultValue}}{{/isContainer}}{{#allowableValues}} [enum: {{#values}}{{{.}}}{{^-last}}, {{/-last}}{{/values}}]{{/allowableValues}} | +{{#allParams}}{{^required}}| **{{paramName}}** | {{#isContainer}}{{#isArray}}{{#items}}{{#isModel}}{{^isFile}}[{{/isFile}}{{/isModel}}**List<{{dataType}}>**{{#isModel}}{{^isFile}}]({{^baseType}}{{dataType}}{{/baseType}}{{baseType}}.md){{/isFile}}{{/isModel}}{{/items}}{{/isArray}}{{#isMap}}{{#items}}{{#isModel}}{{^isFile}}[{{/isFile}}{{/isModel}}**Map<String,{{dataType}}>**{{#isModel}}{{^isFile}}]({{^baseType}}{{dataType}}{{/baseType}}{{baseType}}.md){{/isFile}}{{/isModel}}{{/items}}{{/isMap}}{{/isContainer}}{{^isContainer}}{{#isModel}}{{^isFile}}[{{/isFile}}{{/isModel}}**{{dataType}}**{{#isModel}}{{^isFile}}]({{^baseType}}{{dataType}}{{/baseType}}{{baseType}}.md){{/isFile}}{{/isModel}}{{/isContainer}}| {{{description}}}{{#isPathParam}} A value that is not a valid identifier is rejected before a request is sent.{{/isPathParam}} |{{^required}} [optional]{{/required}}{{^isContainer}}{{#defaultValue}} [default to {{.}}]{{/defaultValue}}{{/isContainer}}{{#allowableValues}} [enum: {{#values}}{{{.}}}{{^-last}}, {{/-last}}{{/values}}]{{/allowableValues}} | {{/required}}{{/allParams}}{{/hasOptionalParams}} ### Return type From f353acf7eec2735b6d06ec40f9d81670d2da2a1f Mon Sep 17 00:00:00 2001 From: Orkun <5910168+Orkuncakilkaya@users.noreply.github.com> Date: Thu, 24 Sep 2026 12:25:36 +0300 Subject: [PATCH 2/7] chore: simplify comment Co-authored-by: Dan McNulty <212590662+mcnulty-fp@users.noreply.github.com> --- template/libraries/jersey3/api.mustache | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/template/libraries/jersey3/api.mustache b/template/libraries/jersey3/api.mustache index ce8ca887..3342f60c 100644 --- a/template/libraries/jersey3/api.mustache +++ b/template/libraries/jersey3/api.mustache @@ -234,12 +234,7 @@ public class {{classname}} { throw new ApiException(400, "Missing the required parameter '{{paramName}}' when calling {{operationId}}"); } {{/required}}{{/allParams}} - {{! A path parameter has to survive as a single, opaque path segment. Percent-encoding keeps }} - {{! slashes from injecting extra segments, but "." and ".." stay literal dots once escaped, so }} - {{! a URL normalizer between the SDK and the API would collapse the segment and address a }} - {{! different endpoint than the caller asked for. No resource is identified by such a value. }} - {{! The thrown message stays generic on purpose: it must not tell a caller which values are }} - {{! rejected, since it can surface to end users. }} + {{! "." and ".." are not valid path parameters}} {{#pathParams}} // verify the path parameter '{{paramName}}' is a valid identifier String escaped{{#lambda.titlecase}}{{paramName}}{{/lambda.titlecase}} = apiClient.escapeString({{{paramName}}}.toString()); From b40b75d2c178f1fc5e3b0f6165b2d2481d66e122 Mon Sep 17 00:00:00 2001 From: Orkun <5910168+Orkuncakilkaya@users.noreply.github.com> Date: Thu, 24 Sep 2026 12:26:05 +0300 Subject: [PATCH 3/7] chore: update exception message Co-authored-by: Dan McNulty <212590662+mcnulty-fp@users.noreply.github.com> --- template/libraries/jersey3/api.mustache | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/template/libraries/jersey3/api.mustache b/template/libraries/jersey3/api.mustache index 3342f60c..1336869f 100644 --- a/template/libraries/jersey3/api.mustache +++ b/template/libraries/jersey3/api.mustache @@ -239,7 +239,7 @@ public class {{classname}} { // verify the path parameter '{{paramName}}' is a valid identifier String escaped{{#lambda.titlecase}}{{paramName}}{{/lambda.titlecase}} = apiClient.escapeString({{{paramName}}}.toString()); if (escaped{{#lambda.titlecase}}{{paramName}}{{/lambda.titlecase}}.equals(".") || escaped{{#lambda.titlecase}}{{paramName}}{{/lambda.titlecase}}.equals("..")) { - throw new ApiException(400, "invalid value for path parameter {{paramName}}"); + throw new ApiException("invalid value \"" + escaped{{#lambda.titlecase}}{{paramName}}{{/lambda.titlecase}} + "\" for path parameter {{paramName}}"); } {{/pathParams}} From 6d95b53e585e3a9c7652b6da7817915fad9f0c66 Mon Sep 17 00:00:00 2001 From: Orkun Date: Thu, 24 Sep 2026 12:32:42 +0300 Subject: [PATCH 4/7] chore: regenerate SDK from updated template Applies the exception message and comment changes from b40b75d and f353acf to the generated code, and updates PathParameterTest for the new message, which now names the offending value and no longer sets an HTTP status code. --- .../main/java/com/fingerprint/v4/api/FingerprintApi.java | 7 ++++--- .../java/com/fingerprint/v4/api/PathParameterTest.java | 7 +++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/sdk/src/main/java/com/fingerprint/v4/api/FingerprintApi.java b/sdk/src/main/java/com/fingerprint/v4/api/FingerprintApi.java index 64f62bd3..07c8feec 100644 --- a/sdk/src/main/java/com/fingerprint/v4/api/FingerprintApi.java +++ b/sdk/src/main/java/com/fingerprint/v4/api/FingerprintApi.java @@ -114,7 +114,8 @@ public ApiResponse deleteVisitorDataWithHttpInfo(String visitorId) throws // verify the path parameter 'visitorId' is a valid identifier String escapedVisitorId = apiClient.escapeString(visitorId.toString()); if (escapedVisitorId.equals(".") || escapedVisitorId.equals("..")) { - throw new ApiException(400, "invalid value for path parameter visitorId"); + throw new ApiException( + "invalid value \"" + escapedVisitorId + "\" for path parameter visitorId"); } // create path and map variables @@ -246,7 +247,7 @@ public ApiResponse getEventWithHttpInfo( // verify the path parameter 'eventId' is a valid identifier String escapedEventId = apiClient.escapeString(eventId.toString()); if (escapedEventId.equals(".") || escapedEventId.equals("..")) { - throw new ApiException(400, "invalid value for path parameter eventId"); + throw new ApiException("invalid value \"" + escapedEventId + "\" for path parameter eventId"); } // create path and map variables @@ -1458,7 +1459,7 @@ public ApiResponse updateEventWithHttpInfo(String eventId, EventUpdate eve // verify the path parameter 'eventId' is a valid identifier String escapedEventId = apiClient.escapeString(eventId.toString()); if (escapedEventId.equals(".") || escapedEventId.equals("..")) { - throw new ApiException(400, "invalid value for path parameter eventId"); + throw new ApiException("invalid value \"" + escapedEventId + "\" for path parameter eventId"); } // create path and map variables diff --git a/sdk/src/test/java/com/fingerprint/v4/api/PathParameterTest.java b/sdk/src/test/java/com/fingerprint/v4/api/PathParameterTest.java index a6e3219f..c35fa8b8 100644 --- a/sdk/src/test/java/com/fingerprint/v4/api/PathParameterTest.java +++ b/sdk/src/test/java/com/fingerprint/v4/api/PathParameterTest.java @@ -239,11 +239,10 @@ Stream pathParameterDotSegmentIsRejected() throws IOException { ApiException exception = assertThrows(ApiException.class, () -> endpoint.call.call(api, id.id)); - assertEquals(400, exception.getCode()); - // The message stays generic on purpose: it must not tell a caller which - // values are rejected, since it can surface to end users. + // The message names the offending value, which is safe to embed because it + // is the escaped form: anything that could break out is percent-encoded. assertEquals( - "invalid value for path parameter " + endpoint.param, + "invalid value \"" + id.encoded + "\" for path parameter " + endpoint.param, exception.getMessage()); assertFalse(stub.wasRequested(), "no request should have been sent"); } From ca7988f25bf556b92dcb0450401e9c4c34f47145 Mon Sep 17 00:00:00 2001 From: Orkun Date: Thu, 24 Sep 2026 12:38:20 +0300 Subject: [PATCH 5/7] fix: use a consistent exception contract for path parameter validation The path parameter guard threw ApiException(String), which leaves the status code at its default 0, while the missing-required-parameter guard in the same method throws ApiException(400, ...). Both are client-side parameter validation, so both now report 400. Also drops the isPathParam note from the optional parameter table in api_doc.mustache: a path parameter is always required, so that branch could never render. Generated docs are unchanged. --- .../main/java/com/fingerprint/v4/api/FingerprintApi.java | 8 +++++--- .../java/com/fingerprint/v4/api/PathParameterTest.java | 2 ++ template/libraries/jersey3/api.mustache | 2 +- template/libraries/jersey3/api_doc.mustache | 2 +- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/sdk/src/main/java/com/fingerprint/v4/api/FingerprintApi.java b/sdk/src/main/java/com/fingerprint/v4/api/FingerprintApi.java index 07c8feec..beb755d6 100644 --- a/sdk/src/main/java/com/fingerprint/v4/api/FingerprintApi.java +++ b/sdk/src/main/java/com/fingerprint/v4/api/FingerprintApi.java @@ -115,7 +115,7 @@ public ApiResponse deleteVisitorDataWithHttpInfo(String visitorId) throws String escapedVisitorId = apiClient.escapeString(visitorId.toString()); if (escapedVisitorId.equals(".") || escapedVisitorId.equals("..")) { throw new ApiException( - "invalid value \"" + escapedVisitorId + "\" for path parameter visitorId"); + 400, "invalid value \"" + escapedVisitorId + "\" for path parameter visitorId"); } // create path and map variables @@ -247,7 +247,8 @@ public ApiResponse getEventWithHttpInfo( // verify the path parameter 'eventId' is a valid identifier String escapedEventId = apiClient.escapeString(eventId.toString()); if (escapedEventId.equals(".") || escapedEventId.equals("..")) { - throw new ApiException("invalid value \"" + escapedEventId + "\" for path parameter eventId"); + throw new ApiException( + 400, "invalid value \"" + escapedEventId + "\" for path parameter eventId"); } // create path and map variables @@ -1459,7 +1460,8 @@ public ApiResponse updateEventWithHttpInfo(String eventId, EventUpdate eve // verify the path parameter 'eventId' is a valid identifier String escapedEventId = apiClient.escapeString(eventId.toString()); if (escapedEventId.equals(".") || escapedEventId.equals("..")) { - throw new ApiException("invalid value \"" + escapedEventId + "\" for path parameter eventId"); + throw new ApiException( + 400, "invalid value \"" + escapedEventId + "\" for path parameter eventId"); } // create path and map variables diff --git a/sdk/src/test/java/com/fingerprint/v4/api/PathParameterTest.java b/sdk/src/test/java/com/fingerprint/v4/api/PathParameterTest.java index c35fa8b8..222d145e 100644 --- a/sdk/src/test/java/com/fingerprint/v4/api/PathParameterTest.java +++ b/sdk/src/test/java/com/fingerprint/v4/api/PathParameterTest.java @@ -239,6 +239,8 @@ Stream pathParameterDotSegmentIsRejected() throws IOException { ApiException exception = assertThrows(ApiException.class, () -> endpoint.call.call(api, id.id)); + // Same contract as the missing-required-parameter check in the same method. + assertEquals(400, exception.getCode()); // The message names the offending value, which is safe to embed because it // is the escaped form: anything that could break out is percent-encoded. assertEquals( diff --git a/template/libraries/jersey3/api.mustache b/template/libraries/jersey3/api.mustache index 1336869f..378bb2ce 100644 --- a/template/libraries/jersey3/api.mustache +++ b/template/libraries/jersey3/api.mustache @@ -239,7 +239,7 @@ public class {{classname}} { // verify the path parameter '{{paramName}}' is a valid identifier String escaped{{#lambda.titlecase}}{{paramName}}{{/lambda.titlecase}} = apiClient.escapeString({{{paramName}}}.toString()); if (escaped{{#lambda.titlecase}}{{paramName}}{{/lambda.titlecase}}.equals(".") || escaped{{#lambda.titlecase}}{{paramName}}{{/lambda.titlecase}}.equals("..")) { - throw new ApiException("invalid value \"" + escaped{{#lambda.titlecase}}{{paramName}}{{/lambda.titlecase}} + "\" for path parameter {{paramName}}"); + throw new ApiException(400, "invalid value \"" + escaped{{#lambda.titlecase}}{{paramName}}{{/lambda.titlecase}} + "\" for path parameter {{paramName}}"); } {{/pathParams}} diff --git a/template/libraries/jersey3/api_doc.mustache b/template/libraries/jersey3/api_doc.mustache index 1f41ce1b..1732ab5e 100644 --- a/template/libraries/jersey3/api_doc.mustache +++ b/template/libraries/jersey3/api_doc.mustache @@ -78,7 +78,7 @@ Object containing optional parameters for API method. Supports a fluent interfac | Name | Type | Description | Notes | |------------- | ------------- | ------------- | -------------| -{{#allParams}}{{^required}}| **{{paramName}}** | {{#isContainer}}{{#isArray}}{{#items}}{{#isModel}}{{^isFile}}[{{/isFile}}{{/isModel}}**List<{{dataType}}>**{{#isModel}}{{^isFile}}]({{^baseType}}{{dataType}}{{/baseType}}{{baseType}}.md){{/isFile}}{{/isModel}}{{/items}}{{/isArray}}{{#isMap}}{{#items}}{{#isModel}}{{^isFile}}[{{/isFile}}{{/isModel}}**Map<String,{{dataType}}>**{{#isModel}}{{^isFile}}]({{^baseType}}{{dataType}}{{/baseType}}{{baseType}}.md){{/isFile}}{{/isModel}}{{/items}}{{/isMap}}{{/isContainer}}{{^isContainer}}{{#isModel}}{{^isFile}}[{{/isFile}}{{/isModel}}**{{dataType}}**{{#isModel}}{{^isFile}}]({{^baseType}}{{dataType}}{{/baseType}}{{baseType}}.md){{/isFile}}{{/isModel}}{{/isContainer}}| {{{description}}}{{#isPathParam}} A value that is not a valid identifier is rejected before a request is sent.{{/isPathParam}} |{{^required}} [optional]{{/required}}{{^isContainer}}{{#defaultValue}} [default to {{.}}]{{/defaultValue}}{{/isContainer}}{{#allowableValues}} [enum: {{#values}}{{{.}}}{{^-last}}, {{/-last}}{{/values}}]{{/allowableValues}} | +{{#allParams}}{{^required}}| **{{paramName}}** | {{#isContainer}}{{#isArray}}{{#items}}{{#isModel}}{{^isFile}}[{{/isFile}}{{/isModel}}**List<{{dataType}}>**{{#isModel}}{{^isFile}}]({{^baseType}}{{dataType}}{{/baseType}}{{baseType}}.md){{/isFile}}{{/isModel}}{{/items}}{{/isArray}}{{#isMap}}{{#items}}{{#isModel}}{{^isFile}}[{{/isFile}}{{/isModel}}**Map<String,{{dataType}}>**{{#isModel}}{{^isFile}}]({{^baseType}}{{dataType}}{{/baseType}}{{baseType}}.md){{/isFile}}{{/isModel}}{{/items}}{{/isMap}}{{/isContainer}}{{^isContainer}}{{#isModel}}{{^isFile}}[{{/isFile}}{{/isModel}}**{{dataType}}**{{#isModel}}{{^isFile}}]({{^baseType}}{{dataType}}{{/baseType}}{{baseType}}.md){{/isFile}}{{/isModel}}{{/isContainer}}| {{{description}}} |{{^required}} [optional]{{/required}}{{^isContainer}}{{#defaultValue}} [default to {{.}}]{{/defaultValue}}{{/isContainer}}{{#allowableValues}} [enum: {{#values}}{{{.}}}{{^-last}}, {{/-last}}{{/values}}]{{/allowableValues}} | {{/required}}{{/allParams}}{{/hasOptionalParams}} ### Return type From b73f22f3642fd7ca049ad3c586a44472ceb72d8e Mon Sep 17 00:00:00 2001 From: Orkun <5910168+Orkuncakilkaya@users.noreply.github.com> Date: Thu, 24 Sep 2026 17:33:40 +0300 Subject: [PATCH 6/7] chore: remove unnecessary status code Co-authored-by: Dan McNulty <212590662+mcnulty-fp@users.noreply.github.com> --- template/libraries/jersey3/api.mustache | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/template/libraries/jersey3/api.mustache b/template/libraries/jersey3/api.mustache index 378bb2ce..1336869f 100644 --- a/template/libraries/jersey3/api.mustache +++ b/template/libraries/jersey3/api.mustache @@ -239,7 +239,7 @@ public class {{classname}} { // verify the path parameter '{{paramName}}' is a valid identifier String escaped{{#lambda.titlecase}}{{paramName}}{{/lambda.titlecase}} = apiClient.escapeString({{{paramName}}}.toString()); if (escaped{{#lambda.titlecase}}{{paramName}}{{/lambda.titlecase}}.equals(".") || escaped{{#lambda.titlecase}}{{paramName}}{{/lambda.titlecase}}.equals("..")) { - throw new ApiException(400, "invalid value \"" + escaped{{#lambda.titlecase}}{{paramName}}{{/lambda.titlecase}} + "\" for path parameter {{paramName}}"); + throw new ApiException("invalid value \"" + escaped{{#lambda.titlecase}}{{paramName}}{{/lambda.titlecase}} + "\" for path parameter {{paramName}}"); } {{/pathParams}} From d03117710681d866cee4abd9f340b840460c1c97 Mon Sep 17 00:00:00 2001 From: Orkun Date: Thu, 24 Sep 2026 17:35:56 +0300 Subject: [PATCH 7/7] chore: regenerate SDK from updated template Applies the exception constructor change from b73f22f to the generated code and drops the matching status code assertion from PathParameterTest. --- .../main/java/com/fingerprint/v4/api/FingerprintApi.java | 8 +++----- .../java/com/fingerprint/v4/api/PathParameterTest.java | 2 -- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/sdk/src/main/java/com/fingerprint/v4/api/FingerprintApi.java b/sdk/src/main/java/com/fingerprint/v4/api/FingerprintApi.java index beb755d6..07c8feec 100644 --- a/sdk/src/main/java/com/fingerprint/v4/api/FingerprintApi.java +++ b/sdk/src/main/java/com/fingerprint/v4/api/FingerprintApi.java @@ -115,7 +115,7 @@ public ApiResponse deleteVisitorDataWithHttpInfo(String visitorId) throws String escapedVisitorId = apiClient.escapeString(visitorId.toString()); if (escapedVisitorId.equals(".") || escapedVisitorId.equals("..")) { throw new ApiException( - 400, "invalid value \"" + escapedVisitorId + "\" for path parameter visitorId"); + "invalid value \"" + escapedVisitorId + "\" for path parameter visitorId"); } // create path and map variables @@ -247,8 +247,7 @@ public ApiResponse getEventWithHttpInfo( // verify the path parameter 'eventId' is a valid identifier String escapedEventId = apiClient.escapeString(eventId.toString()); if (escapedEventId.equals(".") || escapedEventId.equals("..")) { - throw new ApiException( - 400, "invalid value \"" + escapedEventId + "\" for path parameter eventId"); + throw new ApiException("invalid value \"" + escapedEventId + "\" for path parameter eventId"); } // create path and map variables @@ -1460,8 +1459,7 @@ public ApiResponse updateEventWithHttpInfo(String eventId, EventUpdate eve // verify the path parameter 'eventId' is a valid identifier String escapedEventId = apiClient.escapeString(eventId.toString()); if (escapedEventId.equals(".") || escapedEventId.equals("..")) { - throw new ApiException( - 400, "invalid value \"" + escapedEventId + "\" for path parameter eventId"); + throw new ApiException("invalid value \"" + escapedEventId + "\" for path parameter eventId"); } // create path and map variables diff --git a/sdk/src/test/java/com/fingerprint/v4/api/PathParameterTest.java b/sdk/src/test/java/com/fingerprint/v4/api/PathParameterTest.java index 222d145e..c35fa8b8 100644 --- a/sdk/src/test/java/com/fingerprint/v4/api/PathParameterTest.java +++ b/sdk/src/test/java/com/fingerprint/v4/api/PathParameterTest.java @@ -239,8 +239,6 @@ Stream pathParameterDotSegmentIsRejected() throws IOException { ApiException exception = assertThrows(ApiException.class, () -> endpoint.call.call(api, id.id)); - // Same contract as the missing-required-parameter check in the same method. - assertEquals(400, exception.getCode()); // The message names the offending value, which is safe to embed because it // is the escaped form: anything that could break out is percent-encoded. assertEquals(