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..07c8feec 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,16 @@ 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( + "invalid value \"" + escapedVisitorId + "\" 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 +173,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 +195,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 +219,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 +244,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("invalid value \"" + escapedEventId + "\" 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 +1406,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 +1426,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 +1456,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("invalid value \"" + escapedEventId + "\" 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..c35fa8b8 --- /dev/null +++ b/sdk/src/test/java/com/fingerprint/v4/api/PathParameterTest.java @@ -0,0 +1,255 @@ +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)); + + // 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 \"" + id.encoded + "\" 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..1336869f 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,18 @@ public class {{classname}} { throw new ApiException(400, "Missing the required parameter '{{paramName}}' when calling {{operationId}}"); } {{/required}}{{/allParams}} + {{! "." 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()); + 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}}"); + } + + {{/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..1732ab5e 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