-
Notifications
You must be signed in to change notification settings - Fork 45
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
feat!: context enrichment via contextEnricher, not from init #991
Changes from all commits
96d1264
90fa155
acef976
20300ab
6390421
387037b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,9 @@ | ||
package dev.openfeature.contrib.providers.flagd; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Collections; | ||
import java.util.Map; | ||
import java.util.List; | ||
import java.util.function.Function; | ||
|
||
import dev.openfeature.contrib.providers.flagd.resolver.Resolver; | ||
import dev.openfeature.contrib.providers.flagd.resolver.common.ConnectionEvent; | ||
|
@@ -10,9 +12,13 @@ | |
import dev.openfeature.contrib.providers.flagd.resolver.process.InProcessResolver; | ||
import dev.openfeature.sdk.EvaluationContext; | ||
import dev.openfeature.sdk.EventProvider; | ||
import dev.openfeature.sdk.Hook; | ||
import dev.openfeature.sdk.ImmutableContext; | ||
import dev.openfeature.sdk.ImmutableStructure; | ||
import dev.openfeature.sdk.Metadata; | ||
import dev.openfeature.sdk.ProviderEvaluation; | ||
import dev.openfeature.sdk.ProviderEventDetails; | ||
import dev.openfeature.sdk.Structure; | ||
import dev.openfeature.sdk.Value; | ||
import lombok.extern.slf4j.Slf4j; | ||
|
||
|
@@ -22,13 +28,14 @@ | |
@Slf4j | ||
@SuppressWarnings({ "PMD.TooManyStaticImports", "checkstyle:NoFinalizer" }) | ||
public class FlagdProvider extends EventProvider { | ||
private Function<Structure, EvaluationContext> contextEnricher; | ||
private static final String FLAGD_PROVIDER = "flagd"; | ||
private final Resolver flagResolver; | ||
private volatile boolean initialized = false; | ||
private volatile boolean connected = false; | ||
private volatile Map<String, Object> syncMetadata = Collections.emptyMap(); | ||
|
||
private EvaluationContext evaluationContext; | ||
private volatile Structure syncMetadata = new ImmutableStructure(); | ||
private volatile EvaluationContext enrichedContext = new ImmutableContext(); | ||
private final List<Hook> hooks = new ArrayList<>(); | ||
|
||
protected final void finalize() { | ||
// DO NOT REMOVE, spotbugs: CT_CONSTRUCTOR_THROW | ||
|
@@ -62,6 +69,13 @@ public FlagdProvider(final FlagdOptions options) { | |
throw new IllegalStateException( | ||
String.format("Requested unsupported resolver type of %s", options.getResolverType())); | ||
} | ||
hooks.add(new SyncMetadataHook(this::getEnrichedContext)); | ||
contextEnricher = options.getContextEnricher(); | ||
} | ||
|
||
@Override | ||
public List<Hook> getProviderHooks() { | ||
return Collections.unmodifiableList(hooks); | ||
} | ||
|
||
@Override | ||
|
@@ -70,7 +84,6 @@ public synchronized void initialize(EvaluationContext evaluationContext) throws | |
return; | ||
} | ||
|
||
this.evaluationContext = evaluationContext; | ||
this.flagResolver.init(); | ||
this.initialized = true; | ||
} | ||
|
@@ -97,48 +110,48 @@ public Metadata getMetadata() { | |
|
||
@Override | ||
public ProviderEvaluation<Boolean> getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) { | ||
return this.flagResolver.booleanEvaluation(key, defaultValue, mergeContext(ctx)); | ||
return this.flagResolver.booleanEvaluation(key, defaultValue, ctx); | ||
} | ||
|
||
@Override | ||
public ProviderEvaluation<String> getStringEvaluation(String key, String defaultValue, EvaluationContext ctx) { | ||
return this.flagResolver.stringEvaluation(key, defaultValue, mergeContext(ctx)); | ||
return this.flagResolver.stringEvaluation(key, defaultValue, ctx); | ||
} | ||
|
||
@Override | ||
public ProviderEvaluation<Double> getDoubleEvaluation(String key, Double defaultValue, EvaluationContext ctx) { | ||
return this.flagResolver.doubleEvaluation(key, defaultValue, mergeContext(ctx)); | ||
return this.flagResolver.doubleEvaluation(key, defaultValue, ctx); | ||
} | ||
|
||
@Override | ||
public ProviderEvaluation<Integer> getIntegerEvaluation(String key, Integer defaultValue, EvaluationContext ctx) { | ||
return this.flagResolver.integerEvaluation(key, defaultValue, mergeContext(ctx)); | ||
return this.flagResolver.integerEvaluation(key, defaultValue, ctx); | ||
} | ||
|
||
@Override | ||
public ProviderEvaluation<Value> getObjectEvaluation(String key, Value defaultValue, EvaluationContext ctx) { | ||
return this.flagResolver.objectEvaluation(key, defaultValue, mergeContext(ctx)); | ||
return this.flagResolver.objectEvaluation(key, defaultValue, ctx); | ||
} | ||
|
||
/** | ||
* An unmodifiable view of an object map representing the latest result of the | ||
* An unmodifiable view of a Structure representing the latest result of the | ||
* SyncMetadata. | ||
* Set on initial connection and updated with every reconnection. | ||
* see: | ||
* https://buf.build/open-feature/flagd/docs/main:flagd.sync.v1#flagd.sync.v1.FlagSyncService.GetMetadata | ||
* | ||
* @return Object map representing sync metadata | ||
*/ | ||
protected Map<String, Object> getSyncMetadata() { | ||
return Collections.unmodifiableMap(syncMetadata); | ||
protected Structure getSyncMetadata() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a much safer type which we already use for this sort of thing. |
||
return new ImmutableStructure(syncMetadata.asMap()); | ||
} | ||
|
||
private EvaluationContext mergeContext(final EvaluationContext clientCallCtx) { | ||
if (this.evaluationContext != null) { | ||
return evaluationContext.merge(clientCallCtx); | ||
} | ||
|
||
return clientCallCtx; | ||
/** | ||
* The updated context mixed into all evaluations based on the sync-metadata. | ||
* @return context | ||
*/ | ||
EvaluationContext getEnrichedContext() { | ||
return enrichedContext; | ||
} | ||
|
||
private boolean isConnected() { | ||
|
@@ -149,6 +162,7 @@ private void onConnectionEvent(ConnectionEvent connectionEvent) { | |
boolean previous = connected; | ||
boolean current = connected = connectionEvent.isConnected(); | ||
syncMetadata = connectionEvent.getSyncMetadata(); | ||
enrichedContext = contextEnricher.apply(connectionEvent.getSyncMetadata()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Every time the resolver connects, we update the cached context by re-running the passed enricher. |
||
|
||
// configuration changed | ||
if (initialized && previous && current) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package dev.openfeature.contrib.providers.flagd; | ||
|
||
import java.util.Map; | ||
import java.util.Optional; | ||
import java.util.function.Supplier; | ||
|
||
import dev.openfeature.sdk.EvaluationContext; | ||
import dev.openfeature.sdk.Hook; | ||
import dev.openfeature.sdk.HookContext; | ||
|
||
class SyncMetadataHook implements Hook<Object> { | ||
|
||
private Supplier<EvaluationContext> contextSupplier; | ||
|
||
SyncMetadataHook(Supplier<EvaluationContext> contextSupplier) { | ||
this.contextSupplier = contextSupplier; | ||
} | ||
|
||
/** | ||
* Return the context adapted from the sync-metadata provided by the supplier. | ||
*/ | ||
@Override | ||
public Optional<EvaluationContext> before(HookContext<Object> ctx, Map<String, Object> hints) { | ||
return Optional.ofNullable(contextSupplier.get()); | ||
} | ||
} |
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.
This can be used to select a subset of fields from the
getMetadata
response. By default, all of it is injected into the context.