From 7a86e285c91a4de048fcbce1d274e4482dd57695 Mon Sep 17 00:00:00 2001 From: Dan McNulty Date: Tue, 24 Feb 2026 14:46:43 -0600 Subject: [PATCH 01/13] chore: remove unused `getDiscriminatorPropertyName` method - Update the `JSON` template to remove the package-private unused `getDiscriminatorPropertyName`. --- .../java/com/fingerprint/v4/sdk/JSON.java | 5 - template/libraries/jersey3/JSON.mustache | 257 ++++++++++++++++++ 2 files changed, 257 insertions(+), 5 deletions(-) create mode 100644 template/libraries/jersey3/JSON.mustache diff --git a/sdk/src/main/java/com/fingerprint/v4/sdk/JSON.java b/sdk/src/main/java/com/fingerprint/v4/sdk/JSON.java index cc766122..63f7d2c2 100644 --- a/sdk/src/main/java/com/fingerprint/v4/sdk/JSON.java +++ b/sdk/src/main/java/com/fingerprint/v4/sdk/JSON.java @@ -104,11 +104,6 @@ private static class ClassDiscriminatorMapping { } } - // Return the name of the discriminator property for this model class. - String getDiscriminatorPropertyName() { - return discriminatorName; - } - // Return the discriminator value or null if the discriminator is not // present in the payload. String getDiscriminatorValue(JsonNode node) { diff --git a/template/libraries/jersey3/JSON.mustache b/template/libraries/jersey3/JSON.mustache new file mode 100644 index 00000000..f73d7eaf --- /dev/null +++ b/template/libraries/jersey3/JSON.mustache @@ -0,0 +1,257 @@ +{{>licenseInfo}} + +package {{invokerPackage}}; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.json.JsonMapper; +{{#openApiNullable}} +import org.openapitools.jackson.nullable.JsonNullableModule; +{{/openApiNullable}} +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +{{#joda}} +import com.fasterxml.jackson.datatype.joda.JodaModule; +{{/joda}} + +import java.text.DateFormat; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import {{javaxPackage}}.ws.rs.core.GenericType; +import {{javaxPackage}}.ws.rs.ext.ContextResolver; + +{{>generatedAnnotation}} + +public class JSON implements ContextResolver { + private ObjectMapper mapper; + + public JSON() { + mapper = JsonMapper.builder() + .serializationInclusion(JsonInclude.Include.NON_NULL) + .configure(MapperFeature.ALLOW_COERCION_OF_SCALARS, false) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, {{failOnUnknownProperties}}) + .configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, true) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING) + .enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING) + .defaultDateFormat(new RFC3339DateFormat()) + .addModule(new JavaTimeModule()) + {{#joda}} + .addModule(new JodaModule()) + {{/joda}} + {{#openApiNullable}} + .addModule(new JsonNullableModule()) + {{/openApiNullable}} + .addModule(new RFC3339JavaTimeModule()) + .build(); + } + + /** + * Set the date format for JSON (de)serialization with Date properties. + * @param dateFormat Date format + */ + public void setDateFormat(DateFormat dateFormat) { + mapper.setDateFormat(dateFormat); + } + + @Override + public ObjectMapper getContext(Class type) { + return mapper; + } + + /** + * Get the object mapper + * + * @return object mapper + */ + public ObjectMapper getMapper() { return mapper; } + + /** + * Returns the target model class that should be used to deserialize the input data. + * The discriminator mappings are used to determine the target model class. + * + * @param node The input data. + * @param modelClass The class that contains the discriminator mappings. + */ + public static Class getClassForElement(JsonNode node, Class modelClass) { + ClassDiscriminatorMapping cdm = modelDiscriminators.get(modelClass); + if (cdm != null) { + return cdm.getClassForElement(node, new HashSet<>()); + } + return null; + } + + /** + * Helper class to register the discriminator mappings. + */ + private static class ClassDiscriminatorMapping { + // The model class name. + Class modelClass; + // The name of the discriminator property. + String discriminatorName; + // The discriminator mappings for a model class. + Map> discriminatorMappings; + + // Constructs a new class discriminator. + ClassDiscriminatorMapping(Class cls, String propertyName, Map> mappings) { + modelClass = cls; + discriminatorName = propertyName; + discriminatorMappings = new HashMap<>(); + if (mappings != null) { + discriminatorMappings.putAll(mappings); + } + } + + // Return the discriminator value or null if the discriminator is not + // present in the payload. + String getDiscriminatorValue(JsonNode node) { + // Determine the value of the discriminator property in the input data. + if (discriminatorName != null) { + // Get the value of the discriminator property, if present in the input payload. + node = node.get(discriminatorName); + if (node != null && node.isValueNode()) { + String discrValue = node.asText(); + if (discrValue != null) { + return discrValue; + } + } + } + return null; + } + + /** + * Returns the target model class that should be used to deserialize the input data. + * This function can be invoked for anyOf/oneOf composed models with discriminator mappings. + * The discriminator mappings are used to determine the target model class. + * + * @param node The input data. + * @param visitedClasses The set of classes that have already been visited. + */ + Class getClassForElement(JsonNode node, Set> visitedClasses) { + if (visitedClasses.contains(modelClass)) { + // Class has already been visited. + return null; + } + // Determine the value of the discriminator property in the input data. + String discrValue = getDiscriminatorValue(node); + if (discrValue == null) { + return null; + } + Class cls = discriminatorMappings.get(discrValue); + // It may not be sufficient to return this cls directly because that target class + // may itself be a composed schema, possibly with its own discriminator. + visitedClasses.add(modelClass); + for (Class childClass : discriminatorMappings.values()) { + ClassDiscriminatorMapping childCdm = modelDiscriminators.get(childClass); + if (childCdm == null) { + continue; + } + if (!discriminatorName.equals(childCdm.discriminatorName)) { + discrValue = getDiscriminatorValue(node); + if (discrValue == null) { + continue; + } + } + if (childCdm != null) { + // Recursively traverse the discriminator mappings. + Class childDiscr = childCdm.getClassForElement(node, visitedClasses); + if (childDiscr != null) { + return childDiscr; + } + } + } + return cls; + } + } + + /** + * Returns true if inst is an instance of modelClass in the OpenAPI model hierarchy. + * + * The Java class hierarchy is not implemented the same way as the OpenAPI model hierarchy, + * so it's not possible to use the instanceof keyword. + * + * @param modelClass A OpenAPI model class. + * @param inst The instance object. + */ + public static boolean isInstanceOf(Class modelClass, Object inst, Set> visitedClasses) { + if (modelClass.isInstance(inst)) { + // This handles the 'allOf' use case with single parent inheritance. + return true; + } + if (visitedClasses.contains(modelClass)) { + // This is to prevent infinite recursion when the composed schemas have + // a circular dependency. + return false; + } + visitedClasses.add(modelClass); + + // Traverse the oneOf/anyOf composed schemas. + Map> descendants = modelDescendants.get(modelClass); + if (descendants != null) { + for (GenericType childType : descendants.values()) { + if (isInstanceOf(childType.getRawType(), inst, visitedClasses)) { + return true; + } + } + } + return false; + } + + /** + * A map of discriminators for all model classes. + */ + private static Map, ClassDiscriminatorMapping> modelDiscriminators = new HashMap<>(); + + /** + * A map of oneOf/anyOf descendants for each model class. + */ + private static Map, Map>> modelDescendants = new HashMap<>(); + + /** + * Register a model class discriminator. + * + * @param modelClass the model class + * @param discriminatorPropertyName the name of the discriminator property + * @param mappings a map with the discriminator mappings. + */ + public static void registerDiscriminator(Class modelClass, String discriminatorPropertyName, Map> mappings) { + ClassDiscriminatorMapping m = new ClassDiscriminatorMapping(modelClass, discriminatorPropertyName, mappings); + modelDiscriminators.put(modelClass, m); + } + + /** + * Register the oneOf/anyOf descendants of the modelClass. + * + * @param modelClass the model class + * @param descendants a map of oneOf/anyOf descendants. + */ + public static void registerDescendants(Class modelClass, Map> descendants) { + modelDescendants.put(modelClass, descendants); + } + + private static JSON json; + + static + { + json = new JSON(); + } + + /** + * Get the default JSON instance. + * + * @return the default JSON instance + */ + public static JSON getDefault() { + return json; + } + + /** + * Set the default JSON instance. + * + * @param json JSON instance to be used + */ + public static void setDefault(JSON json) { + JSON.json = json; + } +} From 98e55e2fea0f304a64b43431330cb44aa0d9a941 Mon Sep 17 00:00:00 2001 From: Dan McNulty Date: Tue, 24 Feb 2026 15:59:21 -0600 Subject: [PATCH 02/13] chore: fail build on warnings - Update Gradle config to specify `-Xlint:all -Werror` to the Java compiler. - Fix warnings in Sealed.java related to serialVersionUID - Fix warnings and general issues in FingerprintApiTest.java --- sdk/sdk.gradle.kts | 4 + .../main/java/com/fingerprint/v4/Sealed.java | 8 ++ .../v4/api/FingerprintApiTest.java | 132 ++++++++++-------- 3 files changed, 86 insertions(+), 58 deletions(-) diff --git a/sdk/sdk.gradle.kts b/sdk/sdk.gradle.kts index 33dbaf28..6d09b36d 100644 --- a/sdk/sdk.gradle.kts +++ b/sdk/sdk.gradle.kts @@ -10,6 +10,10 @@ java { targetCompatibility = JavaVersion.VERSION_11 } +tasks.withType { + options.compilerArgs.addAll(listOf("-Xlint:all", "-Werror")) +} + plugins { alias(libs.plugins.openapi.generator) `java-library` diff --git a/sdk/src/main/java/com/fingerprint/v4/Sealed.java b/sdk/src/main/java/com/fingerprint/v4/Sealed.java index ee332a5b..208b151a 100644 --- a/sdk/src/main/java/com/fingerprint/v4/Sealed.java +++ b/sdk/src/main/java/com/fingerprint/v4/Sealed.java @@ -40,24 +40,32 @@ public String toString() { } public static class UnsealAggregateException extends Exception { + private static final long serialVersionUID = 2144464350313234299L; + public UnsealAggregateException() { super("Failed to unseal with all decryption keys"); } } public static class InvalidSealedDataException extends IllegalArgumentException { + private static final long serialVersionUID = 7664915119162688449L; + public InvalidSealedDataException() { super("Invalid sealed data"); } } public static class InvalidSealedDataHeaderException extends IllegalArgumentException { + private static final long serialVersionUID = 808199780881125013L; + public InvalidSealedDataHeaderException() { super("Invalid sealed data header"); } } public static class UnsealException extends Exception { + private static final long serialVersionUID = 9223372036854775807L; + public final String decryptionKeyDescription; public UnsealException(String message, Throwable cause, DecryptionKey decryptionKey) { diff --git a/sdk/src/test/java/com/fingerprint/v4/api/FingerprintApiTest.java b/sdk/src/test/java/com/fingerprint/v4/api/FingerprintApiTest.java index 3fedce1f..593a387e 100644 --- a/sdk/src/test/java/com/fingerprint/v4/api/FingerprintApiTest.java +++ b/sdk/src/test/java/com/fingerprint/v4/api/FingerprintApiTest.java @@ -1,7 +1,15 @@ package com.fingerprint.v4.api; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonProcessingException; @@ -13,12 +21,14 @@ import com.fingerprint.v4.sdk.ApiException; import com.fingerprint.v4.sdk.ApiResponse; import com.fingerprint.v4.sdk.Pair; -import jakarta.ws.rs.core.GenericType; import java.io.IOException; import java.io.InputStream; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -106,12 +116,12 @@ private void addMock(String operation, String path, ApiAnswerFunction ans Mockito.doAnswer( invocation -> { validateIntegrationInfo(invocation.getArgument(3)); - ApiResponse result = answerFunction.apply(invocation); + ApiResponse result = answerFunction.apply(invocation); if (result.getStatusCode() == 200) { return result; } else { - throw new ApiException( - result.getStatusCode(), result.getHeaders(), result.getData().toString()); + String responseBody = MAPPER.writeValueAsString(result.getData()); + throw new ApiException(result.getStatusCode(), result.getHeaders(), responseBody); } }) .when(apiClient) @@ -139,20 +149,13 @@ private void addMock(String operation, String path, ApiAnswerFunction ans ); } - ApiResponse mockFileToResponse(int statusCode, InvocationOnMock invocation, String path) + ApiResponse mockFileToResponse( + int statusCode, InvocationOnMock invocation, String path, Class responseType) throws IOException { - GenericType returnType = invocation.getArgument(11); - if (statusCode == 200) { - return new ApiResponse<>( - statusCode, - null, - path != null ? MAPPER.readValue(getFileAsIOStream(path), returnType.getRawType()) : null); - } else { - return new ApiResponse<>( - statusCode, - null, - new String(getFileAsIOStream(path).readAllBytes(), StandardCharsets.UTF_8)); - } + return new ApiResponse( + statusCode, + null, + path != null ? MAPPER.readValue(getFileAsIOStream(path), responseType) : null); } public static boolean listContainsPair(List pairs, String key, Object value) { @@ -182,7 +185,8 @@ public void getEventTest() throws ApiException { "getEvent", MOCK_REQUEST_ID, invocation -> { - return mockFileToResponse(200, invocation, "mocks/events/get_event_200.json"); + return mockFileToResponse( + 200, invocation, "mocks/events/get_event_200.json", Event.class); }); Event response = api.getEvent(MOCK_REQUEST_ID); @@ -224,7 +228,7 @@ public void updateEventLinkedIdRequest() throws ApiException { assertEquals(LINKED_ID, body.getLinkedId()); assertNull(body.getTags()); assertNull(body.getSuspect()); - return mockFileToResponse(200, invocation, null); + return mockFileToResponse(200, invocation, null, Void.class); }); api.updateEvent(MOCK_REQUEST_ID, request); } @@ -261,7 +265,7 @@ public void updateEventTagRequest() throws ApiException { assertNull(body.getLinkedId()); assertEquals(TAG, body.getTags()); assertNull(body.getSuspect()); - return mockFileToResponse(200, invocation, null); + return mockFileToResponse(200, invocation, null, Void.class); }); api.updateEvent(MOCK_REQUEST_ID, request); } @@ -282,7 +286,7 @@ public void updateEventSuspectPositiveRequest() throws ApiException { assertNull(body.getLinkedId()); assertNull(body.getTags()); assertTrue(body.getSuspect()); - return mockFileToResponse(200, invocation, null); + return mockFileToResponse(200, invocation, null, Void.class); }); api.updateEvent(MOCK_REQUEST_ID, request); } @@ -303,7 +307,7 @@ public void updateEventSuspectNegativeRequest() throws ApiException { assertNull(body.getLinkedId()); assertNull(body.getTags()); assertFalse(body.getSuspect()); - return mockFileToResponse(200, invocation, null); + return mockFileToResponse(200, invocation, null, Void.class); }); api.updateEvent(MOCK_REQUEST_ID, request); } @@ -332,7 +336,7 @@ public void updateMultipleFieldsEventRequest() throws ApiException { assertEquals(LINKED_ID, body.getLinkedId()); assertEquals(TAG, body.getTags()); assertTrue(body.getSuspect()); - return mockFileToResponse(200, invocation, null); + return mockFileToResponse(200, invocation, null, Void.class); }); api.updateEvent(MOCK_REQUEST_ID, request); } @@ -346,7 +350,7 @@ public void deleteVisitorDataTest() throws ApiException { List queryParams = invocation.getArgument(3); assertEquals(1, queryParams.size()); - return mockFileToResponse(200, invocation, null); + return mockFileToResponse(200, invocation, null, Void.class); }); api.deleteVisitorData(MOCK_VISITOR_ID); } @@ -382,7 +386,7 @@ public void searchEventsMinimumParamsTest() throws ApiException { assertTrue(listContainsPair(queryParams, "limit", String.valueOf(LIMIT))); return mockFileToResponse( - 200, invocation, "mocks/events/search/get_event_search_200.json"); + 200, invocation, "mocks/events/search/get_event_search_200.json", EventSearch.class); }); EventSearch response = @@ -417,7 +421,12 @@ public void searchEventsMaximumParamsTest() throws ApiException { final String PAGINATION_KEY = "1741187431959"; final SearchEventsBot BOT = SearchEventsBot.GOOD; final String IP_ADDRESS = "192.168.0.1/32"; + final String ASN = "testAsn"; final String LINKED_ID = "some_id"; + final String URL = "https://example.com/page"; + final String BUNDLE_ID = "com.example.bundleId"; + final String PACKAGE_NAME = "com.example"; + final String ORIGIN = "https://example.com"; final Long START = 1582299576511L; final Long END = 1582299576513L; final Boolean REVERSE = true; @@ -436,8 +445,6 @@ public void searchEventsMaximumParamsTest() throws ApiException { final SearchEventsVpnConfidence VPN_CONFIDENCE = SearchEventsVpnConfidence.MEDIUM; final Boolean EMULATOR = true; final Boolean INCOGNITO = true; - final Boolean IP_BLOCKLIST = true; - final Boolean DATACENTER = true; final Boolean DEVELOPER_TOOLS = true; final Boolean LOCATION_SPOOFING = true; final Boolean MITM_ATTACK = true; @@ -448,8 +455,8 @@ public void searchEventsMaximumParamsTest() throws ApiException { ENVIRONMENT.add("env1"); ENVIRONMENT.add("env2"); final String PROXIMITY_ID = "testProximityId"; - final String ASN = "testAsn"; - // final Integer PROXIMITY_PRECISION_RADIUS = 10; + final Long TOTAL_HITS = 10L; + final Boolean TOR_NODE = true; Map expectedQueryParams = new HashMap<>(); expectedQueryParams.put("limit", String.valueOf(LIMIT)); @@ -457,7 +464,12 @@ public void searchEventsMaximumParamsTest() throws ApiException { expectedQueryParams.put("visitor_id", MOCK_VISITOR_ID); expectedQueryParams.put("bot", String.valueOf(BOT)); expectedQueryParams.put("ip_address", IP_ADDRESS); + expectedQueryParams.put("asn", ASN); expectedQueryParams.put("linked_id", LINKED_ID); + expectedQueryParams.put("url", URL); + expectedQueryParams.put("bundle_id", BUNDLE_ID); + expectedQueryParams.put("package_name", PACKAGE_NAME); + expectedQueryParams.put("origin", ORIGIN); expectedQueryParams.put("start", START.toString()); expectedQueryParams.put("end", END.toString()); expectedQueryParams.put("reverse", String.valueOf(REVERSE)); @@ -476,8 +488,6 @@ public void searchEventsMaximumParamsTest() throws ApiException { expectedQueryParams.put("vpn_confidence", String.valueOf(VPN_CONFIDENCE)); expectedQueryParams.put("emulator", String.valueOf(EMULATOR)); expectedQueryParams.put("incognito", String.valueOf(INCOGNITO)); - // expectedQueryParams.put("ip_blocklist", String.valueOf(IP_BLOCKLIST)); - // expectedQueryParams.put("datacenter", String.valueOf(DATACENTER)); expectedQueryParams.put("developer_tools", String.valueOf(DEVELOPER_TOOLS)); expectedQueryParams.put("location_spoofing", String.valueOf(LOCATION_SPOOFING)); expectedQueryParams.put("mitm_attack", String.valueOf(MITM_ATTACK)); @@ -485,9 +495,8 @@ public void searchEventsMaximumParamsTest() throws ApiException { expectedQueryParams.put("sdk_version", SDK_VERSION); expectedQueryParams.put("sdk_platform", String.valueOf(SDK_PLATFORM)); expectedQueryParams.put("proximity_id", PROXIMITY_ID); - expectedQueryParams.put("asn", ASN); - // expectedQueryParams.put("proximity_precision_radius", - // String.valueOf(PROXIMITY_PRECISION_RADIUS)); + expectedQueryParams.put("total_hits", String.valueOf(TOTAL_HITS)); + expectedQueryParams.put("tor_node", String.valueOf(TOR_NODE)); addMock( "searchEvents", @@ -512,7 +521,7 @@ public void searchEventsMaximumParamsTest() throws ApiException { assertEquals(ENVIRONMENT, actualEnv); return mockFileToResponse( - 200, invocation, "mocks/events/search/get_event_search_200.json"); + 200, invocation, "mocks/events/search/get_event_search_200.json", EventSearch.class); }); EventSearch response = @@ -523,27 +532,30 @@ public void searchEventsMaximumParamsTest() throws ApiException { .setVisitorId(MOCK_VISITOR_ID) .setBot(BOT) .setIpAddress(IP_ADDRESS) + .setAsn(ASN) .setLinkedId(LINKED_ID) + .setUrl(URL) + .setBundleId(BUNDLE_ID) + .setPackageName(PACKAGE_NAME) + .setOrigin(ORIGIN) .setStart(START) .setEnd(END) .setReverse(REVERSE) .setSuspect(SUSPECT) + .setVpn(VPN) + .setVirtualMachine(VIRTUAL_MACHINE) + .setTampering(TAMPERING) .setAntiDetectBrowser(ANTI_DETECT_BROWSER) - .setClonedApp(CLONED_APP) - .setFactoryReset(FACTORY_RESET) - .setFrida(FRIDA) - .setJailbroken(JAILBROKEN) - .setMinSuspectScore(MIN_SUSPECT_SCORE) + .setIncognito(INCOGNITO) .setPrivacySettings(PRIVACY_SETTINGS) + .setJailbroken(JAILBROKEN) + .setFrida(FRIDA) + .setFactoryReset(FACTORY_RESET) + .setClonedApp(CLONED_APP) + .setEmulator(EMULATOR) .setRootApps(ROOT_APPS) - .setTampering(TAMPERING) - .setVirtualMachine(VIRTUAL_MACHINE) - .setVpn(VPN) .setVpnConfidence(VPN_CONFIDENCE) - .setEmulator(EMULATOR) - .setIncognito(INCOGNITO) - // .setIpBlocklist(IP_BLOCKLIST) - // .setDatacenter(DATACENTER) + .setMinSuspectScore(MIN_SUSPECT_SCORE) .setDeveloperTools(DEVELOPER_TOOLS) .setLocationSpoofing(LOCATION_SPOOFING) .setMitmAttack(MITM_ATTACK) @@ -552,24 +564,24 @@ public void searchEventsMaximumParamsTest() throws ApiException { .setSdkPlatform(SDK_PLATFORM) .setEnvironment(ENVIRONMENT) .setProximityId(PROXIMITY_ID) - .setAsn(ASN) - // .setProximityPrecisionRadius(PROXIMITY_PRECISION_RADIUS) - ); + .setTotalHits(TOTAL_HITS) + .setTorNode(TOR_NODE)); List events = response.getEvents(); assertEquals(events.size(), 1); } @Test public void searchEvents400ErrorTest() throws ApiException, JsonProcessingException { - int LIMIT = 1; addMock( "searchEvents", null, invocation -> { List queryParams = invocation.getArgument(3); assertEquals(1, queryParams.size()); + assertEquals(FingerprintApi.INTEGRATION_INFO, queryParams.get(0).getValue()); - return mockFileToResponse(400, invocation, "mocks/errors/400_ip_address_invalid.json"); + return mockFileToResponse( + 400, invocation, "mocks/errors/400_ip_address_invalid.json", ErrorResponse.class); }); ApiException exception = assertThrows(ApiException.class, () -> api.searchEvents(null)); @@ -582,18 +594,22 @@ public void searchEvents400ErrorTest() throws ApiException, JsonProcessingExcept @Test public void searchEvents403ErrorTest() throws ApiException, JsonProcessingException { - int LIMIT = 1; addMock( "searchEvents", null, invocation -> { List queryParams = invocation.getArgument(3); assertEquals(1, queryParams.size()); + assertEquals(FingerprintApi.INTEGRATION_INFO, queryParams.get(0).getValue()); - return mockFileToResponse(403, invocation, "mocks/errors/403_feature_not_enabled.json"); + return mockFileToResponse( + 403, invocation, "mocks/errors/403_feature_not_enabled.json", ErrorResponse.class); }); - ApiException exception = assertThrows(ApiException.class, () -> api.searchEvents(null)); + ApiException exception = + assertThrows( + ApiException.class, + () -> api.searchEvents(new FingerprintApi.SearchEventsOptionalParams())); assertEquals(403, exception.getCode()); ErrorResponse response = MAPPER.readValue(exception.getResponseBody(), ErrorResponse.class); From 18c4e0f9dc04c62d81f29799bc35b6f976ec4c1a Mon Sep 17 00:00:00 2001 From: Dan McNulty Date: Tue, 24 Feb 2026 17:00:51 -0600 Subject: [PATCH 03/13] chore: require source to only use Java 11 features - Update the gradle config for the examples to only allow use of Java 11 features. This was previously already done for the sdk module. --- examples/examples.gradle.kts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/examples/examples.gradle.kts b/examples/examples.gradle.kts index e99389c9..0614501e 100644 --- a/examples/examples.gradle.kts +++ b/examples/examples.gradle.kts @@ -8,6 +8,12 @@ plugins { java } +java { + // Target the earliest support Java version + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 +} + repositories { mavenLocal() mavenCentral() From 8cc343163488118d697e73ec4535efdfc6322d74 Mon Sep 17 00:00:00 2001 From: Dan McNulty Date: Tue, 24 Feb 2026 17:43:58 -0600 Subject: [PATCH 04/13] test: add ruleset evaluation tests - Update FunctionalTests to include an example/test for evaluating an event against a ruleset. - Update sync script to pull down ruleset examples - Update FingerprintApiTest to use the same `ObjectMapper` that the SDK uses to deserialize data to avoid discrepancies in configuration. - Add ruleset test cases to FingerprintApiTest - Fix issue found in template where the `type` value was not being set on the deserialized EventRuleAction. --- .../fingerprint/example/FunctionalTests.java | 44 +++ scripts/sync.sh | 2 + .../fingerprint/v4/model/EventRuleAction.java | 3 +- .../v4/api/FingerprintApiTest.java | 61 +++- .../mocks/errors/400_ruleset_not_found.json | 6 + .../mocks/events/get_event_ruleset_200.json | 296 ++++++++++++++++++ .../libraries/jersey3/oneof_model.mustache | 4 +- 7 files changed, 402 insertions(+), 14 deletions(-) create mode 100644 sdk/src/test/resources/mocks/errors/400_ruleset_not_found.json create mode 100644 sdk/src/test/resources/mocks/events/get_event_ruleset_200.json diff --git a/examples/src/main/java/com/fingerprint/example/FunctionalTests.java b/examples/src/main/java/com/fingerprint/example/FunctionalTests.java index 7a6b3f82..0f70129d 100644 --- a/examples/src/main/java/com/fingerprint/example/FunctionalTests.java +++ b/examples/src/main/java/com/fingerprint/example/FunctionalTests.java @@ -121,6 +121,50 @@ public static void main(String... args) { System.exit(1); } + // Evaluate a ruleset + final String rulesetId = System.getenv("FPJS_RULESET_ID"); + if (rulesetId != null) { + try { + final Event event = + api.getEvent( + eventId, new FingerprintApi.GetEventOptionalParams().setRulesetId(rulesetId)); + if (event.getRuleAction() instanceof EventRuleActionAllow) { + EventRuleActionAllow allow = (EventRuleActionAllow) event.getRuleAction(); + System.out.printf( + "Rule action is allow\nRule id: %s\nRule expression: %s\n", + allow.getRuleId(), allow.getRuleExpression()); + if (allow.getRequestHeaderModifications() != null) { + RequestHeaderModifications requestHeaderModifications = + allow.getRequestHeaderModifications(); + System.out.printf( + "Request header modifications to set %s, to append %s, to remove %s\n", + requestHeaderModifications.getSet(), + requestHeaderModifications.getAppend(), + requestHeaderModifications.getRemove()); + } + } else if (event.getRuleAction() instanceof EventRuleActionBlock) { + EventRuleActionBlock block = (EventRuleActionBlock) event.getRuleAction(); + System.out.printf( + "Rule action is block. Rule id: %s\nRule expression: %s\nStatus code: %d\nHeaders: %s\nBody: %s\n", + block.getRuleId(), + block.getRuleExpression(), + block.getStatusCode(), + block.getHeaders(), + block.getBody()); + } else if (event.getRuleAction() != null) { + System.out.println( + "Action type is unexpected (please make sure the library is at the latest version"); + } else { + System.err.println( + "Fingerprint.getEvent ruleset evaluation: failed to produce a rule action"); + System.exit(1); + } + } catch (ApiException e) { + System.err.println("Exception when trying to evaluate ruleset:" + e.getMessage()); + System.exit(1); + } + } + System.out.println("Checks Passed"); System.exit(0); } diff --git a/scripts/sync.sh b/scripts/sync.sh index 41a00147..23e78fff 100755 --- a/scripts/sync.sh +++ b/scripts/sync.sh @@ -7,6 +7,7 @@ curl -s -o ./res/fingerprint-server-api.yaml https://fingerprintjs.github.io/fin examplesList=( 'webhook/webhook_event.json' 'events/get_event_200.json' + 'events/get_event_ruleset_200.json' 'events/search/get_event_search_200.json' 'events/update_event_multiple_fields_request.json' 'events/update_event_one_field_request.json' @@ -19,6 +20,7 @@ examplesList=( 'errors/400_pagination_key_invalid.json' 'errors/400_request_body_invalid.json' 'errors/400_reverse_invalid.json' + 'errors/400_ruleset_not_found.json' 'errors/400_start_time_invalid.json' 'errors/400_visitor_id_invalid.json' 'errors/400_visitor_id_required.json' diff --git a/sdk/src/main/java/com/fingerprint/v4/model/EventRuleAction.java b/sdk/src/main/java/com/fingerprint/v4/model/EventRuleAction.java index 9bb438c4..481907c0 100644 --- a/sdk/src/main/java/com/fingerprint/v4/model/EventRuleAction.java +++ b/sdk/src/main/java/com/fingerprint/v4/model/EventRuleAction.java @@ -24,7 +24,8 @@ use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type", - defaultImpl = EventRuleAction.UnknownEventRuleAction.class) + defaultImpl = EventRuleAction.UnknownEventRuleAction.class, + visible = true) @JsonSubTypes({ @JsonSubTypes.Type(value = EventRuleActionAllow.class, name = "allow"), @JsonSubTypes.Type(value = EventRuleActionBlock.class, name = "block") diff --git a/sdk/src/test/java/com/fingerprint/v4/api/FingerprintApiTest.java b/sdk/src/test/java/com/fingerprint/v4/api/FingerprintApiTest.java index 593a387e..19e95cf7 100644 --- a/sdk/src/test/java/com/fingerprint/v4/api/FingerprintApiTest.java +++ b/sdk/src/test/java/com/fingerprint/v4/api/FingerprintApiTest.java @@ -13,13 +13,13 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fingerprint.v4.model.*; import com.fingerprint.v4.sdk.ApiClient; import com.fingerprint.v4.sdk.ApiException; import com.fingerprint.v4.sdk.ApiResponse; +import com.fingerprint.v4.sdk.JSON; import com.fingerprint.v4.sdk.Pair; import java.io.IOException; import java.io.InputStream; @@ -47,7 +47,7 @@ public class FingerprintApiTest { private static final String MOCK_WEBHOOK_VISITOR_ID = "Ibk1527CUFmcnjLwIs4A9"; private static final String MOCK_WEBHOOK_EVENT_ID = "1708102555327.NLOjmg"; - private static final ObjectMapper MAPPER = getMapper(); + private static final ObjectMapper MAPPER = JSON.getDefault().getMapper(); private InputStream getFileAsIOStream(final String fileName) { InputStream ioStream = this.getClass().getClassLoader().getResourceAsStream(fileName); @@ -72,17 +72,9 @@ private void validateIntegrationInfo(List queryParams) { public void before() { ApiClient realApiClient = new ApiClient(); ApiClient apiClient = Mockito.spy(realApiClient); - // apiClient.setBearerToken("MOCK_API_KEY"); api = new FingerprintApi(apiClient); } - private static ObjectMapper getMapper() { - ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule()); - mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); - mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - return mapper; - } - @FunctionalInterface public interface ApiAnswerFunction { ApiResponse apply(InvocationOnMock invocation) throws ApiException, IOException; @@ -126,7 +118,8 @@ private void addMock(String operation, String path, ApiAnswerFunction ans }) .when(apiClient) .invokeAPI( - eq(operationName), // operation, for example "FingerprintApi.getEvent" + eq(operationName), // operation, for example + // "FingerprintApi.getEvent" eq(path), // path eq(httpMethod), // HTTP-method any(), // queryParams @@ -616,4 +609,50 @@ public void searchEvents403ErrorTest() throws ApiException, JsonProcessingExcept assertEquals(ErrorCode.FEATURE_NOT_ENABLED, response.getError().getCode()); assertEquals("feature not enabled", response.getError().getMessage()); } + + @Test + public void getEventEvaluateRulesetTest() throws ApiException { + addMock( + "getEvent", + MOCK_REQUEST_ID, + invocation -> { + return mockFileToResponse( + 200, invocation, "mocks/events/get_event_ruleset_200.json", Event.class); + }); + + Event response = api.getEvent(MOCK_REQUEST_ID); + assertNotNull(response); + assertNotNull(response.getRuleAction()); + assertInstanceOf(EventRuleActionBlock.class, response.getRuleAction()); + + EventRuleActionBlock ruleAction = (EventRuleActionBlock) response.getRuleAction(); + assertEquals(RuleActionType.BLOCK, ruleAction.getType()); + assertEquals(403, ruleAction.getStatusCode()); + assertEquals("{\"title\":\"Forbidden\"}", ruleAction.getBody()); + assertEquals("rs_b1k1blhqpOX3kU", ruleAction.getRulesetId()); + assertEquals("r_uE0af8497PFAOD", ruleAction.getRuleId()); + assertEquals("bot in [\"bad\"] || incognito", ruleAction.getRuleExpression()); + assertNotNull(ruleAction.getHeaders()); + assertEquals(1, ruleAction.getHeaders().size()); + assertEquals("Content-Type", ruleAction.getHeaders().get(0).getName()); + assertEquals("application/json", ruleAction.getHeaders().get(0).getValue()); + } + + @Test + public void getEventRulesetNotFoundErrorTest() throws ApiException, JsonProcessingException { + addMock( + "getEvent", + MOCK_REQUEST_ID, + invocation -> { + return mockFileToResponse( + 400, invocation, "mocks/errors/400_ruleset_not_found.json", ErrorResponse.class); + }); + + ApiException exception = assertThrows(ApiException.class, () -> api.getEvent(MOCK_REQUEST_ID)); + + assertEquals(400, exception.getCode()); + ErrorResponse response = MAPPER.readValue(exception.getResponseBody(), ErrorResponse.class); + assertEquals(ErrorCode.RULESET_NOT_FOUND, response.getError().getCode()); + assertEquals("ruleset not found", response.getError().getMessage()); + } } diff --git a/sdk/src/test/resources/mocks/errors/400_ruleset_not_found.json b/sdk/src/test/resources/mocks/errors/400_ruleset_not_found.json new file mode 100644 index 00000000..1e4ac5dd --- /dev/null +++ b/sdk/src/test/resources/mocks/errors/400_ruleset_not_found.json @@ -0,0 +1,6 @@ +{ + "error": { + "code": "ruleset_not_found", + "message": "ruleset not found" + } +} diff --git a/sdk/src/test/resources/mocks/events/get_event_ruleset_200.json b/sdk/src/test/resources/mocks/events/get_event_ruleset_200.json new file mode 100644 index 00000000..b02bfb2d --- /dev/null +++ b/sdk/src/test/resources/mocks/events/get_event_ruleset_200.json @@ -0,0 +1,296 @@ +{ + "linked_id": "somelinkedId", + "tags": {}, + "timestamp": 1708102555327, + "event_id": "1708102555327.NLOjmg", + "url": "https://www.example.com/login?hope{this{works[!", + "ip_address": "61.127.217.15", + "user_agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) ....", + "client_referrer": "https://example.com/blog/my-article", + "browser_details": { + "browser_name": "Chrome", + "browser_major_version": "74", + "browser_full_version": "74.0.3729", + "os": "Windows", + "os_version": "7", + "device": "Other" + }, + "identification": { + "visitor_id": "Ibk1527CUFmcnjLwIs4A9", + "confidence": { + "score": 0.97, + "version": "1.1" + }, + "visitor_found": false, + "first_seen_at": 1708102555327, + "last_seen_at": 1708102555327 + }, + "supplementary_id_high_recall": { + "visitor_id": "3HNey93AkBW6CRbxV6xP", + "visitor_found": true, + "confidence": { + "score": 0.97, + "version": "1.1" + }, + "first_seen_at": 1708102555327, + "last_seen_at": 1708102555327 + }, + "proximity": { + "id": "w1aTfd4MCvl", + "precision_radius": 10, + "confidence": 0.95 + }, + "bot": "not_detected", + "root_apps": false, + "emulator": false, + "ip_info": { + "v4": { + "address": "94.142.239.124", + "geolocation": { + "accuracy_radius": 20, + "latitude": 50.05, + "longitude": 14.4, + "postal_code": "150 00", + "timezone": "Europe/Prague", + "city_name": "Prague", + "country_code": "CZ", + "country_name": "Czechia", + "continent_code": "EU", + "continent_name": "Europe", + "subdivisions": [ + { + "iso_code": "10", + "name": "Hlavni mesto Praha" + } + ] + }, + "asn": "7922", + "asn_name": "COMCAST-7922", + "asn_network": "73.136.0.0/13", + "asn_type": "isp", + "datacenter_result": true, + "datacenter_name": "DediPath" + }, + "v6": { + "address": "2001:db8:3333:4444:5555:6666:7777:8888", + "geolocation": { + "accuracy_radius": 5, + "latitude": 49.982, + "longitude": 36.2566, + "postal_code": "10112", + "timezone": "Europe/Berlin", + "city_name": "Berlin", + "country_code": "DE", + "country_name": "Germany", + "continent_code": "EU", + "continent_name": "Europe", + "subdivisions": [ + { + "iso_code": "BE", + "name": "Land Berlin" + } + ] + }, + "asn": "6805", + "asn_name": "Telefonica Germany", + "asn_network": "2a02:3100::/24", + "asn_type": "isp", + "datacenter_result": false, + "datacenter_name": "" + } + }, + "ip_blocklist": { + "email_spam": false, + "attack_source": false, + "tor_node": false + }, + "proxy": true, + "proxy_confidence": "low", + "proxy_details": { + "proxy_type": "residential", + "last_seen_at": 1708102555327, + "provider": "Massive" + }, + "vpn": false, + "vpn_confidence": "high", + "vpn_origin_timezone": "Europe/Berlin", + "vpn_origin_country": "unknown", + "vpn_methods": { + "timezone_mismatch": false, + "public_vpn": false, + "auxiliary_mobile": false, + "os_mismatch": false, + "relay": false + }, + "incognito": false, + "tampering": false, + "tampering_details": { + "anomaly_score": 0.1955, + "anti_detect_browser": false + }, + "cloned_app": false, + "factory_reset_timestamp": 0, + "jailbroken": false, + "frida": false, + "privacy_settings": false, + "virtual_machine": false, + "location_spoofing": false, + "velocity": { + "distinct_ip": { + "5_minutes": 1, + "1_hour": 1, + "24_hours": 1 + }, + "distinct_country": { + "5_minutes": 1, + "1_hour": 2, + "24_hours": 2 + }, + "events": { + "5_minutes": 1, + "1_hour": 5, + "24_hours": 5 + }, + "ip_events": { + "5_minutes": 1, + "1_hour": 5, + "24_hours": 5 + }, + "distinct_ip_by_linked_id": { + "5_minutes": 1, + "1_hour": 5, + "24_hours": 5 + }, + "distinct_visitor_id_by_linked_id": { + "5_minutes": 1, + "1_hour": 5, + "24_hours": 5 + } + }, + "developer_tools": false, + "mitm_attack": false, + "sdk": { + "platform": "js", + "version": "3.11.10", + "integrations": [ + { + "name": "fingerprint-pro-react", + "version": "3.11.10", + "subintegration": { + "name": "preact", + "version": "10.21.0" + } + } + ] + }, + "replayed": false, + "high_activity_device": false, + "raw_device_attributes": { + "math": "5f030fa7d2e5f9f757bfaf81642eb1a6", + "vendor": "Google Inc.", + "plugins": [ + { + "description": "Portable Document Format", + "mimeTypes": [ + { + "suffixes": "pdf", + "type": "application/pdf" + }, + { + "suffixes": "pdf", + "type": "text/pdf" + } + ], + "name": "PDF Viewer" + } + ], + "webgl_extensions": { + "context_attributes": "6b1ed336830d2bc96442a9d76373252a", + "extension_parameters": "86a8abb36f0cb30b5946dec0c761d042", + "extensions": "57233d7b10f89fcd1ff95e3837ccd72d", + "parameters": "ea118c48e308bc4b0677118bbb3019ec", + "shader_precisions": "f223dfbcd580cf142da156d93790eb83", + "unsupported_extensions": [] + }, + "cookies_enabled": true, + "webgl_basics": { + "renderer": "WebKit WebGL", + "renderer_unmasked": "ANGLE (Apple, ANGLE Metal Renderer: Apple M4, Unspecified Version)", + "shading_language_version": "WebGL GLSL ES 1.0 (OpenGL ES GLSL ES 1.0 Chromium)", + "vendor": "WebKit", + "vendor_unmasked": "Google Inc. (Apple)", + "version": "WebGL 1.0 (OpenGL ES 2.0 Chromium)" + }, + "canvas": { + "geometry": "db3c1462576a399a03ae93d0ab9eb5c4", + "text": "70c3d3f7eb4408dc37a6bf8af1c51029", + "winding": true + }, + "hardware_concurrency": 10, + "languages": [ + [ + "en-US" + ] + ], + "color_depth": 24, + "fonts": [ + "Arial Unicode MS", + "Gill Sans", + "Helvetica Neue", + "Menlo" + ], + "indexed_db": true, + "touch_support": { + "max_touch_points": 0, + "touch_event": false, + "touch_start": false + }, + "device_memory": 8, + "oscpu": "Windows NT 6.1; Win64; x64", + "architecture": 127, + "screen_resolution": [ + 1920, + 1080 + ], + "timezone": "America/Sao_Paulo", + "emoji": { + "bottom": 32, + "font": "Times", + "height": 18, + "left": 8, + "right": 1608, + "top": 14, + "width": 1600, + "x": 8, + "y": 14 + }, + "font_preferences": { + "apple": 147.5625, + "default": 147.5625, + "min": 9.234375, + "mono": 133.0625, + "sans": 144.015625, + "serif": 147.5625, + "system": 146.09375 + }, + "platform": "MacIntel", + "local_storage": true, + "session_storage": true, + "date_time_locale": "en-US", + "audio": 124.04347745512496 + }, + "rule_action": { + "type": "block", + "status_code": 403, + "headers": [ + { + "name": "Content-Type", + "value": "application/json" + } + ], + "body": "{\"title\":\"Forbidden\"}", + "ruleset_id": "rs_b1k1blhqpOX3kU", + "rule_id": "r_uE0af8497PFAOD", + "rule_expression": "bot in [\"bad\"] || incognito" + } +} \ No newline at end of file diff --git a/template/libraries/jersey3/oneof_model.mustache b/template/libraries/jersey3/oneof_model.mustache index 888cfd6c..72bf3bb5 100644 --- a/template/libraries/jersey3/oneof_model.mustache +++ b/template/libraries/jersey3/oneof_model.mustache @@ -4,8 +4,8 @@ use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "{{propertyName}}", - defaultImpl = {{classname}}.Unknown{{classname}}.class -) + defaultImpl = {{classname}}.Unknown{{classname}}.class, + visible = true) @JsonSubTypes({ {{#mappedModels}} @JsonSubTypes.Type(value = {{modelName}}.class, name = "{{mappingName}}"){{^-last}},{{/-last}} From 85b751a4f5bd1615a84493e692b56730501bd72e Mon Sep 17 00:00:00 2001 From: Dan McNulty Date: Wed, 25 Feb 2026 17:40:54 -0600 Subject: [PATCH 05/13] chore: updates for latest schema - Apply doc updates from the schema - Update handling for `tags` properties in the `Event` and `EventUpdate` to assume the `tags` default to an empty map. --- docs/Event.md | 10 ++-- docs/EventUpdate.md | 2 +- docs/FingerprintApi.md | 34 +++++++------- docs/SearchEventsBot.md | 2 +- res/fingerprint-server-api.yaml | 46 ++++++++++--------- .../fingerprint/v4/api/FingerprintApi.java | 34 +++++++------- .../java/com/fingerprint/v4/model/Event.java | 30 ++++++++---- .../com/fingerprint/v4/model/EventUpdate.java | 22 ++++++--- .../fingerprint/v4/model/SearchEventsBot.java | 2 +- .../v4/api/FingerprintApiTest.java | 11 +++-- 10 files changed, 110 insertions(+), 83 deletions(-) diff --git a/docs/Event.md b/docs/Event.md index 143395fc..b5a93437 100644 --- a/docs/Event.md +++ b/docs/Event.md @@ -12,12 +12,12 @@ Contains results from Fingerprint Identification and all active Smart Signals. |**timestamp** | **Long** | Timestamp of the event with millisecond precision in Unix time. | | |**linkedId** | **String** | A customer-provided id that was sent with the request. | [optional] | |**environmentId** | **String** | Environment Id of the event. For example: `ae_47abaca3db2c7c43` | [optional] | -|**suspect** | **Boolean** | Field is `true` if you have previously set the `suspect` flag for this event using the [Server API Update event endpoint](https://dev.fingerprint.com/reference/updateevent). | [optional] | +|**suspect** | **Boolean** | Field is `true` if you have previously set the `suspect` flag for this event using the [Server API Update event endpoint](https://docs.fingerprint.com/reference/server-api-v4-update-event). | [optional] | |**sdk** | [**SDK**](SDK.md) | | [optional] | |**replayed** | **Boolean** | `true` if we determined that this payload was replayed, `false` otherwise. | [optional] | |**identification** | [**Identification**](Identification.md) | | [optional] | |**supplementaryIdHighRecall** | [**SupplementaryIDHighRecall**](SupplementaryIDHighRecall.md) | | [optional] | -|**tags** | **Object** | A customer-provided value or an object that was sent with the identification request or updated later. | [optional] | +|**tags** | **Map<String, Object>** | A customer-provided value or an object that was sent with the identification request or updated later. | [optional] | |**url** | **String** | Page URL from which the request was sent. For example `https://example.com/` | [optional] | |**bundleId** | **String** | Bundle Id of the iOS application integrated with the Fingerprint SDK for the event. For example: `com.foo.app` | [optional] | |**packageName** | **String** | Package name of the Android application integrated with the Fingerprint SDK for the event. For example: `com.foo.app` | [optional] | @@ -32,7 +32,7 @@ Contains results from Fingerprint Identification and all active Smart Signals. |**clonedApp** | **Boolean** | Android specific cloned application detection. There are 2 values: * `true` - Presence of app cloners work detected (e.g. fully cloned application found or launch of it inside of a not main working profile detected). * `false` - No signs of cloned application detected or the client is not Android. | [optional] | |**developerTools** | **Boolean** | `true` if the browser is Chrome with DevTools open or Firefox with Developer Tools open, `false` otherwise. | [optional] | |**emulator** | **Boolean** | Android specific emulator detection. There are 2 values: * `true` - Emulated environment detected (e.g. launch inside of AVD). * `false` - No signs of emulated environment detected or the client is not Android. | [optional] | -|**factoryResetTimestamp** | **Long** | The time of the most recent factory reset that happened on the **mobile device** is expressed as Unix epoch time. When a factory reset cannot be detected on the mobile device or when the request is initiated from a browser, this field will correspond to the *epoch* time (i.e 1 Jan 1970 UTC) as a value of 0. See [Factory Reset Detection](https://dev.fingerprint.com/docs/smart-signals-overview#factory-reset-detection) to learn more about this Smart Signal. | [optional] | +|**factoryResetTimestamp** | **Long** | The time of the most recent factory reset that happened on the **mobile device** is expressed as Unix epoch time. When a factory reset cannot be detected on the mobile device or when the request is initiated from a browser, this field will correspond to the *epoch* time (i.e 1 Jan 1970 UTC) as a value of 0. See [Factory Reset Detection](https://docs.fingerprint.com/docs/smart-signals-reference#factory-reset-detection) to learn more about this Smart Signal. | [optional] | |**frida** | **Boolean** | [Frida](https://frida.re/docs/) detection for Android and iOS devices. There are 2 values: * `true` - Frida detected * `false` - No signs of Frida or the client is not a mobile device. | [optional] | |**ipBlocklist** | [**IPBlockList**](IPBlockList.md) | | [optional] | |**ipInfo** | [**IPInfo**](IPInfo.md) | | [optional] | @@ -42,11 +42,11 @@ Contains results from Fingerprint Identification and all active Smart Signals. |**incognito** | **Boolean** | `true` if we detected incognito mode used in the browser, `false` otherwise. | [optional] | |**jailbroken** | **Boolean** | iOS specific jailbreak detection. There are 2 values: * `true` - Jailbreak detected. * `false` - No signs of jailbreak or the client is not iOS. | [optional] | |**locationSpoofing** | **Boolean** | Flag indicating whether the request came from a mobile device with location spoofing enabled. | [optional] | -|**mitmAttack** | **Boolean** | * `true` - When requests made from your users' mobile devices to Fingerprint servers have been intercepted and potentially modified. * `false` - Otherwise or when the request originated from a browser. See [MitM Attack Detection](https://dev.fingerprint.com/docs/smart-signals-reference#mitm-attack-detection) to learn more about this Smart Signal. | [optional] | +|**mitmAttack** | **Boolean** | * `true` - When requests made from your users' mobile devices to Fingerprint servers have been intercepted and potentially modified. * `false` - Otherwise or when the request originated from a browser. See [MitM Attack Detection](https://docs.fingerprint.com/docs/smart-signals-reference#mitm-attack-detection) to learn more about this Smart Signal. | [optional] | |**privacySettings** | **Boolean** | `true` if the request is from a privacy aware browser (e.g. Tor) or from a browser in which fingerprinting is blocked. Otherwise `false`. | [optional] | |**rootApps** | **Boolean** | Android specific root management apps detection. There are 2 values: * `true` - Root Management Apps detected (e.g. Magisk). * `false` - No Root Management Apps detected or the client isn't Android. | [optional] | |**ruleAction** | [**EventRuleAction**](EventRuleAction.md) | | [optional] | -|**suspectScore** | **Integer** | Suspect Score is an easy way to integrate Smart Signals into your fraud protection work flow. It is a weighted representation of all Smart Signals present in the payload that helps identify suspicious activity. The value range is [0; S] where S is sum of all Smart Signals weights. See more details here: https://dev.fingerprint.com/docs/suspect-score | [optional] | +|**suspectScore** | **Integer** | Suspect Score is an easy way to integrate Smart Signals into your fraud protection work flow. It is a weighted representation of all Smart Signals present in the payload that helps identify suspicious activity. The value range is [0; S] where S is sum of all Smart Signals weights. See more details here: https://docs.fingerprint.com/docs/suspect-score | [optional] | |**tampering** | **Boolean** | Flag indicating browser tampering was detected. This happens when either: * There are inconsistencies in the browser configuration that cross internal tampering thresholds (see `tampering_details.anomaly_score`). * The browser signature resembles an \"anti-detect\" browser specifically designed to evade fingerprinting (see `tampering_details.anti_detect_browser`). | [optional] | |**tamperingDetails** | [**TamperingDetails**](TamperingDetails.md) | | [optional] | |**velocity** | [**Velocity**](Velocity.md) | | [optional] | diff --git a/docs/EventUpdate.md b/docs/EventUpdate.md index d88cb7e4..ba5dd259 100644 --- a/docs/EventUpdate.md +++ b/docs/EventUpdate.md @@ -8,7 +8,7 @@ | Name | Type | Description | Notes | |------------ | ------------- | ------------- | -------------| |**linkedId** | **String** | Linked Id value to assign to the existing event | [optional] | -|**tags** | **Object** | A customer-provided value or an object that was sent with the identification request or updated later. | [optional] | +|**tags** | **Map<String, Object>** | A customer-provided value or an object that was sent with the identification request or updated later. | [optional] | |**suspect** | **Boolean** | Suspect flag indicating observed suspicious or fraudulent event | [optional] | diff --git a/docs/FingerprintApi.md b/docs/FingerprintApi.md index 9fef22c2..8cbf0b83 100644 --- a/docs/FingerprintApi.md +++ b/docs/FingerprintApi.md @@ -25,10 +25,10 @@ Request deleting all data associated with the specified visitor ID. This API is #### Browser (or device) properties - Represents the data that Fingerprint collected from this specific browser (or device) and everything inferred and derived from it. -- Upon request to delete, this data is deleted asynchronously (typically within a few minutes) and it will no longer be used to identify this browser (or device) for your [Fingerprint Workspace](https://dev.fingerprint.com/docs/glossary#fingerprint-workspace). +- Upon request to delete, this data is deleted asynchronously (typically within a few minutes) and it will no longer be used to identify this browser (or device) for your [Fingerprint Workspace](https://docs.fingerprint.com/docs/glossary#fingerprint-workspace). #### Identification requests made from this browser (or device) -- Fingerprint stores the identification requests made from a browser (or device) for up to 30 (or 90) days depending on your plan. To learn more, see [Data Retention](https://dev.fingerprint.com/docs/regions#data-retention). +- Fingerprint stores the identification requests made from a browser (or device) for up to 30 (or 90) days depending on your plan. To learn more, see [Data Retention](https://docs.fingerprint.com/docs/regions#data-retention). - Upon request to delete, the identification requests that were made by this browser - Within the past 10 days are deleted within 24 hrs. - Outside of 10 days are allowed to purge as per your data retention period. @@ -36,7 +36,7 @@ Request deleting all data associated with the specified visitor ID. This API is ### Corollary After requesting to delete a visitor ID, - If the same browser (or device) requests to identify, it will receive a different visitor ID. -- If you request [`/v4/events` API](https://dev.fingerprint.com/reference/getevent) with an `event_id` that was made outside of the 10 days, you will still receive a valid response. +- If you request [`/v4/events` API](https://docs.fingerprint.com/reference/server-api-v4-get-event) with an `event_id` that was made outside of the 10 days, you will still receive a valid response. ### Interested? Please [contact our support team](https://fingerprint.com/support/) to enable it for you. Otherwise, you will receive a 403. @@ -67,7 +67,7 @@ public class FingerprintApiExample { Region.ASIA */ FingerprintApi api = new FingerprintApi(FPJS_API_SECRET, Region.EUROPE); - String visitorId = "visitorId_example"; // String | The [visitor ID](https://dev.fingerprint.com/reference/get-function#visitorid) you want to delete. + String visitorId = "visitorId_example"; // String | The [visitor ID](https://docs.fingerprint.com/reference/js-agent-v4-get-function#visitor_id) you want to delete. try { api.deleteVisitorData(visitorId); } catch (ApiException e) { @@ -83,7 +83,7 @@ public class FingerprintApiExample { | Name | Type | Description | Notes | |------------- | ------------- | ------------- | -------------| -| **visitorId** | **String**| The [visitor ID](https://dev.fingerprint.com/reference/get-function#visitorid) 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. | | ### Return type @@ -144,7 +144,7 @@ public class FingerprintApiExample { Region.ASIA */ FingerprintApi api = new FingerprintApi(FPJS_API_SECRET, Region.EUROPE); - String eventId = "eventId_example"; // String | The unique [identifier](https://dev.fingerprint.com/reference/get-function#requestid) of each identification request (`requestId` can be used in its place). + String eventId = "eventId_example"; // 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). String rulesetId = "rulesetId_example"; // String | The ID of the ruleset to evaluate against the event, producing the action to take for this event. The resulting action is returned in the `rule_action` attribute of the response. try { Event result = api.getEvent(eventId, new FingerprintApi.GetEventOptionalParams() @@ -163,7 +163,7 @@ public class FingerprintApiExample { | Name | Type | Description | Notes | |------------- | ------------- | ------------- | -------------| -| **eventId** | **String**| The unique [identifier](https://dev.fingerprint.com/reference/get-function#requestid) 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). | | | **getEventOptionalParams** | [**FingerprintApi.GetEventOptionalParams**](#fingerprintapigeteventoptionalparams) | | [optional] | #### FingerprintApi.GetEventOptionalParams @@ -254,11 +254,11 @@ public class FingerprintApiExample { FingerprintApi api = new FingerprintApi(FPJS_API_SECRET, Region.EUROPE); Integer limit = 10; // Integer | Limit the number of events returned. String paginationKey = "paginationKey_example"; // String | Use `pagination_key` to get the next page of results. When more results are available (e.g., you requested up to 100 results for your query using `limit`, but there are more than 100 events total matching your request), the `pagination_key` field is added to the response. The pagination key is an arbitrary string that should not be interpreted in any way and should be passed as-is. In the following request, use that value in the `pagination_key` parameter to get the next page of results: 1. First request, returning most recent 200 events: `GET api-base-url/events?limit=100` 2. Use `response.pagination_key` to get the next page of results: `GET api-base-url/events?limit=100&pagination_key=1740815825085` - String visitorId = "visitorId_example"; // String | Unique [visitor identifier](https://dev.fingerprint.com/reference/get-function#visitorid) issued by Fingerprint Identification and all active Smart Signals. Filter for events matching this `visitor_id`. - SearchEventsBot bot = SearchEventsBot.fromValue("all"); // SearchEventsBot | Filter events by the Bot Detection result, specifically: `all` - events where any kind of bot was detected. `good` - events where a good bot was detected. `bad` - events where a bad bot was detected. `none` - events where no bot was detected. > Note: When using this parameter, only events with the `botd.bot` property set to a valid value are returned. Events without a `botd` Smart Signal result are left out of the response. + String visitorId = "visitorId_example"; // String | Unique [visitor identifier](https://docs.fingerprint.com/reference/js-agent-v4-get-function#visitor_id) issued by Fingerprint Identification and all active Smart Signals. Filter for events matching this `visitor_id`. + SearchEventsBot bot = SearchEventsBot.fromValue("all"); // SearchEventsBot | Filter events by the Bot Detection result, specifically: `all` - events where any kind of bot was detected. `good` - events where a good bot was detected. `bad` - events where a bad bot was detected. `none` - events where no bot was detected. > Note: When using this parameter, only events with the `bot` property set to a valid value are returned. Events without a `bot` Smart Signal result are left out of the response. String ipAddress = "ipAddress_example"; // String | Filter events by IP address or IP range (if CIDR notation is used). If CIDR notation is not used, a /32 for IPv4 or /128 for IPv6 is assumed. Examples of range based queries: 10.0.0.0/24, 192.168.0.1/32 String asn = "asn_example"; // String | Filter events by the ASN associated with the event's IP address. This corresponds to the `ip_info.(v4|v6).asn` property in the response. - String linkedId = "linkedId_example"; // String | Filter events by your custom identifier. You can use [linked Ids](https://dev.fingerprint.com/reference/get-function#linkedid) to associate identification requests with your own identifier, for example, session Id, purchase Id, or transaction Id. You can then use this `linked_id` parameter to retrieve all events associated with your custom identifier. + String linkedId = "linkedId_example"; // String | Filter events by your custom identifier. You can use [linked Ids](https://docs.fingerprint.com/reference/js-agent-v4-get-function#linkedid) to associate identification requests with your own identifier, for example, session Id, purchase Id, or transaction Id. You can then use this `linked_id` parameter to retrieve all events associated with your custom identifier. String url = "url_example"; // String | Filter events by the URL (`url` property) associated with the event. String bundleId = "bundleId_example"; // String | Filter events by the Bundle ID (iOS) associated with the event. String packageName = "packageName_example"; // String | Filter events by the Package Name (Android) associated with the event. @@ -266,7 +266,7 @@ public class FingerprintApiExample { Long start = 56L; // Long | Filter events with a timestamp greater than the start time, in Unix time (milliseconds). Long end = 56L; // Long | Filter events with a timestamp smaller than the end time, in Unix time (milliseconds). Boolean reverse = true; // Boolean | Sort events in reverse timestamp order. - Boolean suspect = true; // Boolean | Filter events previously tagged as suspicious via the [Update API](https://dev.fingerprint.com/reference/updateevent). > Note: When using this parameter, only events with the `suspect` property explicitly set to `true` or `false` are returned. Events with undefined `suspect` property are left out of the response. + Boolean suspect = true; // Boolean | Filter events previously tagged as suspicious via the [Update API](https://docs.fingerprint.com/reference/server-api-v4-update-event). > Note: When using this parameter, only events with the `suspect` property explicitly set to `true` or `false` are returned. Events with undefined `suspect` property are left out of the response. Boolean vpn = true; // Boolean | Filter events by VPN Detection result. > Note: When using this parameter, only events with the `vpn` property set to `true` or `false` are returned. Events without a `vpn` Smart Signal result are left out of the response. Boolean virtualMachine = true; // Boolean | Filter events by Virtual Machine Detection result. > Note: When using this parameter, only events with the `virtual_machine` property set to `true` or `false` are returned. Events without a `virtual_machine` Smart Signal result are left out of the response. Boolean tampering = true; // Boolean | Filter events by Browser Tampering Detection result. > Note: When using this parameter, only events with the `tampering.result` property set to `true` or `false` are returned. Events without a `tampering` Smart Signal result are left out of the response. @@ -356,11 +356,11 @@ Object containing optional parameters for API method. Supports a fluent interfac |------------- | ------------- | ------------- | -------------| | **limit** | **Integer**| Limit the number of events returned. | [optional] [default to 10] | | **paginationKey** | **String**| Use `pagination_key` to get the next page of results. When more results are available (e.g., you requested up to 100 results for your query using `limit`, but there are more than 100 events total matching your request), the `pagination_key` field is added to the response. The pagination key is an arbitrary string that should not be interpreted in any way and should be passed as-is. In the following request, use that value in the `pagination_key` parameter to get the next page of results: 1. First request, returning most recent 200 events: `GET api-base-url/events?limit=100` 2. Use `response.pagination_key` to get the next page of results: `GET api-base-url/events?limit=100&pagination_key=1740815825085` | [optional] | -| **visitorId** | **String**| Unique [visitor identifier](https://dev.fingerprint.com/reference/get-function#visitorid) issued by Fingerprint Identification and all active Smart Signals. Filter for events matching this `visitor_id`. | [optional] | -| **bot** | **SearchEventsBot**| Filter events by the Bot Detection result, specifically: `all` - events where any kind of bot was detected. `good` - events where a good bot was detected. `bad` - events where a bad bot was detected. `none` - events where no bot was detected. > Note: When using this parameter, only events with the `botd.bot` property set to a valid value are returned. Events without a `botd` Smart Signal result are left out of the response. | [optional] [enum: all, good, bad, none] | +| **visitorId** | **String**| Unique [visitor identifier](https://docs.fingerprint.com/reference/js-agent-v4-get-function#visitor_id) issued by Fingerprint Identification and all active Smart Signals. Filter for events matching this `visitor_id`. | [optional] | +| **bot** | **SearchEventsBot**| Filter events by the Bot Detection result, specifically: `all` - events where any kind of bot was detected. `good` - events where a good bot was detected. `bad` - events where a bad bot was detected. `none` - events where no bot was detected. > Note: When using this parameter, only events with the `bot` property set to a valid value are returned. Events without a `bot` Smart Signal result are left out of the response. | [optional] [enum: all, good, bad, none] | | **ipAddress** | **String**| Filter events by IP address or IP range (if CIDR notation is used). If CIDR notation is not used, a /32 for IPv4 or /128 for IPv6 is assumed. Examples of range based queries: 10.0.0.0/24, 192.168.0.1/32 | [optional] | | **asn** | **String**| Filter events by the ASN associated with the event's IP address. This corresponds to the `ip_info.(v4|v6).asn` property in the response. | [optional] | -| **linkedId** | **String**| Filter events by your custom identifier. You can use [linked Ids](https://dev.fingerprint.com/reference/get-function#linkedid) to associate identification requests with your own identifier, for example, session Id, purchase Id, or transaction Id. You can then use this `linked_id` parameter to retrieve all events associated with your custom identifier. | [optional] | +| **linkedId** | **String**| Filter events by your custom identifier. You can use [linked Ids](https://docs.fingerprint.com/reference/js-agent-v4-get-function#linkedid) to associate identification requests with your own identifier, for example, session Id, purchase Id, or transaction Id. You can then use this `linked_id` parameter to retrieve all events associated with your custom identifier. | [optional] | | **url** | **String**| Filter events by the URL (`url` property) associated with the event. | [optional] | | **bundleId** | **String**| Filter events by the Bundle ID (iOS) associated with the event. | [optional] | | **packageName** | **String**| Filter events by the Package Name (Android) associated with the event. | [optional] | @@ -368,7 +368,7 @@ Object containing optional parameters for API method. Supports a fluent interfac | **start** | **Long**| Filter events with a timestamp greater than the start time, in Unix time (milliseconds). | [optional] | | **end** | **Long**| Filter events with a timestamp smaller than the end time, in Unix time (milliseconds). | [optional] | | **reverse** | **Boolean**| Sort events in reverse timestamp order. | [optional] | -| **suspect** | **Boolean**| Filter events previously tagged as suspicious via the [Update API](https://dev.fingerprint.com/reference/updateevent). > Note: When using this parameter, only events with the `suspect` property explicitly set to `true` or `false` are returned. Events with undefined `suspect` property are left out of the response. | [optional] | +| **suspect** | **Boolean**| Filter events previously tagged as suspicious via the [Update API](https://docs.fingerprint.com/reference/server-api-v4-update-event). > Note: When using this parameter, only events with the `suspect` property explicitly set to `true` or `false` are returned. Events with undefined `suspect` property are left out of the response. | [optional] | | **vpn** | **Boolean**| Filter events by VPN Detection result. > Note: When using this parameter, only events with the `vpn` property set to `true` or `false` are returned. Events without a `vpn` Smart Signal result are left out of the response. | [optional] | | **virtualMachine** | **Boolean**| Filter events by Virtual Machine Detection result. > Note: When using this parameter, only events with the `virtual_machine` property set to `true` or `false` are returned. Events without a `virtual_machine` Smart Signal result are left out of the response. | [optional] | | **tampering** | **Boolean**| Filter events by Browser Tampering Detection result. > Note: When using this parameter, only events with the `tampering.result` property set to `true` or `false` are returned. Events without a `tampering` Smart Signal result are left out of the response. | [optional] | @@ -458,7 +458,7 @@ public class FingerprintApiExample { Region.ASIA */ FingerprintApi api = new FingerprintApi(FPJS_API_SECRET, Region.EUROPE); - String eventId = "eventId_example"; // String | The unique event [identifier](https://dev.fingerprint.com/reference/get-function#event_id). + String eventId = "eventId_example"; // String | The unique event [identifier](https://docs.fingerprint.com/reference/js-agent-v4-get-function#event_id). EventUpdate eventUpdate = new EventUpdate(); // EventUpdate | try { api.updateEvent(eventId, eventUpdate); @@ -475,7 +475,7 @@ public class FingerprintApiExample { | Name | Type | Description | Notes | |------------- | ------------- | ------------- | -------------| -| **eventId** | **String**| The unique event [identifier](https://dev.fingerprint.com/reference/get-function#event_id). | | +| **eventId** | **String**| The unique event [identifier](https://docs.fingerprint.com/reference/js-agent-v4-get-function#event_id). | | | **eventUpdate** | [**EventUpdate**](EventUpdate.md)| | | ### Return type diff --git a/docs/SearchEventsBot.md b/docs/SearchEventsBot.md index 35ae3ab7..74c0ce63 100644 --- a/docs/SearchEventsBot.md +++ b/docs/SearchEventsBot.md @@ -6,7 +6,7 @@ Filter events by the Bot Detection result, specifically: `good` - events where a good bot was detected. `bad` - events where a bad bot was detected. `none` - events where no bot was detected. -> Note: When using this parameter, only events with the `botd.bot` property set to a valid value are returned. Events without a `botd` Smart Signal result are left out of the response. +> Note: When using this parameter, only events with the `bot` property set to a valid value are returned. Events without a `bot` Smart Signal result are left out of the response. ## Enum diff --git a/res/fingerprint-server-api.yaml b/res/fingerprint-server-api.yaml index c92fea00..db15fe10 100644 --- a/res/fingerprint-server-api.yaml +++ b/res/fingerprint-server-api.yaml @@ -23,7 +23,7 @@ tags: analysis events or event history of individual visitors. externalDocs: description: API documentation - url: https://dev.fingerprint.com/reference/pro-server-api + url: https://docs.fingerprint.com/reference/server-api servers: - url: https://api.fpjs.io/v4 description: Global @@ -55,7 +55,7 @@ paths: type: string description: >- The unique - [identifier](https://dev.fingerprint.com/reference/get-function#requestid) + [identifier](https://docs.fingerprint.com/reference/js-agent-v4-get-function#event_id) of each identification request (`requestId` can be used in its place). - name: ruleset_id @@ -140,7 +140,7 @@ paths: type: string description: >- The unique event - [identifier](https://dev.fingerprint.com/reference/get-function#event_id). + [identifier](https://docs.fingerprint.com/reference/js-agent-v4-get-function#event_id). requestBody: required: true content: @@ -268,7 +268,7 @@ paths: type: string description: > Unique [visitor - identifier](https://dev.fingerprint.com/reference/get-function#visitorid) + identifier](https://docs.fingerprint.com/reference/js-agent-v4-get-function#visitor_id) issued by Fingerprint Identification and all active Smart Signals. Filter for events matching this `visitor_id`. @@ -282,8 +282,8 @@ paths: `good` - events where a good bot was detected. `bad` - events where a bad bot was detected. `none` - events where no bot was detected. - > Note: When using this parameter, only events with the `botd.bot` - property set to a valid value are returned. Events without a `botd` + > Note: When using this parameter, only events with the `bot` + property set to a valid value are returned. Events without a `bot` Smart Signal result are left out of the response. - name: ip_address in: query @@ -313,8 +313,8 @@ paths: You can use [linked - Ids](https://dev.fingerprint.com/reference/get-function#linkedid) to - associate identification requests with your own identifier, for + Ids](https://docs.fingerprint.com/reference/js-agent-v4-get-function#linkedid) + to associate identification requests with your own identifier, for example, session Id, purchase Id, or transaction Id. You can then use this `linked_id` parameter to retrieve all events associated with your custom identifier. @@ -372,7 +372,7 @@ paths: type: boolean description: > Filter events previously tagged as suspicious via the [Update - API](https://dev.fingerprint.com/reference/updateevent). + API](https://docs.fingerprint.com/reference/server-api-v4-update-event). > Note: When using this parameter, only events with the `suspect` property explicitly set to `true` or `false` are returned. Events @@ -688,7 +688,7 @@ paths: - Upon request to delete, this data is deleted asynchronously (typically within a few minutes) and it will no longer be used to identify this browser (or device) for your [Fingerprint - Workspace](https://dev.fingerprint.com/docs/glossary#fingerprint-workspace). + Workspace](https://docs.fingerprint.com/docs/glossary#fingerprint-workspace). #### Identification requests made from this browser (or device) @@ -696,7 +696,7 @@ paths: - Fingerprint stores the identification requests made from a browser (or device) for up to 30 (or 90) days depending on your plan. To learn more, see [Data - Retention](https://dev.fingerprint.com/docs/regions#data-retention). + Retention](https://docs.fingerprint.com/docs/regions#data-retention). - Upon request to delete, the identification requests that were made by this browser @@ -711,9 +711,9 @@ paths: a different visitor ID. - If you request [`/v4/events` - API](https://dev.fingerprint.com/reference/getevent) with an `event_id` - that was made outside of the 10 days, you will still receive a valid - response. + API](https://docs.fingerprint.com/reference/server-api-v4-get-event) + with an `event_id` that was made outside of the 10 days, you will still + receive a valid response. ### Interested? @@ -728,7 +728,7 @@ paths: type: string description: >- The [visitor - ID](https://dev.fingerprint.com/reference/get-function#visitorid) + ID](https://docs.fingerprint.com/reference/js-agent-v4-get-function#visitor_id) you want to delete. responses: '200': @@ -791,7 +791,7 @@ components: description: >- Field is `true` if you have previously set the `suspect` flag for this event using the [Server API Update event - endpoint](https://dev.fingerprint.com/reference/updateevent). + endpoint](https://docs.fingerprint.com/reference/server-api-v4-update-event). Integration: type: object properties: @@ -932,6 +932,7 @@ components: description: >- A customer-provided value or an object that was sent with the identification request or updated later. + additionalProperties: true Url: type: string description: > @@ -1112,7 +1113,7 @@ components: detected on the mobile device or when the request is initiated from a browser, this field will correspond to the *epoch* time (i.e 1 Jan 1970 UTC) as a value of 0. See [Factory Reset - Detection](https://dev.fingerprint.com/docs/smart-signals-overview#factory-reset-detection) + Detection](https://docs.fingerprint.com/docs/smart-signals-reference#factory-reset-detection) to learn more about this Smart Signal. Frida: type: boolean @@ -1308,7 +1309,7 @@ components: * `false` - Otherwise or when the request originated from a browser. See [MitM Attack - Detection](https://dev.fingerprint.com/docs/smart-signals-reference#mitm-attack-detection) + Detection](https://docs.fingerprint.com/docs/smart-signals-reference#mitm-attack-detection) to learn more about this Smart Signal. PrivacySettings: type: boolean @@ -1451,7 +1452,7 @@ components: protection work flow. It is a weighted representation of all Smart Signals present in the payload that helps identify suspicious activity. The value range is [0; S] where S is sum of all Smart Signals weights. - See more details here: https://dev.fingerprint.com/docs/suspect-score + See more details here: https://docs.fingerprint.com/docs/suspect-score Tampering: type: boolean description: > @@ -2311,6 +2312,7 @@ components: description: >- A customer-provided value or an object that was sent with the identification request or updated later. + additionalProperties: true suspect: type: boolean description: Suspect flag indicating observed suspicious or fraudulent event @@ -2352,9 +2354,9 @@ components: `good` - events where a good bot was detected. `bad` - events where a bad bot was detected. `none` - events where no bot was detected. - > Note: When using this parameter, only events with the `botd.bot` - property set to a valid value are returned. Events without a `botd` - Smart Signal result are left out of the response. + > Note: When using this parameter, only events with the `bot` property + set to a valid value are returned. Events without a `bot` Smart Signal + result are left out of the response. SearchEventsVpnConfidence: type: string enum: 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 7ef551f2..2cb7cca8 100644 --- a/sdk/src/main/java/com/fingerprint/v4/api/FingerprintApi.java +++ b/sdk/src/main/java/com/fingerprint/v4/api/FingerprintApi.java @@ -61,8 +61,8 @@ public void setApiClient(ApiClient apiClient) { /** * Delete data by visitor ID - * Request deleting all data associated with the specified visitor ID. This API is useful for compliance with privacy regulations. ### Which data is deleted? - Browser (or device) properties - Identification requests made from this browser (or device) #### Browser (or device) properties - Represents the data that Fingerprint collected from this specific browser (or device) and everything inferred and derived from it. - Upon request to delete, this data is deleted asynchronously (typically within a few minutes) and it will no longer be used to identify this browser (or device) for your [Fingerprint Workspace](https://dev.fingerprint.com/docs/glossary#fingerprint-workspace). #### Identification requests made from this browser (or device) - Fingerprint stores the identification requests made from a browser (or device) for up to 30 (or 90) days depending on your plan. To learn more, see [Data Retention](https://dev.fingerprint.com/docs/regions#data-retention). - Upon request to delete, the identification requests that were made by this browser - Within the past 10 days are deleted within 24 hrs. - Outside of 10 days are allowed to purge as per your data retention period. ### Corollary After requesting to delete a visitor ID, - If the same browser (or device) requests to identify, it will receive a different visitor ID. - If you request [`/v4/events` API](https://dev.fingerprint.com/reference/getevent) with an `event_id` that was made outside of the 10 days, you will still receive a valid response. ### Interested? Please [contact our support team](https://fingerprint.com/support/) to enable it for you. Otherwise, you will receive a 403. - * @param visitorId The [visitor ID](https://dev.fingerprint.com/reference/get-function#visitorid) you want to delete. (required) + * Request deleting all data associated with the specified visitor ID. This API is useful for compliance with privacy regulations. ### Which data is deleted? - Browser (or device) properties - Identification requests made from this browser (or device) #### Browser (or device) properties - Represents the data that Fingerprint collected from this specific browser (or device) and everything inferred and derived from it. - Upon request to delete, this data is deleted asynchronously (typically within a few minutes) and it will no longer be used to identify this browser (or device) for your [Fingerprint Workspace](https://docs.fingerprint.com/docs/glossary#fingerprint-workspace). #### Identification requests made from this browser (or device) - Fingerprint stores the identification requests made from a browser (or device) for up to 30 (or 90) days depending on your plan. To learn more, see [Data Retention](https://docs.fingerprint.com/docs/regions#data-retention). - Upon request to delete, the identification requests that were made by this browser - Within the past 10 days are deleted within 24 hrs. - Outside of 10 days are allowed to purge as per your data retention period. ### Corollary After requesting to delete a visitor ID, - If the same browser (or device) requests to identify, it will receive a different visitor ID. - If you request [`/v4/events` API](https://docs.fingerprint.com/reference/server-api-v4-get-event) with an `event_id` that was made outside of the 10 days, you will still receive a valid response. ### Interested? Please [contact our support team](https://fingerprint.com/support/) to enable it for you. Otherwise, you will receive a 403. + * @param visitorId The [visitor ID](https://docs.fingerprint.com/reference/js-agent-v4-get-function#visitor_id) you want to delete. (required) * @throws ApiException if fails to make API call * @http.response.details * @@ -80,8 +80,8 @@ public void deleteVisitorData(String visitorId) throws ApiException { /** * Delete data by visitor ID - * Request deleting all data associated with the specified visitor ID. This API is useful for compliance with privacy regulations. ### Which data is deleted? - Browser (or device) properties - Identification requests made from this browser (or device) #### Browser (or device) properties - Represents the data that Fingerprint collected from this specific browser (or device) and everything inferred and derived from it. - Upon request to delete, this data is deleted asynchronously (typically within a few minutes) and it will no longer be used to identify this browser (or device) for your [Fingerprint Workspace](https://dev.fingerprint.com/docs/glossary#fingerprint-workspace). #### Identification requests made from this browser (or device) - Fingerprint stores the identification requests made from a browser (or device) for up to 30 (or 90) days depending on your plan. To learn more, see [Data Retention](https://dev.fingerprint.com/docs/regions#data-retention). - Upon request to delete, the identification requests that were made by this browser - Within the past 10 days are deleted within 24 hrs. - Outside of 10 days are allowed to purge as per your data retention period. ### Corollary After requesting to delete a visitor ID, - If the same browser (or device) requests to identify, it will receive a different visitor ID. - If you request [`/v4/events` API](https://dev.fingerprint.com/reference/getevent) with an `event_id` that was made outside of the 10 days, you will still receive a valid response. ### Interested? Please [contact our support team](https://fingerprint.com/support/) to enable it for you. Otherwise, you will receive a 403. - * @param visitorId The [visitor ID](https://dev.fingerprint.com/reference/get-function#visitorid) you want to delete. (required) + * Request deleting all data associated with the specified visitor ID. This API is useful for compliance with privacy regulations. ### Which data is deleted? - Browser (or device) properties - Identification requests made from this browser (or device) #### Browser (or device) properties - Represents the data that Fingerprint collected from this specific browser (or device) and everything inferred and derived from it. - Upon request to delete, this data is deleted asynchronously (typically within a few minutes) and it will no longer be used to identify this browser (or device) for your [Fingerprint Workspace](https://docs.fingerprint.com/docs/glossary#fingerprint-workspace). #### Identification requests made from this browser (or device) - Fingerprint stores the identification requests made from a browser (or device) for up to 30 (or 90) days depending on your plan. To learn more, see [Data Retention](https://docs.fingerprint.com/docs/regions#data-retention). - Upon request to delete, the identification requests that were made by this browser - Within the past 10 days are deleted within 24 hrs. - Outside of 10 days are allowed to purge as per your data retention period. ### Corollary After requesting to delete a visitor ID, - If the same browser (or device) requests to identify, it will receive a different visitor ID. - If you request [`/v4/events` API](https://docs.fingerprint.com/reference/server-api-v4-get-event) with an `event_id` that was made outside of the 10 days, you will still receive a valid response. ### Interested? Please [contact our support team](https://fingerprint.com/support/) to enable it for you. Otherwise, you will receive a 403. + * @param visitorId The [visitor ID](https://docs.fingerprint.com/reference/js-agent-v4-get-function#visitor_id) you want to delete. (required) * @return ApiResponse * @throws ApiException if fails to make API call * @http.response.details @@ -159,7 +159,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://dev.fingerprint.com/reference/get-function#requestid) 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) * @return Event * @throws ApiException if fails to make API call * @http.response.details @@ -180,7 +180,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://dev.fingerprint.com/reference/get-function#requestid) 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) * @param getEventOptionalParams Object containing optional parameters for API method. (optional) * @return Event * @throws ApiException if fails to make API call @@ -203,7 +203,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://dev.fingerprint.com/reference/get-function#requestid) 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) * @param getEventOptionalParams Object containing optional parameters for API method. (optional) * @return ApiResponse * @throws ApiException if fails to make API call @@ -338,14 +338,14 @@ public SearchEventsOptionalParams setPaginationKey(String paginationKey) { } /** - * getter for visitorId - Unique [visitor identifier](https://dev.fingerprint.com/reference/get-function#visitorid) issued by Fingerprint Identification and all active Smart Signals. Filter for events matching this `visitor_id`. + * getter for visitorId - Unique [visitor identifier](https://docs.fingerprint.com/reference/js-agent-v4-get-function#visitor_id) issued by Fingerprint Identification and all active Smart Signals. Filter for events matching this `visitor_id`. */ public String getVisitorId() { return visitorId; } /** - * setter for visitorId - Unique [visitor identifier](https://dev.fingerprint.com/reference/get-function#visitorid) issued by Fingerprint Identification and all active Smart Signals. Filter for events matching this `visitor_id`. + * setter for visitorId - Unique [visitor identifier](https://docs.fingerprint.com/reference/js-agent-v4-get-function#visitor_id) issued by Fingerprint Identification and all active Smart Signals. Filter for events matching this `visitor_id`. */ public SearchEventsOptionalParams setVisitorId(String visitorId) { this.visitorId = visitorId; @@ -353,14 +353,14 @@ public SearchEventsOptionalParams setVisitorId(String visitorId) { } /** - * getter for bot - Filter events by the Bot Detection result, specifically: `all` - events where any kind of bot was detected. `good` - events where a good bot was detected. `bad` - events where a bad bot was detected. `none` - events where no bot was detected. > Note: When using this parameter, only events with the `botd.bot` property set to a valid value are returned. Events without a `botd` Smart Signal result are left out of the response. + * getter for bot - Filter events by the Bot Detection result, specifically: `all` - events where any kind of bot was detected. `good` - events where a good bot was detected. `bad` - events where a bad bot was detected. `none` - events where no bot was detected. > Note: When using this parameter, only events with the `bot` property set to a valid value are returned. Events without a `bot` Smart Signal result are left out of the response. */ public SearchEventsBot getBot() { return bot; } /** - * setter for bot - Filter events by the Bot Detection result, specifically: `all` - events where any kind of bot was detected. `good` - events where a good bot was detected. `bad` - events where a bad bot was detected. `none` - events where no bot was detected. > Note: When using this parameter, only events with the `botd.bot` property set to a valid value are returned. Events without a `botd` Smart Signal result are left out of the response. + * setter for bot - Filter events by the Bot Detection result, specifically: `all` - events where any kind of bot was detected. `good` - events where a good bot was detected. `bad` - events where a bad bot was detected. `none` - events where no bot was detected. > Note: When using this parameter, only events with the `bot` property set to a valid value are returned. Events without a `bot` Smart Signal result are left out of the response. */ public SearchEventsOptionalParams setBot(SearchEventsBot bot) { this.bot = bot; @@ -398,14 +398,14 @@ public SearchEventsOptionalParams setAsn(String asn) { } /** - * getter for linkedId - Filter events by your custom identifier. You can use [linked Ids](https://dev.fingerprint.com/reference/get-function#linkedid) to associate identification requests with your own identifier, for example, session Id, purchase Id, or transaction Id. You can then use this `linked_id` parameter to retrieve all events associated with your custom identifier. + * getter for linkedId - Filter events by your custom identifier. You can use [linked Ids](https://docs.fingerprint.com/reference/js-agent-v4-get-function#linkedid) to associate identification requests with your own identifier, for example, session Id, purchase Id, or transaction Id. You can then use this `linked_id` parameter to retrieve all events associated with your custom identifier. */ public String getLinkedId() { return linkedId; } /** - * setter for linkedId - Filter events by your custom identifier. You can use [linked Ids](https://dev.fingerprint.com/reference/get-function#linkedid) to associate identification requests with your own identifier, for example, session Id, purchase Id, or transaction Id. You can then use this `linked_id` parameter to retrieve all events associated with your custom identifier. + * setter for linkedId - Filter events by your custom identifier. You can use [linked Ids](https://docs.fingerprint.com/reference/js-agent-v4-get-function#linkedid) to associate identification requests with your own identifier, for example, session Id, purchase Id, or transaction Id. You can then use this `linked_id` parameter to retrieve all events associated with your custom identifier. */ public SearchEventsOptionalParams setLinkedId(String linkedId) { this.linkedId = linkedId; @@ -518,14 +518,14 @@ public SearchEventsOptionalParams setReverse(Boolean reverse) { } /** - * getter for suspect - Filter events previously tagged as suspicious via the [Update API](https://dev.fingerprint.com/reference/updateevent). > Note: When using this parameter, only events with the `suspect` property explicitly set to `true` or `false` are returned. Events with undefined `suspect` property are left out of the response. + * getter for suspect - Filter events previously tagged as suspicious via the [Update API](https://docs.fingerprint.com/reference/server-api-v4-update-event). > Note: When using this parameter, only events with the `suspect` property explicitly set to `true` or `false` are returned. Events with undefined `suspect` property are left out of the response. */ public Boolean getSuspect() { return suspect; } /** - * setter for suspect - Filter events previously tagged as suspicious via the [Update API](https://dev.fingerprint.com/reference/updateevent). > Note: When using this parameter, only events with the `suspect` property explicitly set to `true` or `false` are returned. Events with undefined `suspect` property are left out of the response. + * setter for suspect - Filter events previously tagged as suspicious via the [Update API](https://docs.fingerprint.com/reference/server-api-v4-update-event). > Note: When using this parameter, only events with the `suspect` property explicitly set to `true` or `false` are returned. Events with undefined `suspect` property are left out of the response. */ public SearchEventsOptionalParams setSuspect(Boolean suspect) { this.suspect = suspect; @@ -1063,7 +1063,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://dev.fingerprint.com/reference/get-function#event_id). (required) + * @param eventId The unique event [identifier](https://docs.fingerprint.com/reference/js-agent-v4-get-function#event_id). (required) * @param eventUpdate (required) * @throws ApiException if fails to make API call * @http.response.details @@ -1083,7 +1083,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://dev.fingerprint.com/reference/get-function#event_id). (required) + * @param eventId The unique event [identifier](https://docs.fingerprint.com/reference/js-agent-v4-get-function#event_id). (required) * @param eventUpdate (required) * @return ApiResponse * @throws ApiException if fails to make API call diff --git a/sdk/src/main/java/com/fingerprint/v4/model/Event.java b/sdk/src/main/java/com/fingerprint/v4/model/Event.java index 1bc51161..52b93012 100644 --- a/sdk/src/main/java/com/fingerprint/v4/model/Event.java +++ b/sdk/src/main/java/com/fingerprint/v4/model/Event.java @@ -15,6 +15,8 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; /** @@ -105,7 +107,7 @@ public class Event { @jakarta.annotation.Nullable private SupplementaryIDHighRecall supplementaryIdHighRecall; public static final String JSON_PROPERTY_TAGS = "tags"; - @jakarta.annotation.Nullable private Object tags; + @jakarta.annotation.Nullable private Map tags = new HashMap<>(); public static final String JSON_PROPERTY_URL = "url"; @jakarta.annotation.Nullable private String url; @@ -323,7 +325,7 @@ public Event suspect(@jakarta.annotation.Nullable Boolean suspect) { } /** - * Field is `true` if you have previously set the `suspect` flag for this event using the [Server API Update event endpoint](https://dev.fingerprint.com/reference/updateevent). + * Field is `true` if you have previously set the `suspect` flag for this event using the [Server API Update event endpoint](https://docs.fingerprint.com/reference/server-api-v4-update-event). * @return suspect */ @jakarta.annotation.Nullable @@ -429,25 +431,33 @@ public void setSupplementaryIdHighRecall( this.supplementaryIdHighRecall = supplementaryIdHighRecall; } - public Event tags(@jakarta.annotation.Nullable Object tags) { + public Event tags(@jakarta.annotation.Nullable Map tags) { this.tags = tags; return this; } + public Event putTagsItem(String key, Object tagsItem) { + if (this.tags == null) { + this.tags = new HashMap<>(); + } + this.tags.put(key, tagsItem); + return this; + } + /** * A customer-provided value or an object that was sent with the identification request or updated later. * @return tags */ @jakarta.annotation.Nullable @JsonProperty(value = JSON_PROPERTY_TAGS, required = false) - @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) - public Object getTags() { + @JsonInclude(content = JsonInclude.Include.ALWAYS, value = JsonInclude.Include.USE_DEFAULTS) + public Map getTags() { return tags; } @JsonProperty(value = JSON_PROPERTY_TAGS, required = false) - @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) - public void setTags(@jakarta.annotation.Nullable Object tags) { + @JsonInclude(content = JsonInclude.Include.ALWAYS, value = JsonInclude.Include.USE_DEFAULTS) + public void setTags(@jakarta.annotation.Nullable Map tags) { this.tags = tags; } @@ -765,7 +775,7 @@ public Event factoryResetTimestamp(@jakarta.annotation.Nullable Long factoryRese } /** - * The time of the most recent factory reset that happened on the **mobile device** is expressed as Unix epoch time. When a factory reset cannot be detected on the mobile device or when the request is initiated from a browser, this field will correspond to the *epoch* time (i.e 1 Jan 1970 UTC) as a value of 0. See [Factory Reset Detection](https://dev.fingerprint.com/docs/smart-signals-overview#factory-reset-detection) to learn more about this Smart Signal. + * The time of the most recent factory reset that happened on the **mobile device** is expressed as Unix epoch time. When a factory reset cannot be detected on the mobile device or when the request is initiated from a browser, this field will correspond to the *epoch* time (i.e 1 Jan 1970 UTC) as a value of 0. See [Factory Reset Detection](https://docs.fingerprint.com/docs/smart-signals-reference#factory-reset-detection) to learn more about this Smart Signal. * @return factoryResetTimestamp */ @jakarta.annotation.Nullable @@ -985,7 +995,7 @@ public Event mitmAttack(@jakarta.annotation.Nullable Boolean mitmAttack) { } /** - * * `true` - When requests made from your users' mobile devices to Fingerprint servers have been intercepted and potentially modified. * `false` - Otherwise or when the request originated from a browser. See [MitM Attack Detection](https://dev.fingerprint.com/docs/smart-signals-reference#mitm-attack-detection) to learn more about this Smart Signal. + * * `true` - When requests made from your users' mobile devices to Fingerprint servers have been intercepted and potentially modified. * `false` - Otherwise or when the request originated from a browser. See [MitM Attack Detection](https://docs.fingerprint.com/docs/smart-signals-reference#mitm-attack-detection) to learn more about this Smart Signal. * @return mitmAttack */ @jakarta.annotation.Nullable @@ -1073,7 +1083,7 @@ public Event suspectScore(@jakarta.annotation.Nullable Integer suspectScore) { } /** - * Suspect Score is an easy way to integrate Smart Signals into your fraud protection work flow. It is a weighted representation of all Smart Signals present in the payload that helps identify suspicious activity. The value range is [0; S] where S is sum of all Smart Signals weights. See more details here: https://dev.fingerprint.com/docs/suspect-score + * Suspect Score is an easy way to integrate Smart Signals into your fraud protection work flow. It is a weighted representation of all Smart Signals present in the payload that helps identify suspicious activity. The value range is [0; S] where S is sum of all Smart Signals weights. See more details here: https://docs.fingerprint.com/docs/suspect-score * @return suspectScore */ @jakarta.annotation.Nullable diff --git a/sdk/src/main/java/com/fingerprint/v4/model/EventUpdate.java b/sdk/src/main/java/com/fingerprint/v4/model/EventUpdate.java index 8db4f19c..21f6d3e3 100644 --- a/sdk/src/main/java/com/fingerprint/v4/model/EventUpdate.java +++ b/sdk/src/main/java/com/fingerprint/v4/model/EventUpdate.java @@ -15,6 +15,8 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; /** @@ -33,7 +35,7 @@ public class EventUpdate { @jakarta.annotation.Nullable private String linkedId; public static final String JSON_PROPERTY_TAGS = "tags"; - @jakarta.annotation.Nullable private Object tags; + @jakarta.annotation.Nullable private Map tags = new HashMap<>(); public static final String JSON_PROPERTY_SUSPECT = "suspect"; @jakarta.annotation.Nullable private Boolean suspect; @@ -62,25 +64,33 @@ public void setLinkedId(@jakarta.annotation.Nullable String linkedId) { this.linkedId = linkedId; } - public EventUpdate tags(@jakarta.annotation.Nullable Object tags) { + public EventUpdate tags(@jakarta.annotation.Nullable Map tags) { this.tags = tags; return this; } + public EventUpdate putTagsItem(String key, Object tagsItem) { + if (this.tags == null) { + this.tags = new HashMap<>(); + } + this.tags.put(key, tagsItem); + return this; + } + /** * A customer-provided value or an object that was sent with the identification request or updated later. * @return tags */ @jakarta.annotation.Nullable @JsonProperty(value = JSON_PROPERTY_TAGS, required = false) - @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) - public Object getTags() { + @JsonInclude(content = JsonInclude.Include.ALWAYS, value = JsonInclude.Include.USE_DEFAULTS) + public Map getTags() { return tags; } @JsonProperty(value = JSON_PROPERTY_TAGS, required = false) - @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) - public void setTags(@jakarta.annotation.Nullable Object tags) { + @JsonInclude(content = JsonInclude.Include.ALWAYS, value = JsonInclude.Include.USE_DEFAULTS) + public void setTags(@jakarta.annotation.Nullable Map tags) { this.tags = tags; } diff --git a/sdk/src/main/java/com/fingerprint/v4/model/SearchEventsBot.java b/sdk/src/main/java/com/fingerprint/v4/model/SearchEventsBot.java index 635bf69a..3d76d6f8 100644 --- a/sdk/src/main/java/com/fingerprint/v4/model/SearchEventsBot.java +++ b/sdk/src/main/java/com/fingerprint/v4/model/SearchEventsBot.java @@ -16,7 +16,7 @@ import com.fasterxml.jackson.annotation.JsonValue; /** - * Filter events by the Bot Detection result, specifically: `all` - events where any kind of bot was detected. `good` - events where a good bot was detected. `bad` - events where a bad bot was detected. `none` - events where no bot was detected. > Note: When using this parameter, only events with the `botd.bot` property set to a valid value are returned. Events without a `botd` Smart Signal result are left out of the response. + * Filter events by the Bot Detection result, specifically: `all` - events where any kind of bot was detected. `good` - events where a good bot was detected. `bad` - events where a bad bot was detected. `none` - events where no bot was detected. > Note: When using this parameter, only events with the `bot` property set to a valid value are returned. Events without a `bot` Smart Signal result are left out of the response. */ public enum SearchEventsBot { ALL("all"), diff --git a/sdk/src/test/java/com/fingerprint/v4/api/FingerprintApiTest.java b/sdk/src/test/java/com/fingerprint/v4/api/FingerprintApiTest.java index 19e95cf7..de5a3210 100644 --- a/sdk/src/test/java/com/fingerprint/v4/api/FingerprintApiTest.java +++ b/sdk/src/test/java/com/fingerprint/v4/api/FingerprintApiTest.java @@ -202,6 +202,8 @@ public void getEventTest() throws ApiException { assertInstanceOf(VelocityData.class, response.getVelocity().getDistinctCountry()); assertFalse(response.getLocationSpoofing()); assertEquals(0L, response.getFactoryResetTimestamp()); + assertNotNull(response.getTags()); + assertTrue(response.getTags().isEmpty()); } @Test @@ -219,7 +221,8 @@ public void updateEventLinkedIdRequest() throws ApiException { EventUpdate body = invocation.getArgument(4); assertEquals(LINKED_ID, body.getLinkedId()); - assertNull(body.getTags()); + assertNotNull(request.getTags()); + assertTrue(request.getTags().isEmpty()); assertNull(body.getSuspect()); return mockFileToResponse(200, invocation, null, Void.class); }); @@ -277,7 +280,8 @@ public void updateEventSuspectPositiveRequest() throws ApiException { EventUpdate body = invocation.getArgument(4); assertNull(body.getLinkedId()); - assertNull(body.getTags()); + assertNotNull(body.getTags()); + assertTrue(body.getTags().isEmpty()); assertTrue(body.getSuspect()); return mockFileToResponse(200, invocation, null, Void.class); }); @@ -298,7 +302,8 @@ public void updateEventSuspectNegativeRequest() throws ApiException { EventUpdate body = invocation.getArgument(4); assertNull(body.getLinkedId()); - assertNull(body.getTags()); + assertNotNull(body.getTags()); + assertTrue(body.getTags().isEmpty()); assertFalse(body.getSuspect()); return mockFileToResponse(200, invocation, null, Void.class); }); From 1362f86e61b5dccbdc0f7383388059c68b5a1d0c Mon Sep 17 00:00:00 2001 From: Dan McNulty Date: Wed, 25 Feb 2026 18:29:08 -0600 Subject: [PATCH 06/13] chore: address compiler warning in generated ApiException on Java 21 --- .../com/fingerprint/v4/sdk/ApiException.java | 4 +- .../libraries/jersey3/apiException.mustache | 102 ++++++++++++++++++ 2 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 template/libraries/jersey3/apiException.mustache diff --git a/sdk/src/main/java/com/fingerprint/v4/sdk/ApiException.java b/sdk/src/main/java/com/fingerprint/v4/sdk/ApiException.java index 66d345a8..92347e0c 100644 --- a/sdk/src/main/java/com/fingerprint/v4/sdk/ApiException.java +++ b/sdk/src/main/java/com/fingerprint/v4/sdk/ApiException.java @@ -22,10 +22,10 @@ value = "org.openapitools.codegen.languages.JavaClientCodegen", comments = "Generator version: 7.16.0") public class ApiException extends Exception { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 5994704121876855946L; private int code = 0; - private Map> responseHeaders = null; + private transient Map> responseHeaders = null; private String responseBody = null; public ApiException() {} diff --git a/template/libraries/jersey3/apiException.mustache b/template/libraries/jersey3/apiException.mustache new file mode 100644 index 00000000..8d813bfb --- /dev/null +++ b/template/libraries/jersey3/apiException.mustache @@ -0,0 +1,102 @@ +{{>licenseInfo}} + +package {{invokerPackage}}; + +import java.util.Map; +import java.util.List; +{{#caseInsensitiveResponseHeaders}} +import java.util.Map.Entry; +import java.util.TreeMap; +{{/caseInsensitiveResponseHeaders}} + +/** + * API Exception + */ +{{>generatedAnnotation}} + +public class ApiException extends{{#useRuntimeException}} RuntimeException {{/useRuntimeException}}{{^useRuntimeException}} Exception {{/useRuntimeException}}{ + private static final long serialVersionUID = 5994704121876855946L; + + private int code = 0; + private transient Map> responseHeaders = null; + private String responseBody = null; + + public ApiException() {} + + public ApiException(Throwable throwable) { + super(throwable); + } + + public ApiException(String message) { + super(message); + } + + public ApiException(String message, Throwable throwable, int code, Map> responseHeaders, String responseBody) { + super(message, throwable); + this.code = code; + {{#caseInsensitiveResponseHeaders}} + Map> headers = new TreeMap>(String.CASE_INSENSITIVE_ORDER); + for(Entry> entry : responseHeaders.entrySet()){ + headers.put(entry.getKey().toLowerCase(), entry.getValue()); + } + {{/caseInsensitiveResponseHeaders}} + this.responseHeaders = {{#caseInsensitiveResponseHeaders}}headers{{/caseInsensitiveResponseHeaders}}{{^caseInsensitiveResponseHeaders}}responseHeaders{{/caseInsensitiveResponseHeaders}}; + this.responseBody = responseBody; + } + + public ApiException(String message, int code, Map> responseHeaders, String responseBody) { + this(message, (Throwable) null, code, responseHeaders, responseBody); + } + + public ApiException(String message, Throwable throwable, int code, Map> responseHeaders) { + this(message, throwable, code, responseHeaders, null); + } + + public ApiException(int code, Map> responseHeaders, String responseBody) { + this((String) null, (Throwable) null, code, responseHeaders, responseBody); + } + + public ApiException(int code, String message) { + super(message); + this.code = code; + } + + public ApiException(int code, String message, Map> responseHeaders, String responseBody) { + this(code, message); + {{#caseInsensitiveResponseHeaders}} + Map> headers = new TreeMap>(String.CASE_INSENSITIVE_ORDER); + for(Entry> entry : responseHeaders.entrySet()){ + headers.put(entry.getKey().toLowerCase(), entry.getValue()); + } + {{/caseInsensitiveResponseHeaders}} + this.responseHeaders = {{#caseInsensitiveResponseHeaders}}headers{{/caseInsensitiveResponseHeaders}}{{^caseInsensitiveResponseHeaders}}responseHeaders{{/caseInsensitiveResponseHeaders}}; + this.responseBody = responseBody; + } + + /** + * Get the HTTP status code. + * + * @return HTTP status code + */ + public int getCode() { + return code; + } + + /** + * Get the HTTP response headers. + * + * @return A map of list of string + */ + public Map> getResponseHeaders() { + return responseHeaders; + } + + /** + * Get the HTTP response body. + * + * @return Response body in the form of string + */ + public String getResponseBody() { + return responseBody; + } +} From 78595e207db888a90484ff94dfc78011843e2621 Mon Sep 17 00:00:00 2001 From: Dan McNulty Date: Wed, 25 Feb 2026 18:40:13 -0600 Subject: [PATCH 07/13] chore: disable a warning when compiling the template on Java 21 - Disable the `this-escape` warning for the `ApiClient` constructor. Fixing this warning would require significant changes to the `ApiClient` class and could precipitate unnecessary breaking changes to SDK users that are leveraging the client customization hooks to setup their own HTTP client in the Jersey configuration. --- .../com/fingerprint/v4/sdk/ApiClient.java | 2 + template/libraries/jersey3/ApiClient.mustache | 1521 +++++++++++++++++ 2 files changed, 1523 insertions(+) create mode 100644 template/libraries/jersey3/ApiClient.mustache diff --git a/sdk/src/main/java/com/fingerprint/v4/sdk/ApiClient.java b/sdk/src/main/java/com/fingerprint/v4/sdk/ApiClient.java index 88577add..f6d4d35a 100644 --- a/sdk/src/main/java/com/fingerprint/v4/sdk/ApiClient.java +++ b/sdk/src/main/java/com/fingerprint/v4/sdk/ApiClient.java @@ -122,6 +122,8 @@ public ApiClient() { * * @param authMap A hash map containing authentication parameters. */ + // Suppress a known issue warning with the HTTP client extension mechanism + @SuppressWarnings("this-escape") public ApiClient(Map authMap) { json = new JSON(); httpClient = buildHttpClient(); diff --git a/template/libraries/jersey3/ApiClient.mustache b/template/libraries/jersey3/ApiClient.mustache new file mode 100644 index 00000000..acece80f --- /dev/null +++ b/template/libraries/jersey3/ApiClient.mustache @@ -0,0 +1,1521 @@ +{{>licenseInfo}} + +package {{invokerPackage}}; + +import {{javaxPackage}}.ws.rs.client.Client; +import {{javaxPackage}}.ws.rs.client.ClientBuilder; +import {{javaxPackage}}.ws.rs.client.Entity; +import {{javaxPackage}}.ws.rs.client.Invocation; +import {{javaxPackage}}.ws.rs.client.WebTarget; +import {{javaxPackage}}.ws.rs.core.Form; +import {{javaxPackage}}.ws.rs.core.GenericType; +import {{javaxPackage}}.ws.rs.core.MediaType; +import {{javaxPackage}}.ws.rs.core.Response; +import {{javaxPackage}}.ws.rs.core.Response.Status; + +{{#hasOAuthMethods}} +import com.github.scribejava.core.model.OAuth2AccessToken; +{{/hasOAuthMethods}} +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.ClientProperties; +import org.glassfish.jersey.client.HttpUrlConnectorProvider; +import org.glassfish.jersey.jackson.JacksonFeature; +import org.glassfish.jersey.media.multipart.FormDataBodyPart; +import org.glassfish.jersey.media.multipart.FormDataContentDisposition; +import org.glassfish.jersey.media.multipart.MultiPart; +import org.glassfish.jersey.media.multipart.MultiPartFeature; + +import java.io.IOException; +import java.io.InputStream; + +import java.net.URI; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import java.security.cert.X509Certificate; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import org.glassfish.jersey.logging.LoggingFeature; +import java.util.AbstractMap.SimpleEntry; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.Date; +import java.util.Locale; +import java.util.stream.Collectors; +import java.util.stream.Stream; +{{#jsr310}} +import java.time.OffsetDateTime; +{{/jsr310}} + +import java.net.URLEncoder; + +import java.io.File; +import java.io.UnsupportedEncodingException; + +import java.text.DateFormat; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import {{invokerPackage}}.auth.Authentication; +import {{invokerPackage}}.auth.HttpBasicAuth; +import {{invokerPackage}}.auth.HttpBearerAuth; +{{#hasHttpSignatureMethods}} +import {{invokerPackage}}.auth.HttpSignatureAuth; +{{/hasHttpSignatureMethods}} +import {{invokerPackage}}.auth.ApiKeyAuth; +{{#hasOAuthMethods}} +import {{invokerPackage}}.auth.OAuth; +{{/hasOAuthMethods}} + +/** + *

ApiClient class.

+ */ +{{>generatedAnnotation}} + +public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { + protected static final Pattern JSON_MIME_PATTERN = Pattern.compile("(?i)^(application/json|[^;/ \t]+/[^;/ \t]+[+]json)[ \t]*(;.*)?$"); + + protected Map defaultHeaderMap = new HashMap<>(); + protected Map defaultCookieMap = new HashMap<>(); + protected String basePath = "{{{basePath}}}"; + protected String userAgent; + protected static final Logger log = Logger.getLogger(ApiClient.class.getName()); + + protected List servers = new ArrayList<>({{#servers}}{{#-first}}Arrays.asList( +{{/-first}} new ServerConfiguration( + "{{{url}}}", + "{{{description}}}{{^description}}No description provided{{/description}}", + {{^variables}} + new LinkedHashMap<>() + {{/variables}} + {{#variables}} + {{#-first}} + Stream.>of( + {{/-first}} + new SimpleEntry<>("{{{name}}}", new ServerVariable( + "{{{description}}}{{^description}}No description provided{{/description}}", + "{{{defaultValue}}}", + new LinkedHashSet<>({{#enumValues}}{{#-first}}Arrays.asList({{/-first}} + "{{{.}}}"{{^-last}},{{/-last}}{{#-last}} + ){{/-last}}{{/enumValues}}) + )){{^-last}},{{/-last}} + {{#-last}} + ).collect(Collectors.toMap(Entry::getKey, Entry::getValue, (a, b) -> a, LinkedHashMap::new)) + {{/-last}} + {{/variables}} + ){{^-last}},{{/-last}} + {{#-last}} + ){{/-last}}{{/servers}}); + protected Integer serverIndex = 0; + protected Map serverVariables = null; + {{^hasOperationServers}} + protected Map> operationServers = new HashMap<>(); + {{/hasOperationServers}} + {{#hasOperationServers}} + protected Map> operationServers; + + { + Map> operationServers = new HashMap<>(); + {{#apiInfo}} + {{#apis}} + {{#operations}} + {{#operation}} + {{#servers}} + {{#-first}} + operationServers.put("{{{classname}}}.{{{operationId}}}", new ArrayList<>(Arrays.asList( + {{/-first}} + new ServerConfiguration( + "{{{url}}}", + "{{{description}}}{{^description}}No description provided{{/description}}", + {{^variables}} + new LinkedHashMap<>() + {{/variables}} + {{#variables}} + {{#-first}} + Stream.>of( + {{/-first}} + new SimpleEntry<>("{{{name}}}", new ServerVariable( + "{{{description}}}{{^description}}No description provided{{/description}}", + "{{{defaultValue}}}", + new LinkedHashSet<>({{#enumValues}}{{#-first}}Arrays.asList({{/-first}} + "{{{.}}}"{{^-last}},{{/-last}}{{#-last}} + ){{/-last}}{{/enumValues}}) + )){{^-last}},{{/-last}} + {{#-last}} + ).collect(Collectors.toMap(Entry::getKey, Entry::getValue, (a, b) -> a, LinkedHashMap::new)) + {{/-last}} + {{/variables}} + ){{^-last}},{{/-last}} + {{#-last}} + ))); + {{/-last}} + {{/servers}} + {{/operation}} + {{/operations}} + {{/apis}} + {{/apiInfo}} + this.operationServers = operationServers; + } + + {{/hasOperationServers}} + protected Map operationServerIndex = new HashMap<>(); + protected Map> operationServerVariables = new HashMap<>(); + protected boolean debugging = false; + protected ClientConfig clientConfig; + protected int connectionTimeout = 0; + protected int readTimeout = 0; + + protected Client httpClient; + protected JSON json; + protected String tempFolderPath = null; + + protected Map authentications; + protected Map authenticationLookup; + + protected DateFormat dateFormat; + + /** + * Constructs a new ApiClient with default parameters. + */ + public ApiClient() { + this(null); + } + + /** + * Constructs a new ApiClient with the specified authentication parameters. + * + * @param authMap A hash map containing authentication parameters. + */ + // Suppress a known issue warning with the HTTP client extension mechanism + @SuppressWarnings("this-escape") + public ApiClient(Map authMap) { + json = new JSON(); + httpClient = buildHttpClient(); + + this.dateFormat = new RFC3339DateFormat(); + + // Set default User-Agent. + setUserAgent("{{{httpUserAgent}}}{{^httpUserAgent}}OpenAPI-Generator/{{{artifactVersion}}}/java{{/httpUserAgent}}"); + + // Setup authentications (key: authentication name, value: authentication). + authentications = new HashMap<>(); + Authentication auth = null; + {{#authMethods}} + if (authMap != null) { + auth = authMap.get("{{name}}"); + } + {{#isBasic}} + {{#isBasicBasic}} + if (auth instanceof HttpBasicAuth) { + authentications.put("{{name}}", auth); + } else { + authentications.put("{{name}}", new HttpBasicAuth()); + } + {{/isBasicBasic}} + {{#isBasicBearer}} + if (auth instanceof HttpBearerAuth) { + authentications.put("{{name}}", auth); + } else { + authentications.put("{{name}}", new HttpBearerAuth("{{scheme}}")); + } + {{/isBasicBearer}} + {{#isHttpSignature}} + if (auth instanceof HttpSignatureAuth) { + authentications.put("{{name}}", auth); + } + {{/isHttpSignature}} + {{/isBasic}} + {{#isApiKey}} + if (auth instanceof ApiKeyAuth) { + authentications.put("{{name}}", auth); + } else { + authentications.put("{{name}}", new ApiKeyAuth({{#isKeyInHeader}}"header"{{/isKeyInHeader}}{{^isKeyInHeader}}"query"{{/isKeyInHeader}}, "{{keyParamName}}")); + } + {{/isApiKey}} + {{#isOAuth}} + if (auth instanceof OAuth) { + authentications.put("{{name}}", auth); + } else { + authentications.put("{{name}}", new OAuth(basePath, "{{{tokenUrl}}}")); + } + {{/isOAuth}} + {{/authMethods}} + // Prevent the authentications from being modified. + authentications = Collections.unmodifiableMap(authentications); + + // Setup authentication lookup (key: authentication alias, value: authentication name) + authenticationLookup = new HashMap<>();{{#authMethods}}{{#vendorExtensions.x-auth-id-alias}} + authenticationLookup.put("{{name}}", "{{.}}");{{/vendorExtensions.x-auth-id-alias}}{{/authMethods}} + } + + /** + * Gets the JSON instance to do JSON serialization and deserialization. + * + * @return JSON + */ + public JSON getJSON() { + return json; + } + + /** + *

Getter for the field httpClient.

+ * + * @return a {@link {{javaxPackage}}.ws.rs.client.Client} object. + */ + public Client getHttpClient() { + return httpClient; + } + + /** + *

Setter for the field httpClient.

+ * + * @param httpClient a {@link {{javaxPackage}}.ws.rs.client.Client} object. + * @return a {@link ApiClient} object. + */ + public ApiClient setHttpClient(Client httpClient) { + this.httpClient = httpClient; + return this; + } + + /** + * Returns the base URL to the location where the OpenAPI document is being served. + * + * @return The base URL to the target host. + */ + public String getBasePath() { + return basePath; + } + + /** + * Sets the base URL to the location where the OpenAPI document is being served. + * + * @param basePath The base URL to the target host. + * @return a {@link ApiClient} object. + */ + public ApiClient setBasePath(String basePath) { + this.basePath = basePath; + {{#hasOAuthMethods}} + setOauthBasePath(basePath); + {{/hasOAuthMethods}} + return this; + } + + /** + *

Getter for the field servers.

+ * + * @return a {@link java.util.List} of servers. + */ + public List getServers() { + return servers; + } + + /** + *

Setter for the field servers.

+ * + * @param servers a {@link java.util.List} of servers. + * @return a {@link ApiClient} object. + */ + public ApiClient setServers(List servers) { + this.servers = servers; + updateBasePath(); + return this; + } + + /** + *

Getter for the field serverIndex.

+ * + * @return a {@link java.lang.Integer} object. + */ + public Integer getServerIndex() { + return serverIndex; + } + + /** + *

Setter for the field serverIndex.

+ * + * @param serverIndex the server index + * @return a {@link ApiClient} object. + */ + public ApiClient setServerIndex(Integer serverIndex) { + this.serverIndex = serverIndex; + updateBasePath(); + return this; + } + + /** + *

Getter for the field serverVariables.

+ * + * @return a {@link java.util.Map} of server variables. + */ + public Map getServerVariables() { + return serverVariables; + } + + /** + *

Setter for the field serverVariables.

+ * + * @param serverVariables a {@link java.util.Map} of server variables. + * @return a {@link ApiClient} object. + */ + public ApiClient setServerVariables(Map serverVariables) { + this.serverVariables = serverVariables; + updateBasePath(); + return this; + } + + protected void updateBasePath() { + if (serverIndex != null) { + setBasePath(servers.get(serverIndex).URL(serverVariables)); + } + } + + {{#hasOAuthMethods}} + protected void setOauthBasePath(String basePath) { + for(Authentication auth : authentications.values()) { + if (auth instanceof OAuth) { + ((OAuth) auth).setBasePath(basePath); + } + } + } + + {{/hasOAuthMethods}} + /** + * Get authentications (key: authentication name, value: authentication). + * + * @return Map of authentication object + */ + public Map getAuthentications() { + return authentications; + } + + /** + * Get authentication for the given name. + * + * @param authName The authentication name + * @return The authentication, null if not found + */ + public Authentication getAuthentication(String authName) { + return authentications.get(authName); + } + + /** + * Helper method to set username for the first HTTP basic authentication. + * + * @param username Username + * @return a {@link ApiClient} object. + */ + public ApiClient setUsername(String username) { + for (Authentication auth : authentications.values()) { + if (auth instanceof HttpBasicAuth) { + ((HttpBasicAuth) auth).setUsername(username); + return this; + } + } + throw new RuntimeException("No HTTP basic authentication configured!"); + } + + /** + * Helper method to set password for the first HTTP basic authentication. + * + * @param password Password + * @return a {@link ApiClient} object. + */ + public ApiClient setPassword(String password) { + for (Authentication auth : authentications.values()) { + if (auth instanceof HttpBasicAuth) { + ((HttpBasicAuth) auth).setPassword(password); + return this; + } + } + throw new RuntimeException("No HTTP basic authentication configured!"); + } + + /** + * Helper method to set API key value for the first API key authentication. + * + * @param apiKey API key + * @return a {@link ApiClient} object. + */ + public ApiClient setApiKey(String apiKey) { + for (Authentication auth : authentications.values()) { + if (auth instanceof ApiKeyAuth) { + ((ApiKeyAuth) auth).setApiKey(apiKey); + return this; + } + } + throw new RuntimeException("No API key authentication configured!"); + } + + /** + * Helper method to configure authentications which respects aliases of API keys. + * + * @param secrets Hash map from authentication name to its secret. + * @return a {@link ApiClient} object. + */ + public ApiClient configureApiKeys(Map secrets) { + for (Map.Entry authEntry : authentications.entrySet()) { + Authentication auth = authEntry.getValue(); + if (auth instanceof ApiKeyAuth) { + String name = authEntry.getKey(); + // respect x-auth-id-alias property + name = authenticationLookup.getOrDefault(name, name); + String secret = secrets.get(name); + if (secret != null) { + ((ApiKeyAuth) auth).setApiKey(secret); + } + } + } + return this; + } + + /** + * Helper method to set API key prefix for the first API key authentication. + * + * @param apiKeyPrefix API key prefix + * @return a {@link ApiClient} object. + */ + public ApiClient setApiKeyPrefix(String apiKeyPrefix) { + for (Authentication auth : authentications.values()) { + if (auth instanceof ApiKeyAuth) { + ((ApiKeyAuth) auth).setApiKeyPrefix(apiKeyPrefix); + return this; + } + } + throw new RuntimeException("No API key authentication configured!"); + } + + /** + * Helper method to set bearer token for the first Bearer authentication. + * + * @param bearerToken Bearer token + * @return a {@link ApiClient} object. + */ + public ApiClient setBearerToken(String bearerToken) { + for (Authentication auth : authentications.values()) { + if (auth instanceof HttpBearerAuth) { + ((HttpBearerAuth) auth).setBearerToken(bearerToken); + return this; + } + } + throw new RuntimeException("No Bearer authentication configured!"); + } + + {{#hasOAuthMethods}} + /** + * Helper method to set access token for the first OAuth2 authentication. + * + * @param accessToken Access token + * @return a {@link ApiClient} object. + */ + public ApiClient setAccessToken(String accessToken) { + for (Authentication auth : authentications.values()) { + if (auth instanceof OAuth) { + ((OAuth) auth).setAccessToken(accessToken); + return this; + } + } + throw new RuntimeException("No OAuth2 authentication configured!"); + } + + /** + * Helper method to set the credentials for the first OAuth2 authentication. + * + * @param clientId the client ID + * @param clientSecret the client secret + * @return a {@link ApiClient} object. + */ + public ApiClient setOauthCredentials(String clientId, String clientSecret) { + for (Authentication auth : authentications.values()) { + if (auth instanceof OAuth) { + ((OAuth) auth).setCredentials(clientId, clientSecret, isDebugging()); + return this; + } + } + throw new RuntimeException("No OAuth2 authentication configured!"); + } + + /** + * Helper method to set the credentials of a public client for the first OAuth2 authentication. + * + * @param clientId the client ID + * @return a {@link ApiClient} object. + */ + public ApiClient setOauthCredentialsForPublicClient(String clientId) { + for (Authentication auth : authentications.values()) { + if (auth instanceof OAuth) { + ((OAuth) auth).setCredentialsForPublicClient(clientId, isDebugging()); + return this; + } + } + throw new RuntimeException("No OAuth2 authentication configured!"); + } + + /** + * Helper method to set the password flow for the first OAuth2 authentication. + * + * @param username the user name + * @param password the user password + * @return a {@link ApiClient} object. + */ + public ApiClient setOauthPasswordFlow(String username, String password) { + for (Authentication auth : authentications.values()) { + if (auth instanceof OAuth) { + ((OAuth) auth).usePasswordFlow(username, password); + return this; + } + } + throw new RuntimeException("No OAuth2 authentication configured!"); + } + + /** + * Helper method to set the authorization code flow for the first OAuth2 authentication. + * + * @param code the authorization code + * @return a {@link ApiClient} object. + */ + public ApiClient setOauthAuthorizationCodeFlow(String code) { + for (Authentication auth : authentications.values()) { + if (auth instanceof OAuth) { + ((OAuth) auth).useAuthorizationCodeFlow(code); + return this; + } + } + throw new RuntimeException("No OAuth2 authentication configured!"); + } + + /** + * Helper method to set the scopes for the first OAuth2 authentication. + * + * @param scope the oauth scope + * @return a {@link ApiClient} object. + */ + public ApiClient setOauthScope(String scope) { + for (Authentication auth : authentications.values()) { + if (auth instanceof OAuth) { + ((OAuth) auth).setScope(scope); + return this; + } + } + throw new RuntimeException("No OAuth2 authentication configured!"); + } + + {{/hasOAuthMethods}} + /** + * Set the User-Agent header's value (by adding to the default header map). + * + * @param userAgent Http user agent + * @return a {@link ApiClient} object. + */ + public ApiClient setUserAgent(String userAgent) { + this.userAgent = userAgent; + addDefaultHeader("User-Agent", userAgent); + return this; + } + + /** + * Get the User-Agent header's value. + * + * @return User-Agent string + */ + public String getUserAgent(){ + return userAgent; + } + + /** + * Add a default header. + * + * @param key The header's key + * @param value The header's value + * @return a {@link ApiClient} object. + */ + public ApiClient addDefaultHeader(String key, String value) { + defaultHeaderMap.put(key, value); + return this; + } + + /** + * Add a default cookie. + * + * @param key The cookie's key + * @param value The cookie's value + * @return a {@link ApiClient} object. + */ + public ApiClient addDefaultCookie(String key, String value) { + defaultCookieMap.put(key, value); + return this; + } + + /** + * Gets the client config. + * + * @return Client config + */ + public ClientConfig getClientConfig() { + return clientConfig; + } + + /** + * Set the client config. + * + * @param clientConfig Set the client config + * @return a {@link ApiClient} object. + */ + public ApiClient setClientConfig(ClientConfig clientConfig) { + this.clientConfig = clientConfig; + // Rebuild HTTP Client according to the new "clientConfig" value. + this.httpClient = buildHttpClient(); + return this; + } + + /** + * Check that whether debugging is enabled for this API client. + * + * @return True if debugging is switched on + */ + public boolean isDebugging() { + return debugging; + } + + /** + * Enable/disable debugging for this API client. + * + * @param debugging To enable (true) or disable (false) debugging + * @return a {@link ApiClient} object. + */ + public ApiClient setDebugging(boolean debugging) { + this.debugging = debugging; + applyDebugSetting(this.clientConfig); + // Rebuild HTTP Client according to the new "debugging" value. + this.httpClient = buildHttpClient(); + return this; + } + + /** + * The path of temporary folder used to store downloaded files from endpoints + * with file response. The default value is null, i.e. using + * the system's default temporary folder. + * + * @return Temp folder path + */ + public String getTempFolderPath() { + return tempFolderPath; + } + + /** + * Set temp folder path + * + * @param tempFolderPath Temp folder path + * @return a {@link ApiClient} object. + */ + public ApiClient setTempFolderPath(String tempFolderPath) { + this.tempFolderPath = tempFolderPath; + return this; + } + + /** + * Connect timeout (in milliseconds). + * + * @return Connection timeout + */ + public int getConnectTimeout() { + return connectionTimeout; + } + + /** + * Set the connect timeout (in milliseconds). + * A value of 0 means no timeout, otherwise values must be between 1 and + * {@link Integer#MAX_VALUE}. + * + * @param connectionTimeout Connection timeout in milliseconds + * @return a {@link ApiClient} object. + */ + public ApiClient setConnectTimeout(int connectionTimeout) { + this.connectionTimeout = connectionTimeout; + httpClient.property(ClientProperties.CONNECT_TIMEOUT, connectionTimeout); + return this; + } + + /** + * read timeout (in milliseconds). + * + * @return Read timeout + */ + public int getReadTimeout() { + return readTimeout; + } + + /** + * Set the read timeout (in milliseconds). + * A value of 0 means no timeout, otherwise values must be between 1 and + * {@link Integer#MAX_VALUE}. + * + * @param readTimeout Read timeout in milliseconds + * @return a {@link ApiClient} object. + */ + public ApiClient setReadTimeout(int readTimeout) { + this.readTimeout = readTimeout; + httpClient.property(ClientProperties.READ_TIMEOUT, readTimeout); + return this; + } + + /** + * Get the date format used to parse/format date parameters. + * + * @return Date format + */ + public DateFormat getDateFormat() { + return dateFormat; + } + + /** + * Set the date format used to parse/format date parameters. + * + * @param dateFormat Date format + * @return a {@link ApiClient} object. + */ + public ApiClient setDateFormat(DateFormat dateFormat) { + this.dateFormat = dateFormat; + // also set the date format for model (de)serialization with Date properties + this.json.setDateFormat((DateFormat) dateFormat.clone()); + return this; + } + + /** + * Parse the given string into Date object. + * + * @param str String + * @return Date + */ + public Date parseDate(String str) { + try { + return dateFormat.parse(str); + } catch (java.text.ParseException e) { + throw new RuntimeException(e); + } + } + + /** + * Format the given Date object into string. + * + * @param date Date + * @return Date in string format + */ + public String formatDate(Date date) { + return dateFormat.format(date); + } + + /** + * Format the given parameter object into string. + * + * @param param Object + * @return Object in string format + */ + public String parameterToString(Object param) { + if (param == null) { + return ""; + } else if (param instanceof Date) { + return formatDate((Date) param); + } {{#jsr310}}else if (param instanceof OffsetDateTime) { + return formatOffsetDateTime((OffsetDateTime) param); + } {{/jsr310}}else if (param instanceof Collection) { + StringBuilder b = new StringBuilder(); + for(Object o : (Collection)param) { + if(b.length() > 0) { + b.append(','); + } + b.append(String.valueOf(o)); + } + return b.toString(); + } else { + return String.valueOf(param); + } + } + + /* + * Format to {@code Pair} objects. + * + * @param collectionFormat Collection format + * @param name Name + * @param value Value + * @return List of pairs + */ + public List parameterToPairs(String collectionFormat, String name, Object value){ + List params = new ArrayList<>(); + + // preconditions + if (name == null || name.isEmpty() || value == null) return params; + + Collection valueCollection; + if (value instanceof Collection) { + valueCollection = (Collection) value; + } else { + params.add(new Pair(name, parameterToString(value))); + return params; + } + + if (valueCollection.isEmpty()){ + return params; + } + + // get the collection format (default: csv) + String format = (collectionFormat == null || collectionFormat.isEmpty() ? "csv" : collectionFormat); + + // create the params based on the collection format + if ("multi".equals(format)) { + for (Object item : valueCollection) { + params.add(new Pair(name, parameterToString(item))); + } + + return params; + } + + String delimiter = ","; + + if ("csv".equals(format)) { + delimiter = ","; + } else if ("ssv".equals(format)) { + delimiter = " "; + } else if ("tsv".equals(format)) { + delimiter = "\t"; + } else if ("pipes".equals(format)) { + delimiter = "|"; + } + + StringBuilder sb = new StringBuilder() ; + for (Object item : valueCollection) { + sb.append(delimiter); + sb.append(parameterToString(item)); + } + + params.add(new Pair(name, sb.substring(1))); + + return params; + } + + /** + * Check if the given MIME is a JSON MIME. + * JSON MIME examples: + * application/json + * application/json; charset=UTF8 + * APPLICATION/JSON + * application/vnd.company+json + * "*{@literal /}*" is also considered JSON by this method. + * + * @param mime MIME + * @return True if the MIME type is JSON + */ + public boolean isJsonMime(String mime) { + return mime != null && (mime.equals("*/*") || JSON_MIME_PATTERN.matcher(mime).matches()); + } + + /** + * Select the Accept header's value from the given accepts array: + * if JSON exists in the given array, use it; + * otherwise use all of them (joining into a string) + * + * @param accepts The accepts array to select from + * @return The Accept header to use. If the given array is empty, + * null will be returned (not to set the Accept header explicitly). + */ + public String selectHeaderAccept(String... accepts) { + if (accepts == null || accepts.length == 0) { + return null; + } + for (String accept : accepts) { + if (isJsonMime(accept)) { + return accept; + } + } + return StringUtil.join(accepts, ","); + } + + /** + * Select the Content-Type header's value from the given array: + * if JSON exists in the given array, use it; + * otherwise use the first one of the array. + * + * @param contentTypes The Content-Type array to select from + * @return The Content-Type header to use. If the given array is empty, + * JSON will be used. + */ + public String selectHeaderContentType(String... contentTypes) { + if (contentTypes == null || contentTypes.length == 0) { + return "application/json"; + } + for (String contentType : contentTypes) { + if (isJsonMime(contentType)) { + return contentType; + } + } + return contentTypes[0]; + } + + /** + * Escape the given string to be used as URL query value. + * + * @param str String + * @return Escaped string + */ + public String escapeString(String str) { + try { + return URLEncoder.encode(str, "utf8").replaceAll("\\+", "%20"); + } catch (UnsupportedEncodingException e) { + return str; + } + } + + /** + * Serialize the given Java object into string entity according the given + * Content-Type (only JSON is supported for now). + * + * @param obj Object + * @param formParams Form parameters + * @param contentType Context type + * @return Entity + * @throws ApiException API exception + */ + public Entity serialize(Object obj, Map formParams, String contentType, boolean isBodyNullable) throws ApiException { + Entity entity; + if (contentType.startsWith("multipart/form-data")) { + MultiPart multiPart = new MultiPart(); + for (Entry param: formParams.entrySet()) { + if (param.getValue() instanceof Iterable) { + ((Iterable)param.getValue()).forEach(v -> addParamToMultipart(v, param.getKey(), multiPart)); + } else { + addParamToMultipart(param.getValue(), param.getKey(), multiPart); + } + } + entity = Entity.entity(multiPart, MediaType.MULTIPART_FORM_DATA_TYPE); + } else if (contentType.startsWith("application/x-www-form-urlencoded")) { + Form form = new Form(); + for (Entry param: formParams.entrySet()) { + form.param(param.getKey(), parameterToString(param.getValue())); + } + entity = Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE); + } else { + // We let jersey handle the serialization + if (isBodyNullable) { // payload is nullable + if (obj instanceof String) { + entity = Entity.entity(obj == null ? "null" : "\"" + ((String)obj).replaceAll("\"", Matcher.quoteReplacement("\\\"")) + "\"", contentType); + } else { + entity = Entity.entity(obj == null ? "null" : obj, contentType); + } + } else { + if (obj instanceof String) { + entity = Entity.entity(obj == null ? "" : "\"" + ((String)obj).replaceAll("\"", Matcher.quoteReplacement("\\\"")) + "\"", contentType); + } else { + entity = Entity.entity(obj == null ? "" : obj, contentType); + } + } + } + return entity; + } + + /** + * Adds the object with the provided key to the MultiPart. + * Based on the object type sets Content-Disposition and Content-Type. + * + * @param obj Object + * @param key Key of the object + * @param multiPart MultiPart to add the form param to + */ + protected void addParamToMultipart(Object value, String key, MultiPart multiPart) { + if (value instanceof File) { + File file = (File) value; + FormDataContentDisposition contentDisp = FormDataContentDisposition.name(key) + .fileName(file.getName()).size(file.length()).build(); + + // Attempt to probe the content type for the file so that the form part is more correctly + // and precisely identified, but fall back to application/octet-stream if that fails. + MediaType type; + try { + type = MediaType.valueOf(Files.probeContentType(file.toPath())); + } catch (IOException | IllegalArgumentException e) { + type = MediaType.APPLICATION_OCTET_STREAM_TYPE; + } + + multiPart.bodyPart(new FormDataBodyPart(contentDisp, file, type)); + } else { + FormDataContentDisposition contentDisp = FormDataContentDisposition.name(key).build(); + multiPart.bodyPart(new FormDataBodyPart(contentDisp, parameterToString(value))); + } + } + + /** + * Serialize the given Java object into string according the given + * Content-Type (only JSON, HTTP form is supported for now). + * + * @param obj Object + * @param formParams Form parameters + * @param contentType Context type + * @param isBodyNullable True if the body is nullable + * @return String + * @throws ApiException API exception + */ + public String serializeToString(Object obj, Map formParams, String contentType, boolean isBodyNullable) throws ApiException { + try { + if (contentType.startsWith("multipart/form-data")) { + throw new ApiException("multipart/form-data not yet supported for serializeToString (http signature authentication)"); + } else if (contentType.startsWith("application/x-www-form-urlencoded")) { + String formString = ""; + for (Entry param : formParams.entrySet()) { + formString = param.getKey() + "=" + URLEncoder.encode(parameterToString(param.getValue()), "UTF-8") + "&"; + } + + if (formString.length() == 0) { // empty string + return formString; + } else { + return formString.substring(0, formString.length() - 1); + } + } else { + if (isBodyNullable) { + return obj == null ? "null" : json.getMapper().writeValueAsString(obj); + } else { + return obj == null ? "" : json.getMapper().writeValueAsString(obj); + } + } + } catch (Exception ex) { + throw new ApiException("Failed to perform serializeToString: " + ex.toString()); + } + } + + /** + * Deserialize response body to Java object according to the Content-Type. + * + * @param Type + * @param response Response + * @param returnType Return type + * @return Deserialize object + * @throws ApiException API exception + */ + @SuppressWarnings("unchecked") + public T deserialize(Response response, GenericType returnType) throws ApiException { + if (response == null || returnType == null) { + return null; + } + + if ("byte[]".equals(returnType.toString())) { + // Handle binary response (byte array). + return (T) response.readEntity(byte[].class); + } else if (returnType.getRawType() == File.class) { + // Handle file downloading. + T file = (T) downloadFileFromResponse(response); + return file; + } + + // read the entity stream multiple times + response.bufferEntity(); + + return response.readEntity(returnType); + } + + /** + * Download file from the given response. + * + * @param response Response + * @return File + * @throws ApiException If fail to read file content from response and write to disk + */ + public File downloadFileFromResponse(Response response) throws ApiException { + try { + File file = prepareDownloadFile(response); + Files.copy(response.readEntity(InputStream.class), file.toPath(), StandardCopyOption.REPLACE_EXISTING); + return file; + } catch (IOException e) { + throw new ApiException(e); + } + } + + /** + *

Prepare the file for download from the response.

+ * + * @param response a {@link {{javaxPackage}}.ws.rs.core.Response} object. + * @return a {@link java.io.File} object. + * @throws java.io.IOException if any. + */ + public File prepareDownloadFile(Response response) throws IOException { + String filename = null; + String contentDisposition = (String) response.getHeaders().getFirst("Content-Disposition"); + if (contentDisposition != null && !"".equals(contentDisposition)) { + // Get filename from the Content-Disposition header. + Pattern pattern = Pattern.compile("filename=['\"]?([^'\"\\s]+)['\"]?"); + Matcher matcher = pattern.matcher(contentDisposition); + if (matcher.find()) + filename = matcher.group(1); + } + + String prefix; + String suffix = null; + if (filename == null) { + prefix = "download-"; + suffix = ""; + } else { + int pos = filename.lastIndexOf('.'); + if (pos == -1) { + prefix = filename + "-"; + } else { + prefix = filename.substring(0, pos) + "-"; + suffix = filename.substring(pos); + } + // Files.createTempFile requires the prefix to be at least three characters long + if (prefix.length() < 3) + prefix = "download-"; + } + + if (tempFolderPath == null) + return Files.createTempFile(prefix, suffix).toFile(); + else + return Files.createTempFile(Paths.get(tempFolderPath), prefix, suffix).toFile(); + } + + /** + * Invoke API by sending HTTP request with the given options. + * + * @param Type + * @param operation The qualified name of the operation + * @param path The sub-path of the HTTP URL + * @param method The request method, one of "GET", "POST", "PUT", "HEAD" and "DELETE" + * @param queryParams The query parameters + * @param body The request body object + * @param headerParams The header parameters + * @param cookieParams The cookie parameters + * @param formParams The form parameters + * @param accept The request's Accept header + * @param contentType The request's Content-Type header + * @param authNames The authentications to apply + * @param returnType The return type into which to deserialize the response + * @param isBodyNullable True if the body is nullable + * @return The response body in type of string + * @throws ApiException API exception + */ + public ApiResponse invokeAPI( + String operation, + String path, + String method, + List queryParams, + Object body, + Map headerParams, + Map cookieParams, + Map formParams, + String accept, + String contentType, + String[] authNames, + GenericType returnType, + boolean isBodyNullable) + throws ApiException { + + String targetURL; + List serverConfigurations; + if (serverIndex != null && (serverConfigurations = operationServers.get(operation)) != null) { + int index = operationServerIndex.getOrDefault(operation, serverIndex).intValue(); + Map variables = operationServerVariables.getOrDefault(operation, serverVariables); + if (index < 0 || index >= serverConfigurations.size()) { + throw new ArrayIndexOutOfBoundsException( + String.format( + Locale.ROOT, + "Invalid index %d when selecting the host settings. Must be less than %d", + index, serverConfigurations.size())); + } + targetURL = serverConfigurations.get(index).URL(variables) + path; + } else { + targetURL = this.basePath + path; + } + // Not using `.target(targetURL).path(path)` below, + // to support (constant) query string in `path`, e.g. "/posts?draft=1" + WebTarget target = httpClient.target(targetURL); + + // put all headers in one place + Map allHeaderParams = new HashMap<>(defaultHeaderMap); + allHeaderParams.putAll(headerParams); + + if (authNames != null) { + // update different parameters (e.g. headers) for authentication + updateParamsForAuth( + authNames, + queryParams, + allHeaderParams, + cookieParams, + {{#hasHttpSignatureMethods}} + serializeToString(body, formParams, contentType, isBodyNullable), + {{/hasHttpSignatureMethods}} + {{^hasHttpSignatureMethods}} + null, + {{/hasHttpSignatureMethods}} + method, + target.getUri()); + } + + if (queryParams != null) { + for (Pair queryParam : queryParams) { + if (queryParam.getValue() != null) { + target = target.queryParam(queryParam.getName(), escapeString(queryParam.getValue())); + } + } + } + + Invocation.Builder invocationBuilder = target.request(); + + if (accept != null) { + invocationBuilder = invocationBuilder.accept(accept); + } + + for (Entry entry : cookieParams.entrySet()) { + String value = entry.getValue(); + if (value != null) { + invocationBuilder = invocationBuilder.cookie(entry.getKey(), value); + } + } + + for (Entry entry : defaultCookieMap.entrySet()) { + String value = entry.getValue(); + if (value != null) { + invocationBuilder = invocationBuilder.cookie(entry.getKey(), value); + } + } + + Entity entity = serialize(body, formParams, contentType, isBodyNullable); + + for (Entry entry : allHeaderParams.entrySet()) { + String value = entry.getValue(); + if (value != null) { + invocationBuilder = invocationBuilder.header(entry.getKey(), value); + } + } + + Response response = null; + + try { + response = sendRequest(method, invocationBuilder, entity); + + final int statusCode = response.getStatusInfo().getStatusCode(); + + {{#hasOAuthMethods}} + // If OAuth is used and a status 401 is received, renew the access token and retry the request + if (authNames != null && statusCode == Status.UNAUTHORIZED.getStatusCode()) { + for (String authName : authNames) { + Authentication authentication = authentications.get(authName); + if (authentication instanceof OAuth) { + OAuth2AccessToken accessToken = ((OAuth) authentication).renewAccessToken(); + if (accessToken != null) { + invocationBuilder.header("Authorization", null); + invocationBuilder.header("Authorization", "Bearer " + accessToken.getAccessToken()); + response = sendRequest(method, invocationBuilder, entity); + } + break; + } + } + } + + {{/hasOAuthMethods}} + Map> responseHeaders = buildResponseHeaders(response); + + if (statusCode == Status.NO_CONTENT.getStatusCode()) { + return new ApiResponse(statusCode, responseHeaders); + } else if (response.getStatusInfo().getFamily() == Status.Family.SUCCESSFUL) { + if (returnType == null) { + return new ApiResponse(statusCode, responseHeaders); + } else { + return new ApiResponse(statusCode, responseHeaders, deserialize(response, returnType)); + } + } else { + String message = "error"; + String respBody = null; + if (response.hasEntity()) { + try { + respBody = String.valueOf(response.readEntity(String.class)); + message = respBody; + } catch (RuntimeException e) { + // e.printStackTrace(); + } + } + throw new ApiException( + response.getStatus(), message, buildResponseHeaders(response), respBody); + } + } finally { + try { + response.close(); + } catch (Exception e) { + // it's not critical, since the response object is local in method invokeAPI; that's fine, + // just continue + } + } + } + + protected Response sendRequest(String method, Invocation.Builder invocationBuilder, Entity entity) { + Response response; + if ("POST".equals(method)) { + response = invocationBuilder.post(entity); + } else if ("PUT".equals(method)) { + response = invocationBuilder.put(entity); + } else if ("DELETE".equals(method)) { + if ("".equals(entity.getEntity())) { + response = invocationBuilder.method("DELETE"); + } else { + response = invocationBuilder.method("DELETE", entity); + } + } else if ("PATCH".equals(method)) { + response = invocationBuilder.method("PATCH", entity); + } else { + response = invocationBuilder.method(method); + } + return response; + } + + /** + * @deprecated Add qualified name of the operation as a first parameter. + */ + @Deprecated + public ApiResponse invokeAPI(String path, String method, List queryParams, Object body, Map headerParams, Map cookieParams, Map formParams, String accept, String contentType, String[] authNames, GenericType returnType, boolean isBodyNullable) throws ApiException { + return invokeAPI(null, path, method, queryParams, body, headerParams, cookieParams, formParams, accept, contentType, authNames, returnType, isBodyNullable); + } + + /** + * Build the Client used to make HTTP requests. + * + * @return Client + */ + protected Client buildHttpClient() { + // Create ClientConfig if it has not been initialized yet + if (clientConfig == null) { + clientConfig = getDefaultClientConfig(); + } + + ClientBuilder clientBuilder = ClientBuilder.newBuilder(); + clientBuilder = clientBuilder.withConfig(clientConfig); + customizeClientBuilder(clientBuilder); + return clientBuilder.build(); + } + + /** + * Get the default client config. + * + * @return Client config + */ + public ClientConfig getDefaultClientConfig() { + ClientConfig clientConfig = new ClientConfig(); + clientConfig.register(MultiPartFeature.class); + clientConfig.register(json); + clientConfig.register(JacksonFeature.class); + clientConfig.property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true); + // turn off compliance validation to be able to send payloads with DELETE calls + clientConfig.property(ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION, true); + applyDebugSetting(clientConfig); + return clientConfig; + } + + protected void applyDebugSetting(ClientConfig clientConfig) { + if (debugging) { + clientConfig.register(new LoggingFeature(java.util.logging.Logger.getLogger(LoggingFeature.DEFAULT_LOGGER_NAME), java.util.logging.Level.INFO, LoggingFeature.Verbosity.PAYLOAD_ANY, 1024*50 /* Log payloads up to 50K */)); + clientConfig.property(LoggingFeature.LOGGING_FEATURE_VERBOSITY, LoggingFeature.Verbosity.PAYLOAD_ANY); + // Set logger to ALL + java.util.logging.Logger.getLogger(LoggingFeature.DEFAULT_LOGGER_NAME).setLevel(java.util.logging.Level.ALL); + } else { + // suppress warnings for payloads with DELETE calls: + java.util.logging.Logger.getLogger("org.glassfish.jersey.client").setLevel(java.util.logging.Level.SEVERE); + } + } + + /** + * Customize the client builder. + * + * This method can be overridden to customize the API client. For example, this can be used to: + * 1. Set the hostname verifier to be used by the client to verify the endpoint's hostname + * against its identification information. + * 2. Set the client-side key store. + * 3. Set the SSL context that will be used when creating secured transport connections to + * server endpoints from web targets created by the client instance that is using this SSL context. + * 4. Set the client-side trust store. + * + * To completely disable certificate validation (at your own risk), you can + * override this method and invoke disableCertificateValidation(clientBuilder). + * + * @param clientBuilder a {@link {{javaxPackage}}.ws.rs.client.ClientBuilder} object. + */ + protected void customizeClientBuilder(ClientBuilder clientBuilder) { + // No-op extension point + } + + /** + * Disable X.509 certificate validation in TLS connections. + * + * Please note that trusting all certificates is extremely risky. + * This may be useful in a development environment with self-signed certificates. + * + * @param clientBuilder a {@link {{javaxPackage}}.ws.rs.client.ClientBuilder} object. + * @throws java.security.KeyManagementException if any. + * @throws java.security.NoSuchAlgorithmException if any. + */ + protected void disableCertificateValidation(ClientBuilder clientBuilder) throws KeyManagementException, NoSuchAlgorithmException { + TrustManager[] trustAllCerts = new X509TrustManager[] { + new X509TrustManager() { + @Override + public X509Certificate[] getAcceptedIssuers() { + return null; + } + @Override + public void checkClientTrusted(X509Certificate[] certs, String authType) { + } + @Override + public void checkServerTrusted(X509Certificate[] certs, String authType) { + } + } + }; + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, trustAllCerts, new SecureRandom()); + clientBuilder.sslContext(sslContext); + } + + /** + *

Build the response headers.

+ * + * @param response a {@link {{javaxPackage}}.ws.rs.core.Response} object. + * @return a {@link java.util.Map} of response headers. + */ + protected Map> buildResponseHeaders(Response response) { + Map> responseHeaders = new HashMap<>(); + for (Entry> entry: response.getHeaders().entrySet()) { + List values = entry.getValue(); + List headers = new ArrayList<>(); + for (Object o : values) { + headers.add(String.valueOf(o)); + } + responseHeaders.put(entry.getKey(), headers); + } + return responseHeaders; + } + + /** + * Update query and header parameters based on authentication settings. + * + * @param authNames The authentications to apply + * @param queryParams List of query parameters + * @param headerParams Map of header parameters + * @param cookieParams Map of cookie parameters + * @param method HTTP method (e.g. POST) + * @param uri HTTP URI + */ + protected void updateParamsForAuth(String[] authNames, List queryParams, Map headerParams, + Map cookieParams, String payload, String method, URI uri) throws ApiException { + for (String authName : authNames) { + Authentication auth = authentications.get(authName); + if (auth == null) { + continue; + } + auth.applyToParams(queryParams, headerParams, cookieParams, payload, method, uri); + } + } +} \ No newline at end of file From a2a71517a8b584aadc0ec7279abf5deb8afeb80f Mon Sep 17 00:00:00 2001 From: Dan McNulty Date: Thu, 26 Feb 2026 15:01:51 -0600 Subject: [PATCH 08/13] chore: remove wild card imports from test code --- .../com/fingerprint/example/FunctionalTests.java | 7 ++++++- .../com/fingerprint/v4/api/FingerprintApiTest.java | 13 ++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/examples/src/main/java/com/fingerprint/example/FunctionalTests.java b/examples/src/main/java/com/fingerprint/example/FunctionalTests.java index 0f70129d..4a048b5a 100644 --- a/examples/src/main/java/com/fingerprint/example/FunctionalTests.java +++ b/examples/src/main/java/com/fingerprint/example/FunctionalTests.java @@ -1,7 +1,12 @@ package com.fingerprint.example; import com.fingerprint.v4.api.FingerprintApi; -import com.fingerprint.v4.model.*; +import com.fingerprint.v4.model.Event; +import com.fingerprint.v4.model.EventRuleActionAllow; +import com.fingerprint.v4.model.EventRuleActionBlock; +import com.fingerprint.v4.model.EventSearch; +import com.fingerprint.v4.model.EventUpdate; +import com.fingerprint.v4.model.RequestHeaderModifications; import com.fingerprint.v4.sdk.ApiException; import com.fingerprint.v4.sdk.Configuration; import java.sql.Timestamp; diff --git a/sdk/src/test/java/com/fingerprint/v4/api/FingerprintApiTest.java b/sdk/src/test/java/com/fingerprint/v4/api/FingerprintApiTest.java index de5a3210..f5f35789 100644 --- a/sdk/src/test/java/com/fingerprint/v4/api/FingerprintApiTest.java +++ b/sdk/src/test/java/com/fingerprint/v4/api/FingerprintApiTest.java @@ -15,7 +15,18 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import com.fingerprint.v4.model.*; +import com.fingerprint.v4.model.ErrorCode; +import com.fingerprint.v4.model.ErrorResponse; +import com.fingerprint.v4.model.Event; +import com.fingerprint.v4.model.EventRuleActionBlock; +import com.fingerprint.v4.model.EventSearch; +import com.fingerprint.v4.model.EventUpdate; +import com.fingerprint.v4.model.ProxyDetails; +import com.fingerprint.v4.model.RuleActionType; +import com.fingerprint.v4.model.SearchEventsBot; +import com.fingerprint.v4.model.SearchEventsSdkPlatform; +import com.fingerprint.v4.model.SearchEventsVpnConfidence; +import com.fingerprint.v4.model.VelocityData; import com.fingerprint.v4.sdk.ApiClient; import com.fingerprint.v4.sdk.ApiException; import com.fingerprint.v4.sdk.ApiResponse; From d43ff2bdb1294fe7ddecbfaf48c3ab3e11722099 Mon Sep 17 00:00:00 2001 From: Dan McNulty Date: Thu, 26 Feb 2026 15:02:10 -0600 Subject: [PATCH 09/13] fix: fix compilation errors in FingerprintApi examples --- docs/FingerprintApi.md | 16 ++++++++++++---- template/libraries/jersey3/api_doc.mustache | 4 +++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/docs/FingerprintApi.md b/docs/FingerprintApi.md index 8cbf0b83..88f1d647 100644 --- a/docs/FingerprintApi.md +++ b/docs/FingerprintApi.md @@ -47,8 +47,10 @@ Please [contact our support team](https://fingerprint.com/support/) to enable it ```java package main; +import java.util.*; + import com.fingerprint.v4.api.FingerprintApi; -import com.fingerprint.v4.sdk.model.*; +import com.fingerprint.v4.model.*; import com.fingerprint.v4.sdk.ApiClient; import com.fingerprint.v4.sdk.ApiException; import com.fingerprint.v4.sdk.Region; @@ -124,8 +126,10 @@ Use `event_id` as the URL path parameter. This API method is scoped to a request ```java package main; +import java.util.*; + import com.fingerprint.v4.api.FingerprintApi; -import com.fingerprint.v4.sdk.model.*; +import com.fingerprint.v4.model.*; import com.fingerprint.v4.sdk.ApiClient; import com.fingerprint.v4.sdk.ApiException; import com.fingerprint.v4.sdk.Region; @@ -232,8 +236,10 @@ Smart Signals not activated for your workspace or are not included in the respon ```java package main; +import java.util.*; + import com.fingerprint.v4.api.FingerprintApi; -import com.fingerprint.v4.sdk.model.*; +import com.fingerprint.v4.model.*; import com.fingerprint.v4.sdk.ApiClient; import com.fingerprint.v4.sdk.ApiException; import com.fingerprint.v4.sdk.Region; @@ -438,8 +444,10 @@ error (HTTP 409 Conflict. The event is not mutable yet.) as the event is fully p ```java package main; +import java.util.*; + import com.fingerprint.v4.api.FingerprintApi; -import com.fingerprint.v4.sdk.model.*; +import com.fingerprint.v4.model.*; import com.fingerprint.v4.sdk.ApiClient; import com.fingerprint.v4.sdk.ApiException; import com.fingerprint.v4.sdk.Region; diff --git a/template/libraries/jersey3/api_doc.mustache b/template/libraries/jersey3/api_doc.mustache index d62e10ff..6a770e34 100644 --- a/template/libraries/jersey3/api_doc.mustache +++ b/template/libraries/jersey3/api_doc.mustache @@ -25,8 +25,10 @@ All URIs are relative to *{{basePath}}* ```java package main; +import java.util.*; + import com.fingerprint.v4.api.FingerprintApi; -import com.fingerprint.v4.sdk.model.*; +import com.fingerprint.v4.model.*; import com.fingerprint.v4.sdk.ApiClient; import com.fingerprint.v4.sdk.ApiException; import com.fingerprint.v4.sdk.Region; From f11b4a3374814dfeaa8f6359309bebd8dacbe199 Mon Sep 17 00:00:00 2001 From: Dan McNulty Date: Thu, 26 Feb 2026 15:31:25 -0600 Subject: [PATCH 10/13] chore: add Java 25 GH workflow matrices - Rework the Gradle build to allow the Java compiler and launcher version to be specified with a command line property. This allows Gradle 8 to use Java 25 without using it for execution (which it doesn't support). - Add logging to the Gradle build to log what Java version is used to confirm the correct version of Java is used for compiling and running tests. - Add `--stacktrace` flag to gradlew builds to improve logs when builds fail. --- .github/workflows/functional.yml | 9 +++---- .github/workflows/test.yml | 13 +++------- build.gradle.kts | 41 ++++++++++++++++++++++++++++++++ examples/examples.gradle.kts | 13 +++++++--- sdk/sdk.gradle.kts | 18 +++++++------- 5 files changed, 65 insertions(+), 29 deletions(-) diff --git a/.github/workflows/functional.yml b/.github/workflows/functional.yml index a3b7a750..14e7cc57 100644 --- a/.github/workflows/functional.yml +++ b/.github/workflows/functional.yml @@ -20,17 +20,14 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java: [ '11', '17', '21' ] + java: [ '11', '17', '21', '25' ] steps: - uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} - - uses: actions/setup-java@v4 - with: - distribution: 'zulu' - java-version: '${{ matrix.java }}' - - run: ./gradlew runFunctionalTests + # Gradle will detect the JVM installation from the ubuntu-latest image and use it + - run: ./gradlew runFunctionalTests -PjavaVersion=${{ matrix.java }} --stacktrace env: FPJS_API_SECRET: "${{ secrets.FPJS_API_SECRET }}" FPJS_API_REGION: "${{ secrets.FPJS_API_REGION }}" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8bb2b92d..18893e88 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,10 +11,6 @@ jobs: name: Check source code formatting steps: - uses: actions/checkout@v4 - - uses: actions/setup-java@v4 - with: - distribution: 'zulu' - java-version: 17 - run: ./gradlew :sdk:checkFormat :examples:checkFormat --continue test: @@ -22,13 +18,10 @@ jobs: name: Java ${{ matrix.Java }} strategy: matrix: - java: [ '11', '17', '21' ] + java: [ '11', '17', '21', '25' ] steps: - uses: actions/checkout@v4 - - uses: actions/setup-java@v4 - with: - distribution: 'zulu' - java-version: '${{ matrix.java }}' + # Gradle will detect the JVM installation from the ubuntu-latest image and use it # includes execution of unit tests - - run: ./gradlew build + - run: ./gradlew build -PjavaVersion=${{ matrix.java }} --stacktrace diff --git a/build.gradle.kts b/build.gradle.kts index e618b855..be3b220c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -92,4 +92,45 @@ fun Project.registerFormatTasks() { subprojects { registerFormatTasks() + + plugins.withType { + tasks.withType { + // Target java 11 + options.release = 11 + + // Enable all warnings and fail the build for any warnings + options.compilerArgs.addAll(listOf("-Xlint:all", "-Werror")) + } + + // Print Java toolchain for compilation + tasks.withType().configureEach { + doFirst { + println("Compiling with Java: ${javaCompiler.get().metadata.installationPath} (version: ${javaCompiler.get().metadata.languageVersion})") + } + } + + // Print Java toolchain used for running tests + tasks.withType().configureEach { + doFirst { + val launcher = javaLauncher.orNull + if (launcher != null) { + println("Testing with Java: ${launcher.metadata.installationPath} (version: ${launcher.metadata.languageVersion})") + } else { + println("Testing with Java: launcher not set") + } + } + } + + // Print Java toolchain used for running + tasks.withType().configureEach { + doFirst { + val launcher = javaLauncher.orNull + if (launcher != null) { + println("Running JavaExec with Java: ${launcher.metadata.installationPath} (version: ${launcher.metadata.languageVersion})") + } else { + println("Running JavaExec with Java: launcher not set") + } + } + } + } } diff --git a/examples/examples.gradle.kts b/examples/examples.gradle.kts index 0614501e..2227d930 100644 --- a/examples/examples.gradle.kts +++ b/examples/examples.gradle.kts @@ -9,9 +9,11 @@ plugins { } java { - // Target the earliest support Java version - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + toolchain { + languageVersion.set(JavaLanguageVersion.of( + findProperty("javaVersion")?.toString() ?: "11" + )) + } } repositories { @@ -54,6 +56,11 @@ tasks.register("runFunctionalTests") { classpath = sourceSets["main"].runtimeClasspath environment(loadEnv()) jvmArgs("-ea") + javaLauncher.set( + javaToolchains.launcherFor { + languageVersion.set(JavaLanguageVersion.of(findProperty("javaVersion")?.toString() ?: "11")) + } + ) } tasks.named("runFunctionalTests") { diff --git a/sdk/sdk.gradle.kts b/sdk/sdk.gradle.kts index 6d09b36d..5eef752a 100644 --- a/sdk/sdk.gradle.kts +++ b/sdk/sdk.gradle.kts @@ -4,22 +4,20 @@ val projectVersion: String by project group = "com.github.fingerprintjs" version = projectVersion -java { - // Target the earliest support Java version - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 -} - -tasks.withType { - options.compilerArgs.addAll(listOf("-Xlint:all", "-Werror")) -} - plugins { alias(libs.plugins.openapi.generator) `java-library` `maven-publish` } +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of( + findProperty("javaVersion")?.toString() ?: "11" + )) + } +} + repositories { mavenLocal() mavenCentral() From d998704d388bcbfde64bfa7009d55b6d19b1d99c Mon Sep 17 00:00:00 2001 From: Dan McNulty Date: Thu, 26 Feb 2026 18:22:02 -0600 Subject: [PATCH 11/13] fix: make enum value deserialization forward compatible - Configure openapi-generator with `enumUnknownDefaultCase` option. This adds an unused enum value to the template data/hash that can be used to substitute a placeholder value for all enums. - Update templates to add a `UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED` value that is used when deserializing an enum value that is unknown to the SDK. This allows events with new enum values to be deserialized without failing deserialization. - Add unit tests to confirm enum value deserialization is forward compatible --- docs/BotInfo.md | 2 + docs/BotResult.md | 2 + docs/ErrorCode.md | 2 + docs/Proximity.md | 1 + docs/ProxyConfidence.md | 2 + docs/ProxyDetails.md | 1 + docs/RuleActionType.md | 2 + docs/SDK.md | 1 + docs/SearchEventsBot.md | 2 + docs/SearchEventsSdkPlatform.md | 2 + docs/SearchEventsVpnConfidence.md | 2 + docs/VpnConfidence.md | 2 + sdk/sdk.gradle.kts | 1 + .../com/fingerprint/v4/model/BotInfo.java | 12 ++-- .../com/fingerprint/v4/model/BotResult.java | 6 +- .../com/fingerprint/v4/model/ErrorCode.java | 6 +- .../com/fingerprint/v4/model/Proximity.java | 6 +- .../fingerprint/v4/model/ProxyConfidence.java | 6 +- .../fingerprint/v4/model/ProxyDetails.java | 6 +- .../fingerprint/v4/model/RuleActionType.java | 6 +- .../java/com/fingerprint/v4/model/SDK.java | 6 +- .../fingerprint/v4/model/SearchEventsBot.java | 6 +- .../v4/model/SearchEventsSdkPlatform.java | 6 +- .../v4/model/SearchEventsVpnConfidence.java | 6 +- .../fingerprint/v4/model/VpnConfidence.java | 6 +- .../com/fingerprint/v4/SerializationTest.java | 56 +++++++++++++++++++ template/enum_outer_doc.mustache | 2 +- template/modelEnum.mustache | 6 +- template/modelInnerEnum.mustache | 6 +- template/pojo_doc.mustache | 2 +- 30 files changed, 138 insertions(+), 34 deletions(-) diff --git a/docs/BotInfo.md b/docs/BotInfo.md index 3464090e..86e33c55 100644 --- a/docs/BotInfo.md +++ b/docs/BotInfo.md @@ -25,6 +25,7 @@ Extended bot information. | SIGNED | "signed" | | SPOOFED | "spoofed" | | UNKNOWN | "unknown" | +| UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED | "unsupported_value_sdk_upgrade_required" | @@ -35,6 +36,7 @@ Extended bot information. | LOW | "low" | | MEDIUM | "medium" | | HIGH | "high" | +| UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED | "unsupported_value_sdk_upgrade_required" | diff --git a/docs/BotResult.md b/docs/BotResult.md index cdbc0a41..736a0de5 100644 --- a/docs/BotResult.md +++ b/docs/BotResult.md @@ -16,5 +16,7 @@ Bot detection result: * `NOT_DETECTED` (value: `"not_detected"`) +* `UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED` (value: `"unsupported_value_sdk_upgrade_required"`) + diff --git a/docs/ErrorCode.md b/docs/ErrorCode.md index 6d22a8ce..bcad66b0 100644 --- a/docs/ErrorCode.md +++ b/docs/ErrorCode.md @@ -66,5 +66,7 @@ Error code: * `RULESET_NOT_FOUND` (value: `"ruleset_not_found"`) +* `UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED` (value: `"unsupported_value_sdk_upgrade_required"`) + diff --git a/docs/Proximity.md b/docs/Proximity.md index b9a3126f..972576a9 100644 --- a/docs/Proximity.md +++ b/docs/Proximity.md @@ -27,6 +27,7 @@ Proximity ID represents a fixed geographical zone in a discrete global grid with | NUMBER_3300 | 3300 | | NUMBER_8500 | 8500 | | NUMBER_22500 | 22500 | +| UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED | Integer.MAX_VALUE | diff --git a/docs/ProxyConfidence.md b/docs/ProxyConfidence.md index f3329de5..d5ad4e49 100644 --- a/docs/ProxyConfidence.md +++ b/docs/ProxyConfidence.md @@ -13,5 +13,7 @@ Confidence level of the proxy detection. If a proxy is not detected, confidence * `HIGH` (value: `"high"`) +* `UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED` (value: `"unsupported_value_sdk_upgrade_required"`) + diff --git a/docs/ProxyDetails.md b/docs/ProxyDetails.md index 8e1876f3..85a60f19 100644 --- a/docs/ProxyDetails.md +++ b/docs/ProxyDetails.md @@ -20,6 +20,7 @@ Proxy detection details (present if `proxy` is `true`) |---- | -----| | RESIDENTIAL | "residential" | | DATA_CENTER | "data_center" | +| UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED | "unsupported_value_sdk_upgrade_required" | diff --git a/docs/RuleActionType.md b/docs/RuleActionType.md index 4257e1ae..99978996 100644 --- a/docs/RuleActionType.md +++ b/docs/RuleActionType.md @@ -10,5 +10,7 @@ Describes the action to take with the request. * `BLOCK` (value: `"block"`) +* `UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED` (value: `"unsupported_value_sdk_upgrade_required"`) + diff --git a/docs/SDK.md b/docs/SDK.md index 3a42f60b..3a902aef 100644 --- a/docs/SDK.md +++ b/docs/SDK.md @@ -22,6 +22,7 @@ Contains information about the SDK used to perform the request. | ANDROID | "android" | | IOS | "ios" | | UNKNOWN | "unknown" | +| UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED | "unsupported_value_sdk_upgrade_required" | diff --git a/docs/SearchEventsBot.md b/docs/SearchEventsBot.md index 74c0ce63..ee96b431 100644 --- a/docs/SearchEventsBot.md +++ b/docs/SearchEventsBot.md @@ -20,5 +20,7 @@ Filter events by the Bot Detection result, specifically: * `NONE` (value: `"none"`) +* `UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED` (value: `"unsupported_value_sdk_upgrade_required"`) + diff --git a/docs/SearchEventsSdkPlatform.md b/docs/SearchEventsSdkPlatform.md index f183a343..20316e1a 100644 --- a/docs/SearchEventsSdkPlatform.md +++ b/docs/SearchEventsSdkPlatform.md @@ -16,5 +16,7 @@ Filter events by the SDK Platform associated with the identification event (`sdk * `IOS` (value: `"ios"`) +* `UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED` (value: `"unsupported_value_sdk_upgrade_required"`) + diff --git a/docs/SearchEventsVpnConfidence.md b/docs/SearchEventsVpnConfidence.md index 3ad6e805..8fd1b8c7 100644 --- a/docs/SearchEventsVpnConfidence.md +++ b/docs/SearchEventsVpnConfidence.md @@ -17,5 +17,7 @@ Filter events by VPN Detection result confidence level. * `LOW` (value: `"low"`) +* `UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED` (value: `"unsupported_value_sdk_upgrade_required"`) + diff --git a/docs/VpnConfidence.md b/docs/VpnConfidence.md index f0d0d9ef..1495ee5e 100644 --- a/docs/VpnConfidence.md +++ b/docs/VpnConfidence.md @@ -12,5 +12,7 @@ A confidence rating for the VPN detection result — "low", "medium", or "high". * `HIGH` (value: `"high"`) +* `UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED` (value: `"unsupported_value_sdk_upgrade_required"`) + diff --git a/sdk/sdk.gradle.kts b/sdk/sdk.gradle.kts index 5eef752a..77a58d65 100644 --- a/sdk/sdk.gradle.kts +++ b/sdk/sdk.gradle.kts @@ -80,6 +80,7 @@ openApiGenerate { configOptions.put("openApiNullable", "false") configOptions.put("disallowAdditionalPropertiesIfNotPresent", "false") configOptions.put("useOneOfInterfaces", "true") + configOptions.put("enumUnknownDefaultCase", "true") } tasks.register("removeDocs") { diff --git a/sdk/src/main/java/com/fingerprint/v4/model/BotInfo.java b/sdk/src/main/java/com/fingerprint/v4/model/BotInfo.java index 10ed401c..9e6e359f 100644 --- a/sdk/src/main/java/com/fingerprint/v4/model/BotInfo.java +++ b/sdk/src/main/java/com/fingerprint/v4/model/BotInfo.java @@ -56,7 +56,9 @@ public enum IdentityEnum { SPOOFED(String.valueOf("spoofed")), - UNKNOWN(String.valueOf("unknown")); + UNKNOWN(String.valueOf("unknown")), + + UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED("unsupported_value_sdk_upgrade_required"); private String value; @@ -81,7 +83,7 @@ public static IdentityEnum fromValue(String value) { return b; } } - throw new IllegalArgumentException("Unexpected value '" + value + "'"); + return UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED; } } @@ -96,7 +98,9 @@ public enum ConfidenceEnum { MEDIUM(String.valueOf("medium")), - HIGH(String.valueOf("high")); + HIGH(String.valueOf("high")), + + UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED("unsupported_value_sdk_upgrade_required"); private String value; @@ -121,7 +125,7 @@ public static ConfidenceEnum fromValue(String value) { return b; } } - throw new IllegalArgumentException("Unexpected value '" + value + "'"); + return UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED; } } diff --git a/sdk/src/main/java/com/fingerprint/v4/model/BotResult.java b/sdk/src/main/java/com/fingerprint/v4/model/BotResult.java index 1d6a4697..6fc37756 100644 --- a/sdk/src/main/java/com/fingerprint/v4/model/BotResult.java +++ b/sdk/src/main/java/com/fingerprint/v4/model/BotResult.java @@ -23,7 +23,9 @@ public enum BotResult { GOOD("good"), - NOT_DETECTED("not_detected"); + NOT_DETECTED("not_detected"), + + UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED("unsupported_value_sdk_upgrade_required"); private String value; @@ -48,6 +50,6 @@ public static BotResult fromValue(String value) { return b; } } - throw new IllegalArgumentException("Unexpected value '" + value + "'"); + return UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED; } } diff --git a/sdk/src/main/java/com/fingerprint/v4/model/ErrorCode.java b/sdk/src/main/java/com/fingerprint/v4/model/ErrorCode.java index 5f2888de..384100db 100644 --- a/sdk/src/main/java/com/fingerprint/v4/model/ErrorCode.java +++ b/sdk/src/main/java/com/fingerprint/v4/model/ErrorCode.java @@ -53,7 +53,9 @@ public enum ErrorCode { SERVICE_UNAVAILABLE("service_unavailable"), - RULESET_NOT_FOUND("ruleset_not_found"); + RULESET_NOT_FOUND("ruleset_not_found"), + + UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED("unsupported_value_sdk_upgrade_required"); private String value; @@ -78,6 +80,6 @@ public static ErrorCode fromValue(String value) { return b; } } - throw new IllegalArgumentException("Unexpected value '" + value + "'"); + return UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED; } } diff --git a/sdk/src/main/java/com/fingerprint/v4/model/Proximity.java b/sdk/src/main/java/com/fingerprint/v4/model/Proximity.java index 5ac8b254..95e7b643 100644 --- a/sdk/src/main/java/com/fingerprint/v4/model/Proximity.java +++ b/sdk/src/main/java/com/fingerprint/v4/model/Proximity.java @@ -54,7 +54,9 @@ public enum PrecisionRadiusEnum { NUMBER_8500(Integer.valueOf(8500)), - NUMBER_22500(Integer.valueOf(22500)); + NUMBER_22500(Integer.valueOf(22500)), + + UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED(Integer.MAX_VALUE); private Integer value; @@ -79,7 +81,7 @@ public static PrecisionRadiusEnum fromValue(Integer value) { return b; } } - throw new IllegalArgumentException("Unexpected value '" + value + "'"); + return UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED; } } diff --git a/sdk/src/main/java/com/fingerprint/v4/model/ProxyConfidence.java b/sdk/src/main/java/com/fingerprint/v4/model/ProxyConfidence.java index 00c8cd1e..4e076d75 100644 --- a/sdk/src/main/java/com/fingerprint/v4/model/ProxyConfidence.java +++ b/sdk/src/main/java/com/fingerprint/v4/model/ProxyConfidence.java @@ -23,7 +23,9 @@ public enum ProxyConfidence { MEDIUM("medium"), - HIGH("high"); + HIGH("high"), + + UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED("unsupported_value_sdk_upgrade_required"); private String value; @@ -48,6 +50,6 @@ public static ProxyConfidence fromValue(String value) { return b; } } - throw new IllegalArgumentException("Unexpected value '" + value + "'"); + return UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED; } } diff --git a/sdk/src/main/java/com/fingerprint/v4/model/ProxyDetails.java b/sdk/src/main/java/com/fingerprint/v4/model/ProxyDetails.java index 96687e6b..d4d7e484 100644 --- a/sdk/src/main/java/com/fingerprint/v4/model/ProxyDetails.java +++ b/sdk/src/main/java/com/fingerprint/v4/model/ProxyDetails.java @@ -37,7 +37,9 @@ public class ProxyDetails { public enum ProxyTypeEnum { RESIDENTIAL(String.valueOf("residential")), - DATA_CENTER(String.valueOf("data_center")); + DATA_CENTER(String.valueOf("data_center")), + + UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED("unsupported_value_sdk_upgrade_required"); private String value; @@ -62,7 +64,7 @@ public static ProxyTypeEnum fromValue(String value) { return b; } } - throw new IllegalArgumentException("Unexpected value '" + value + "'"); + return UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED; } } diff --git a/sdk/src/main/java/com/fingerprint/v4/model/RuleActionType.java b/sdk/src/main/java/com/fingerprint/v4/model/RuleActionType.java index 335da2d7..c89f8720 100644 --- a/sdk/src/main/java/com/fingerprint/v4/model/RuleActionType.java +++ b/sdk/src/main/java/com/fingerprint/v4/model/RuleActionType.java @@ -21,7 +21,9 @@ public enum RuleActionType { ALLOW("allow"), - BLOCK("block"); + BLOCK("block"), + + UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED("unsupported_value_sdk_upgrade_required"); private String value; @@ -46,6 +48,6 @@ public static RuleActionType fromValue(String value) { return b; } } - throw new IllegalArgumentException("Unexpected value '" + value + "'"); + return UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED; } } diff --git a/sdk/src/main/java/com/fingerprint/v4/model/SDK.java b/sdk/src/main/java/com/fingerprint/v4/model/SDK.java index f3265b5a..52f51f84 100644 --- a/sdk/src/main/java/com/fingerprint/v4/model/SDK.java +++ b/sdk/src/main/java/com/fingerprint/v4/model/SDK.java @@ -43,7 +43,9 @@ public enum PlatformEnum { IOS(String.valueOf("ios")), - UNKNOWN(String.valueOf("unknown")); + UNKNOWN(String.valueOf("unknown")), + + UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED("unsupported_value_sdk_upgrade_required"); private String value; @@ -68,7 +70,7 @@ public static PlatformEnum fromValue(String value) { return b; } } - throw new IllegalArgumentException("Unexpected value '" + value + "'"); + return UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED; } } diff --git a/sdk/src/main/java/com/fingerprint/v4/model/SearchEventsBot.java b/sdk/src/main/java/com/fingerprint/v4/model/SearchEventsBot.java index 3d76d6f8..6663be5f 100644 --- a/sdk/src/main/java/com/fingerprint/v4/model/SearchEventsBot.java +++ b/sdk/src/main/java/com/fingerprint/v4/model/SearchEventsBot.java @@ -25,7 +25,9 @@ public enum SearchEventsBot { BAD("bad"), - NONE("none"); + NONE("none"), + + UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED("unsupported_value_sdk_upgrade_required"); private String value; @@ -50,6 +52,6 @@ public static SearchEventsBot fromValue(String value) { return b; } } - throw new IllegalArgumentException("Unexpected value '" + value + "'"); + return UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED; } } diff --git a/sdk/src/main/java/com/fingerprint/v4/model/SearchEventsSdkPlatform.java b/sdk/src/main/java/com/fingerprint/v4/model/SearchEventsSdkPlatform.java index ec655caa..1e8fa911 100644 --- a/sdk/src/main/java/com/fingerprint/v4/model/SearchEventsSdkPlatform.java +++ b/sdk/src/main/java/com/fingerprint/v4/model/SearchEventsSdkPlatform.java @@ -23,7 +23,9 @@ public enum SearchEventsSdkPlatform { ANDROID("android"), - IOS("ios"); + IOS("ios"), + + UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED("unsupported_value_sdk_upgrade_required"); private String value; @@ -48,6 +50,6 @@ public static SearchEventsSdkPlatform fromValue(String value) { return b; } } - throw new IllegalArgumentException("Unexpected value '" + value + "'"); + return UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED; } } diff --git a/sdk/src/main/java/com/fingerprint/v4/model/SearchEventsVpnConfidence.java b/sdk/src/main/java/com/fingerprint/v4/model/SearchEventsVpnConfidence.java index 61d27e62..556e2d51 100644 --- a/sdk/src/main/java/com/fingerprint/v4/model/SearchEventsVpnConfidence.java +++ b/sdk/src/main/java/com/fingerprint/v4/model/SearchEventsVpnConfidence.java @@ -23,7 +23,9 @@ public enum SearchEventsVpnConfidence { MEDIUM("medium"), - LOW("low"); + LOW("low"), + + UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED("unsupported_value_sdk_upgrade_required"); private String value; @@ -48,6 +50,6 @@ public static SearchEventsVpnConfidence fromValue(String value) { return b; } } - throw new IllegalArgumentException("Unexpected value '" + value + "'"); + return UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED; } } diff --git a/sdk/src/main/java/com/fingerprint/v4/model/VpnConfidence.java b/sdk/src/main/java/com/fingerprint/v4/model/VpnConfidence.java index e68cc3b5..f76aec4c 100644 --- a/sdk/src/main/java/com/fingerprint/v4/model/VpnConfidence.java +++ b/sdk/src/main/java/com/fingerprint/v4/model/VpnConfidence.java @@ -23,7 +23,9 @@ public enum VpnConfidence { MEDIUM("medium"), - HIGH("high"); + HIGH("high"), + + UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED("unsupported_value_sdk_upgrade_required"); private String value; @@ -48,6 +50,6 @@ public static VpnConfidence fromValue(String value) { return b; } } - throw new IllegalArgumentException("Unexpected value '" + value + "'"); + return UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED; } } diff --git a/sdk/src/test/java/com/fingerprint/v4/SerializationTest.java b/sdk/src/test/java/com/fingerprint/v4/SerializationTest.java index 1218e615..447363ad 100644 --- a/sdk/src/test/java/com/fingerprint/v4/SerializationTest.java +++ b/sdk/src/test/java/com/fingerprint/v4/SerializationTest.java @@ -1,9 +1,14 @@ package com.fingerprint.v4; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fingerprint.v4.model.Event; +import com.fingerprint.v4.model.EventRuleAction; import com.fingerprint.v4.sdk.JSON; import java.io.IOException; import java.io.InputStream; @@ -40,4 +45,55 @@ public void deserializeSerializeEvent() throws IOException { ObjectMapper springLikeObjectMapper = getMapper(); springLikeObjectMapper.writeValueAsString(event); } + + @Test + public void deserializeEventWithUnknownBotResultValue() throws IOException { + ObjectMapper sdkObjectMapper = JSON.getDefault().getMapper(); + + ObjectNode eventNode = + sdkObjectMapper.readValue( + getFileAsIOStream("mocks/events/get_event_200.json"), ObjectNode.class); + + // Set bot to an unknown enum value + eventNode.put("bot", "unknown_future_value"); + + assertDoesNotThrow( + () -> { + // Convert the modified ObjectNode back to an Event object to test deserialization + sdkObjectMapper.treeToValue(eventNode, Event.class); + }); + } + + @Test + public void deserializeEventWithUnknownSdkPlatformValue() throws IOException { + ObjectMapper sdkObjectMapper = JSON.getDefault().getMapper(); + + ObjectNode eventNode = + sdkObjectMapper.readValue( + getFileAsIOStream("mocks/events/get_event_200.json"), ObjectNode.class); + + eventNode.withObject("/sdk").put("platform", "unknown_future_value"); + + assertDoesNotThrow( + () -> { + // Convert the modified ObjectNode back to an Event object to test deserialization + sdkObjectMapper.treeToValue(eventNode, Event.class); + }); + } + + @Test + public void deserializeEventWithUnknownRuleActionTypeValue() throws IOException { + ObjectMapper sdkObjectMapper = JSON.getDefault().getMapper(); + + ObjectNode eventNode = + sdkObjectMapper.readValue( + getFileAsIOStream("mocks/events/get_event_ruleset_200.json"), ObjectNode.class); + + eventNode.withObject("/rule_action").put("type", "unknown_future_value"); + + // Convert the modified ObjectNode back to an Event object to test deserialization + Event event = sdkObjectMapper.treeToValue(eventNode, Event.class); + + assertInstanceOf(EventRuleAction.UnknownEventRuleAction.class, event.getRuleAction()); + } } diff --git a/template/enum_outer_doc.mustache b/template/enum_outer_doc.mustache index 11dbee32..46fdca76 100644 --- a/template/enum_outer_doc.mustache +++ b/template/enum_outer_doc.mustache @@ -6,5 +6,5 @@ ## Enum {{#allowableValues}}{{#enumVars}} -* `{{name}}` (value: `{{{value}}}`) +{{^-last}}* `{{name}}` (value: `{{{value}}}`){{/-last}}{{#-last}}* `UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED` (value: `{{#isString}}"unsupported_value_sdk_upgrade_required"{{/isString}}{{#isInteger}}Integer.MAX_VALUE{{/isInteger}}`){{/-last}} {{/enumVars}}{{/allowableValues}} diff --git a/template/modelEnum.mustache b/template/modelEnum.mustache index 8c1adb89..7c47f2cb 100644 --- a/template/modelEnum.mustache +++ b/template/modelEnum.mustache @@ -34,8 +34,8 @@ import java.util.Locale; {{#withXml}} @XmlEnumValue({{#isInteger}}"{{/isInteger}}{{#isDouble}}"{{/isDouble}}{{#isLong}}"{{/isLong}}{{#isFloat}}"{{/isFloat}}{{{value}}}{{#isInteger}}"{{/isInteger}}{{#isDouble}}"{{/isDouble}}{{#isLong}}"{{/isLong}}{{#isFloat}}"{{/isFloat}}) {{/withXml}} - {{{name}}}({{{value}}}){{^-last}}, - {{/-last}}{{#-last}};{{/-last}}{{/enumVars}}{{/allowableValues}} + {{^-last}}{{{name}}}({{{value}}}),{{/-last}} + {{#-last}}UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED({{#isString}}"unsupported_value_sdk_upgrade_required"{{/isString}}{{#isInteger}}Integer.MAX_VALUE{{/isInteger}});{{/-last}}{{/enumVars}}{{/allowableValues}} private {{{dataType}}} value; @@ -64,7 +64,7 @@ import java.util.Locale; return b; } } - {{#isNullable}}return null;{{/isNullable}}{{^isNullable}}{{#enumUnknownDefaultCase}}{{#allowableValues}}{{#enumVars}}{{#-last}}return {{{name}}};{{/-last}}{{/enumVars}}{{/allowableValues}}{{/enumUnknownDefaultCase}}{{^enumUnknownDefaultCase}}throw new IllegalArgumentException("Unexpected value '" + value + "'");{{/enumUnknownDefaultCase}}{{/isNullable}} + {{#isNullable}}return null;{{/isNullable}}{{^isNullable}}{{#enumUnknownDefaultCase}}return UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED;{{/enumUnknownDefaultCase}}{{^enumUnknownDefaultCase}}throw new IllegalArgumentException("Unexpected value '" + value + "'");{{/enumUnknownDefaultCase}}{{/isNullable}} } {{#gson}} diff --git a/template/modelInnerEnum.mustache b/template/modelInnerEnum.mustache index 17825230..7e7cc97a 100644 --- a/template/modelInnerEnum.mustache +++ b/template/modelInnerEnum.mustache @@ -23,8 +23,8 @@ {{#withXml}} @XmlEnumValue({{#isInteger}}"{{/isInteger}}{{#isDouble}}"{{/isDouble}}{{#isLong}}"{{/isLong}}{{#isFloat}}"{{/isFloat}}{{{value}}}{{#isInteger}}"{{/isInteger}}{{#isDouble}}"{{/isDouble}}{{#isLong}}"{{/isLong}}{{#isFloat}}"{{/isFloat}}) {{/withXml}} - {{{name}}}({{^isUri}}{{dataType}}.valueOf({{/isUri}}{{{value}}}{{^isUri}}){{/isUri}}){{^-last}}, - {{/-last}}{{#-last}};{{/-last}} + {{^-last}}{{{name}}}({{^isUri}}{{dataType}}.valueOf({{/isUri}}{{{value}}}{{^isUri}}){{/isUri}}),{{/-last}} + {{#-last}}UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED({{#isString}}"unsupported_value_sdk_upgrade_required"{{/isString}}{{#isInteger}}Integer.MAX_VALUE{{/isInteger}});{{/-last}} {{/enumVars}} {{/allowableValues}} @@ -55,7 +55,7 @@ return b; } } - {{#isNullable}}return null;{{/isNullable}}{{^isNullable}}{{#enumUnknownDefaultCase}}{{#allowableValues}}{{#enumVars}}{{#-last}}return {{{name}}};{{/-last}}{{/enumVars}}{{/allowableValues}}{{/enumUnknownDefaultCase}}{{^enumUnknownDefaultCase}}throw new IllegalArgumentException("Unexpected value '" + value + "'");{{/enumUnknownDefaultCase}}{{/isNullable}} + {{#isNullable}}return null;{{/isNullable}}{{^isNullable}}{{#enumUnknownDefaultCase}}return UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED;{{/enumUnknownDefaultCase}}{{^enumUnknownDefaultCase}}throw new IllegalArgumentException("Unexpected value '" + value + "'");{{/enumUnknownDefaultCase}}{{/isNullable}} } {{#gson}} diff --git a/template/pojo_doc.mustache b/template/pojo_doc.mustache index ffb3528f..1598e6fb 100644 --- a/template/pojo_doc.mustache +++ b/template/pojo_doc.mustache @@ -17,7 +17,7 @@ | Name | Value | |---- | -----|{{#allowableValues}}{{#enumVars}} -| {{name}} | {{{value}}} |{{/enumVars}}{{/allowableValues}} +{{^-last}}| {{name}} | {{{value}}} |{{/-last}}{{#-last}}| UNSUPPORTED_VALUE_SDK_UPGRADE_REQUIRED | {{#isString}}"unsupported_value_sdk_upgrade_required"{{/isString}}{{#isInteger}}Integer.MAX_VALUE{{/isInteger}} |{{/-last}}{{/enumVars}}{{/allowableValues}} {{/isEnum}}{{/vars}} {{#vendorExtensions.x-implements.0}} From 4f461d4bfda31ca65d49bb40909ea9540a6d9279 Mon Sep 17 00:00:00 2001 From: Dan McNulty <212590662+mcnulty-fp@users.noreply.github.com> Date: Fri, 27 Feb 2026 08:19:59 -0600 Subject: [PATCH 12/13] chore: fix typo in java parameter name Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 18893e88..2631448b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,7 +15,7 @@ jobs: test: runs-on: ubuntu-latest - name: Java ${{ matrix.Java }} + name: Java ${{ matrix.java }} strategy: matrix: java: [ '11', '17', '21', '25' ] From 8b28246bd9168311dee8f593fcc3ba61f1665328 Mon Sep 17 00:00:00 2001 From: Dan McNulty <212590662+mcnulty-fp@users.noreply.github.com> Date: Fri, 27 Feb 2026 08:24:45 -0600 Subject: [PATCH 13/13] chore: fix typo in FingerprintApiTest Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../test/java/com/fingerprint/v4/api/FingerprintApiTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/src/test/java/com/fingerprint/v4/api/FingerprintApiTest.java b/sdk/src/test/java/com/fingerprint/v4/api/FingerprintApiTest.java index f5f35789..fdd3fe0e 100644 --- a/sdk/src/test/java/com/fingerprint/v4/api/FingerprintApiTest.java +++ b/sdk/src/test/java/com/fingerprint/v4/api/FingerprintApiTest.java @@ -232,8 +232,8 @@ public void updateEventLinkedIdRequest() throws ApiException { EventUpdate body = invocation.getArgument(4); assertEquals(LINKED_ID, body.getLinkedId()); - assertNotNull(request.getTags()); - assertTrue(request.getTags().isEmpty()); + assertNotNull(body.getTags()); + assertTrue(body.getTags().isEmpty()); assertNull(body.getSuspect()); return mockFileToResponse(200, invocation, null, Void.class); });