From 2b2a48db958f93bfae5fafe995b072e90b2c043d Mon Sep 17 00:00:00 2001 From: Thomas Poignant Date: Thu, 22 Sep 2022 22:01:00 +0200 Subject: [PATCH 1/7] GO Feature Flag provider init Signed-off-by: Thomas Poignant --- providers/go-feature-flag/pom.xml | 53 ++++ .../provider/GoFeatureFlagProvider.java | 246 +++++++++++++++ .../GoFeatureFlagProviderOptions.java | 17 ++ .../provider/bean/GoFeatureFlagRequest.java | 11 + .../provider/bean/GoFeatureFlagResponse.java | 20 ++ .../provider/bean/GoFeatureFlagUser.java | 36 +++ .../exception/GoFeatureFlagException.java | 7 + .../provider/exception/InvalidEndpoint.java | 7 + .../provider/exception/InvalidOptions.java | 7 + .../provider/GoFeatureFlagProviderTest.java | 280 ++++++++++++++++++ .../mock_responses/bool_targeting_match.json | 9 + .../resources/mock_responses/disabled.json | 9 + .../resources/mock_responses/double_key.json | 9 + .../mock_responses/flag_not_found.json | 9 + .../resources/mock_responses/integer_key.json | 9 + .../resources/mock_responses/list_key.json | 15 + .../resources/mock_responses/object_key.json | 13 + .../resources/mock_responses/string_key.json | 9 + .../mock_responses/unknown_reason.json | 9 + 19 files changed, 775 insertions(+) create mode 100644 providers/go-feature-flag/pom.xml create mode 100644 providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/GoFeatureFlagProvider.java create mode 100644 providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/GoFeatureFlagProviderOptions.java create mode 100644 providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/bean/GoFeatureFlagRequest.java create mode 100644 providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/bean/GoFeatureFlagResponse.java create mode 100644 providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/bean/GoFeatureFlagUser.java create mode 100644 providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/exception/GoFeatureFlagException.java create mode 100644 providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/exception/InvalidEndpoint.java create mode 100644 providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/exception/InvalidOptions.java create mode 100644 providers/go-feature-flag/src/test/java/org/gofeatureflag/provider/GoFeatureFlagProviderTest.java create mode 100644 providers/go-feature-flag/src/test/resources/mock_responses/bool_targeting_match.json create mode 100644 providers/go-feature-flag/src/test/resources/mock_responses/disabled.json create mode 100644 providers/go-feature-flag/src/test/resources/mock_responses/double_key.json create mode 100644 providers/go-feature-flag/src/test/resources/mock_responses/flag_not_found.json create mode 100644 providers/go-feature-flag/src/test/resources/mock_responses/integer_key.json create mode 100644 providers/go-feature-flag/src/test/resources/mock_responses/list_key.json create mode 100644 providers/go-feature-flag/src/test/resources/mock_responses/object_key.json create mode 100644 providers/go-feature-flag/src/test/resources/mock_responses/string_key.json create mode 100644 providers/go-feature-flag/src/test/resources/mock_responses/unknown_reason.json diff --git a/providers/go-feature-flag/pom.xml b/providers/go-feature-flag/pom.xml new file mode 100644 index 000000000..83295746c --- /dev/null +++ b/providers/go-feature-flag/pom.xml @@ -0,0 +1,53 @@ + + + 4.0.0 + + dev.openfeature.contrib + java-sdk-contrib + 0.0.0 + ../../pom.xml + + dev.openfeature.contrib.providers + go-feature-flag + 0.1.0 + + go-feature-flag + GO Feature Flag provider for Java + https://gofeatureflag.org + + + + thomaspoignant + Thomas Poignant + go-feature-flag + https://gofeatureflag.org + + + + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + 2.13.4 + + + + com.fasterxml.jackson.core + jackson-databind + 2.13.4 + + + + com.squareup.okhttp3 + okhttp + 4.10.0 + + + + com.squareup.okhttp3 + mockwebserver + 4.10.0 + test + + + diff --git a/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/GoFeatureFlagProvider.java b/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/GoFeatureFlagProvider.java new file mode 100644 index 000000000..a6f300c9c --- /dev/null +++ b/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/GoFeatureFlagProvider.java @@ -0,0 +1,246 @@ +package org.gofeatureflag.provider; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import dev.openfeature.javasdk.*; +import dev.openfeature.javasdk.exceptions.FlagNotFoundError; +import dev.openfeature.javasdk.exceptions.GeneralError; +import dev.openfeature.javasdk.exceptions.OpenFeatureError; +import dev.openfeature.javasdk.exceptions.TypeMismatchError; +import okhttp3.*; +import org.gofeatureflag.provider.bean.GoFeatureFlagRequest; +import org.gofeatureflag.provider.bean.GoFeatureFlagResponse; +import org.gofeatureflag.provider.bean.GoFeatureFlagUser; +import org.gofeatureflag.provider.exception.InvalidEndpoint; +import org.gofeatureflag.provider.exception.InvalidOptions; + +import java.io.IOException; +import java.time.Instant; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; + +public class GoFeatureFlagProvider implements FeatureProvider { + private static final String NAME = "GO Feature Flag Provider"; + private static final ObjectMapper requestMapper = new ObjectMapper(); + private static final ObjectMapper responseMapper = new ObjectMapper(); + private final String endpoint; + // httpClient is the instance of the OkHttpClient used by the provider + private OkHttpClient httpClient; + + public GoFeatureFlagProvider(GoFeatureFlagProviderOptions options) throws InvalidOptions { + this.validateInputOptions(options); + this.endpoint = options.getEndpoint(); + this.initializeProvider(options); + } + + + /** + * validateInputOptions is validating the different options provided when creating the provider. + * + * @param options - Options used while creating the provider + * @throws InvalidOptions - if no options are provided + * @throws InvalidEndpoint - if the endpoint provided is not valid + */ + private void validateInputOptions(GoFeatureFlagProviderOptions options) throws InvalidEndpoint, InvalidOptions { + if (options == null) { + throw new InvalidOptions("No options provided"); + } + + if (options.getEndpoint() == null || "".equals(options.getEndpoint())) { + throw new InvalidEndpoint("endpoint is a mandatory field when initializing the provider"); + } + } + + /** + * initializeProvider is initializing the different class element used by the provider + * + * @param options - Options used while creating the provider + */ + private void initializeProvider(GoFeatureFlagProviderOptions options) { + // Register JavaTimeModule to be able to deserialized java.time.Instant Object + requestMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + requestMapper.enable(SerializationFeature.INDENT_OUTPUT); + requestMapper.registerModule(new JavaTimeModule()); + + // init httpClient to call the GO Feature Flag API + int timeout = options.getTimeout() == 0 ? 10000 : options.getTimeout(); + this.httpClient = new OkHttpClient.Builder() + .connectTimeout(timeout, TimeUnit.MILLISECONDS) + .readTimeout(timeout, TimeUnit.MILLISECONDS) + .callTimeout(timeout, TimeUnit.MILLISECONDS) + .readTimeout(timeout, TimeUnit.MILLISECONDS) + .writeTimeout(timeout, TimeUnit.MILLISECONDS) + .build(); + } + + @Override + public Metadata getMetadata() { + return () -> NAME; + } + + @Override + public List getProviderHooks() { + return FeatureProvider.super.getProviderHooks(); + } + + + @Override + public ProviderEvaluation getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext evaluationContext) { + return resolveEvaluationGoFeatureFlagProxy(key, defaultValue, evaluationContext, Boolean.class); + } + + @Override + public ProviderEvaluation getStringEvaluation(String key, String defaultValue, EvaluationContext evaluationContext) { + return resolveEvaluationGoFeatureFlagProxy(key, defaultValue, evaluationContext, String.class); + } + + @Override + public ProviderEvaluation getIntegerEvaluation(String key, Integer defaultValue, EvaluationContext evaluationContext) { + return resolveEvaluationGoFeatureFlagProxy(key, defaultValue, evaluationContext, Integer.class); + } + + @Override + public ProviderEvaluation getDoubleEvaluation(String key, Double defaultValue, EvaluationContext evaluationContext) { + return resolveEvaluationGoFeatureFlagProxy(key, defaultValue, evaluationContext, Double.class); + } + + @Override + public ProviderEvaluation getObjectEvaluation(String key, Value defaultValue, EvaluationContext evaluationContext) { + return resolveEvaluationGoFeatureFlagProxy(key, defaultValue, evaluationContext, Value.class); + } + + /** + * resolveEvaluationGoFeatureFlagProxy is calling the GO Feature Flag API to retrieve the flag value + * + * @param key - name of the feature flag + * @param defaultValue - value used if something is not working as expected + * @param ctx - EvaluationContext used for the request + * @param expectedType - type expected for the value + * @return a ProviderEvaluation that contains the open-feature response + * @throws OpenFeatureError - if an error happen + */ + private ProviderEvaluation resolveEvaluationGoFeatureFlagProxy(String key, T defaultValue, EvaluationContext ctx, Class expectedType) throws OpenFeatureError { + try { + GoFeatureFlagUser user = GoFeatureFlagUser.fromEvaluationContext(ctx); + GoFeatureFlagRequest goffRequest = new GoFeatureFlagRequest(user, defaultValue); + HttpUrl url = Objects.requireNonNull(HttpUrl.parse(this.endpoint)) + .newBuilder() + .addEncodedPathSegment("v1") + .addEncodedPathSegment("feature") + .addEncodedPathSegment(key) + .addEncodedPathSegment("eval") + .build(); + + Request request = new Request.Builder() + .url(url) + .addHeader("Content-Type", "application/json") + .post(RequestBody.create(requestMapper.writeValueAsBytes(goffRequest), MediaType.get("application/json; charset=utf-8"))) + .build(); + + try (Response response = this.httpClient.newCall(request).execute()) { + if (response.code() >= HTTP_BAD_REQUEST) { + throw new GeneralError("impossible to contact GO Feature Flag relay proxy instance"); + } + GoFeatureFlagResponse goffResp = responseMapper.readValue(response.body().string(), GoFeatureFlagResponse.class); + + if (Reason.DISABLED.name().equalsIgnoreCase(goffResp.getReason())) { + // we don't set a variant since we are using the default value, and we are not able to know + // which variant it is. + return ProviderEvaluation.builder().value(defaultValue).reason(Reason.DISABLED).build(); + } + + if (ErrorCode.FLAG_NOT_FOUND.name().equalsIgnoreCase(goffResp.getErrorCode())) { + throw new FlagNotFoundError("Flag " + key + " was not found in your configuration"); + } + + boolean isPrimitive = expectedType == Boolean.class || + expectedType == String.class || + expectedType == Integer.class || + expectedType == Double.class; + + // Convert the value received from the API. + T flagValue = isPrimitive + ? goffResp.getValue() : (T) objectToValue(goffResp.getValue()); + + if (flagValue.getClass() != expectedType) { + throw new TypeMismatchError("Flag value " + key + " had unexpected type " + flagValue.getClass() + ", expected " + expectedType + "."); + } + + return ProviderEvaluation.builder() + .errorCode(goffResp.getErrorCode()) + .reason(mapReason(goffResp.getReason())) + .value(flagValue) + .variant(goffResp.getVariationType()) + .build(); + + } + } catch (IOException e) { + throw new GeneralError("unknown error while retrieving flag " + key); + } + } + + + /** + * mapReason is mapping the reason in string received by the API + * to our internal SDK reason enum. + * + * @param reason - string of the reason received from the API + * @return an item from the enum + */ + private Reason mapReason(String reason) { + try { + return Reason.valueOf(reason); + } catch (IllegalArgumentException e) { + return Reason.UNKNOWN; + } + } + + + /** + * objectToValue is wrapping an object into a Value. + * + * @param object the object you want to wrap + * @return the wrapped object + */ + private Value objectToValue(Object object) { + if (object instanceof Value) { + return (Value) object; + } else if (object instanceof String) { + return new Value((String) object); + } else if (object instanceof Boolean) { + return new Value((Boolean) object); + } else if (object instanceof Integer) { + return new Value((Integer) object); + } else if (object instanceof Double) { + return new Value((Double) object); + } else if (object instanceof Structure) { + return new Value((Structure) object); + } else if (object instanceof List) { + // need to translate each elem in list to a value + return new Value(((List) object).stream().map(this::objectToValue).collect(Collectors.toList())); + } else if (object instanceof Instant) { + return new Value((Instant) object); + } else if (object instanceof Map) { + return new Value(mapToStructure((Map) object)); + } else { + throw new ClassCastException("Could not cast Object to Value"); + } + } + + /** + * mapToStructure transform a map comming from a JSON Object to a Structure type + * + * @param map - JSON object return by the API + * @return a Structure object in the SDK format + */ + private Structure mapToStructure(Map map) { + return new Structure( + map.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> objectToValue(e.getValue())))); + } +} diff --git a/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/GoFeatureFlagProviderOptions.java b/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/GoFeatureFlagProviderOptions.java new file mode 100644 index 000000000..4dbd311b8 --- /dev/null +++ b/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/GoFeatureFlagProviderOptions.java @@ -0,0 +1,17 @@ +package org.gofeatureflag.provider; + +import lombok.Builder; +import lombok.Getter; + +@Builder +public class GoFeatureFlagProviderOptions { + /** + * timeout in millisecond we are waiting when calling the + * go-feature-flag relay proxy API. + */ + @Getter + private int timeout; + + @Getter + private String endpoint; +} diff --git a/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/bean/GoFeatureFlagRequest.java b/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/bean/GoFeatureFlagRequest.java new file mode 100644 index 000000000..4febd2e2b --- /dev/null +++ b/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/bean/GoFeatureFlagRequest.java @@ -0,0 +1,11 @@ +package org.gofeatureflag.provider.bean; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class GoFeatureFlagRequest { + private GoFeatureFlagUser user; + private T defaultValue; +} diff --git a/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/bean/GoFeatureFlagResponse.java b/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/bean/GoFeatureFlagResponse.java new file mode 100644 index 000000000..0c1dc98a0 --- /dev/null +++ b/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/bean/GoFeatureFlagResponse.java @@ -0,0 +1,20 @@ +package org.gofeatureflag.provider.bean; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +public class GoFeatureFlagResponse { + private boolean trackEvents; + private String variationType; + private boolean failed; + private String version; + private String reason; + private String errorCode; + private T value; +} + + diff --git a/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/bean/GoFeatureFlagUser.java b/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/bean/GoFeatureFlagUser.java new file mode 100644 index 000000000..4470d6306 --- /dev/null +++ b/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/bean/GoFeatureFlagUser.java @@ -0,0 +1,36 @@ +package org.gofeatureflag.provider.bean; + + +import com.fasterxml.jackson.annotation.JsonInclude; +import dev.openfeature.javasdk.EvaluationContext; +import dev.openfeature.javasdk.Value; +import lombok.Builder; +import lombok.Getter; + +import java.util.Map; + +@Builder +@Getter +@JsonInclude(JsonInclude.Include.NON_NULL) +public class GoFeatureFlagUser { + private final String key; + private final boolean anonymous; + private final Map custom; + + /** + * fromEvaluationContext is transforming the evaluationContext into a GoFeatureFlagUser + * + * @param ctx - EvaluationContext from open-feature + * @return GoFeatureFlagUser format for GO Feature Flag + */ + public static GoFeatureFlagUser fromEvaluationContext(EvaluationContext ctx) { + String key = ctx.getTargetingKey(); //TODO: tester si on a une key, sinon on hash le context + Value anonymousValue = ctx.getValue("anonymous"); + boolean anonymous = anonymousValue != null && anonymousValue.isBoolean() ? anonymousValue.asBoolean() : false; + Map custom = ctx.asObjectMap(); + if (ctx.getValue("anonymous") != null) { + custom.remove("anonymous"); + } + return GoFeatureFlagUser.builder().anonymous(anonymous).key(key).custom(custom).build(); + } +} diff --git a/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/exception/GoFeatureFlagException.java b/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/exception/GoFeatureFlagException.java new file mode 100644 index 000000000..6018b2f93 --- /dev/null +++ b/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/exception/GoFeatureFlagException.java @@ -0,0 +1,7 @@ +package org.gofeatureflag.provider.exception; + +import lombok.experimental.StandardException; + +@StandardException +public class GoFeatureFlagException extends Exception { +} diff --git a/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/exception/InvalidEndpoint.java b/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/exception/InvalidEndpoint.java new file mode 100644 index 000000000..93fae8a18 --- /dev/null +++ b/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/exception/InvalidEndpoint.java @@ -0,0 +1,7 @@ +package org.gofeatureflag.provider.exception; + +import lombok.experimental.StandardException; + +@StandardException +public class InvalidEndpoint extends InvalidOptions { +} diff --git a/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/exception/InvalidOptions.java b/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/exception/InvalidOptions.java new file mode 100644 index 000000000..a6ab51221 --- /dev/null +++ b/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/exception/InvalidOptions.java @@ -0,0 +1,7 @@ +package org.gofeatureflag.provider.exception; + +import lombok.experimental.StandardException; + +@StandardException +public class InvalidOptions extends GoFeatureFlagException { +} diff --git a/providers/go-feature-flag/src/test/java/org/gofeatureflag/provider/GoFeatureFlagProviderTest.java b/providers/go-feature-flag/src/test/java/org/gofeatureflag/provider/GoFeatureFlagProviderTest.java new file mode 100644 index 000000000..c2b437415 --- /dev/null +++ b/providers/go-feature-flag/src/test/java/org/gofeatureflag/provider/GoFeatureFlagProviderTest.java @@ -0,0 +1,280 @@ +package org.gofeatureflag.provider; + +import dev.openfeature.javasdk.*; +import dev.openfeature.javasdk.exceptions.FlagNotFoundError; +import dev.openfeature.javasdk.exceptions.GeneralError; +import dev.openfeature.javasdk.exceptions.TypeMismatchError; +import lombok.SneakyThrows; +import okhttp3.HttpUrl; +import okhttp3.mockwebserver.Dispatcher; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import org.gofeatureflag.provider.exception.InvalidEndpoint; +import org.gofeatureflag.provider.exception.InvalidOptions; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class GoFeatureFlagProviderTest { + private MockWebServer server; + private HttpUrl baseUrl; + private EvaluationContext evaluationContext; + // Dispatcher is the configuration of the mock server to test the provider. + final Dispatcher dispatcher = new Dispatcher() { + @SneakyThrows + @Override + public MockResponse dispatch(RecordedRequest request) { + if (request.getPath().contains("fail_500")) { + return new MockResponse().setResponseCode(500); + } + if (request.getPath().startsWith("/v1/feature/")) { + String flagName = request.getPath().replace("/v1/feature/", "").replace("/eval", ""); + return new MockResponse() + .setResponseCode(200) + .setBody(readMockResponse(flagName + ".json")); + } + return new MockResponse().setResponseCode(404); + } + }; + + @BeforeEach + void beforeEach() throws IOException { + this.server = new MockWebServer(); + this.server.setDispatcher(dispatcher); + this.server.start(); + this.baseUrl = server.url(""); + + this.evaluationContext = new EvaluationContext(); + this.evaluationContext.setTargetingKey("d45e303a-38c2-11ed-a261-0242ac120002"); + this.evaluationContext.add("email", "john.doe@gofeatureflag.org"); + this.evaluationContext.add("firstname", "john"); + this.evaluationContext.add("lastname", "doe"); + this.evaluationContext.add("anonymous", false); + this.evaluationContext.add("professional", true); + this.evaluationContext.add("rate", 3.14); + this.evaluationContext.add("age", 30); + this.evaluationContext.add("company_info", new Structure().add("name", "my_company").add("size", 120)); + List labels = new ArrayList(); + labels.add(new Value("pro")); + labels.add(new Value("beta")); + this.evaluationContext.add("labels", labels); + } + + @AfterEach + void afterEach() throws IOException { + this.server.close(); + this.server = null; + this.baseUrl = null; + } + + @Test + void getMetadata_validate_name() throws InvalidOptions { + assertEquals("GO Feature Flag Provider", new GoFeatureFlagProvider(GoFeatureFlagProviderOptions.builder().endpoint(this.baseUrl.toString()).timeout(1000).build()).getMetadata().getName()); + } + + @Test + void constructor_options_null() { + assertThrows(InvalidOptions.class, () -> new GoFeatureFlagProvider(null)); + } + + @Test + void constructor_options_empty() { + assertThrows(InvalidOptions.class, () -> new GoFeatureFlagProvider(GoFeatureFlagProviderOptions.builder().build())); + } + + @Test + void constructor_options_empty_endpoint() { + assertThrows(InvalidEndpoint.class, () -> new GoFeatureFlagProvider(GoFeatureFlagProviderOptions.builder().endpoint("").build())); + } + + @Test + void constructor_options_only_timeout() { + assertThrows(InvalidEndpoint.class, () -> new GoFeatureFlagProvider(GoFeatureFlagProviderOptions.builder().timeout(10000).build())); + } + + @Test + void constructor_options_valid_endpoint() { + assertDoesNotThrow(() -> new GoFeatureFlagProvider(GoFeatureFlagProviderOptions.builder().endpoint("http://localhost:1031").build())); + } + + @Test + void should_throw_an_error_if_endpoint_not_available() throws InvalidOptions { + GoFeatureFlagProvider g = new GoFeatureFlagProvider(GoFeatureFlagProviderOptions.builder().endpoint(this.baseUrl.toString()).timeout(1000).build()); + assertThrows(GeneralError.class, () -> g.getBooleanEvaluation("fail_500", false, this.evaluationContext)); + } + + @Test + void should_throw_an_error_if_flag_does_not_exists() throws InvalidOptions { + GoFeatureFlagProvider g = new GoFeatureFlagProvider(GoFeatureFlagProviderOptions.builder().endpoint(this.baseUrl.toString()).timeout(1000).build()); + assertThrows(FlagNotFoundError.class, () -> g.getBooleanEvaluation("flag_not_found", false, this.evaluationContext)); + } + + @Test + void should_throw_an_error_if_we_expect_a_boolean_and_got_another_type() throws InvalidOptions { + GoFeatureFlagProvider g = new GoFeatureFlagProvider(GoFeatureFlagProviderOptions.builder().endpoint(this.baseUrl.toString()).timeout(1000).build()); + assertThrows(TypeMismatchError.class, () -> g.getBooleanEvaluation("string_key", false, this.evaluationContext)); + } + + @Test + void should_resolve_a_valid_boolean_flag_with_TARGETING_MATCH_reason() throws InvalidOptions { + GoFeatureFlagProvider g = new GoFeatureFlagProvider(GoFeatureFlagProviderOptions.builder().endpoint(this.baseUrl.toString()).timeout(1000).build()); + ProviderEvaluation res = g.getBooleanEvaluation("bool_targeting_match", false, this.evaluationContext); + assertEquals(true, res.getValue()); + assertEquals("", res.getErrorCode()); + assertEquals(Reason.TARGETING_MATCH, res.getReason()); + assertEquals("True", res.getVariant()); + } + + @Test + void should_return_unknown_reason_if_not_exists_in_SDK() throws InvalidOptions { + GoFeatureFlagProvider g = new GoFeatureFlagProvider(GoFeatureFlagProviderOptions.builder().endpoint(this.baseUrl.toString()).timeout(1000).build()); + ProviderEvaluation res = g.getBooleanEvaluation("unknown_reason", false, this.evaluationContext); + assertEquals(true, res.getValue()); + assertEquals("", res.getErrorCode()); + assertEquals(Reason.UNKNOWN, res.getReason()); + assertEquals("True", res.getVariant()); + } + + @Test + void should_use_boolean_default_value_if_the_flag_is_disabled() throws InvalidOptions { + GoFeatureFlagProvider g = new GoFeatureFlagProvider(GoFeatureFlagProviderOptions.builder().endpoint(this.baseUrl.toString()).timeout(1000).build()); + ProviderEvaluation res = g.getBooleanEvaluation("disabled", false, this.evaluationContext); + assertEquals(false, res.getValue()); + assertEquals(Reason.DISABLED, res.getReason()); + } + + @Test + void should_throw_an_error_if_we_expect_a_string_and_got_another_type() throws InvalidOptions { + GoFeatureFlagProvider g = new GoFeatureFlagProvider(GoFeatureFlagProviderOptions.builder().endpoint(this.baseUrl.toString()).timeout(1000).build()); + assertThrows(TypeMismatchError.class, () -> g.getStringEvaluation("bool_targeting_match", "defaultValue", this.evaluationContext)); + } + + @Test + void should_resolve_a_valid_string_flag_with_TARGETING_MATCH_reason() throws InvalidOptions { + GoFeatureFlagProvider g = new GoFeatureFlagProvider(GoFeatureFlagProviderOptions.builder().endpoint(this.baseUrl.toString()).timeout(1000).build()); + ProviderEvaluation res = g.getStringEvaluation("string_key", "defaultValue", this.evaluationContext); + assertEquals("CC0000", res.getValue()); + assertEquals("", res.getErrorCode()); + assertEquals(Reason.TARGETING_MATCH, res.getReason()); + assertEquals("True", res.getVariant()); + } + + @Test + void should_use_string_default_value_if_the_flag_is_disabled() throws InvalidOptions { + GoFeatureFlagProvider g = new GoFeatureFlagProvider(GoFeatureFlagProviderOptions.builder().endpoint(this.baseUrl.toString()).timeout(1000).build()); + ProviderEvaluation res = g.getStringEvaluation("disabled", "defaultValue", this.evaluationContext); + assertEquals("defaultValue", res.getValue()); + assertEquals(Reason.DISABLED, res.getReason()); + } + + @Test + void should_throw_an_error_if_we_expect_a_integer_and_got_another_type() throws InvalidOptions { + GoFeatureFlagProvider g = new GoFeatureFlagProvider(GoFeatureFlagProviderOptions.builder().endpoint(this.baseUrl.toString()).timeout(1000).build()); + assertThrows(TypeMismatchError.class, () -> g.getIntegerEvaluation("string_key", 200, this.evaluationContext)); + } + + @Test + void should_resolve_a_valid_integer_flag_with_TARGETING_MATCH_reason() throws InvalidOptions { + GoFeatureFlagProvider g = new GoFeatureFlagProvider(GoFeatureFlagProviderOptions.builder().endpoint(this.baseUrl.toString()).timeout(1000).build()); + ProviderEvaluation res = g.getIntegerEvaluation("integer_key", 1200, this.evaluationContext); + assertEquals(100, res.getValue()); + assertEquals("", res.getErrorCode()); + assertEquals(Reason.TARGETING_MATCH, res.getReason()); + assertEquals("True", res.getVariant()); + } + + @Test + void should_use_integer_default_value_if_the_flag_is_disabled() throws InvalidOptions { + GoFeatureFlagProvider g = new GoFeatureFlagProvider(GoFeatureFlagProviderOptions.builder().endpoint(this.baseUrl.toString()).timeout(1000).build()); + ProviderEvaluation res = g.getIntegerEvaluation("disabled", 1225, this.evaluationContext); + assertEquals(1225, res.getValue()); + assertEquals(Reason.DISABLED, res.getReason()); + } + + @Test + void should_throw_an_error_if_we_expect_a_integer_and_double_type() throws InvalidOptions { + GoFeatureFlagProvider g = new GoFeatureFlagProvider(GoFeatureFlagProviderOptions.builder().endpoint(this.baseUrl.toString()).timeout(1000).build()); + assertThrows(TypeMismatchError.class, () -> g.getIntegerEvaluation("double_key", 200, this.evaluationContext)); + } + + @Test + void should_resolve_a_valid_double_flag_with_TARGETING_MATCH_reason() throws InvalidOptions { + GoFeatureFlagProvider g = new GoFeatureFlagProvider(GoFeatureFlagProviderOptions.builder().endpoint(this.baseUrl.toString()).timeout(1000).build()); + ProviderEvaluation res = g.getDoubleEvaluation("double_key", 1200.25, this.evaluationContext); + assertEquals(100.25, res.getValue()); + assertEquals("", res.getErrorCode()); + assertEquals(Reason.TARGETING_MATCH, res.getReason()); + assertEquals("True", res.getVariant()); + } + + @Test + void should_use_double_default_value_if_the_flag_is_disabled() throws InvalidOptions { + GoFeatureFlagProvider g = new GoFeatureFlagProvider(GoFeatureFlagProviderOptions.builder().endpoint(this.baseUrl.toString()).timeout(1000).build()); + ProviderEvaluation res = g.getDoubleEvaluation("disabled", 1225.34, this.evaluationContext); + assertEquals(1225.34, res.getValue()); + assertEquals(Reason.DISABLED, res.getReason()); + } + + @Test + void should_resolve_a_valid_value_flag_with_TARGETING_MATCH_reason() throws InvalidOptions { + GoFeatureFlagProvider g = new GoFeatureFlagProvider(GoFeatureFlagProviderOptions.builder().endpoint(this.baseUrl.toString()).timeout(1000).build()); + ProviderEvaluation res = g.getObjectEvaluation("object_key", null, this.evaluationContext); + Value want = new Value(new Structure().add("test", "test1").add("test2", false).add("test3", 123.3)); + assertEquals(want, res.getValue()); + assertEquals("", res.getErrorCode()); + assertEquals(Reason.TARGETING_MATCH, res.getReason()); + assertEquals("True", res.getVariant()); + } + + @Test + void should_wrap_into_value_if_wrong_type() throws InvalidOptions { + GoFeatureFlagProvider g = new GoFeatureFlagProvider(GoFeatureFlagProviderOptions.builder().endpoint(this.baseUrl.toString()).timeout(1000).build()); + ProviderEvaluation res = g.getObjectEvaluation("string_key", null, this.evaluationContext); + Value want = new Value("CC0000"); + assertEquals(want, res.getValue()); + assertEquals("", res.getErrorCode()); + assertEquals(Reason.TARGETING_MATCH, res.getReason()); + assertEquals("True", res.getVariant()); + } + + @Test + void should_use_object_default_value_if_the_flag_is_disabled() throws InvalidOptions { + GoFeatureFlagProvider g = new GoFeatureFlagProvider(GoFeatureFlagProviderOptions.builder().endpoint(this.baseUrl.toString()).timeout(1000).build()); + ProviderEvaluation res = g.getObjectEvaluation("disabled", new Value("default"), this.evaluationContext); + assertEquals(new Value("default"), res.getValue()); + assertEquals(Reason.DISABLED, res.getReason()); + } + + + @Test + void should_resolve_a_valid_value_flag_with_a_list() throws InvalidOptions { + GoFeatureFlagProvider g = new GoFeatureFlagProvider(GoFeatureFlagProviderOptions.builder().endpoint(this.baseUrl.toString()).timeout(1000).build()); + ProviderEvaluation res = g.getObjectEvaluation("list_key", null, this.evaluationContext); + Value want = new Value(new ArrayList<>( + Arrays.asList(new Value("test"), + new Value("test1"), + new Value("test2"), + new Value("false"), + new Value("test3")))); + assertEquals(want, res.getValue()); + assertEquals("", res.getErrorCode()); + assertEquals(Reason.TARGETING_MATCH, res.getReason()); + assertEquals("True", res.getVariant()); + } + + private String readMockResponse(String filename) throws IOException { + String file = getClass().getClassLoader().getResource("mock_responses/" + filename).getFile(); + byte[] bytes = Files.readAllBytes(Paths.get(file)); + return new String(bytes); + } +} 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 new file mode 100644 index 000000000..dd20b510c --- /dev/null +++ b/providers/go-feature-flag/src/test/resources/mock_responses/bool_targeting_match.json @@ -0,0 +1,9 @@ +{ + "trackEvents": true, + "variationType": "True", + "failed": false, + "version": 0, + "reason": "TARGETING_MATCH", + "errorCode": "", + "value": true +} 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 new file mode 100644 index 000000000..85a9949d0 --- /dev/null +++ b/providers/go-feature-flag/src/test/resources/mock_responses/disabled.json @@ -0,0 +1,9 @@ +{ + "trackEvents": true, + "variationType": "defaultSdk", + "failed": false, + "version": 0, + "reason": "DISABLED", + "errorCode": "", + "value": true +} 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 new file mode 100644 index 000000000..d6bb81828 --- /dev/null +++ b/providers/go-feature-flag/src/test/resources/mock_responses/double_key.json @@ -0,0 +1,9 @@ +{ + "trackEvents": true, + "variationType": "True", + "failed": false, + "version": 0, + "reason": "TARGETING_MATCH", + "errorCode": "", + "value": 100.25 +} 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 new file mode 100644 index 000000000..209436b4f --- /dev/null +++ b/providers/go-feature-flag/src/test/resources/mock_responses/flag_not_found.json @@ -0,0 +1,9 @@ +{ + "trackEvents": true, + "variationType": "SdkDefault", + "failed": true, + "version": 0, + "reason": "ERROR", + "errorCode": "FLAG_NOT_FOUND", + "value": "false" +} 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 new file mode 100644 index 000000000..d6ddc655b --- /dev/null +++ b/providers/go-feature-flag/src/test/resources/mock_responses/integer_key.json @@ -0,0 +1,9 @@ +{ + "trackEvents": true, + "variationType": "True", + "failed": false, + "version": 0, + "reason": "TARGETING_MATCH", + "errorCode": "", + "value": 100 +} 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 new file mode 100644 index 000000000..0e0e3965b --- /dev/null +++ b/providers/go-feature-flag/src/test/resources/mock_responses/list_key.json @@ -0,0 +1,15 @@ +{ + "trackEvents": true, + "variationType": "True", + "failed": false, + "version": 0, + "reason": "TARGETING_MATCH", + "errorCode": "", + "value": [ + "test", + "test1", + "test2", + "false", + "test3" + ] +} 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 new file mode 100644 index 000000000..79961cbe3 --- /dev/null +++ b/providers/go-feature-flag/src/test/resources/mock_responses/object_key.json @@ -0,0 +1,13 @@ +{ + "trackEvents": true, + "variationType": "True", + "failed": false, + "version": 0, + "reason": "TARGETING_MATCH", + "errorCode": "", + "value": { + "test": "test1", + "test2": false, + "test3": 123.3 + } +} 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 new file mode 100644 index 000000000..53683df0a --- /dev/null +++ b/providers/go-feature-flag/src/test/resources/mock_responses/string_key.json @@ -0,0 +1,9 @@ +{ + "trackEvents": true, + "variationType": "True", + "failed": false, + "version": 0, + "reason": "TARGETING_MATCH", + "errorCode": "", + "value": "CC0000" +} 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 new file mode 100644 index 000000000..ff025b40a --- /dev/null +++ b/providers/go-feature-flag/src/test/resources/mock_responses/unknown_reason.json @@ -0,0 +1,9 @@ +{ + "trackEvents": true, + "variationType": "True", + "failed": false, + "version": 0, + "reason": "CUSTOM_REASON", + "errorCode": "", + "value": true +} From 7d9f536c736889ad27c42730e34b24b4098b6450 Mon Sep 17 00:00:00 2001 From: Thomas Poignant Date: Fri, 23 Sep 2022 14:57:40 +0200 Subject: [PATCH 2/7] Fix feedback on the PR Signed-off-by: Thomas Poignant --- pom.xml | 3 ++- .../provider/GoFeatureFlagProvider.java | 6 ++++-- .../provider/bean/GoFeatureFlagUser.java | 11 +++++++---- .../provider/exception/InvalidTargetingKey.java | 13 +++++++++++++ .../provider/GoFeatureFlagProviderTest.java | 9 ++++++++- .../test/resources/mock_responses/object_key.json | 4 +++- 6 files changed, 37 insertions(+), 9 deletions(-) create mode 100644 providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/exception/InvalidTargetingKey.java diff --git a/pom.xml b/pom.xml index 1ec6e791e..e147779e7 100644 --- a/pom.xml +++ b/pom.xml @@ -29,6 +29,7 @@ hooks/open-telemetry providers/flagd + providers/go-feature-flag @@ -45,7 +46,7 @@ - + dev.openfeature javasdk diff --git a/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/GoFeatureFlagProvider.java b/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/GoFeatureFlagProvider.java index a6f300c9c..cc510d233 100644 --- a/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/GoFeatureFlagProvider.java +++ b/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/GoFeatureFlagProvider.java @@ -211,6 +211,8 @@ private Reason mapReason(String reason) { private Value objectToValue(Object object) { if (object instanceof Value) { return (Value) object; + } else if (object == null) { + return null; } else if (object instanceof String) { return new Value((String) object); } else if (object instanceof Boolean) { @@ -234,13 +236,13 @@ private Value objectToValue(Object object) { } /** - * mapToStructure transform a map comming from a JSON Object to a Structure type + * mapToStructure transform a map coming from a JSON Object to a Structure type * * @param map - JSON object return by the API * @return a Structure object in the SDK format */ private Structure mapToStructure(Map map) { return new Structure( - map.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> objectToValue(e.getValue())))); + map.entrySet().stream().filter(e -> e.getValue() != null).collect(Collectors.toMap(Map.Entry::getKey, e -> objectToValue(e.getValue())))); } } diff --git a/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/bean/GoFeatureFlagUser.java b/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/bean/GoFeatureFlagUser.java index 52b837fd6..a3c3cb2d7 100644 --- a/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/bean/GoFeatureFlagUser.java +++ b/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/bean/GoFeatureFlagUser.java @@ -6,6 +6,7 @@ import dev.openfeature.javasdk.Value; import lombok.Builder; import lombok.Getter; +import org.gofeatureflag.provider.exception.InvalidTargetingKey; import java.util.Map; @@ -16,7 +17,7 @@ public class GoFeatureFlagUser { private final String key; private final boolean anonymous; private final Map custom; - + private static final String anonymousFieldName = "anonymous"; /** * fromEvaluationContext is transforming the evaluationContext into a GoFeatureFlagUser * @@ -25,11 +26,13 @@ public class GoFeatureFlagUser { */ public static GoFeatureFlagUser fromEvaluationContext(EvaluationContext ctx) { String key = ctx.getTargetingKey(); - Value anonymousValue = ctx.getValue("anonymous"); + if (key == null || "".equals(key)) { throw new InvalidTargetingKey(); } + + Value anonymousValue = ctx.getValue(anonymousFieldName); boolean anonymous = anonymousValue != null && anonymousValue.isBoolean() ? anonymousValue.asBoolean() : false; Map custom = ctx.asObjectMap(); - if (ctx.getValue("anonymous") != null) { - custom.remove("anonymous"); + if (ctx.getValue(anonymousFieldName) != null) { + custom.remove(anonymousFieldName); } return GoFeatureFlagUser.builder().anonymous(anonymous).key(key).custom(custom).build(); } diff --git a/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/exception/InvalidTargetingKey.java b/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/exception/InvalidTargetingKey.java new file mode 100644 index 000000000..02c952412 --- /dev/null +++ b/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/exception/InvalidTargetingKey.java @@ -0,0 +1,13 @@ +package org.gofeatureflag.provider.exception; + +import dev.openfeature.javasdk.ErrorCode; +import dev.openfeature.javasdk.exceptions.OpenFeatureError; + +import javax.management.openmbean.OpenDataException; + +public class InvalidTargetingKey extends OpenFeatureError { + public ErrorCode getErrorCode() { + // Should change as soon as we have a better error type. + return ErrorCode.GENERAL; + } +} diff --git a/providers/go-feature-flag/src/test/java/org/gofeatureflag/provider/GoFeatureFlagProviderTest.java b/providers/go-feature-flag/src/test/java/org/gofeatureflag/provider/GoFeatureFlagProviderTest.java index c2b437415..2ddae408e 100644 --- a/providers/go-feature-flag/src/test/java/org/gofeatureflag/provider/GoFeatureFlagProviderTest.java +++ b/providers/go-feature-flag/src/test/java/org/gofeatureflag/provider/GoFeatureFlagProviderTest.java @@ -12,6 +12,7 @@ import okhttp3.mockwebserver.RecordedRequest; import org.gofeatureflag.provider.exception.InvalidEndpoint; import org.gofeatureflag.provider.exception.InvalidOptions; +import org.gofeatureflag.provider.exception.InvalidTargetingKey; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -229,7 +230,7 @@ void should_use_double_default_value_if_the_flag_is_disabled() throws InvalidOpt void should_resolve_a_valid_value_flag_with_TARGETING_MATCH_reason() throws InvalidOptions { GoFeatureFlagProvider g = new GoFeatureFlagProvider(GoFeatureFlagProviderOptions.builder().endpoint(this.baseUrl.toString()).timeout(1000).build()); ProviderEvaluation res = g.getObjectEvaluation("object_key", null, this.evaluationContext); - Value want = new Value(new Structure().add("test", "test1").add("test2", false).add("test3", 123.3)); + Value want = new Value(new Structure().add("test", "test1").add("test2", false).add("test3", 123.3).add("test4",1)); assertEquals(want, res.getValue()); assertEquals("", res.getErrorCode()); assertEquals(Reason.TARGETING_MATCH, res.getReason()); @@ -258,6 +259,12 @@ void should_use_object_default_value_if_the_flag_is_disabled() throws InvalidOpt @Test void should_resolve_a_valid_value_flag_with_a_list() throws InvalidOptions { + GoFeatureFlagProvider g = new GoFeatureFlagProvider(GoFeatureFlagProviderOptions.builder().endpoint(this.baseUrl.toString()).timeout(1000).build()); + assertThrows(InvalidTargetingKey.class,() -> g.getObjectEvaluation("list_key", null, new EvaluationContext())); + } + + @Test + void should_throw_an_error_if_no_targeting_key() throws InvalidOptions { GoFeatureFlagProvider g = new GoFeatureFlagProvider(GoFeatureFlagProviderOptions.builder().endpoint(this.baseUrl.toString()).timeout(1000).build()); ProviderEvaluation res = g.getObjectEvaluation("list_key", null, this.evaluationContext); Value want = new Value(new ArrayList<>( 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 79961cbe3..27db6711b 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 @@ -8,6 +8,8 @@ "value": { "test": "test1", "test2": false, - "test3": 123.3 + "test3": 123.3, + "test4": 1, + "test5": null } } From 304c748077d275b7c6bdd46541a941f0a7e62dd1 Mon Sep 17 00:00:00 2001 From: Thomas Poignant Date: Fri, 23 Sep 2022 15:30:43 +0200 Subject: [PATCH 3/7] Add connection pool for OkHttp Signed-off-by: Thomas Poignant --- .../provider/GoFeatureFlagProvider.java | 3 +++ .../GoFeatureFlagProviderOptions.java | 25 +++++++++++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/GoFeatureFlagProvider.java b/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/GoFeatureFlagProvider.java index cc510d233..1c7a25ed6 100644 --- a/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/GoFeatureFlagProvider.java +++ b/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/GoFeatureFlagProvider.java @@ -70,12 +70,15 @@ private void initializeProvider(GoFeatureFlagProviderOptions options) { // init httpClient to call the GO Feature Flag API int timeout = options.getTimeout() == 0 ? 10000 : options.getTimeout(); + long keepAliveDuration = options.getKeepAliveDuration() == null ? 7200000 : options.getKeepAliveDuration(); + int maxIdleConnections = options.getMaxIdleConnections() == 0 ? 1000 : options.getMaxIdleConnections(); this.httpClient = new OkHttpClient.Builder() .connectTimeout(timeout, TimeUnit.MILLISECONDS) .readTimeout(timeout, TimeUnit.MILLISECONDS) .callTimeout(timeout, TimeUnit.MILLISECONDS) .readTimeout(timeout, TimeUnit.MILLISECONDS) .writeTimeout(timeout, TimeUnit.MILLISECONDS) + .connectionPool(new ConnectionPool(maxIdleConnections, keepAliveDuration, TimeUnit.MILLISECONDS)) .build(); } diff --git a/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/GoFeatureFlagProviderOptions.java b/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/GoFeatureFlagProviderOptions.java index 4dbd311b8..fc7f9a6a0 100644 --- a/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/GoFeatureFlagProviderOptions.java +++ b/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/GoFeatureFlagProviderOptions.java @@ -5,13 +5,34 @@ @Builder public class GoFeatureFlagProviderOptions { + /** - * timeout in millisecond we are waiting when calling the + * (mandatory) endpoint contains the DNS of your GO Feature Flag relay proxy + * example: https://mydomain.com/gofeatureflagproxy/ + */ + @Getter + private String endpoint; + + /** + * (optional) timeout in millisecond we are waiting when calling the * go-feature-flag relay proxy API. + * Default: 10000 ms */ @Getter private int timeout; + + /** + * (optional) maxIdleConnections is the maximum number of connexions in the connexion pool. + * Default: 1000 + */ @Getter - private String endpoint; + private int maxIdleConnections; + + /** + * (optional) keepAliveDuration is the time in millisecond we keep the connexion open. + * Default: 7200000 (2 hours) + */ + @Getter + private Long keepAliveDuration; } From eeede5363000e36358aa447f8fb09548b1cc7ce8 Mon Sep 17 00:00:00 2001 From: Thomas Poignant Date: Fri, 23 Sep 2022 17:24:15 +0200 Subject: [PATCH 4/7] Change package Signed-off-by: Thomas Poignant --- providers/go-feature-flag/README.md | 30 ++++++++++++++++ providers/go-feature-flag/pom.xml | 3 +- .../gofeatureflag}/GoFeatureFlagProvider.java | 35 ++++++++++++++----- .../GoFeatureFlagProviderOptions.java | 2 +- .../bean/GoFeatureFlagRequest.java | 2 +- .../bean/GoFeatureFlagResponse.java | 2 +- .../bean/GoFeatureFlagUser.java | 11 +++--- .../exception/GoFeatureFlagException.java | 2 +- .../exception/InvalidEndpoint.java | 2 +- .../exception/InvalidOptions.java | 2 +- .../exception/InvalidTargetingKey.java | 4 +-- .../GoFeatureFlagProviderTest.java | 28 ++++++++------- 12 files changed, 89 insertions(+), 34 deletions(-) create mode 100644 providers/go-feature-flag/README.md rename providers/go-feature-flag/src/main/java/{org/gofeatureflag/provider => dev/openfeature/contrib/providers/gofeatureflag}/GoFeatureFlagProvider.java (90%) rename providers/go-feature-flag/src/main/java/{org/gofeatureflag/provider => dev/openfeature/contrib/providers/gofeatureflag}/GoFeatureFlagProviderOptions.java (93%) rename providers/go-feature-flag/src/main/java/{org/gofeatureflag/provider => dev/openfeature/contrib/providers/gofeatureflag}/bean/GoFeatureFlagRequest.java (75%) rename providers/go-feature-flag/src/main/java/{org/gofeatureflag/provider => dev/openfeature/contrib/providers/gofeatureflag}/bean/GoFeatureFlagResponse.java (84%) rename providers/go-feature-flag/src/main/java/{org/gofeatureflag/provider => dev/openfeature/contrib/providers/gofeatureflag}/bean/GoFeatureFlagUser.java (84%) rename providers/go-feature-flag/src/main/java/{org/gofeatureflag/provider => dev/openfeature/contrib/providers/gofeatureflag}/exception/GoFeatureFlagException.java (65%) rename providers/go-feature-flag/src/main/java/{org/gofeatureflag/provider => dev/openfeature/contrib/providers/gofeatureflag}/exception/InvalidEndpoint.java (64%) rename providers/go-feature-flag/src/main/java/{org/gofeatureflag/provider => dev/openfeature/contrib/providers/gofeatureflag}/exception/InvalidOptions.java (65%) rename providers/go-feature-flag/src/main/java/{org/gofeatureflag/provider => dev/openfeature/contrib/providers/gofeatureflag}/exception/InvalidTargetingKey.java (75%) rename providers/go-feature-flag/src/test/java/{org/gofeatureflag/provider => dev/openfeature/contrib/providers/gofeatureflag}/GoFeatureFlagProviderTest.java (94%) diff --git a/providers/go-feature-flag/README.md b/providers/go-feature-flag/README.md new file mode 100644 index 000000000..3b75b01c0 --- /dev/null +++ b/providers/go-feature-flag/README.md @@ -0,0 +1,30 @@ +# GO Feature Flag Java Provider + +GO Feature Flag provider allows you to connect to your [GO Feature Flag relay proxy](https://gofeatureflag.org) instance. + +## How to use this provider? + +To initialize your instance please follow this example: + +```java +import dev.openfeature.contrib.providers.gofeatureflag; + +// ... +new GoFeatureFlagProvider( + GoFeatureFlagProviderOptions + .builder() + .endpoint("https://my-gofeatureflag-instance.org") + .timeout(1000) + .build()); +``` + +You will have a new instance ready to be used with your `open-feature` java SDK. + +### Options + +| name | mandatory | Description | +|--------------------------|-----------|----------------------------------------------------------------------------------------------------------------| +| **`endpoint`** | `true` | endpoint contains the DNS of your GO Feature Flag relay proxy _(ex: https://mydomain.com/gofeatureflagproxy/)_ | +| **`timeout`** | `false` | timeout in millisecond we are waiting when calling the go-feature-flag relay proxy API. _(default: 10000)_ | +| **`maxIdleConnections`** | `false` | maxIdleConnections is the maximum number of connexions in the connexion pool. _(default: 1000)_ | +| **`keepAliveDuration`** | `false` | keepAliveDuration is the time in millisecond we keep the connexion open. _(default: 7200000 (2 hours))_ | diff --git a/providers/go-feature-flag/pom.xml b/providers/go-feature-flag/pom.xml index 83295746c..119d450e5 100644 --- a/providers/go-feature-flag/pom.xml +++ b/providers/go-feature-flag/pom.xml @@ -1,5 +1,6 @@ - + 4.0.0 dev.openfeature.contrib diff --git a/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/GoFeatureFlagProvider.java b/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/GoFeatureFlagProvider.java similarity index 90% rename from providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/GoFeatureFlagProvider.java rename to providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/GoFeatureFlagProvider.java index 1c7a25ed6..5586f886e 100644 --- a/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/GoFeatureFlagProvider.java +++ b/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/GoFeatureFlagProvider.java @@ -1,19 +1,33 @@ -package org.gofeatureflag.provider; +package dev.openfeature.contrib.providers.gofeatureflag; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import dev.openfeature.javasdk.*; +import dev.openfeature.contrib.providers.gofeatureflag.bean.GoFeatureFlagRequest; +import dev.openfeature.contrib.providers.gofeatureflag.bean.GoFeatureFlagResponse; +import dev.openfeature.contrib.providers.gofeatureflag.bean.GoFeatureFlagUser; +import dev.openfeature.contrib.providers.gofeatureflag.exception.InvalidEndpoint; +import dev.openfeature.contrib.providers.gofeatureflag.exception.InvalidOptions; +import dev.openfeature.javasdk.ErrorCode; +import dev.openfeature.javasdk.EvaluationContext; +import dev.openfeature.javasdk.FeatureProvider; +import dev.openfeature.javasdk.Hook; +import dev.openfeature.javasdk.Metadata; +import dev.openfeature.javasdk.ProviderEvaluation; +import dev.openfeature.javasdk.Reason; +import dev.openfeature.javasdk.Structure; +import dev.openfeature.javasdk.Value; import dev.openfeature.javasdk.exceptions.FlagNotFoundError; import dev.openfeature.javasdk.exceptions.GeneralError; import dev.openfeature.javasdk.exceptions.OpenFeatureError; import dev.openfeature.javasdk.exceptions.TypeMismatchError; -import okhttp3.*; -import org.gofeatureflag.provider.bean.GoFeatureFlagRequest; -import org.gofeatureflag.provider.bean.GoFeatureFlagResponse; -import org.gofeatureflag.provider.bean.GoFeatureFlagUser; -import org.gofeatureflag.provider.exception.InvalidEndpoint; -import org.gofeatureflag.provider.exception.InvalidOptions; +import okhttp3.ConnectionPool; +import okhttp3.HttpUrl; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; import java.io.IOException; import java.time.Instant; @@ -33,6 +47,11 @@ public class GoFeatureFlagProvider implements FeatureProvider { // httpClient is the instance of the OkHttpClient used by the provider private OkHttpClient httpClient; + /** + * Constructor of the provider + * @param options - options to configure the provider + * @throws InvalidOptions - if options are invalid + */ public GoFeatureFlagProvider(GoFeatureFlagProviderOptions options) throws InvalidOptions { this.validateInputOptions(options); this.endpoint = options.getEndpoint(); diff --git a/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/GoFeatureFlagProviderOptions.java b/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/GoFeatureFlagProviderOptions.java similarity index 93% rename from providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/GoFeatureFlagProviderOptions.java rename to providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/GoFeatureFlagProviderOptions.java index fc7f9a6a0..e1c28df62 100644 --- a/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/GoFeatureFlagProviderOptions.java +++ b/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/GoFeatureFlagProviderOptions.java @@ -1,4 +1,4 @@ -package org.gofeatureflag.provider; +package dev.openfeature.contrib.providers.gofeatureflag; import lombok.Builder; import lombok.Getter; diff --git a/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/bean/GoFeatureFlagRequest.java b/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/bean/GoFeatureFlagRequest.java similarity index 75% rename from providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/bean/GoFeatureFlagRequest.java rename to providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/bean/GoFeatureFlagRequest.java index 4febd2e2b..9ed9d063b 100644 --- a/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/bean/GoFeatureFlagRequest.java +++ b/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/bean/GoFeatureFlagRequest.java @@ -1,4 +1,4 @@ -package org.gofeatureflag.provider.bean; +package dev.openfeature.contrib.providers.gofeatureflag.bean; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/bean/GoFeatureFlagResponse.java b/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/bean/GoFeatureFlagResponse.java similarity index 84% rename from providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/bean/GoFeatureFlagResponse.java rename to providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/bean/GoFeatureFlagResponse.java index 0c1dc98a0..29ea57771 100644 --- a/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/bean/GoFeatureFlagResponse.java +++ b/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/bean/GoFeatureFlagResponse.java @@ -1,4 +1,4 @@ -package org.gofeatureflag.provider.bean; +package dev.openfeature.contrib.providers.gofeatureflag.bean; import lombok.Getter; import lombok.Setter; diff --git a/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/bean/GoFeatureFlagUser.java b/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/bean/GoFeatureFlagUser.java similarity index 84% rename from providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/bean/GoFeatureFlagUser.java rename to providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/bean/GoFeatureFlagUser.java index a3c3cb2d7..8a63256a9 100644 --- a/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/bean/GoFeatureFlagUser.java +++ b/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/bean/GoFeatureFlagUser.java @@ -1,12 +1,12 @@ -package org.gofeatureflag.provider.bean; +package dev.openfeature.contrib.providers.gofeatureflag.bean; import com.fasterxml.jackson.annotation.JsonInclude; +import dev.openfeature.contrib.providers.gofeatureflag.exception.InvalidTargetingKey; import dev.openfeature.javasdk.EvaluationContext; import dev.openfeature.javasdk.Value; import lombok.Builder; import lombok.Getter; -import org.gofeatureflag.provider.exception.InvalidTargetingKey; import java.util.Map; @@ -14,10 +14,11 @@ @Getter @JsonInclude(JsonInclude.Include.NON_NULL) public class GoFeatureFlagUser { + private static final String anonymousFieldName = "anonymous"; private final String key; private final boolean anonymous; private final Map custom; - private static final String anonymousFieldName = "anonymous"; + /** * fromEvaluationContext is transforming the evaluationContext into a GoFeatureFlagUser * @@ -26,7 +27,9 @@ public class GoFeatureFlagUser { */ public static GoFeatureFlagUser fromEvaluationContext(EvaluationContext ctx) { String key = ctx.getTargetingKey(); - if (key == null || "".equals(key)) { throw new InvalidTargetingKey(); } + if (key == null || "".equals(key)) { + throw new InvalidTargetingKey(); + } Value anonymousValue = ctx.getValue(anonymousFieldName); boolean anonymous = anonymousValue != null && anonymousValue.isBoolean() ? anonymousValue.asBoolean() : false; diff --git a/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/exception/GoFeatureFlagException.java b/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/exception/GoFeatureFlagException.java similarity index 65% rename from providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/exception/GoFeatureFlagException.java rename to providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/exception/GoFeatureFlagException.java index 6018b2f93..8cd3eb240 100644 --- a/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/exception/GoFeatureFlagException.java +++ b/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/exception/GoFeatureFlagException.java @@ -1,4 +1,4 @@ -package org.gofeatureflag.provider.exception; +package dev.openfeature.contrib.providers.gofeatureflag.exception; import lombok.experimental.StandardException; diff --git a/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/exception/InvalidEndpoint.java b/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/exception/InvalidEndpoint.java similarity index 64% rename from providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/exception/InvalidEndpoint.java rename to providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/exception/InvalidEndpoint.java index 93fae8a18..0ead98746 100644 --- a/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/exception/InvalidEndpoint.java +++ b/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/exception/InvalidEndpoint.java @@ -1,4 +1,4 @@ -package org.gofeatureflag.provider.exception; +package dev.openfeature.contrib.providers.gofeatureflag.exception; import lombok.experimental.StandardException; diff --git a/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/exception/InvalidOptions.java b/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/exception/InvalidOptions.java similarity index 65% rename from providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/exception/InvalidOptions.java rename to providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/exception/InvalidOptions.java index a6ab51221..033feca44 100644 --- a/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/exception/InvalidOptions.java +++ b/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/exception/InvalidOptions.java @@ -1,4 +1,4 @@ -package org.gofeatureflag.provider.exception; +package dev.openfeature.contrib.providers.gofeatureflag.exception; import lombok.experimental.StandardException; diff --git a/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/exception/InvalidTargetingKey.java b/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/exception/InvalidTargetingKey.java similarity index 75% rename from providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/exception/InvalidTargetingKey.java rename to providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/exception/InvalidTargetingKey.java index 02c952412..de8400cf6 100644 --- a/providers/go-feature-flag/src/main/java/org/gofeatureflag/provider/exception/InvalidTargetingKey.java +++ b/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/exception/InvalidTargetingKey.java @@ -1,10 +1,8 @@ -package org.gofeatureflag.provider.exception; +package dev.openfeature.contrib.providers.gofeatureflag.exception; import dev.openfeature.javasdk.ErrorCode; import dev.openfeature.javasdk.exceptions.OpenFeatureError; -import javax.management.openmbean.OpenDataException; - public class InvalidTargetingKey extends OpenFeatureError { public ErrorCode getErrorCode() { // Should change as soon as we have a better error type. diff --git a/providers/go-feature-flag/src/test/java/org/gofeatureflag/provider/GoFeatureFlagProviderTest.java b/providers/go-feature-flag/src/test/java/dev/openfeature/contrib/providers/gofeatureflag/GoFeatureFlagProviderTest.java similarity index 94% rename from providers/go-feature-flag/src/test/java/org/gofeatureflag/provider/GoFeatureFlagProviderTest.java rename to providers/go-feature-flag/src/test/java/dev/openfeature/contrib/providers/gofeatureflag/GoFeatureFlagProviderTest.java index 2ddae408e..927339c8d 100644 --- a/providers/go-feature-flag/src/test/java/org/gofeatureflag/provider/GoFeatureFlagProviderTest.java +++ b/providers/go-feature-flag/src/test/java/dev/openfeature/contrib/providers/gofeatureflag/GoFeatureFlagProviderTest.java @@ -1,6 +1,13 @@ -package org.gofeatureflag.provider; - -import dev.openfeature.javasdk.*; +package dev.openfeature.contrib.providers.gofeatureflag; + +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.javasdk.EvaluationContext; +import dev.openfeature.javasdk.ProviderEvaluation; +import dev.openfeature.javasdk.Reason; +import dev.openfeature.javasdk.Structure; +import dev.openfeature.javasdk.Value; import dev.openfeature.javasdk.exceptions.FlagNotFoundError; import dev.openfeature.javasdk.exceptions.GeneralError; import dev.openfeature.javasdk.exceptions.TypeMismatchError; @@ -10,9 +17,6 @@ import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; -import org.gofeatureflag.provider.exception.InvalidEndpoint; -import org.gofeatureflag.provider.exception.InvalidOptions; -import org.gofeatureflag.provider.exception.InvalidTargetingKey; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -27,9 +31,6 @@ import static org.junit.jupiter.api.Assertions.*; class GoFeatureFlagProviderTest { - private MockWebServer server; - private HttpUrl baseUrl; - private EvaluationContext evaluationContext; // Dispatcher is the configuration of the mock server to test the provider. final Dispatcher dispatcher = new Dispatcher() { @SneakyThrows @@ -47,6 +48,9 @@ public MockResponse dispatch(RecordedRequest request) { return new MockResponse().setResponseCode(404); } }; + private MockWebServer server; + private HttpUrl baseUrl; + private EvaluationContext evaluationContext; @BeforeEach void beforeEach() throws IOException { @@ -65,7 +69,7 @@ void beforeEach() throws IOException { this.evaluationContext.add("rate", 3.14); this.evaluationContext.add("age", 30); this.evaluationContext.add("company_info", new Structure().add("name", "my_company").add("size", 120)); - List labels = new ArrayList(); + List labels = new ArrayList<>(); labels.add(new Value("pro")); labels.add(new Value("beta")); this.evaluationContext.add("labels", labels); @@ -230,7 +234,7 @@ void should_use_double_default_value_if_the_flag_is_disabled() throws InvalidOpt void should_resolve_a_valid_value_flag_with_TARGETING_MATCH_reason() throws InvalidOptions { GoFeatureFlagProvider g = new GoFeatureFlagProvider(GoFeatureFlagProviderOptions.builder().endpoint(this.baseUrl.toString()).timeout(1000).build()); ProviderEvaluation res = g.getObjectEvaluation("object_key", null, this.evaluationContext); - Value want = new Value(new Structure().add("test", "test1").add("test2", false).add("test3", 123.3).add("test4",1)); + Value want = new Value(new Structure().add("test", "test1").add("test2", false).add("test3", 123.3).add("test4", 1)); assertEquals(want, res.getValue()); assertEquals("", res.getErrorCode()); assertEquals(Reason.TARGETING_MATCH, res.getReason()); @@ -260,7 +264,7 @@ void should_use_object_default_value_if_the_flag_is_disabled() throws InvalidOpt @Test void should_resolve_a_valid_value_flag_with_a_list() throws InvalidOptions { GoFeatureFlagProvider g = new GoFeatureFlagProvider(GoFeatureFlagProviderOptions.builder().endpoint(this.baseUrl.toString()).timeout(1000).build()); - assertThrows(InvalidTargetingKey.class,() -> g.getObjectEvaluation("list_key", null, new EvaluationContext())); + assertThrows(InvalidTargetingKey.class, () -> g.getObjectEvaluation("list_key", null, new EvaluationContext())); } @Test From 949f971c4c73bf73dec8c0e387529b175cba5e5a Mon Sep 17 00:00:00 2001 From: Thomas Poignant Date: Fri, 23 Sep 2022 17:29:31 +0200 Subject: [PATCH 5/7] Fix parent version Signed-off-by: Thomas Poignant --- providers/go-feature-flag/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/providers/go-feature-flag/pom.xml b/providers/go-feature-flag/pom.xml index 119d450e5..80ab079c3 100644 --- a/providers/go-feature-flag/pom.xml +++ b/providers/go-feature-flag/pom.xml @@ -4,8 +4,8 @@ 4.0.0 dev.openfeature.contrib - java-sdk-contrib - 0.0.0 + parent + 0.0.2 ../../pom.xml dev.openfeature.contrib.providers From 6db011c5d0f9d82f35f78f9408d4685204c4c4d2 Mon Sep 17 00:00:00 2001 From: Thomas Poignant Date: Fri, 23 Sep 2022 17:46:48 +0200 Subject: [PATCH 6/7] Linting issue Signed-off-by: Thomas Poignant --- .../gofeatureflag/GoFeatureFlagProvider.java | 61 +++++++++++++------ .../GoFeatureFlagProviderOptions.java | 3 + .../bean/GoFeatureFlagRequest.java | 5 ++ .../bean/GoFeatureFlagResponse.java | 5 ++ .../gofeatureflag/bean/GoFeatureFlagUser.java | 5 +- .../exception/GoFeatureFlagException.java | 3 + .../exception/InvalidEndpoint.java | 3 + .../exception/InvalidOptions.java | 3 + .../exception/InvalidTargetingKey.java | 5 ++ 9 files changed, 72 insertions(+), 21 deletions(-) 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 5586f886e..5e56a1917 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 @@ -39,6 +39,9 @@ import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; +/** + * GoFeatureFlagProvider is the JAVA provider implementation for the feature flag solution GO Feature Flag. + */ public class GoFeatureFlagProvider implements FeatureProvider { private static final String NAME = "GO Feature Flag Provider"; private static final ObjectMapper requestMapper = new ObjectMapper(); @@ -48,7 +51,8 @@ public class GoFeatureFlagProvider implements FeatureProvider { private OkHttpClient httpClient; /** - * Constructor of the provider + * Constructor of the provider. + * * @param options - options to configure the provider * @throws InvalidOptions - if options are invalid */ @@ -77,7 +81,7 @@ private void validateInputOptions(GoFeatureFlagProviderOptions options) throws I } /** - * initializeProvider is initializing the different class element used by the provider + * initializeProvider is initializing the different class element used by the provider. * * @param options - Options used while creating the provider */ @@ -113,32 +117,42 @@ public List getProviderHooks() { @Override - public ProviderEvaluation getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext evaluationContext) { + public ProviderEvaluation getBooleanEvaluation( + String key, Boolean defaultValue, EvaluationContext evaluationContext + ) { return resolveEvaluationGoFeatureFlagProxy(key, defaultValue, evaluationContext, Boolean.class); } @Override - public ProviderEvaluation getStringEvaluation(String key, String defaultValue, EvaluationContext evaluationContext) { + public ProviderEvaluation getStringEvaluation( + String key, String defaultValue, EvaluationContext evaluationContext + ) { return resolveEvaluationGoFeatureFlagProxy(key, defaultValue, evaluationContext, String.class); } @Override - public ProviderEvaluation getIntegerEvaluation(String key, Integer defaultValue, EvaluationContext evaluationContext) { + public ProviderEvaluation getIntegerEvaluation( + String key, Integer defaultValue, EvaluationContext evaluationContext + ) { return resolveEvaluationGoFeatureFlagProxy(key, defaultValue, evaluationContext, Integer.class); } @Override - public ProviderEvaluation getDoubleEvaluation(String key, Double defaultValue, EvaluationContext evaluationContext) { + public ProviderEvaluation getDoubleEvaluation( + String key, Double defaultValue, EvaluationContext evaluationContext + ) { return resolveEvaluationGoFeatureFlagProxy(key, defaultValue, evaluationContext, Double.class); } @Override - public ProviderEvaluation getObjectEvaluation(String key, Value defaultValue, EvaluationContext evaluationContext) { + public ProviderEvaluation getObjectEvaluation( + String key, Value defaultValue, EvaluationContext evaluationContext + ) { return resolveEvaluationGoFeatureFlagProxy(key, defaultValue, evaluationContext, Value.class); } /** - * resolveEvaluationGoFeatureFlagProxy is calling the GO Feature Flag API to retrieve the flag value + * resolveEvaluationGoFeatureFlagProxy is calling the GO Feature Flag API to retrieve the flag value. * * @param key - name of the feature flag * @param defaultValue - value used if something is not working as expected @@ -147,7 +161,9 @@ public ProviderEvaluation getObjectEvaluation(String key, Value defaultVa * @return a ProviderEvaluation that contains the open-feature response * @throws OpenFeatureError - if an error happen */ - private ProviderEvaluation resolveEvaluationGoFeatureFlagProxy(String key, T defaultValue, EvaluationContext ctx, Class expectedType) throws OpenFeatureError { + private ProviderEvaluation resolveEvaluationGoFeatureFlagProxy( + String key, T defaultValue, EvaluationContext ctx, Class expectedType) + throws OpenFeatureError { try { GoFeatureFlagUser user = GoFeatureFlagUser.fromEvaluationContext(ctx); GoFeatureFlagRequest goffRequest = new GoFeatureFlagRequest(user, defaultValue); @@ -162,14 +178,17 @@ private ProviderEvaluation resolveEvaluationGoFeatureFlagProxy(String key Request request = new Request.Builder() .url(url) .addHeader("Content-Type", "application/json") - .post(RequestBody.create(requestMapper.writeValueAsBytes(goffRequest), MediaType.get("application/json; charset=utf-8"))) + .post(RequestBody.create( + requestMapper.writeValueAsBytes(goffRequest), + MediaType.get("application/json; charset=utf-8"))) .build(); try (Response response = this.httpClient.newCall(request).execute()) { if (response.code() >= HTTP_BAD_REQUEST) { throw new GeneralError("impossible to contact GO Feature Flag relay proxy instance"); } - GoFeatureFlagResponse goffResp = responseMapper.readValue(response.body().string(), GoFeatureFlagResponse.class); + GoFeatureFlagResponse goffResp = + responseMapper.readValue(response.body().string(), GoFeatureFlagResponse.class); if (Reason.DISABLED.name().equalsIgnoreCase(goffResp.getReason())) { // we don't set a variant since we are using the default value, and we are not able to know @@ -181,17 +200,18 @@ private ProviderEvaluation resolveEvaluationGoFeatureFlagProxy(String key throw new FlagNotFoundError("Flag " + key + " was not found in your configuration"); } - boolean isPrimitive = expectedType == Boolean.class || - expectedType == String.class || - expectedType == Integer.class || - expectedType == Double.class; + boolean isPrimitive = expectedType == Boolean.class + || expectedType == String.class + || expectedType == Integer.class + || expectedType == Double.class; // Convert the value received from the API. T flagValue = isPrimitive ? goffResp.getValue() : (T) objectToValue(goffResp.getValue()); if (flagValue.getClass() != expectedType) { - throw new TypeMismatchError("Flag value " + key + " had unexpected type " + flagValue.getClass() + ", expected " + expectedType + "."); + throw new TypeMismatchError("Flag value " + key + " had unexpected type " + + flagValue.getClass() + ", expected " + expectedType + "."); } return ProviderEvaluation.builder() @@ -209,8 +229,7 @@ private ProviderEvaluation resolveEvaluationGoFeatureFlagProxy(String key /** - * mapReason is mapping the reason in string received by the API - * to our internal SDK reason enum. + * mapReason is mapping the reason in string received by the API to our internal SDK reason enum. * * @param reason - string of the reason received from the API * @return an item from the enum @@ -258,13 +277,15 @@ private Value objectToValue(Object object) { } /** - * mapToStructure transform a map coming from a JSON Object to a Structure type + * mapToStructure transform a map coming from a JSON Object to a Structure type. * * @param map - JSON object return by the API * @return a Structure object in the SDK format */ private Structure mapToStructure(Map map) { return new Structure( - map.entrySet().stream().filter(e -> e.getValue() != null).collect(Collectors.toMap(Map.Entry::getKey, e -> objectToValue(e.getValue())))); + map.entrySet().stream() + .filter(e -> e.getValue() != null) + .collect(Collectors.toMap(Map.Entry::getKey, e -> objectToValue(e.getValue())))); } } 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 e1c28df62..f35d8b8c7 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 @@ -3,6 +3,9 @@ import lombok.Builder; import lombok.Getter; +/** + * GoFeatureFlagProviderOptions contains the options to initialise the provider. + */ @Builder public class GoFeatureFlagProviderOptions { diff --git a/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/bean/GoFeatureFlagRequest.java b/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/bean/GoFeatureFlagRequest.java index 9ed9d063b..a0a98321f 100644 --- a/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/bean/GoFeatureFlagRequest.java +++ b/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/bean/GoFeatureFlagRequest.java @@ -3,6 +3,11 @@ import lombok.AllArgsConstructor; import lombok.Getter; +/** + * GoFeatureFlagRequest is the request send to the relay proxy. + * + * @param The default value we are using. + */ @Getter @AllArgsConstructor public class GoFeatureFlagRequest { 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 29ea57771..19c8d352c 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,11 @@ import lombok.Setter; import lombok.ToString; +/** + * GoFeatureFlagResponse is the response returned by the relay proxy. + * + * @param The type of the response. + */ @Getter @Setter @ToString diff --git a/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/bean/GoFeatureFlagUser.java b/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/bean/GoFeatureFlagUser.java index 8a63256a9..24c807010 100644 --- a/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/bean/GoFeatureFlagUser.java +++ b/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/bean/GoFeatureFlagUser.java @@ -10,6 +10,9 @@ import java.util.Map; +/** + * GoFeatureFlagUser is the representation of a user for GO Feature Flag. + */ @Builder @Getter @JsonInclude(JsonInclude.Include.NON_NULL) @@ -20,7 +23,7 @@ public class GoFeatureFlagUser { private final Map custom; /** - * fromEvaluationContext is transforming the evaluationContext into a GoFeatureFlagUser + * fromEvaluationContext is transforming the evaluationContext into a GoFeatureFlagUser. * * @param ctx - EvaluationContext from open-feature * @return GoFeatureFlagUser format for GO Feature Flag diff --git a/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/exception/GoFeatureFlagException.java b/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/exception/GoFeatureFlagException.java index 8cd3eb240..272914a7b 100644 --- a/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/exception/GoFeatureFlagException.java +++ b/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/exception/GoFeatureFlagException.java @@ -2,6 +2,9 @@ import lombok.experimental.StandardException; +/** + * GoFeatureFlagException is the main exception for the provider. + */ @StandardException public class GoFeatureFlagException extends Exception { } diff --git a/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/exception/InvalidEndpoint.java b/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/exception/InvalidEndpoint.java index 0ead98746..e1762929d 100644 --- a/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/exception/InvalidEndpoint.java +++ b/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/exception/InvalidEndpoint.java @@ -2,6 +2,9 @@ import lombok.experimental.StandardException; +/** + * InvalidEndpoint is thrown when we don't have any endpoint in the configuration. + */ @StandardException public class InvalidEndpoint extends InvalidOptions { } diff --git a/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/exception/InvalidOptions.java b/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/exception/InvalidOptions.java index 033feca44..432d5a8cc 100644 --- a/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/exception/InvalidOptions.java +++ b/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/exception/InvalidOptions.java @@ -2,6 +2,9 @@ import lombok.experimental.StandardException; +/** + * InvalidOptions is the super Exception used when we have a configuration exception. + */ @StandardException public class InvalidOptions extends GoFeatureFlagException { } diff --git a/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/exception/InvalidTargetingKey.java b/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/exception/InvalidTargetingKey.java index de8400cf6..506e5d689 100644 --- a/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/exception/InvalidTargetingKey.java +++ b/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/exception/InvalidTargetingKey.java @@ -2,7 +2,12 @@ import dev.openfeature.javasdk.ErrorCode; import dev.openfeature.javasdk.exceptions.OpenFeatureError; +import lombok.experimental.StandardException; +/** + * InvalidTargetingKey is the error send when we don't have a targeting key. + */ +@StandardException public class InvalidTargetingKey extends OpenFeatureError { public ErrorCode getErrorCode() { // Should change as soon as we have a better error type. From 034878b1aa49be4e3c2d35f4c5f2fdf54b5fda21 Mon Sep 17 00:00:00 2001 From: Thomas Poignant Date: Sat, 24 Sep 2022 12:35:50 +0200 Subject: [PATCH 7/7] Fix findbugs issue Signed-off-by: Thomas Poignant --- providers/go-feature-flag/lombok.config | 5 ++ .../gofeatureflag/GoFeatureFlagProvider.java | 55 +++++++++++++------ .../bean/GoFeatureFlagResponse.java | 6 +- .../gofeatureflag/bean/GoFeatureFlagUser.java | 8 +-- .../GoFeatureFlagProviderTest.java | 8 +-- 5 files changed, 52 insertions(+), 30 deletions(-) create mode 100644 providers/go-feature-flag/lombok.config diff --git a/providers/go-feature-flag/lombok.config b/providers/go-feature-flag/lombok.config new file mode 100644 index 000000000..bcd1afdae --- /dev/null +++ b/providers/go-feature-flag/lombok.config @@ -0,0 +1,5 @@ +# This file is needed to avoid errors throw by findbugs when working with lombok. +lombok.addSuppressWarnings = true +lombok.addLombokGeneratedAnnotation = true +config.stopBubbling = true +lombok.extern.findbugs.addSuppressFBWarnings = true 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 5e56a1917..33d7d1635 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 @@ -28,12 +28,12 @@ import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; +import okhttp3.ResponseBody; import java.io.IOException; import java.time.Instant; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -46,7 +46,7 @@ public class GoFeatureFlagProvider implements FeatureProvider { private static final String NAME = "GO Feature Flag Provider"; private static final ObjectMapper requestMapper = new ObjectMapper(); private static final ObjectMapper responseMapper = new ObjectMapper(); - private final String endpoint; + private HttpUrl parsedEndpoint; // httpClient is the instance of the OkHttpClient used by the provider private OkHttpClient httpClient; @@ -58,7 +58,6 @@ public class GoFeatureFlagProvider implements FeatureProvider { */ public GoFeatureFlagProvider(GoFeatureFlagProviderOptions options) throws InvalidOptions { this.validateInputOptions(options); - this.endpoint = options.getEndpoint(); this.initializeProvider(options); } @@ -85,7 +84,7 @@ private void validateInputOptions(GoFeatureFlagProviderOptions options) throws I * * @param options - Options used while creating the provider */ - private void initializeProvider(GoFeatureFlagProviderOptions options) { + private void initializeProvider(GoFeatureFlagProviderOptions options) throws InvalidEndpoint { // Register JavaTimeModule to be able to deserialized java.time.Instant Object requestMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); requestMapper.enable(SerializationFeature.INDENT_OUTPUT); @@ -103,6 +102,11 @@ private void initializeProvider(GoFeatureFlagProviderOptions options) { .writeTimeout(timeout, TimeUnit.MILLISECONDS) .connectionPool(new ConnectionPool(maxIdleConnections, keepAliveDuration, TimeUnit.MILLISECONDS)) .build(); + + this.parsedEndpoint = HttpUrl.parse(options.getEndpoint()); + if (this.parsedEndpoint == null) { + throw new InvalidEndpoint(); + } } @Override @@ -162,13 +166,13 @@ public ProviderEvaluation getObjectEvaluation( * @throws OpenFeatureError - if an error happen */ private ProviderEvaluation resolveEvaluationGoFeatureFlagProxy( - String key, T defaultValue, EvaluationContext ctx, Class expectedType) - throws OpenFeatureError { + String key, T defaultValue, EvaluationContext ctx, Class expectedType + ) throws OpenFeatureError { try { GoFeatureFlagUser user = GoFeatureFlagUser.fromEvaluationContext(ctx); GoFeatureFlagRequest goffRequest = new GoFeatureFlagRequest(user, defaultValue); - HttpUrl url = Objects.requireNonNull(HttpUrl.parse(this.endpoint)) - .newBuilder() + + HttpUrl url = this.parsedEndpoint.newBuilder() .addEncodedPathSegment("v1") .addEncodedPathSegment("feature") .addEncodedPathSegment(key) @@ -187,8 +191,11 @@ private ProviderEvaluation resolveEvaluationGoFeatureFlagProxy( if (response.code() >= HTTP_BAD_REQUEST) { throw new GeneralError("impossible to contact GO Feature Flag relay proxy instance"); } - GoFeatureFlagResponse goffResp = - responseMapper.readValue(response.body().string(), GoFeatureFlagResponse.class); + + ResponseBody responseBody = response.body(); + String body = responseBody != null ? responseBody.string() : ""; + GoFeatureFlagResponse goffResp = + responseMapper.readValue(body, GoFeatureFlagResponse.class); if (Reason.DISABLED.name().equalsIgnoreCase(goffResp.getReason())) { // we don't set a variant since we are using the default value, and we are not able to know @@ -200,14 +207,8 @@ private ProviderEvaluation resolveEvaluationGoFeatureFlagProxy( throw new FlagNotFoundError("Flag " + key + " was not found in your configuration"); } - boolean isPrimitive = expectedType == Boolean.class - || expectedType == String.class - || expectedType == Integer.class - || expectedType == Double.class; - // Convert the value received from the API. - T flagValue = isPrimitive - ? goffResp.getValue() : (T) objectToValue(goffResp.getValue()); + T flagValue = convertValue(goffResp.getValue(), expectedType); if (flagValue.getClass() != expectedType) { throw new TypeMismatchError("Flag value " + key + " had unexpected type " @@ -243,6 +244,26 @@ private Reason mapReason(String reason) { } + /** + * convertValue is converting the object return by the proxy response in the right type. + * + * @param value - The value we have received + * @param expectedType - the type we expect for this value + * @param the type we want to convert to. + * @return A converted object + */ + private T convertValue(Object value, Class expectedType) { + boolean isPrimitive = expectedType == Boolean.class + || expectedType == String.class + || expectedType == Integer.class + || expectedType == Double.class; + + if (isPrimitive) { + return (T) value; + } + return (T) objectToValue(value); + } + /** * objectToValue is wrapping an object into a Value. * 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 19c8d352c..75b229755 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 @@ -6,20 +6,18 @@ /** * GoFeatureFlagResponse is the response returned by the relay proxy. - * - * @param The type of the response. */ @Getter @Setter @ToString -public class GoFeatureFlagResponse { +public class GoFeatureFlagResponse { private boolean trackEvents; private String variationType; private boolean failed; private String version; private String reason; private String errorCode; - private T value; + private Object value; } diff --git a/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/bean/GoFeatureFlagUser.java b/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/bean/GoFeatureFlagUser.java index 24c807010..874c07496 100644 --- a/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/bean/GoFeatureFlagUser.java +++ b/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/bean/GoFeatureFlagUser.java @@ -1,13 +1,12 @@ package dev.openfeature.contrib.providers.gofeatureflag.bean; - -import com.fasterxml.jackson.annotation.JsonInclude; import dev.openfeature.contrib.providers.gofeatureflag.exception.InvalidTargetingKey; import dev.openfeature.javasdk.EvaluationContext; import dev.openfeature.javasdk.Value; import lombok.Builder; import lombok.Getter; +import java.util.HashMap; import java.util.Map; /** @@ -15,7 +14,6 @@ */ @Builder @Getter -@JsonInclude(JsonInclude.Include.NON_NULL) public class GoFeatureFlagUser { private static final String anonymousFieldName = "anonymous"; private final String key; @@ -35,8 +33,8 @@ public static GoFeatureFlagUser fromEvaluationContext(EvaluationContext ctx) { } Value anonymousValue = ctx.getValue(anonymousFieldName); - boolean anonymous = anonymousValue != null && anonymousValue.isBoolean() ? anonymousValue.asBoolean() : false; - Map custom = ctx.asObjectMap(); + boolean anonymous = anonymousValue.asBoolean(); + Map custom = new HashMap<>(ctx.asObjectMap()); if (ctx.getValue(anonymousFieldName) != null) { custom.remove(anonymousFieldName); } 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 927339c8d..7220466c5 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 @@ -273,10 +273,10 @@ void should_throw_an_error_if_no_targeting_key() throws InvalidOptions { ProviderEvaluation res = g.getObjectEvaluation("list_key", null, this.evaluationContext); Value want = new Value(new ArrayList<>( Arrays.asList(new Value("test"), - new Value("test1"), - new Value("test2"), - new Value("false"), - new Value("test3")))); + new Value("test1"), + new Value("test2"), + new Value("false"), + new Value("test3")))); assertEquals(want, res.getValue()); assertEquals("", res.getErrorCode()); assertEquals(Reason.TARGETING_MATCH, res.getReason());