From 0377b2b1cef79e1d67b6427ef608ea00998bfaa2 Mon Sep 17 00:00:00 2001 From: Kim Gustyr Date: Tue, 28 Oct 2025 18:12:52 +0000 Subject: [PATCH] feat: Send a standard `User-Agent: sdk-name/version` header --- pom.xml | 12 ++++++++++ .../com/flagsmith/FlagsmithApiWrapper.java | 2 ++ src/main/java/com/flagsmith/Versions.java | 10 ++++++++ .../flagsmith/FlagsmithApiWrapperTest.java | 5 ++++ .../com/flagsmith/FlagsmithClientTest.java | 24 ++++++++++++++++++- 5 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/flagsmith/Versions.java diff --git a/pom.xml b/pom.xml index bafddad..a122840 100644 --- a/pom.xml +++ b/pom.xml @@ -188,6 +188,18 @@ flagsmith-java-client-${project.version} + + org.apache.maven.plugins + maven-jar-plugin + 3.3.0 + + + + true + + + + org.jsonschema2pojo jsonschema2pojo-maven-plugin diff --git a/src/main/java/com/flagsmith/FlagsmithApiWrapper.java b/src/main/java/com/flagsmith/FlagsmithApiWrapper.java index aa6b3f4..c1478f9 100644 --- a/src/main/java/com/flagsmith/FlagsmithApiWrapper.java +++ b/src/main/java/com/flagsmith/FlagsmithApiWrapper.java @@ -33,6 +33,7 @@ public class FlagsmithApiWrapper implements FlagsmithSdk { private static final String AUTH_HEADER = "X-Environment-Key"; + private static final String USER_AGENT_HEADER = "User-Agent"; private static final String ACCEPT_HEADER = "Accept"; private static final Integer TIMEOUT = 15000; @@ -300,6 +301,7 @@ public FlagsmithLogger getLogger() { private Request.Builder newRequestBuilder() { final Request.Builder builder = new Request.Builder() .header(AUTH_HEADER, apiKey) + .header(USER_AGENT_HEADER, "flagsmith-java-sdk/" + Versions.getVersion()) .addHeader(ACCEPT_HEADER, "application/json"); if (this.customHeaders != null && !this.customHeaders.isEmpty()) { diff --git a/src/main/java/com/flagsmith/Versions.java b/src/main/java/com/flagsmith/Versions.java new file mode 100644 index 0000000..df9f9b9 --- /dev/null +++ b/src/main/java/com/flagsmith/Versions.java @@ -0,0 +1,10 @@ +package com.flagsmith; + +public final class Versions { + private Versions() {} + + public static String getVersion() { + String version = Versions.class.getPackage().getImplementationVersion(); + return version != null ? version : "unknown"; + } +} diff --git a/src/test/java/com/flagsmith/FlagsmithApiWrapperTest.java b/src/test/java/com/flagsmith/FlagsmithApiWrapperTest.java index 7f6deb0..ad4df9f 100644 --- a/src/test/java/com/flagsmith/FlagsmithApiWrapperTest.java +++ b/src/test/java/com/flagsmith/FlagsmithApiWrapperTest.java @@ -30,6 +30,7 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.regex.Pattern; import com.flagsmith.threads.AnalyticsProcessor; import com.flagsmith.threads.RequestProcessor; @@ -92,6 +93,8 @@ public void getFeatureFlags_noUser_success() throws JsonProcessingException { // Arrange interceptor.addRule() .get(BASE_URL + "/flags/") + .headerMatches("X-Environment-Key", Pattern.compile(API_KEY)) + .headerMatches("User-Agent", Pattern.compile("flagsmith-java-sdk/.*")) .respond(mapper.writeValueAsString(Arrays.asList(getNewFlag())), MEDIATYPE_JSON); // Act @@ -128,6 +131,8 @@ public void identifyUserWithTraits_success() throws JsonProcessingException { String responseBody = mapper.writeValueAsString(getFlagsAndTraitsResponse(Arrays.asList(getNewFlag()), Arrays.asList(new TraitModel()))); interceptor.addRule() .post(BASE_URL + "/identities/") + .headerMatches("X-Environment-Key", Pattern.compile(API_KEY)) + .headerMatches("User-Agent", Pattern.compile("flagsmith-java-sdk/.*")) .respond(responseBody, MEDIATYPE_JSON); // Act diff --git a/src/test/java/com/flagsmith/FlagsmithClientTest.java b/src/test/java/com/flagsmith/FlagsmithClientTest.java index 3efe94b..53dc1bd 100644 --- a/src/test/java/com/flagsmith/FlagsmithClientTest.java +++ b/src/test/java/com/flagsmith/FlagsmithClientTest.java @@ -39,6 +39,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -125,6 +126,8 @@ public void testClient_errorEnvironmentApi() { interceptor.addRule() .get(baseUrl + "/environment-document/") + .headerMatches("X-Environment-Key", Pattern.compile("api-key")) + .headerMatches("User-Agent", Pattern.compile("flagsmith-java-sdk/.*")) .respond( 500, ResponseBody.create("error", MEDIATYPE_JSON)); @@ -162,6 +165,8 @@ public void testClient_validateEnvironment() interceptor.addRule() .get(baseUrl + "/environment-document/") + .headerMatches("X-Environment-Key", Pattern.compile("api-key")) + .headerMatches("User-Agent", Pattern.compile("flagsmith-java-sdk/.*")) .anyTimes() .respond( FlagsmithTestHelper.environmentString(), @@ -188,6 +193,8 @@ public void testClient_flagsApiException() interceptor.addRule() .get(baseUrl + "/flags/") + .headerMatches("X-Environment-Key", Pattern.compile("api-key")) + .headerMatches("User-Agent", Pattern.compile("flagsmith-java-sdk/.*")) .respond( 500, ResponseBody.create("error", MEDIATYPE_JSON)); @@ -211,6 +218,8 @@ public void testClient_flagsApiEmpty() interceptor.addRule() .get(baseUrl + "/flags/") + .headerMatches("X-Environment-Key", Pattern.compile("api-key")) + .headerMatches("User-Agent", Pattern.compile("flagsmith-java-sdk/.*")) .respond( "[]", MEDIATYPE_JSON); @@ -238,6 +247,8 @@ public void testClient_flagsApi() interceptor.addRule() .get(baseUrl + "/flags/") + .headerMatches("X-Environment-Key", Pattern.compile("api-key")) + .headerMatches("User-Agent", Pattern.compile("flagsmith-java-sdk/.*")) .respond( MapperFactory.getMapper().writeValueAsString(featureStateModel), MEDIATYPE_JSON); @@ -264,6 +275,8 @@ public void testClient_identityFlagsApiNoTraitsException() throws FlagsmithClien interceptor.addRule() .post(baseUrl + "/identities/") + .headerMatches("X-Environment-Key", Pattern.compile("api-key")) + .headerMatches("User-Agent", Pattern.compile("flagsmith-java-sdk/.*")) .respond( 500, ResponseBody.create("error", MEDIATYPE_JSON)); @@ -289,6 +302,8 @@ public void testClient_identityFlagsApiNoTraits() throws FlagsmithClientError { interceptor.addRule() .post(baseUrl + "/identities/") + .headerMatches("X-Environment-Key", Pattern.compile("api-key")) + .headerMatches("User-Agent", Pattern.compile("flagsmith-java-sdk/.*")) .respond( json, MEDIATYPE_JSON); @@ -407,7 +422,8 @@ public void testClient_identityFlagsApiWithTraitsWithLocalEnvironment() { .build(); interceptor.addRule() - .get(baseUrl + "/flags/").anyTimes() + .get(baseUrl + "/flags/") + .anyTimes() .respond(500, ResponseBody.create("error", MEDIATYPE_JSON)); assertThrows(FlagsmithApiError.class, @@ -443,6 +459,8 @@ public void testClient_defaultFlagWithNoEnvironment() throws FlagsmithClientErro interceptor.addRule() .get(baseUrl + "/flags/") + .headerMatches("X-Environment-Key", Pattern.compile("api-key")) + .headerMatches("User-Agent", Pattern.compile("flagsmith-java-sdk/.*")) .respond( "[]", MEDIATYPE_JSON); @@ -481,6 +499,8 @@ public void testGetIdentitySegmentsNoTraits() throws JsonProcessingException, MockInterceptor interceptor = new MockInterceptor(); interceptor.addRule() .get(baseUrl + "/environment-document/") + .headerMatches("X-Environment-Key", Pattern.compile("ser.abcdefg")) + .headerMatches("User-Agent", Pattern.compile("flagsmith-java-sdk/.*")) .anyTimes() .respond( MapperFactory.getMapper().writeValueAsString(environmentModel), @@ -514,6 +534,8 @@ public void testGetIdentitySegmentsWithValidTrait() throws JsonProcessingExcepti MockInterceptor interceptor = new MockInterceptor(); interceptor.addRule() .get(baseUrl + "/environment-document/") + .headerMatches("X-Environment-Key", Pattern.compile("ser.abcdefg")) + .headerMatches("User-Agent", Pattern.compile("flagsmith-java-sdk/.*")) .anyTimes() .respond( MapperFactory.getMapper().writeValueAsString(environmentModel),