diff --git a/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/GoFeatureFlagProvider.java b/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/GoFeatureFlagProvider.java index dacbb27e3..637e80191 100644 --- a/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/GoFeatureFlagProvider.java +++ b/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/GoFeatureFlagProvider.java @@ -1,8 +1,5 @@ package dev.openfeature.contrib.providers.gofeatureflag; -import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; -import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; - import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; @@ -16,6 +13,7 @@ import dev.openfeature.sdk.EvaluationContext; import dev.openfeature.sdk.FeatureProvider; import dev.openfeature.sdk.Hook; +import dev.openfeature.sdk.ImmutableMetadata; import dev.openfeature.sdk.Metadata; import dev.openfeature.sdk.MutableStructure; import dev.openfeature.sdk.ProviderEvaluation; @@ -34,6 +32,7 @@ import okhttp3.RequestBody; import okhttp3.Response; import okhttp3.ResponseBody; + import java.io.IOException; import java.time.Instant; import java.util.List; @@ -41,6 +40,9 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; +import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; + /** * GoFeatureFlagProvider is the JAVA provider implementation for the feature flag solution GO Feature Flag. */ @@ -226,7 +228,7 @@ private ProviderEvaluation resolveEvaluationGoFeatureFlagProxy( if (flagValue.getClass() != expectedType) { throw new TypeMismatchError("Flag value " + key + " had unexpected type " - + flagValue.getClass() + ", expected " + expectedType + "."); + + flagValue.getClass() + ", expected " + expectedType + "."); } return ProviderEvaluation.builder() @@ -234,6 +236,7 @@ private ProviderEvaluation resolveEvaluationGoFeatureFlagProxy( .reason(goffResp.getReason()) .value(flagValue) .variant(goffResp.getVariationType()) + .flagMetadata(this.convertFlagMetadata(goffResp.getMetadata())) .build(); } @@ -256,6 +259,32 @@ private ErrorCode mapErrorCode(String errorCode) { } } + /** + * convertFlagMetadata is converting the flagMetadata object received from the server + * to an ImmutableMetadata format known by Open Feature. + * + * @param flagMetadata - metadata received from the server + * @return a converted metadata object. + */ + private ImmutableMetadata convertFlagMetadata(Map flagMetadata) { + ImmutableMetadata.ImmutableMetadataBuilder builder = ImmutableMetadata.builder(); + flagMetadata.forEach((k, v) -> { + if (v instanceof Long) { + builder.addLong(k, (Long) v); + } else if (v instanceof Integer) { + builder.addInteger(k, (Integer) v); + } else if (v instanceof Float) { + builder.addFloat(k, (Float) v); + } else if (v instanceof Double) { + builder.addDouble(k, (Double) v); + } else if (v instanceof Boolean) { + builder.addBoolean(k, (Boolean) v); + } else { + builder.addString(k, v.toString()); + } + }); + return builder.build(); + } /** * convertValue is converting the object return by the proxy response in the right type. diff --git a/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/GoFeatureFlagProviderOptions.java b/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/GoFeatureFlagProviderOptions.java index 2956291df..470f0c02d 100644 --- a/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/GoFeatureFlagProviderOptions.java +++ b/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/GoFeatureFlagProviderOptions.java @@ -40,11 +40,11 @@ public class GoFeatureFlagProviderOptions { private Long keepAliveDuration; /** - * (optional) If the relay proxy is configured to authenticate the requests, you should provide - * an API Key to the provider. - * Please ask the administrator of the relay proxy to provide an API Key. - * (This feature is available only if you are using GO Feature Flag relay proxy v1.7.0 or above) - * Default: null + * (optional) If the relay proxy is configured to authenticate the requests, you should provide + * an API Key to the provider. + * Please ask the administrator of the relay proxy to provide an API Key. + * (This feature is available only if you are using GO Feature Flag relay proxy v1.7.0 or above) + * Default: null */ @Getter private String apiKey; diff --git a/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/bean/GoFeatureFlagResponse.java b/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/bean/GoFeatureFlagResponse.java index 75b229755..5c827cbad 100644 --- a/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/bean/GoFeatureFlagResponse.java +++ b/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/bean/GoFeatureFlagResponse.java @@ -4,6 +4,8 @@ import lombok.Setter; import lombok.ToString; +import java.util.Map; + /** * GoFeatureFlagResponse is the response returned by the relay proxy. */ @@ -18,6 +20,7 @@ public class GoFeatureFlagResponse { private String reason; private String errorCode; private Object value; + private Map metadata; } diff --git a/providers/go-feature-flag/src/test/java/dev/openfeature/contrib/providers/gofeatureflag/GoFeatureFlagProviderTest.java b/providers/go-feature-flag/src/test/java/dev/openfeature/contrib/providers/gofeatureflag/GoFeatureFlagProviderTest.java index 07cc8be88..82e8fb5fc 100644 --- a/providers/go-feature-flag/src/test/java/dev/openfeature/contrib/providers/gofeatureflag/GoFeatureFlagProviderTest.java +++ b/providers/go-feature-flag/src/test/java/dev/openfeature/contrib/providers/gofeatureflag/GoFeatureFlagProviderTest.java @@ -7,6 +7,12 @@ import java.util.Arrays; import java.util.List; +import dev.openfeature.sdk.ImmutableMetadata; +import dev.openfeature.sdk.MutableContext; +import dev.openfeature.sdk.MutableStructure; +import dev.openfeature.sdk.ProviderEvaluation; +import dev.openfeature.sdk.Reason; +import dev.openfeature.sdk.Value; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -14,11 +20,6 @@ import dev.openfeature.contrib.providers.gofeatureflag.exception.InvalidEndpoint; import dev.openfeature.contrib.providers.gofeatureflag.exception.InvalidOptions; import dev.openfeature.contrib.providers.gofeatureflag.exception.InvalidTargetingKey; -import dev.openfeature.sdk.MutableContext; -import dev.openfeature.sdk.MutableStructure; -import dev.openfeature.sdk.ProviderEvaluation; -import dev.openfeature.sdk.Reason; -import dev.openfeature.sdk.Value; import dev.openfeature.sdk.exceptions.FlagNotFoundError; import dev.openfeature.sdk.exceptions.GeneralError; import dev.openfeature.sdk.exceptions.TypeMismatchError; @@ -29,7 +30,11 @@ import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; class GoFeatureFlagProviderTest { // Dispatcher is the configuration of the mock server to test the provider. @@ -56,6 +61,12 @@ public MockResponse dispatch(RecordedRequest request) { private HttpUrl baseUrl; private MutableContext evaluationContext; + private final static ImmutableMetadata defaultMetadata = + ImmutableMetadata.builder() + .addString("pr_link", "https://github.com/thomaspoignant/go-feature-flag/pull/916") + .addInteger("version", 1) + .build(); + @BeforeEach void beforeEach() throws IOException { this.server = new MockWebServer(); @@ -153,6 +164,9 @@ void should_resolve_a_valid_boolean_flag_with_TARGETING_MATCH_reason() throws In assertNull(res.getErrorCode()); assertEquals(Reason.TARGETING_MATCH.toString(), res.getReason()); assertEquals("True", res.getVariant()); + assertAll("Test flagMetadata", + () -> assertEquals(defaultMetadata.getInteger("version"), res.getFlagMetadata().getInteger("version")), + () -> assertEquals(defaultMetadata.getString("pr_link"), res.getFlagMetadata().getString("pr_link"))); } @Test @@ -163,6 +177,9 @@ void should_return_custom_reason_if_returned_by_relay_proxy() throws InvalidOpti assertNull(res.getErrorCode()); assertEquals("CUSTOM_REASON", res.getReason()); assertEquals("True", res.getVariant()); + assertAll("Test flagMetadata", + () -> assertEquals(defaultMetadata.getInteger("version"), res.getFlagMetadata().getInteger("version")), + () -> assertEquals(defaultMetadata.getString("pr_link"), res.getFlagMetadata().getString("pr_link"))); } @Test @@ -187,6 +204,9 @@ void should_resolve_a_valid_string_flag_with_TARGETING_MATCH_reason() throws Inv assertNull(res.getErrorCode()); assertEquals(Reason.TARGETING_MATCH.toString(), res.getReason()); assertEquals("True", res.getVariant()); + assertAll("Test flagMetadata", + () -> assertEquals(defaultMetadata.getInteger("version"), res.getFlagMetadata().getInteger("version")), + () -> assertEquals(defaultMetadata.getString("pr_link"), res.getFlagMetadata().getString("pr_link"))); } @Test @@ -211,6 +231,9 @@ void should_resolve_a_valid_integer_flag_with_TARGETING_MATCH_reason() throws In assertNull(res.getErrorCode()); assertEquals(Reason.TARGETING_MATCH.toString(), res.getReason()); assertEquals("True", res.getVariant()); + assertAll("Test flagMetadata", + () -> assertEquals(defaultMetadata.getInteger("version"), res.getFlagMetadata().getInteger("version")), + () -> assertEquals(defaultMetadata.getString("pr_link"), res.getFlagMetadata().getString("pr_link"))); } @Test @@ -235,6 +258,9 @@ void should_resolve_a_valid_double_flag_with_TARGETING_MATCH_reason() throws Inv assertNull(res.getErrorCode()); assertEquals(Reason.TARGETING_MATCH.toString(), res.getReason()); assertEquals("True", res.getVariant()); + assertAll("Test flagMetadata", + () -> assertEquals(defaultMetadata.getInteger("version"), res.getFlagMetadata().getInteger("version")), + () -> assertEquals(defaultMetadata.getString("pr_link"), res.getFlagMetadata().getString("pr_link"))); } @Test @@ -254,6 +280,9 @@ void should_resolve_a_valid_value_flag_with_TARGETING_MATCH_reason() throws Inva assertNull(res.getErrorCode()); assertEquals(Reason.TARGETING_MATCH.toString(), res.getReason()); assertEquals("True", res.getVariant()); + assertAll("Test flagMetadata", + () -> assertEquals(defaultMetadata.getInteger("version"), res.getFlagMetadata().getInteger("version")), + () -> assertEquals(defaultMetadata.getString("pr_link"), res.getFlagMetadata().getString("pr_link"))); } @Test @@ -265,6 +294,9 @@ void should_wrap_into_value_if_wrong_type() throws InvalidOptions { assertNull(res.getErrorCode()); assertEquals(Reason.TARGETING_MATCH.toString(), res.getReason()); assertEquals("True", res.getVariant()); + assertAll("Test flagMetadata", + () -> assertEquals(defaultMetadata.getInteger("version"), res.getFlagMetadata().getInteger("version")), + () -> assertEquals(defaultMetadata.getString("pr_link"), res.getFlagMetadata().getString("pr_link"))); } @Test @@ -296,6 +328,9 @@ void should_throw_an_error_if_no_targeting_key() throws InvalidOptions { assertNull(res.getErrorCode()); assertEquals(Reason.TARGETING_MATCH.toString(), res.getReason()); assertEquals("True", res.getVariant()); + assertAll("Test flagMetadata", + () -> assertEquals(defaultMetadata.getInteger("version"), res.getFlagMetadata().getInteger("version")), + () -> assertEquals(defaultMetadata.getString("pr_link"), res.getFlagMetadata().getString("pr_link"))); } @Test @@ -306,6 +341,9 @@ void should_not_fail_if_receive_an_unknown_field_in_response() throws InvalidOpt assertNull(res.getErrorCode()); assertEquals(Reason.TARGETING_MATCH.toString(), res.getReason()); assertEquals("True", res.getVariant()); + assertAll("Test flagMetadata", + () -> assertEquals(defaultMetadata.getInteger("version"), res.getFlagMetadata().getInteger("version")), + () -> assertEquals(defaultMetadata.getString("pr_link"), res.getFlagMetadata().getString("pr_link"))); } private String readMockResponse(String filename) throws IOException { diff --git a/providers/go-feature-flag/src/test/resources/mock_responses/bool_targeting_match.json b/providers/go-feature-flag/src/test/resources/mock_responses/bool_targeting_match.json index dd20b510c..f38810f07 100644 --- a/providers/go-feature-flag/src/test/resources/mock_responses/bool_targeting_match.json +++ b/providers/go-feature-flag/src/test/resources/mock_responses/bool_targeting_match.json @@ -5,5 +5,9 @@ "version": 0, "reason": "TARGETING_MATCH", "errorCode": "", - "value": true + "value": true, + "metadata": { + "pr_link": "https://github.com/thomaspoignant/go-feature-flag/pull/916", + "version": 1 + } } diff --git a/providers/go-feature-flag/src/test/resources/mock_responses/disabled.json b/providers/go-feature-flag/src/test/resources/mock_responses/disabled.json index 85a9949d0..6a0c697cd 100644 --- a/providers/go-feature-flag/src/test/resources/mock_responses/disabled.json +++ b/providers/go-feature-flag/src/test/resources/mock_responses/disabled.json @@ -5,5 +5,9 @@ "version": 0, "reason": "DISABLED", "errorCode": "", - "value": true + "value": true, + "metadata": { + "pr_link": "https://github.com/thomaspoignant/go-feature-flag/pull/916", + "version": 1 + } } diff --git a/providers/go-feature-flag/src/test/resources/mock_responses/double_key.json b/providers/go-feature-flag/src/test/resources/mock_responses/double_key.json index d6bb81828..a22972072 100644 --- a/providers/go-feature-flag/src/test/resources/mock_responses/double_key.json +++ b/providers/go-feature-flag/src/test/resources/mock_responses/double_key.json @@ -5,5 +5,9 @@ "version": 0, "reason": "TARGETING_MATCH", "errorCode": "", - "value": 100.25 + "value": 100.25, + "metadata": { + "pr_link": "https://github.com/thomaspoignant/go-feature-flag/pull/916", + "version": 1 + } } diff --git a/providers/go-feature-flag/src/test/resources/mock_responses/flag_not_found.json b/providers/go-feature-flag/src/test/resources/mock_responses/flag_not_found.json index 209436b4f..12d3b6905 100644 --- a/providers/go-feature-flag/src/test/resources/mock_responses/flag_not_found.json +++ b/providers/go-feature-flag/src/test/resources/mock_responses/flag_not_found.json @@ -5,5 +5,9 @@ "version": 0, "reason": "ERROR", "errorCode": "FLAG_NOT_FOUND", - "value": "false" + "value": "false", + "metadata": { + "pr_link": "https://github.com/thomaspoignant/go-feature-flag/pull/916", + "version": 1 + } } diff --git a/providers/go-feature-flag/src/test/resources/mock_responses/integer_key.json b/providers/go-feature-flag/src/test/resources/mock_responses/integer_key.json index d6ddc655b..9c8c672b0 100644 --- a/providers/go-feature-flag/src/test/resources/mock_responses/integer_key.json +++ b/providers/go-feature-flag/src/test/resources/mock_responses/integer_key.json @@ -5,5 +5,9 @@ "version": 0, "reason": "TARGETING_MATCH", "errorCode": "", - "value": 100 + "value": 100, + "metadata": { + "pr_link": "https://github.com/thomaspoignant/go-feature-flag/pull/916", + "version": 1 + } } diff --git a/providers/go-feature-flag/src/test/resources/mock_responses/list_key.json b/providers/go-feature-flag/src/test/resources/mock_responses/list_key.json index 0e0e3965b..1a222d884 100644 --- a/providers/go-feature-flag/src/test/resources/mock_responses/list_key.json +++ b/providers/go-feature-flag/src/test/resources/mock_responses/list_key.json @@ -11,5 +11,9 @@ "test2", "false", "test3" - ] + ], + "metadata": { + "pr_link": "https://github.com/thomaspoignant/go-feature-flag/pull/916", + "version": 1 + } } diff --git a/providers/go-feature-flag/src/test/resources/mock_responses/object_key.json b/providers/go-feature-flag/src/test/resources/mock_responses/object_key.json index 27db6711b..d12270ea1 100644 --- a/providers/go-feature-flag/src/test/resources/mock_responses/object_key.json +++ b/providers/go-feature-flag/src/test/resources/mock_responses/object_key.json @@ -11,5 +11,9 @@ "test3": 123.3, "test4": 1, "test5": null + }, + "metadata": { + "pr_link": "https://github.com/thomaspoignant/go-feature-flag/pull/916", + "version": 1 } } diff --git a/providers/go-feature-flag/src/test/resources/mock_responses/string_key.json b/providers/go-feature-flag/src/test/resources/mock_responses/string_key.json index 53683df0a..b53babd74 100644 --- a/providers/go-feature-flag/src/test/resources/mock_responses/string_key.json +++ b/providers/go-feature-flag/src/test/resources/mock_responses/string_key.json @@ -5,5 +5,9 @@ "version": 0, "reason": "TARGETING_MATCH", "errorCode": "", - "value": "CC0000" + "value": "CC0000", + "metadata": { + "pr_link": "https://github.com/thomaspoignant/go-feature-flag/pull/916", + "version": 1 + } } diff --git a/providers/go-feature-flag/src/test/resources/mock_responses/unknown_field.json b/providers/go-feature-flag/src/test/resources/mock_responses/unknown_field.json index 61c503869..51dcdbe7a 100644 --- a/providers/go-feature-flag/src/test/resources/mock_responses/unknown_field.json +++ b/providers/go-feature-flag/src/test/resources/mock_responses/unknown_field.json @@ -7,5 +7,9 @@ "errorCode": "", "value": true, "unknown_field": true, - "unknown_field2": "unknown_field2" + "unknown_field2": "unknown_field2", + "metadata": { + "pr_link": "https://github.com/thomaspoignant/go-feature-flag/pull/916", + "version": 1 + } } diff --git a/providers/go-feature-flag/src/test/resources/mock_responses/unknown_reason.json b/providers/go-feature-flag/src/test/resources/mock_responses/unknown_reason.json index ff025b40a..73aa30752 100644 --- a/providers/go-feature-flag/src/test/resources/mock_responses/unknown_reason.json +++ b/providers/go-feature-flag/src/test/resources/mock_responses/unknown_reason.json @@ -5,5 +5,9 @@ "version": 0, "reason": "CUSTOM_REASON", "errorCode": "", - "value": true + "value": true, + "metadata": { + "pr_link": "https://github.com/thomaspoignant/go-feature-flag/pull/916", + "version": 1 + } }