Merge contexts from api and client
This commit is contained in:
parent
f5a49ea164
commit
df1a083398
|
|
@ -8,6 +8,18 @@ import java.util.List;
|
|||
public interface Client extends Features {
|
||||
Metadata getMetadata();
|
||||
|
||||
/**
|
||||
* Return an optional client-level evaluation context.
|
||||
* @return {@link EvaluationContext}
|
||||
*/
|
||||
EvaluationContext getEvaluationContext();
|
||||
|
||||
/**
|
||||
* Set the client-level evaluation context.
|
||||
* @param ctx Client level context.
|
||||
*/
|
||||
void setEvaluationContext(EvaluationContext ctx);
|
||||
|
||||
/**
|
||||
* Adds hooks for evaluation.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -55,6 +55,9 @@ public class EvaluationContext {
|
|||
|
||||
private <T> T getAttributeByType(String key, FlagValueType type) {
|
||||
Pair<FlagValueType, Object> val = attributes.get(key);
|
||||
if (val == null) {
|
||||
return null;
|
||||
}
|
||||
if (val.getFirst() == type) {
|
||||
return (T) val.getSecond();
|
||||
}
|
||||
|
|
@ -106,6 +109,12 @@ public class EvaluationContext {
|
|||
*/
|
||||
public static EvaluationContext merge(EvaluationContext ctx1, EvaluationContext ctx2) {
|
||||
EvaluationContext ec = new EvaluationContext();
|
||||
if (ctx1 == null) {
|
||||
return ctx2;
|
||||
} else if (ctx2 == null) {
|
||||
return ctx1;
|
||||
}
|
||||
|
||||
ec.attributes.putAll(ctx1.attributes);
|
||||
ec.attributes.putAll(ctx2.attributes);
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,11 @@ public class NoOpProvider implements FeatureProvider {
|
|||
public static final String PASSED_IN_DEFAULT = "Passed in default";
|
||||
@Getter
|
||||
private final String name = "No-op Provider";
|
||||
private EvaluationContext ctx;
|
||||
|
||||
public EvaluationContext getMergedContext() {
|
||||
return ctx;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Metadata getMetadata() {
|
||||
|
|
@ -22,6 +27,7 @@ public class NoOpProvider implements FeatureProvider {
|
|||
|
||||
@Override
|
||||
public ProviderEvaluation<Boolean> getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx, FlagEvaluationOptions options) {
|
||||
this.ctx = ctx;
|
||||
return ProviderEvaluation.<Boolean>builder()
|
||||
.value(defaultValue)
|
||||
.variant(PASSED_IN_DEFAULT)
|
||||
|
|
@ -31,6 +37,7 @@ public class NoOpProvider implements FeatureProvider {
|
|||
|
||||
@Override
|
||||
public ProviderEvaluation<String> getStringEvaluation(String key, String defaultValue, EvaluationContext ctx, FlagEvaluationOptions options) {
|
||||
this.ctx = ctx;
|
||||
return ProviderEvaluation.<String>builder()
|
||||
.value(defaultValue)
|
||||
.variant(PASSED_IN_DEFAULT)
|
||||
|
|
@ -40,6 +47,7 @@ public class NoOpProvider implements FeatureProvider {
|
|||
|
||||
@Override
|
||||
public ProviderEvaluation<Integer> getIntegerEvaluation(String key, Integer defaultValue, EvaluationContext ctx, FlagEvaluationOptions options) {
|
||||
this.ctx = ctx;
|
||||
return ProviderEvaluation.<Integer>builder()
|
||||
.value(defaultValue)
|
||||
.variant(PASSED_IN_DEFAULT)
|
||||
|
|
@ -48,6 +56,7 @@ public class NoOpProvider implements FeatureProvider {
|
|||
}
|
||||
@Override
|
||||
public ProviderEvaluation<Double> getDoubleEvaluation(String key, Double defaultValue, EvaluationContext ctx, FlagEvaluationOptions options) {
|
||||
this.ctx = ctx;
|
||||
return ProviderEvaluation.<Double>builder()
|
||||
.value(defaultValue)
|
||||
.variant(PASSED_IN_DEFAULT)
|
||||
|
|
@ -56,6 +65,7 @@ public class NoOpProvider implements FeatureProvider {
|
|||
}
|
||||
@Override
|
||||
public <T> ProviderEvaluation<T> getObjectEvaluation(String key, T defaultValue, EvaluationContext invocationContext, FlagEvaluationOptions options) {
|
||||
this.ctx = ctx;
|
||||
return ProviderEvaluation.<T>builder()
|
||||
.value(defaultValue)
|
||||
.variant(PASSED_IN_DEFAULT)
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ import java.util.List;
|
|||
public class OpenFeatureAPI {
|
||||
@Getter @Setter private FeatureProvider provider;
|
||||
private static OpenFeatureAPI api;
|
||||
|
||||
@Getter @Setter private EvaluationContext ctx;
|
||||
@Getter private List<Hook> apiHooks;
|
||||
|
||||
public static OpenFeatureAPI getInstance() {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import java.util.*;
|
|||
import dev.openfeature.javasdk.exceptions.GeneralError;
|
||||
import dev.openfeature.javasdk.internal.ObjectUtils;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
|
|
@ -17,6 +18,9 @@ public class OpenFeatureClient implements Client {
|
|||
@Getter private final List<Hook> clientHooks;
|
||||
private final HookSupport hookSupport;
|
||||
|
||||
@Getter @Setter private EvaluationContext evaluationContext;
|
||||
|
||||
|
||||
public OpenFeatureClient(OpenFeatureAPI openFeatureAPI, String name, String version) {
|
||||
this.openfeatureApi = openFeatureAPI;
|
||||
this.name = name;
|
||||
|
|
@ -38,7 +42,6 @@ public class OpenFeatureClient implements Client {
|
|||
ctx = new EvaluationContext();
|
||||
}
|
||||
|
||||
// merge of: API.context, client.context, invocation.context
|
||||
HookContext<T> hookCtx = HookContext.from(key, type, this.getMetadata(), openfeatureApi.getProvider().getMetadata(), ctx, defaultValue);
|
||||
|
||||
List<Hook> mergedHooks = ObjectUtils.merge(provider.getProviderHooks(), flagOptions.getHooks(), clientHooks, openfeatureApi.getApiHooks());
|
||||
|
|
@ -46,9 +49,19 @@ public class OpenFeatureClient implements Client {
|
|||
FlagEvaluationDetails<T> details = null;
|
||||
try {
|
||||
EvaluationContext ctxFromHook = hookSupport.beforeHooks(type, hookCtx, mergedHooks, hints);
|
||||
EvaluationContext invocationContext = EvaluationContext.merge(ctxFromHook, ctx);
|
||||
|
||||
ProviderEvaluation<T> providerEval = (ProviderEvaluation<T>) createProviderEvaluation(type, key, defaultValue, options, provider, invocationContext);
|
||||
EvaluationContext invocationCtx = EvaluationContext.merge(ctxFromHook, ctx);
|
||||
|
||||
// merge of: API.context, client.context, invocation.context
|
||||
EvaluationContext mergedCtx = EvaluationContext.merge(
|
||||
EvaluationContext.merge(
|
||||
openfeatureApi.getCtx(),
|
||||
this.getEvaluationContext()
|
||||
),
|
||||
invocationCtx
|
||||
);
|
||||
|
||||
ProviderEvaluation<T> providerEval = (ProviderEvaluation<T>) createProviderEvaluation(type, key, defaultValue, options, provider, mergedCtx);
|
||||
|
||||
details = FlagEvaluationDetails.from(providerEval, key);
|
||||
hookSupport.afterHooks(type, hookCtx, details, mergedHooks, hints);
|
||||
|
|
|
|||
|
|
@ -20,6 +20,11 @@ class FlagEvaluationSpecTest implements HookFixtures {
|
|||
return api.getClient();
|
||||
}
|
||||
|
||||
@AfterEach void reset_ctx() {
|
||||
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
|
||||
api.setCtx(null);
|
||||
}
|
||||
|
||||
@Specification(number="1.1.1", text="The API, and any state it maintains SHOULD exist as a global singleton, even in cases wherein multiple versions of the API are present at runtime.")
|
||||
@Test void global_singleton() {
|
||||
assertSame(OpenFeatureAPI.getInstance(), OpenFeatureAPI.getInstance());
|
||||
|
|
@ -209,6 +214,41 @@ class FlagEvaluationSpecTest implements HookFixtures {
|
|||
assertEquals(Reason.ERROR, result.getReason());
|
||||
}
|
||||
|
||||
@Specification(number="3.2.1", text="The API, Client and invocation MUST have a method for supplying evaluation context.")
|
||||
@Specification(number="3.2.2", text="Evaluation context MUST be merged in the order: API (global) - client - invocation, with duplicate values being overwritten.")
|
||||
@Test void multi_layer_context_merges_correctly() {
|
||||
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
|
||||
NoOpProvider provider = new NoOpProvider();
|
||||
api.setProvider(provider);
|
||||
|
||||
EvaluationContext apiCtx = new EvaluationContext();
|
||||
apiCtx.addStringAttribute("common", "1");
|
||||
apiCtx.addStringAttribute("common2", "1");
|
||||
apiCtx.addStringAttribute("api", "2");
|
||||
api.setCtx(apiCtx);
|
||||
|
||||
Client c = api.getClient();
|
||||
EvaluationContext clientCtx = new EvaluationContext();
|
||||
clientCtx.addStringAttribute("common", "3");
|
||||
clientCtx.addStringAttribute("common2", "3");
|
||||
clientCtx.addStringAttribute("client", "4");
|
||||
c.setEvaluationContext(clientCtx);
|
||||
|
||||
EvaluationContext invocationCtx = new EvaluationContext();
|
||||
clientCtx.addStringAttribute("common", "5");
|
||||
clientCtx.addStringAttribute("invocation", "6");
|
||||
|
||||
assertFalse(c.getBooleanValue("key", false, invocationCtx));
|
||||
|
||||
EvaluationContext merged = provider.getMergedContext();
|
||||
assertEquals("6", merged.getStringAttribute("invocation"));
|
||||
assertEquals("5", merged.getStringAttribute("common"), "invocation merge is incorrect");
|
||||
assertEquals("4", merged.getStringAttribute("client"));
|
||||
assertEquals("3", merged.getStringAttribute("common2"), "api client merge is incorrect");
|
||||
assertEquals("2", merged.getStringAttribute("api"));
|
||||
|
||||
}
|
||||
|
||||
@Specification(number="1.3.3", text="The client SHOULD guarantee the returned value of any typed flag evaluation method is of the expected type. If the value returned by the underlying provider implementation does not match the expected type, it's to be considered abnormal execution, and the supplied default value should be returned.")
|
||||
@Specification(number="1.1.6", text="The client creation function MUST NOT throw, or otherwise abnormally terminate.")
|
||||
@Disabled("Not sure how to test?")
|
||||
|
|
|
|||
Loading…
Reference in New Issue