diff --git a/providers/flipt/README.md b/providers/flipt/README.md index 34a0ae50b..107a9abed 100644 --- a/providers/flipt/README.md +++ b/providers/flipt/README.md @@ -7,26 +7,34 @@ ```xml - dev.openfeature.contrib.providers flipt - 0.0.2 + 0.1.0 ``` ## Concepts + * Boolean evaluation gets feature boolean evaluation / enabled status. * Non-boolean evaluation gets feature variant key. ## Usage -Flipt OpenFeature Provider is using Flipt Java SDK. + +Flipt OpenFeature Provider uses Flipt's [Server Java SDK](https://github.com/flipt-io/flipt-server-sdks/tree/main/flipt-java). ### Usage Example -``` +```java +// create a Flipt client and provider +FliptClientBuilder fliptClientBuilder = FliptClient.builder().url(apiUrl); +FliptProviderConfig fliptProviderConfig = FliptProviderConfig.builder() + .fliptClientBuilder(fliptClientBuilder) + .build(); + +// create OpenFeature provider FeatureProvider featureProvider = new FliptProvider(fliptProviderConfig); OpenFeatureAPI.getInstance().setProviderAndWait("sync", fliptProvider); client = OpenFeatureAPI.getInstance().getClient("sync"); diff --git a/providers/flipt/pom.xml b/providers/flipt/pom.xml index e05ef34f9..32f90396f 100644 --- a/providers/flipt/pom.xml +++ b/providers/flipt/pom.xml @@ -10,7 +10,7 @@ dev.openfeature.contrib.providers flipt - 0.0.2 + 0.1.0 flipt Flipt provider for Java @@ -24,7 +24,7 @@ io.flipt flipt-java - 0.2.15 + 1.0.1 @@ -40,12 +40,32 @@ test + + com.fasterxml.jackson.core + jackson-core + 2.16.1 + test + + + + com.fasterxml.jackson.core + jackson-databind + 2.16.1 + test + + + + com.fasterxml.jackson.core + jackson-annotations + 2.16.1 + test + + org.apache.logging.log4j log4j-slf4j2-impl 2.22.1 test - diff --git a/providers/flipt/src/main/java/dev/openfeature/contrib/providers/flipt/FliptProvider.java b/providers/flipt/src/main/java/dev/openfeature/contrib/providers/flipt/FliptProvider.java index bb01ba135..5d69576a6 100644 --- a/providers/flipt/src/main/java/dev/openfeature/contrib/providers/flipt/FliptProvider.java +++ b/providers/flipt/src/main/java/dev/openfeature/contrib/providers/flipt/FliptProvider.java @@ -1,9 +1,5 @@ package dev.openfeature.contrib.providers.flipt; -import com.flipt.api.FliptApiClient; -import com.flipt.api.resources.evaluation.types.BooleanEvaluationResponse; -import com.flipt.api.resources.evaluation.types.EvaluationRequest; -import com.flipt.api.resources.evaluation.types.VariantEvaluationResponse; import dev.openfeature.sdk.EvaluationContext; import dev.openfeature.sdk.EventProvider; import dev.openfeature.sdk.ImmutableMetadata; @@ -14,6 +10,10 @@ import dev.openfeature.sdk.Value; import dev.openfeature.sdk.exceptions.GeneralError; import dev.openfeature.sdk.exceptions.ProviderNotReadyError; +import io.flipt.api.FliptClient; +import io.flipt.api.evaluation.models.BooleanEvaluationResponse; +import io.flipt.api.evaluation.models.EvaluationRequest; +import io.flipt.api.evaluation.models.VariantEvaluationResponse; import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; @@ -41,7 +41,7 @@ public class FliptProvider extends EventProvider { @Setter(AccessLevel.PROTECTED) @Getter - private FliptApiClient fliptApiClient; + private FliptClient fliptClient; @Setter(AccessLevel.PROTECTED) @Getter @@ -51,6 +51,7 @@ public class FliptProvider extends EventProvider { /** * Constructor. + * * @param fliptProviderConfig FliptProviderConfig */ public FliptProvider(FliptProviderConfig fliptProviderConfig) { @@ -59,6 +60,7 @@ public FliptProvider(FliptProviderConfig fliptProviderConfig) { /** * Initialize the provider. + * * @param evaluationContext evaluation context * @throws Exception on error */ @@ -69,7 +71,7 @@ public void initialize(EvaluationContext evaluationContext) throws Exception { throw new GeneralError("already initialized"); } super.initialize(evaluationContext); - fliptApiClient = fliptProviderConfig.getFliptApiClientBuilder().build(); + fliptClient = fliptProviderConfig.getFliptClientBuilder().build(); state = ProviderState.READY; log.info("finished initializing provider, state: {}", state); @@ -103,32 +105,32 @@ public ProviderEvaluation getBooleanEvaluation(String key, Boolean defa Map contextMap = ContextTransformer.transform(ctx); EvaluationRequest request = EvaluationRequest.builder().namespaceKey(fliptProviderConfig.getNamespace()) - .flagKey(key).entityId(ctx.getTargetingKey()).context(contextMap).build(); + .flagKey(key).entityId(ctx.getTargetingKey()).context(contextMap).build(); BooleanEvaluationResponse response = null; try { - response = fliptApiClient.evaluation().boolean_(request); + response = fliptClient.evaluation().evaluateBoolean(request); } catch (Exception e) { log.error("Error evaluating boolean", e); throw new GeneralError(e.getMessage()); } return ProviderEvaluation.builder() - .value(response.getEnabled()) - .reason(response.getReason().toString()) - .build(); + .value(response.isEnabled()) + .reason(response.getReason().toString()) + .build(); } @Override public ProviderEvaluation getStringEvaluation(String key, String defaultValue, EvaluationContext ctx) { ProviderEvaluation valueProviderEvaluation = getObjectEvaluation(key, new Value(defaultValue), ctx); return ProviderEvaluation.builder() - .value(valueProviderEvaluation.getValue().asString()) - .variant(valueProviderEvaluation.getVariant()) + .value(valueProviderEvaluation.getValue().asString()) + .variant(valueProviderEvaluation.getVariant()) .errorCode(valueProviderEvaluation.getErrorCode()) .reason(valueProviderEvaluation.getReason()) .flagMetadata(valueProviderEvaluation.getFlagMetadata()) - .build(); + .build(); } @Override @@ -136,12 +138,12 @@ public ProviderEvaluation getIntegerEvaluation(String key, Integer defa ProviderEvaluation valueProviderEvaluation = getObjectEvaluation(key, new Value(defaultValue), ctx); Integer value = getIntegerValue(valueProviderEvaluation, defaultValue); return ProviderEvaluation.builder() - .value(value) - .variant(valueProviderEvaluation.getVariant()) - .errorCode(valueProviderEvaluation.getErrorCode()) - .reason(valueProviderEvaluation.getReason()) - .flagMetadata(valueProviderEvaluation.getFlagMetadata()) - .build(); + .value(value) + .variant(valueProviderEvaluation.getVariant()) + .errorCode(valueProviderEvaluation.getErrorCode()) + .reason(valueProviderEvaluation.getReason()) + .flagMetadata(valueProviderEvaluation.getFlagMetadata()) + .build(); } private static Integer getIntegerValue(ProviderEvaluation valueProviderEvaluation, Integer defaultValue) { @@ -158,12 +160,12 @@ public ProviderEvaluation getDoubleEvaluation(String key, Double default ProviderEvaluation valueProviderEvaluation = getObjectEvaluation(key, new Value(defaultValue), ctx); Double value = getDoubleValue(valueProviderEvaluation, defaultValue); return ProviderEvaluation.builder() - .value(value) - .variant(valueProviderEvaluation.getVariant()) - .errorCode(valueProviderEvaluation.getErrorCode()) - .reason(valueProviderEvaluation.getReason()) - .flagMetadata(valueProviderEvaluation.getFlagMetadata()) - .build(); + .value(value) + .variant(valueProviderEvaluation.getVariant()) + .errorCode(valueProviderEvaluation.getErrorCode()) + .reason(valueProviderEvaluation.getReason()) + .flagMetadata(valueProviderEvaluation.getFlagMetadata()) + .build(); } private static Double getDoubleValue(ProviderEvaluation valueProviderEvaluation, Double defaultValue) { @@ -185,22 +187,22 @@ public ProviderEvaluation getObjectEvaluation(String key, Value defaultVa } Map contextMap = ContextTransformer.transform(ctx); EvaluationRequest request = EvaluationRequest.builder().namespaceKey(fliptProviderConfig.getNamespace()) - .flagKey(key).entityId(ctx.getTargetingKey()).context(contextMap).build(); + .flagKey(key).entityId(ctx.getTargetingKey()).context(contextMap).build(); VariantEvaluationResponse response; try { - response = fliptApiClient.evaluation().variant(request); + response = fliptClient.evaluation().evaluateVariant(request); } catch (Exception e) { log.error("Error evaluating variant", e); throw new GeneralError(e.getMessage()); } - if (!response.getMatch()) { + if (!response.isMatch()) { log.debug("non matching variant for {} : {}", key, response.getReason()); return ProviderEvaluation.builder() - .value(defaultValue) - .reason(DEFAULT.name()) - .build(); + .value(defaultValue) + .reason(DEFAULT.name()) + .build(); } ImmutableMetadata.ImmutableMetadataBuilder flagMetadataBuilder = ImmutableMetadata.builder(); @@ -209,11 +211,11 @@ public ProviderEvaluation getObjectEvaluation(String key, Value defaultVa } return ProviderEvaluation.builder() - .value(new Value(response.getVariantKey())) - .variant(response.getVariantKey()) - .reason(TARGETING_MATCH.name()) - .flagMetadata(flagMetadataBuilder.build()) - .build(); + .value(new Value(response.getVariantKey())) + .variant(response.getVariantKey()) + .reason(TARGETING_MATCH.name()) + .flagMetadata(flagMetadataBuilder.build()) + .build(); } @Override diff --git a/providers/flipt/src/main/java/dev/openfeature/contrib/providers/flipt/FliptProviderConfig.java b/providers/flipt/src/main/java/dev/openfeature/contrib/providers/flipt/FliptProviderConfig.java index 4efff5470..9d26bb3d7 100644 --- a/providers/flipt/src/main/java/dev/openfeature/contrib/providers/flipt/FliptProviderConfig.java +++ b/providers/flipt/src/main/java/dev/openfeature/contrib/providers/flipt/FliptProviderConfig.java @@ -1,17 +1,16 @@ package dev.openfeature.contrib.providers.flipt; -import com.flipt.api.FliptApiClientBuilder; +import io.flipt.api.FliptClient.FliptClientBuilder; import lombok.Builder; import lombok.Getter; - /** * FliptProvider config. */ @Getter @Builder public class FliptProviderConfig { - private FliptApiClientBuilder fliptApiClientBuilder; + private FliptClientBuilder fliptClientBuilder; @Builder.Default private String namespace = "default"; diff --git a/providers/flipt/src/test/java/dev/openfeature/contrib/providers/flipt/FliptProviderTest.java b/providers/flipt/src/test/java/dev/openfeature/contrib/providers/flipt/FliptProviderTest.java index 8bfa97395..3378f840f 100644 --- a/providers/flipt/src/test/java/dev/openfeature/contrib/providers/flipt/FliptProviderTest.java +++ b/providers/flipt/src/test/java/dev/openfeature/contrib/providers/flipt/FliptProviderTest.java @@ -1,8 +1,7 @@ package dev.openfeature.contrib.providers.flipt; -import com.flipt.api.FliptApiClient; -import com.flipt.api.FliptApiClientBuilder; -import com.flipt.api.core.Environment; +import io.flipt.api.FliptClient; +import io.flipt.api.FliptClient.FliptClientBuilder; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; import com.github.tomakehurst.wiremock.junit5.WireMockTest; @@ -58,7 +57,7 @@ class FliptProviderTest { @BeforeAll void setUp(WireMockRuntimeInfo wmRuntimeInfo) { - apiUrl = "http://localhost:" + wmRuntimeInfo.getHttpPort() + "/"; + apiUrl = "http://localhost:" + wmRuntimeInfo.getHttpPort(); fliptProvider = buildFliptProvider(); OpenFeatureAPI.getInstance().setProviderAndWait("sync", fliptProvider); client = OpenFeatureAPI.getInstance().getClient("sync"); @@ -71,22 +70,23 @@ public void shutdown() { private void mockFliptAPI(String url, String resourceName, String flagKey) { stubFor( - post(urlEqualTo(url)) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(WireMock.containing(flagKey)) - .willReturn( - aResponse() - .withStatus(200) - .withHeader("Content-Type", "application/json") - .withBody(readResourceFileContent(resourceName)))); + post(urlEqualTo(url)) + .withHeader("Content-Type", equalTo("application/json; charset=UTF-8")) + .withRequestBody(WireMock.containing(flagKey)) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json; charset=UTF-8") + .withBody(readResourceFileContent(resourceName)))); } @SneakyThrows private FliptProvider buildFliptProvider() { - FliptApiClientBuilder fliptApiClientBuilder = FliptApiClient.builder().url(apiUrl).environment(Environment.custom(apiUrl)); + FliptClientBuilder fliptClientBuilder = FliptClient.builder().url(apiUrl); FliptProviderConfig fliptProviderConfig = FliptProviderConfig.builder() - .fliptApiClientBuilder(fliptApiClientBuilder) - .build(); + .fliptClientBuilder(fliptClientBuilder) + .namespace("default") + .build(); return new FliptProvider(fliptProviderConfig); } @@ -112,7 +112,7 @@ void getStringVariantEvaluation() { MutableContext evaluationContext = new MutableContext(); evaluationContext.setTargetingKey(TARGETING_KEY); assertEquals(VARIANT_FLAG_VALUE, fliptProvider.getStringEvaluation(VARIANT_FLAG_NAME, "", - evaluationContext).getValue()); + evaluationContext).getValue()); assertEquals(VARIANT_FLAG_VALUE, client.getStringValue(VARIANT_FLAG_NAME, "", evaluationContext)); assertEquals("fallback_str", client.getStringValue("non-existing", "fallback_str", evaluationContext)); } @@ -124,7 +124,7 @@ void getIntegerEvaluation() { evaluationContext.setTargetingKey(TARGETING_KEY); evaluationContext.add("userId", "int"); assertEquals(INT_FLAG_VALUE, fliptProvider.getIntegerEvaluation(INT_FLAG_NAME, 1, - evaluationContext).getValue()); + evaluationContext).getValue()); assertEquals(INT_FLAG_VALUE, client.getIntegerValue(INT_FLAG_NAME, 1, evaluationContext)); assertEquals(1, client.getIntegerValue("non-existing", 1, evaluationContext)); @@ -139,7 +139,7 @@ void getDoubleEvaluation() { evaluationContext.setTargetingKey(TARGETING_KEY); evaluationContext.add("userId", "double"); assertEquals(DOUBLE_FLAG_VALUE, fliptProvider.getDoubleEvaluation(DOUBLE_FLAG_NAME, 1.1, - evaluationContext).getValue()); + evaluationContext).getValue()); assertEquals(DOUBLE_FLAG_VALUE, client.getDoubleValue(DOUBLE_FLAG_NAME, 1.1, evaluationContext)); assertEquals(1.1, client.getDoubleValue("non-existing", 1.1, evaluationContext)); @@ -153,7 +153,8 @@ void getStringEvaluationByUser() { MutableContext evaluationContext = new MutableContext(); evaluationContext.setTargetingKey(TARGETING_KEY); evaluationContext.add("userId", "111"); - assertEquals(VARIANT_FLAG_VALUE, fliptProvider.getStringEvaluation(USERS_FLAG_NAME, "", evaluationContext).getValue()); + assertEquals(VARIANT_FLAG_VALUE, + fliptProvider.getStringEvaluation(USERS_FLAG_NAME, "", evaluationContext).getValue()); assertEquals(VARIANT_FLAG_VALUE, client.getStringValue(USERS_FLAG_NAME, "", evaluationContext)); evaluationContext.add("userId", "2"); assertEquals("", client.getStringValue(USERS_FLAG_NAME, "", evaluationContext)); @@ -165,10 +166,11 @@ void getEvaluationMetadataTest() { MutableContext evaluationContext = new MutableContext(); evaluationContext.setTargetingKey(TARGETING_KEY); ProviderEvaluation stringEvaluation = fliptProvider.getStringEvaluation(VARIANT_FLAG_NAME, "", - evaluationContext); + evaluationContext); ImmutableMetadata flagMetadata = stringEvaluation.getFlagMetadata(); assertEquals("attachment-1", flagMetadata.getString("variant-attachment")); - FlagEvaluationDetails nonExistingFlagEvaluation = client.getStringDetails("non-existing", "", evaluationContext); + FlagEvaluationDetails nonExistingFlagEvaluation = client.getStringDetails("non-existing", "", + evaluationContext); assertEquals(null, nonExistingFlagEvaluation.getFlagMetadata().getBoolean("variant-attachment")); } @@ -179,11 +181,13 @@ void shouldThrowIfNotInitialized() { assertEquals(ProviderState.NOT_READY, asyncInitfliptProvider.getState()); // ErrorCode.PROVIDER_NOT_READY should be returned when evaluated via the client - assertThrows(ProviderNotReadyError.class, ()-> asyncInitfliptProvider.getBooleanEvaluation("fail_not_initialized", false, new ImmutableContext())); - assertThrows(ProviderNotReadyError.class, ()-> asyncInitfliptProvider.getStringEvaluation("fail_not_initialized", "", new ImmutableContext())); + assertThrows(ProviderNotReadyError.class, () -> asyncInitfliptProvider + .getBooleanEvaluation("fail_not_initialized", false, new ImmutableContext())); + assertThrows(ProviderNotReadyError.class, + () -> asyncInitfliptProvider.getStringEvaluation("fail_not_initialized", "", new ImmutableContext())); asyncInitfliptProvider.initialize(null); - assertThrows(GeneralError.class, ()-> asyncInitfliptProvider.initialize(null)); + assertThrows(GeneralError.class, () -> asyncInitfliptProvider.initialize(null)); asyncInitfliptProvider.shutdown(); } @@ -197,8 +201,10 @@ void shouldThrowIfErrorEvent() { asyncInitfliptProvider.emitProviderError(ProviderEventDetails.builder().build()); // ErrorCode.PROVIDER_NOT_READY should be returned when evaluated via the client - assertThrows(GeneralError.class, ()-> asyncInitfliptProvider.getBooleanEvaluation("fail", false, new ImmutableContext())); - assertThrows(GeneralError.class, ()-> asyncInitfliptProvider.getStringEvaluation("fail", "", new ImmutableContext())); + assertThrows(GeneralError.class, + () -> asyncInitfliptProvider.getBooleanEvaluation("fail", false, new ImmutableContext())); + assertThrows(GeneralError.class, + () -> asyncInitfliptProvider.getStringEvaluation("fail", "", new ImmutableContext())); asyncInitfliptProvider.shutdown(); } diff --git a/providers/flipt/version.txt b/providers/flipt/version.txt index 4e379d2bf..6e8bf73aa 100644 --- a/providers/flipt/version.txt +++ b/providers/flipt/version.txt @@ -1 +1 @@ -0.0.2 +0.1.0