-
Notifications
You must be signed in to change notification settings - Fork 74
feat: add Prefab provider #915
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| # Changelog |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| # Unofficial Prefab OpenFeature Provider for Java | ||
|
|
||
| [Prefab](https://www.prefab.cloud/) OpenFeature Provider can provide usage for Prefab via OpenFeature Java SDK. | ||
|
|
||
| ## Installation | ||
|
|
||
| <!-- x-release-please-start-version --> | ||
|
|
||
| ```xml | ||
|
|
||
| <dependency> | ||
| <groupId>dev.openfeature.contrib.providers</groupId> | ||
| <artifactId>prefab</artifactId> | ||
| <version>0.0.1</version> | ||
| </dependency> | ||
| ``` | ||
|
|
||
| <!-- x-release-please-end-version --> | ||
|
|
||
| ## Usage | ||
| Prefab OpenFeature Provider is using Prefab Java SDK. | ||
|
|
||
| ### Usage Example | ||
|
|
||
| ``` | ||
| PrefabProviderConfig prefabProviderConfig = PrefabProviderConfig.builder().sdkKey(sdkKey).build(); | ||
| prefabProvider = new PrefabProvider(prefabProviderConfig); | ||
| OpenFeatureAPI.getInstance().setProviderAndWait(prefabProvider); | ||
|
|
||
|
|
||
| Options options = new Options().setApikey(sdkKey); | ||
| PrefabProviderConfig prefabProviderConfig = PrefabProviderConfig.builder() | ||
| .options(options).build(); | ||
| PrefabProvider prefabProvider = new PrefabProvider(prefabProviderConfig); | ||
| OpenFeatureAPI.getInstance().setProviderAndWait(prefabProvider); | ||
|
|
||
| boolean featureEnabled = client.getBooleanValue(FLAG_NAME, false); | ||
|
|
||
| MutableContext evaluationContext = new MutableContext(); | ||
| evaluationContext.add("user.key", "key1"); | ||
| evaluationContext.add("team.domain", "prefab.cloud"); | ||
| featureEnabled = client.getBooleanValue(USERS_FLAG_NAME, false, evaluationContext); | ||
| ``` | ||
|
|
||
| See [PrefabProviderTest](./src/test/java/dev/openfeature/contrib/providers/prefab/PrefabProviderTest.java) | ||
| for more information. | ||
|
|
||
| ## Notes | ||
| Some Prefab custom operations are supported from the provider client via: | ||
|
|
||
| ```java | ||
| prefabProvider.getPrefabCloudClient()... | ||
| ``` | ||
|
|
||
| ## Prefab Provider Tests Strategies | ||
|
|
||
| Unit test based on Prefab local features file. | ||
| See [PrefabProviderTest](./src/test/java/dev/openfeature/contrib/providers/prefab/PrefabProviderTest.java) | ||
| for more information. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" | ||
| xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
| <modelVersion>4.0.0</modelVersion> | ||
| <parent> | ||
| <groupId>dev.openfeature.contrib</groupId> | ||
| <artifactId>parent</artifactId> | ||
| <version>0.1.0</version> | ||
| <relativePath>../../pom.xml</relativePath> | ||
| </parent> | ||
| <groupId>dev.openfeature.contrib.providers</groupId> | ||
| <artifactId>prefab</artifactId> | ||
| <version>0.0.1</version> <!--x-release-please-version --> | ||
|
|
||
| <name>prefab</name> | ||
| <description>Prefab provider for Java</description> | ||
| <url>https://www.prefab.cloud</url> | ||
|
|
||
| <dependencies> | ||
| <dependency> | ||
| <groupId>cloud.prefab</groupId> | ||
| <artifactId>client</artifactId> | ||
| <version>0.3.20</version> | ||
| </dependency> | ||
|
|
||
| <dependency> | ||
| <groupId>org.slf4j</groupId> | ||
| <artifactId>slf4j-api</artifactId> | ||
| <version>2.0.16</version> | ||
| </dependency> | ||
|
|
||
| <dependency> | ||
| <groupId>org.apache.logging.log4j</groupId> | ||
| <artifactId>log4j-slf4j2-impl</artifactId> | ||
| <version>2.23.1</version> | ||
| <scope>test</scope> | ||
| </dependency> | ||
|
|
||
| </dependencies> | ||
| </project> | ||
37 changes: 37 additions & 0 deletions
37
...ers/prefab/src/main/java/dev/openfeature/contrib/providers/prefab/ContextTransformer.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| package dev.openfeature.contrib.providers.prefab; | ||
|
|
||
| import cloud.prefab.context.PrefabContext; | ||
| import cloud.prefab.context.PrefabContextSet; | ||
| import cloud.prefab.context.PrefabContextSetReadable; | ||
| import dev.openfeature.sdk.EvaluationContext; | ||
|
|
||
| import java.util.HashMap; | ||
| import java.util.Map; | ||
| import java.util.Objects; | ||
|
|
||
| /** | ||
| * Transformer from OpenFeature context to Prefab context. | ||
| */ | ||
| public class ContextTransformer { | ||
|
|
||
| protected static PrefabContextSetReadable transform(EvaluationContext ctx) { | ||
| Map<String, PrefabContext.Builder> contextsMap = new HashMap<>(); | ||
| ctx.asObjectMap().forEach((k, v) -> { | ||
| String[] parts = k.split("\\.", 2); | ||
| if (parts.length < 2) { | ||
| throw new IllegalArgumentException("context key structure should be in the form of x.y: " + k); | ||
| } | ||
| contextsMap.putIfAbsent(parts[0], PrefabContext.newBuilder(parts[0])); | ||
| PrefabContext.Builder contextBuilder = contextsMap.get(parts[0]); | ||
| contextBuilder.put(parts[1], Objects.toString(v, null)); | ||
| }); | ||
| PrefabContextSet prefabContextSet = new PrefabContextSet(); | ||
| contextsMap.forEach((key, value) -> { | ||
| PrefabContext prefabContext = value.build(); | ||
| prefabContextSet.addContext(prefabContext); | ||
| }); | ||
|
|
||
| return prefabContextSet; | ||
| } | ||
|
|
||
| } |
172 changes: 172 additions & 0 deletions
172
providers/prefab/src/main/java/dev/openfeature/contrib/providers/prefab/PrefabProvider.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,172 @@ | ||
| package dev.openfeature.contrib.providers.prefab; | ||
|
|
||
| import cloud.prefab.client.PrefabCloudClient; | ||
| import cloud.prefab.context.PrefabContextSetReadable; | ||
| import cloud.prefab.domain.Prefab; | ||
| import dev.openfeature.sdk.EvaluationContext; | ||
| import dev.openfeature.sdk.EventProvider; | ||
| import dev.openfeature.sdk.Metadata; | ||
| import dev.openfeature.sdk.ProviderEvaluation; | ||
| import dev.openfeature.sdk.ProviderEventDetails; | ||
| import dev.openfeature.sdk.ProviderState; | ||
| import dev.openfeature.sdk.Value; | ||
| import dev.openfeature.sdk.exceptions.GeneralError; | ||
| import dev.openfeature.sdk.exceptions.ProviderNotReadyError; | ||
| import lombok.Getter; | ||
| import lombok.SneakyThrows; | ||
| import lombok.extern.slf4j.Slf4j; | ||
|
|
||
| import java.util.Collections; | ||
| import java.util.Optional; | ||
| import java.util.concurrent.atomic.AtomicBoolean; | ||
|
|
||
| /** | ||
| * Provider implementation for Prefab. | ||
| */ | ||
| @Slf4j | ||
| public class PrefabProvider extends EventProvider { | ||
|
|
||
| @Getter | ||
| private static final String NAME = "Prefab"; | ||
|
|
||
| public static final String PROVIDER_NOT_YET_INITIALIZED = "provider not yet initialized"; | ||
| public static final String UNKNOWN_ERROR = "unknown error"; | ||
|
|
||
| private final PrefabProviderConfig prefabProviderConfig; | ||
|
|
||
| @Getter | ||
| private PrefabCloudClient prefabCloudClient; | ||
|
|
||
| @Getter | ||
| private ProviderState state = ProviderState.NOT_READY; | ||
|
|
||
| private final AtomicBoolean isInitialized = new AtomicBoolean(false); | ||
|
|
||
| /** | ||
| * Constructor. | ||
| * @param prefabProviderConfig prefabProvider Config | ||
| */ | ||
| public PrefabProvider(PrefabProviderConfig prefabProviderConfig) { | ||
| this.prefabProviderConfig = prefabProviderConfig; | ||
| } | ||
|
|
||
| /** | ||
| * Initialize the provider. | ||
| * @param evaluationContext evaluation context | ||
| * @throws Exception on error | ||
| */ | ||
| @Override | ||
| public void initialize(EvaluationContext evaluationContext) throws Exception { | ||
| boolean initialized = isInitialized.getAndSet(true); | ||
| if (initialized) { | ||
| throw new GeneralError("already initialized"); | ||
| } | ||
| super.initialize(evaluationContext); | ||
| prefabCloudClient = new PrefabCloudClient(prefabProviderConfig.getOptions()); | ||
| prefabProviderConfig.postInit(); | ||
| state = ProviderState.READY; | ||
| log.info("finished initializing provider, state: {}", state); | ||
|
|
||
| prefabProviderConfig.getOptions().addConfigChangeListener(changeEvent -> { | ||
| ProviderEventDetails providerEventDetails = ProviderEventDetails.builder() | ||
| .flagsChanged(Collections.singletonList(changeEvent.getKey())) | ||
| .message("config changed") | ||
| .build(); | ||
| emitProviderConfigurationChanged(providerEventDetails); | ||
| }); | ||
| } | ||
|
|
||
| @Override | ||
| public Metadata getMetadata() { | ||
| return () -> NAME; | ||
| } | ||
|
|
||
| @Override | ||
| public ProviderEvaluation<Boolean> getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) { | ||
| verifyEvaluation(); | ||
| PrefabContextSetReadable context = ctx == null ? null : ContextTransformer.transform(ctx); | ||
| Boolean evaluatedValue = prefabCloudClient.featureFlagClient().featureIsOn(key, context); | ||
| return ProviderEvaluation.<Boolean>builder() | ||
| .value(evaluatedValue) | ||
| .build(); | ||
| } | ||
|
|
||
| @Override | ||
| public ProviderEvaluation<String> getStringEvaluation(String key, String defaultValue, EvaluationContext ctx) { | ||
| verifyEvaluation(); | ||
| PrefabContextSetReadable context = ctx == null ? null : ContextTransformer.transform(ctx); | ||
| String evaluatedValue = defaultValue; | ||
| Optional<Prefab.ConfigValue> opt = prefabCloudClient.featureFlagClient().get(key, context); | ||
| if (opt.isPresent() && Prefab.ConfigValue.TypeCase.STRING.equals(opt.get().getTypeCase())) { | ||
| evaluatedValue = opt.get().getString(); | ||
| } | ||
| return ProviderEvaluation.<String>builder() | ||
| .value(evaluatedValue) | ||
| .build(); | ||
| } | ||
|
|
||
| @Override | ||
| public ProviderEvaluation<Integer> getIntegerEvaluation(String key, Integer defaultValue, EvaluationContext ctx) { | ||
| verifyEvaluation(); | ||
| PrefabContextSetReadable context = ctx == null ? null : ContextTransformer.transform(ctx); | ||
| Integer evaluatedValue = defaultValue; | ||
| Optional<Prefab.ConfigValue> opt = prefabCloudClient.featureFlagClient().get(key, context); | ||
| if (opt.isPresent() && Prefab.ConfigValue.TypeCase.INT.equals(opt.get().getTypeCase())) { | ||
| evaluatedValue = Math.toIntExact(opt.get().getInt()); | ||
| } | ||
| return ProviderEvaluation.<Integer>builder() | ||
| .value(evaluatedValue) | ||
| .build(); | ||
| } | ||
|
|
||
| @Override | ||
| public ProviderEvaluation<Double> getDoubleEvaluation(String key, Double defaultValue, EvaluationContext ctx) { | ||
| verifyEvaluation(); | ||
| PrefabContextSetReadable context = ctx == null ? null : ContextTransformer.transform(ctx); | ||
| Double evaluatedValue = defaultValue; | ||
| Optional<Prefab.ConfigValue> opt = prefabCloudClient.featureFlagClient().get(key, context); | ||
| if (opt.isPresent() && Prefab.ConfigValue.TypeCase.DOUBLE.equals(opt.get().getTypeCase())) { | ||
| evaluatedValue = opt.get().getDouble(); | ||
| } | ||
| return ProviderEvaluation.<Double>builder() | ||
| .value(evaluatedValue) | ||
| .build(); | ||
| } | ||
|
|
||
| @SneakyThrows | ||
| @Override | ||
| public ProviderEvaluation<Value> getObjectEvaluation(String key, Value defaultValue, EvaluationContext ctx) { | ||
| String defaultValueString = defaultValue == null ? null : defaultValue.asString(); | ||
| ProviderEvaluation<String> stringEvaluation = getStringEvaluation(key, defaultValueString, ctx); | ||
| Value evaluatedValue = new Value(stringEvaluation.getValue()); | ||
| return ProviderEvaluation.<Value>builder() | ||
| .value(evaluatedValue) | ||
| .build(); | ||
| } | ||
|
|
||
| private void verifyEvaluation() throws ProviderNotReadyError, GeneralError { | ||
| if (!ProviderState.READY.equals(state)) { | ||
|
|
||
| /* | ||
| According to spec Requirement 2.4.5: | ||
| "The provider SHOULD indicate an error if flag resolution is attempted before the provider is ready." | ||
| https://github.com/open-feature/spec/blob/main/specification/sections/02-providers.md#requirement-245 | ||
| */ | ||
| if (ProviderState.NOT_READY.equals(state)) { | ||
| throw new ProviderNotReadyError(PROVIDER_NOT_YET_INITIALIZED); | ||
| } | ||
| throw new GeneralError(UNKNOWN_ERROR); | ||
| } | ||
| } | ||
|
|
||
| @SneakyThrows | ||
| @Override | ||
| public void shutdown() { | ||
| super.shutdown(); | ||
| log.info("shutdown"); | ||
| if (prefabCloudClient != null) { | ||
| prefabCloudClient.close(); | ||
| } | ||
| state = ProviderState.NOT_READY; | ||
| } | ||
| } |
18 changes: 18 additions & 0 deletions
18
...s/prefab/src/main/java/dev/openfeature/contrib/providers/prefab/PrefabProviderConfig.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| package dev.openfeature.contrib.providers.prefab; | ||
|
|
||
| import cloud.prefab.client.Options; | ||
| import lombok.Builder; | ||
| import lombok.Getter; | ||
|
|
||
| /** | ||
| * Options for initializing prefab provider. | ||
| */ | ||
| @Getter | ||
| @Builder | ||
| public class PrefabProviderConfig { | ||
| private Options options; | ||
|
|
||
| public void postInit() { | ||
|
|
||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hi @jkebinger,
OpenFeature SDK built with Java 8, so it will require Java 8 version.
Is it possible to release Java 8 Prefab client ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It appears the primary change to get to java8 compatibility will be removing the use of the java 11 HttpClient, so not too bad. Can't make any promises on when we may be able to prioritize that work; I'll probably aim to implement it such that there's a java8 compatibility add on so java 11+ users don't have to get an additional set of dependencies (probably okhttp). We'll see