diff --git a/src/main/java/dev/openfeature/sdk/EvaluationContext.java b/src/main/java/dev/openfeature/sdk/EvaluationContext.java index ed65ad9ed..d613c7008 100644 --- a/src/main/java/dev/openfeature/sdk/EvaluationContext.java +++ b/src/main/java/dev/openfeature/sdk/EvaluationContext.java @@ -1,130 +1,21 @@ package dev.openfeature.sdk; -import java.time.Instant; -import java.util.List; - -import lombok.Getter; -import lombok.Setter; -import lombok.ToString; -import lombok.experimental.Delegate; - -@ToString +/** + * The EvaluationContext is a container for arbitrary contextual data + * that can be used as a basis for dynamic evaluation. + */ @SuppressWarnings("PMD.BeanMembersShouldSerialize") -public class EvaluationContext { - - @Setter @Getter private String targetingKey; - @Delegate(excludes = HideDelegateAddMethods.class) private final Structure structure = new Structure(); - - public EvaluationContext() { - super(); - this.targetingKey = ""; - } - - public EvaluationContext(String targetingKey) { - this(); - this.targetingKey = targetingKey; - } +public interface EvaluationContext extends Structure { + String getTargetingKey(); + + void setTargetingKey(String targetingKey); /** - * Merges two EvaluationContext objects with the second overriding the first in + * Merges this EvaluationContext object with the second overriding the this in * case of conflict. * - * @param ctx1 base context - * @param ctx2 overriding context + * @param overridingContext overriding context * @return resulting merged context */ - public static EvaluationContext merge(EvaluationContext ctx1, EvaluationContext ctx2) { - EvaluationContext ec = new EvaluationContext(); - if (ctx1 == null) { - return ctx2; - } else if (ctx2 == null) { - return ctx1; - } - - ec.structure.attributes.putAll(ctx1.structure.attributes); - ec.structure.attributes.putAll(ctx2.structure.attributes); - - if (ctx1.getTargetingKey() != null && !ctx1.getTargetingKey().trim().equals("")) { - ec.setTargetingKey(ctx1.getTargetingKey()); - } - - if (ctx2.getTargetingKey() != null && !ctx2.getTargetingKey().trim().equals("")) { - ec.setTargetingKey(ctx2.getTargetingKey()); - } - - return ec; - } - - // override @Delegate methods so that we can use "add" methods and still return EvaluationContext, not Structure - public EvaluationContext add(String key, Boolean value) { - this.structure.add(key, value); - return this; - } - - public EvaluationContext add(String key, String value) { - this.structure.add(key, value); - return this; - } - - public EvaluationContext add(String key, Integer value) { - this.structure.add(key, value); - return this; - } - - public EvaluationContext add(String key, Double value) { - this.structure.add(key, value); - return this; - } - - public EvaluationContext add(String key, Instant value) { - this.structure.add(key, value); - return this; - } - - public EvaluationContext add(String key, Structure value) { - this.structure.add(key, value); - return this; - } - - public EvaluationContext add(String key, List value) { - this.structure.add(key, value); - return this; - } - - /** - * Hidden class to tell Lombok not to copy these methods over via delegation. - */ - private static class HideDelegateAddMethods { - public Structure add(String ignoredKey, Boolean ignoredValue) { - return null; - } - - public Structure add(String ignoredKey, Double ignoredValue) { - return null; - } - - public Structure add(String ignoredKey, String ignoredValue) { - return null; - } - - public Structure add(String ignoredKey, Value ignoredValue) { - return null; - } - - public Structure add(String ignoredKey, Integer ignoredValue) { - return null; - } - - public Structure add(String ignoredKey, List ignoredValue) { - return null; - } - - public Structure add(String ignoredKey, Structure ignoredValue) { - return null; - } - - public Structure add(String ignoredKey, Instant ignoredValue) { - return null; - } - } + EvaluationContext merge(EvaluationContext overridingContext); } diff --git a/src/main/java/dev/openfeature/sdk/HookSupport.java b/src/main/java/dev/openfeature/sdk/HookSupport.java index 6e29eae36..eb2b40784 100644 --- a/src/main/java/dev/openfeature/sdk/HookSupport.java +++ b/src/main/java/dev/openfeature/sdk/HookSupport.java @@ -11,46 +11,44 @@ @Slf4j @RequiredArgsConstructor -@SuppressWarnings({"unchecked", "rawtypes"}) +@SuppressWarnings({ "unchecked", "rawtypes" }) class HookSupport { public void errorHooks(FlagValueType flagValueType, HookContext hookCtx, Exception e, List hooks, - Map hints) { + Map hints) { executeHooks(flagValueType, hooks, "error", hook -> hook.error(hookCtx, e, hints)); } public void afterAllHooks(FlagValueType flagValueType, HookContext hookCtx, List hooks, - Map hints) { + Map hints) { executeHooks(flagValueType, hooks, "finally", hook -> hook.finallyAfter(hookCtx, hints)); } public void afterHooks(FlagValueType flagValueType, HookContext hookContext, FlagEvaluationDetails details, - List hooks, Map hints) { + List hooks, Map hints) { executeHooksUnchecked(flagValueType, hooks, hook -> hook.after(hookContext, details, hints)); } private void executeHooks( FlagValueType flagValueType, List hooks, String hookMethod, - Consumer> hookCode - ) { + Consumer> hookCode) { if (hooks != null) { hooks - .stream() - .filter(hook -> hook.supportsFlagValueType(flagValueType)) - .forEach(hook -> executeChecked(hook, hookCode, hookMethod)); + .stream() + .filter(hook -> hook.supportsFlagValueType(flagValueType)) + .forEach(hook -> executeChecked(hook, hookCode, hookMethod)); } } private void executeHooksUnchecked( FlagValueType flagValueType, List hooks, - Consumer> hookCode - ) { + Consumer> hookCode) { if (hooks != null) { hooks - .stream() - .filter(hook -> hook.supportsFlagValueType(flagValueType)) - .forEach(hookCode::accept); + .stream() + .filter(hook -> hook.supportsFlagValueType(flagValueType)) + .forEach(hookCode::accept); } } @@ -63,13 +61,16 @@ private void executeChecked(Hook hook, Consumer> hookCode, String } public EvaluationContext beforeHooks(FlagValueType flagValueType, HookContext hookCtx, List hooks, - Map hints) { + Map hints) { Stream result = callBeforeHooks(flagValueType, hookCtx, hooks, hints); - return EvaluationContext.merge(hookCtx.getCtx(), collectContexts(result)); + return hookCtx.getCtx().merge( + result.reduce(hookCtx.getCtx(), (EvaluationContext accumulated, EvaluationContext current) -> { + return accumulated.merge(current); + })); } private Stream callBeforeHooks(FlagValueType flagValueType, HookContext hookCtx, - List hooks, Map hints) { + List hooks, Map hints) { // These traverse backwards from normal. List reversedHooks = IntStream .range(0, hooks.size()) @@ -86,12 +87,4 @@ private Stream callBeforeHooks(FlagValueType flagValueType, H .map(Optional::get) .map(EvaluationContext.class::cast); } - - //for whatever reason, an error `error: incompatible types: invalid method reference` is thrown on compilation - // with javac - //when the reduce call is appended directly as stream call chain above. moving it to its own method works however... - private EvaluationContext collectContexts(Stream result) { - return result - .reduce(new EvaluationContext(), EvaluationContext::merge, EvaluationContext::merge); - } } diff --git a/src/main/java/dev/openfeature/sdk/MutableContext.java b/src/main/java/dev/openfeature/sdk/MutableContext.java new file mode 100644 index 000000000..b11503c25 --- /dev/null +++ b/src/main/java/dev/openfeature/sdk/MutableContext.java @@ -0,0 +1,148 @@ +package dev.openfeature.sdk; + +import java.time.Instant; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Delegate; + +/** + * The EvaluationContext is a container for arbitrary contextual data + * that can be used as a basis for dynamic evaluation. + * The MutableContext is an EvaluationContext implementation which is not threadsafe, and whose attributes can + * be modified after instantiation. + */ +@ToString +@SuppressWarnings("PMD.BeanMembersShouldSerialize") +public class MutableContext implements EvaluationContext { + + @Setter() @Getter private String targetingKey; + @Delegate(excludes = HideDelegateAddMethods.class) private final MutableStructure structure; + + public MutableContext() { + this.structure = new MutableStructure(); + this.targetingKey = ""; + } + + public MutableContext(String targetingKey) { + this(); + this.targetingKey = targetingKey; + } + + public MutableContext(Map attributes) { + this.structure = new MutableStructure(attributes); + this.targetingKey = ""; + } + + public MutableContext(String targetingKey, Map attributes) { + this(attributes); + this.targetingKey = targetingKey; + } + + // override @Delegate methods so that we can use "add" methods and still return MutableContext, not Structure + public MutableContext add(String key, Boolean value) { + this.structure.add(key, value); + return this; + } + + public MutableContext add(String key, String value) { + this.structure.add(key, value); + return this; + } + + public MutableContext add(String key, Integer value) { + this.structure.add(key, value); + return this; + } + + public MutableContext add(String key, Double value) { + this.structure.add(key, value); + return this; + } + + public MutableContext add(String key, Instant value) { + this.structure.add(key, value); + return this; + } + + public MutableContext add(String key, Structure value) { + this.structure.add(key, value); + return this; + } + + public MutableContext add(String key, List value) { + this.structure.add(key, value); + return this; + } + + /** + * Merges this EvaluationContext objects with the second overriding the this in + * case of conflict. + * + * @param overridingContext overriding context + * @return resulting merged context + */ + @Override + public EvaluationContext merge(EvaluationContext overridingContext) { + if (overridingContext == null) { + return new MutableContext(this.asMap()); + } + + Map merged = new HashMap(); + + merged.putAll(this.asMap()); + merged.putAll(overridingContext.asMap()); + EvaluationContext ec = new MutableContext(merged); + + if (this.getTargetingKey() != null && !this.getTargetingKey().trim().equals("")) { + ec.setTargetingKey(this.getTargetingKey()); + } + + if (overridingContext.getTargetingKey() != null && !overridingContext.getTargetingKey().trim().equals("")) { + ec.setTargetingKey(overridingContext.getTargetingKey()); + } + + return ec; + } + + /** + * Hidden class to tell Lombok not to copy these methods over via delegation. + */ + private static class HideDelegateAddMethods { + public MutableStructure add(String ignoredKey, Boolean ignoredValue) { + return null; + } + + public MutableStructure add(String ignoredKey, Double ignoredValue) { + return null; + } + + public MutableStructure add(String ignoredKey, String ignoredValue) { + return null; + } + + public MutableStructure add(String ignoredKey, Value ignoredValue) { + return null; + } + + public MutableStructure add(String ignoredKey, Integer ignoredValue) { + return null; + } + + public MutableStructure add(String ignoredKey, List ignoredValue) { + return null; + } + + public MutableStructure add(String ignoredKey, MutableStructure ignoredValue) { + return null; + } + + public MutableStructure add(String ignoredKey, Instant ignoredValue) { + return null; + } + } +} diff --git a/src/main/java/dev/openfeature/sdk/MutableStructure.java b/src/main/java/dev/openfeature/sdk/MutableStructure.java new file mode 100644 index 000000000..99e741df5 --- /dev/null +++ b/src/main/java/dev/openfeature/sdk/MutableStructure.java @@ -0,0 +1,164 @@ +package dev.openfeature.sdk; + +import java.time.Instant; +import java.util.*; +import java.util.stream.Collectors; + +import dev.openfeature.sdk.exceptions.ValueNotConvertableError; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +/** + * {@link MutableStructure} represents a potentially nested object type which is used to represent + * structured data. + * The MutableStructure is a Structure implementation which is not threadsafe, and whose attributes can + * be modified after instantiation. + */ +@ToString +@EqualsAndHashCode +@SuppressWarnings("PMD.BeanMembersShouldSerialize") +public class MutableStructure implements Structure { + + protected final Map attributes; + + public MutableStructure() { + this.attributes = new HashMap<>(); + } + + public MutableStructure(Map attributes) { + this.attributes = new HashMap<>(attributes); + } + + @Override + public Set keySet() { + return this.attributes.keySet(); + } + + // getters + @Override + public Value getValue(String key) { + return this.attributes.get(key); + } + + // adders + public MutableStructure add(String key, Value value) { + attributes.put(key, value); + return this; + } + + public MutableStructure add(String key, Boolean value) { + attributes.put(key, new Value(value)); + return this; + } + + public MutableStructure add(String key, String value) { + attributes.put(key, new Value(value)); + return this; + } + + public MutableStructure add(String key, Integer value) { + attributes.put(key, new Value(value)); + return this; + } + + public MutableStructure add(String key, Double value) { + attributes.put(key, new Value(value)); + return this; + } + + /** + * Add date-time relevant key. + * + * @param key feature key + * @param value date-time value + * @return Structure + */ + public MutableStructure add(String key, Instant value) { + attributes.put(key, new Value(value)); + return this; + } + + public MutableStructure add(String key, Structure value) { + attributes.put(key, new Value(value)); + return this; + } + + public MutableStructure add(String key, List value) { + attributes.put(key, new Value(value)); + return this; + } + + /** + * Get all values. + * + * @return all attributes on the structure + */ + @Override + public Map asMap() { + return new HashMap<>(this.attributes); + } + + /** + * Get all values, with primitives types. + * + * @return all attributes on the structure into a Map + */ + @Override + public Map asObjectMap() { + return attributes + .entrySet() + .stream() + .collect(Collectors.toMap( + Map.Entry::getKey, + e -> convertValue(getValue(e.getKey())) + )); + } + + /** + * convertValue is converting the object type Value in a primitive type. + * @param value - Value object to convert + * @return an Object containing the primitive type. + */ + private Object convertValue(Value value) { + if (value.isBoolean()) { + return value.asBoolean(); + } + + if (value.isNumber()) { + Double valueAsDouble = value.asDouble(); + if (valueAsDouble == Math.floor(valueAsDouble) && !Double.isInfinite(valueAsDouble)) { + return value.asInteger(); + } + return valueAsDouble; + } + + if (value.isString()) { + return value.asString(); + } + + if (value.isInstant()) { + return value.asInstant(); + } + + if (value.isList()) { + return value.asList() + .stream() + .map(this::convertValue) + .collect(Collectors.toList()); + } + + if (value.isStructure()) { + Structure s = value.asStructure(); + return s.asMap() + .keySet() + .stream() + .collect( + Collectors.toMap( + key -> key, + key -> convertValue(s.getValue(key)) + ) + ); + } + throw new ValueNotConvertableError(); + } +} diff --git a/src/main/java/dev/openfeature/sdk/NoOpProvider.java b/src/main/java/dev/openfeature/sdk/NoOpProvider.java index 00abedc85..c2e841a53 100644 --- a/src/main/java/dev/openfeature/sdk/NoOpProvider.java +++ b/src/main/java/dev/openfeature/sdk/NoOpProvider.java @@ -58,7 +58,7 @@ public ProviderEvaluation getDoubleEvaluation(String key, Double default @Override public ProviderEvaluation getObjectEvaluation(String key, Value defaultValue, - EvaluationContext invocationContext) { + EvaluationContext invocationContext) { return ProviderEvaluation.builder() .value(defaultValue) .variant(PASSED_IN_DEFAULT) diff --git a/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java b/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java index 957afc88e..0f99e4941 100644 --- a/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java +++ b/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java @@ -14,7 +14,7 @@ import lombok.extern.slf4j.Slf4j; @Slf4j -@SuppressWarnings({"PMD.DataflowAnomalyAnalysis", "PMD.BeanMembersShouldSerialize", "unchecked", "rawtypes"}) +@SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.BeanMembersShouldSerialize", "unchecked", "rawtypes" }) public class OpenFeatureClient implements Client { private final OpenFeatureAPI openfeatureApi; @@ -31,10 +31,12 @@ public class OpenFeatureClient implements Client { private EvaluationContext evaluationContext; /** - * Client for evaluating the flag. There may be multiples of these floating around. + * Client for evaluating the flag. There may be multiples of these floating + * around. + * * @param openFeatureAPI Backing global singleton - * @param name Name of the client (used by observability tools). - * @param version Version of the client (used by observability tools). + * @param name Name of the client (used by observability tools). + * @param version Version of the client (used by observability tools). */ public OpenFeatureClient(OpenFeatureAPI openFeatureAPI, String name, String version) { this.openfeatureApi = openFeatureAPI; @@ -50,11 +52,11 @@ public void addHooks(Hook... hooks) { } private FlagEvaluationDetails evaluateFlag(FlagValueType type, String key, T defaultValue, - EvaluationContext ctx, FlagEvaluationOptions options) { + EvaluationContext ctx, FlagEvaluationOptions options) { FlagEvaluationOptions flagOptions = ObjectUtils.defaultIfNull(options, - () -> FlagEvaluationOptions.builder().build()); + () -> FlagEvaluationOptions.builder().build()); Map hints = Collections.unmodifiableMap(flagOptions.getHookHints()); - ctx = ObjectUtils.defaultIfNull(ctx, () -> new EvaluationContext()); + ctx = ObjectUtils.defaultIfNull(ctx, () -> new MutableContext()); FeatureProvider provider = ObjectUtils.defaultIfNull(openfeatureApi.getProvider(), () -> { log.debug("No provider configured, using no-op provider."); return new NoOpProvider(); @@ -67,23 +69,23 @@ private FlagEvaluationDetails evaluateFlag(FlagValueType type, String key try { hookCtx = HookContext.from(key, type, this.getMetadata(), - openfeatureApi.getProvider().getMetadata(), ctx, defaultValue); + openfeatureApi.getProvider().getMetadata(), ctx, defaultValue); mergedHooks = ObjectUtils.merge(provider.getProviderHooks(), flagOptions.getHooks(), clientHooks, - openfeatureApi.getApiHooks()); + openfeatureApi.getApiHooks()); EvaluationContext ctxFromHook = hookSupport.beforeHooks(type, hookCtx, mergedHooks, hints); - EvaluationContext invocationCtx = EvaluationContext.merge(ctx, ctxFromHook); + EvaluationContext invocationCtx = ctx.merge(ctxFromHook); // merge of: API.context, client.context, invocation.context - EvaluationContext mergedCtx = EvaluationContext.merge( - EvaluationContext.merge( - openfeatureApi.getEvaluationContext(), - this.getEvaluationContext() - ), - invocationCtx - ); + EvaluationContext apiContext = openfeatureApi.getEvaluationContext() != null + ? openfeatureApi.getEvaluationContext() + : new MutableContext(); + EvaluationContext clientContext = openfeatureApi.getEvaluationContext() != null + ? this.getEvaluationContext() + : new MutableContext(); + EvaluationContext mergedCtx = apiContext.merge(clientContext.merge(invocationCtx)); ProviderEvaluation providerEval = (ProviderEvaluation) createProviderEvaluation(type, key, defaultValue, provider, mergedCtx); @@ -96,7 +98,7 @@ private FlagEvaluationDetails evaluateFlag(FlagValueType type, String key details = FlagEvaluationDetails.builder().build(); } if (e instanceof OpenFeatureError) { - details.setErrorCode(((OpenFeatureError)e).getErrorCode()); + details.setErrorCode(((OpenFeatureError) e).getErrorCode()); } else { details.setErrorCode(ErrorCode.GENERAL); } @@ -116,8 +118,7 @@ private ProviderEvaluation createProviderEvaluation( String key, T defaultValue, FeatureProvider provider, - EvaluationContext invocationContext - ) { + EvaluationContext invocationContext) { switch (type) { case BOOLEAN: return provider.getBooleanEvaluation(key, (Boolean) defaultValue, invocationContext); @@ -146,13 +147,13 @@ public Boolean getBooleanValue(String key, Boolean defaultValue, EvaluationConte @Override public Boolean getBooleanValue(String key, Boolean defaultValue, EvaluationContext ctx, - FlagEvaluationOptions options) { + FlagEvaluationOptions options) { return getBooleanDetails(key, defaultValue, ctx, options).getValue(); } @Override public FlagEvaluationDetails getBooleanDetails(String key, Boolean defaultValue) { - return getBooleanDetails(key, defaultValue, new EvaluationContext()); + return getBooleanDetails(key, defaultValue, null); } @Override @@ -162,7 +163,7 @@ public FlagEvaluationDetails getBooleanDetails(String key, Boolean defa @Override public FlagEvaluationDetails getBooleanDetails(String key, Boolean defaultValue, EvaluationContext ctx, - FlagEvaluationOptions options) { + FlagEvaluationOptions options) { return this.evaluateFlag(FlagValueType.BOOLEAN, key, defaultValue, ctx, options); } @@ -178,7 +179,7 @@ public String getStringValue(String key, String defaultValue, EvaluationContext @Override public String getStringValue(String key, String defaultValue, EvaluationContext ctx, - FlagEvaluationOptions options) { + FlagEvaluationOptions options) { return getStringDetails(key, defaultValue, ctx, options).getValue(); } @@ -194,7 +195,7 @@ public FlagEvaluationDetails getStringDetails(String key, String default @Override public FlagEvaluationDetails getStringDetails(String key, String defaultValue, EvaluationContext ctx, - FlagEvaluationOptions options) { + FlagEvaluationOptions options) { return this.evaluateFlag(FlagValueType.STRING, key, defaultValue, ctx, options); } @@ -210,13 +211,13 @@ public Integer getIntegerValue(String key, Integer defaultValue, EvaluationConte @Override public Integer getIntegerValue(String key, Integer defaultValue, EvaluationContext ctx, - FlagEvaluationOptions options) { + FlagEvaluationOptions options) { return getIntegerDetails(key, defaultValue, ctx, options).getValue(); } @Override public FlagEvaluationDetails getIntegerDetails(String key, Integer defaultValue) { - return getIntegerDetails(key, defaultValue, new EvaluationContext()); + return getIntegerDetails(key, defaultValue, null); } @Override @@ -226,7 +227,7 @@ public FlagEvaluationDetails getIntegerDetails(String key, Integer defa @Override public FlagEvaluationDetails getIntegerDetails(String key, Integer defaultValue, EvaluationContext ctx, - FlagEvaluationOptions options) { + FlagEvaluationOptions options) { return this.evaluateFlag(FlagValueType.INTEGER, key, defaultValue, ctx, options); } @@ -242,7 +243,7 @@ public Double getDoubleValue(String key, Double defaultValue, EvaluationContext @Override public Double getDoubleValue(String key, Double defaultValue, EvaluationContext ctx, - FlagEvaluationOptions options) { + FlagEvaluationOptions options) { return this.evaluateFlag(FlagValueType.DOUBLE, key, defaultValue, ctx, options).getValue(); } @@ -258,7 +259,7 @@ public FlagEvaluationDetails getDoubleDetails(String key, Double default @Override public FlagEvaluationDetails getDoubleDetails(String key, Double defaultValue, EvaluationContext ctx, - FlagEvaluationOptions options) { + FlagEvaluationOptions options) { return this.evaluateFlag(FlagValueType.DOUBLE, key, defaultValue, ctx, options); } @@ -285,13 +286,13 @@ public FlagEvaluationDetails getObjectDetails(String key, Value defaultVa @Override public FlagEvaluationDetails getObjectDetails(String key, Value defaultValue, - EvaluationContext ctx) { + EvaluationContext ctx) { return getObjectDetails(key, defaultValue, ctx, FlagEvaluationOptions.builder().build()); } @Override public FlagEvaluationDetails getObjectDetails(String key, Value defaultValue, EvaluationContext ctx, - FlagEvaluationOptions options) { + FlagEvaluationOptions options) { return this.evaluateFlag(FlagValueType.OBJECT, key, defaultValue, ctx, options); } diff --git a/src/main/java/dev/openfeature/sdk/Structure.java b/src/main/java/dev/openfeature/sdk/Structure.java index 70c98b774..5c67551f4 100644 --- a/src/main/java/dev/openfeature/sdk/Structure.java +++ b/src/main/java/dev/openfeature/sdk/Structure.java @@ -1,161 +1,41 @@ package dev.openfeature.sdk; -import java.time.Instant; -import java.util.*; -import java.util.stream.Collectors; - -import dev.openfeature.sdk.exceptions.ValueNotConvertableError; -import lombok.EqualsAndHashCode; -import lombok.ToString; - -// Note: We don't accept instances of T for EC b/c walking arbitrary objects to serialize them isn't quite a straight -// forward for providers. They may not have access to tools like Jackson or Gson. +import java.util.Map; +import java.util.Set; /** - * {@link Structure} represents a potentially nested object type which is used to pass complex objects via - * {@link EvaluationContext}. + * {@link Structure} represents a potentially nested object type which is used to represent + * structured data. */ -@ToString -@EqualsAndHashCode @SuppressWarnings("PMD.BeanMembersShouldSerialize") -public class Structure { - - protected final Map attributes; - - public Structure() { - this.attributes = new HashMap<>(); - } - - public Structure(Map attributes) { - this.attributes = new HashMap<>(attributes); - } - - public Set keySet() { - return this.attributes.keySet(); - } - - // getters - public Value getValue(String key) { - return this.attributes.get(key); - } - - // adders - public Structure add(String key, Value value) { - attributes.put(key, value); - return this; - } - - public Structure add(String key, Boolean value) { - attributes.put(key, new Value(value)); - return this; - } - - public Structure add(String key, String value) { - attributes.put(key, new Value(value)); - return this; - } - - public Structure add(String key, Integer value) { - attributes.put(key, new Value(value)); - return this; - } - - public Structure add(String key, Double value) { - attributes.put(key, new Value(value)); - return this; - } - +public interface Structure { + /** - * Add date-time relevant key. + * Get all keys. * - * @param key feature key - * @param value date-time value - * @return Structure + * @return the set of keys */ - public Structure add(String key, Instant value) { - attributes.put(key, new Value(value)); - return this; - } - - public Structure add(String key, Structure value) { - attributes.put(key, new Value(value)); - return this; - } - - public Structure add(String key, List value) { - attributes.put(key, new Value(value)); - return this; - } + Set keySet(); /** - * Get all values. + * Get the value indexed by key. * - * @return all attributes on the structure + * @param key String the key. + * @return the Value */ - public Map asMap() { - return new HashMap<>(this.attributes); - } + Value getValue(String key); /** - * Get all values, with primitives types. + * Get all values, as a map of Values. * * @return all attributes on the structure into a Map */ - public Map asObjectMap() { - return attributes - .entrySet() - .stream() - .collect(Collectors.toMap( - Map.Entry::getKey, - e -> convertValue(getValue(e.getKey())) - )); - } + Map asMap(); /** - * convertValue is converting the object type Value in a primitive type. - * @param value - Value object to convert - * @return an Object containing the primitive type. + * Get all values, with as a map of Object. + * + * @return all attributes on the structure into a Map */ - private Object convertValue(Value value) { - if (value.isBoolean()) { - return value.asBoolean(); - } - - if (value.isNumber()) { - Double valueAsDouble = value.asDouble(); - if (valueAsDouble == Math.floor(valueAsDouble) && !Double.isInfinite(valueAsDouble)) { - return value.asInteger(); - } - return valueAsDouble; - } - - if (value.isString()) { - return value.asString(); - } - - if (value.isInstant()) { - return value.asInstant(); - } - - if (value.isList()) { - return value.asList() - .stream() - .map(this::convertValue) - .collect(Collectors.toList()); - } - - if (value.isStructure()) { - Structure s = value.asStructure(); - return s.asMap() - .keySet() - .stream() - .collect( - Collectors.toMap( - key -> key, - key -> convertValue(s.getValue(key)) - ) - ); - } - throw new ValueNotConvertableError(); - } + Map asObjectMap(); } diff --git a/src/test/java/dev/openfeature/sdk/DeveloperExperienceTest.java b/src/test/java/dev/openfeature/sdk/DeveloperExperienceTest.java index bb0825b0d..454f16709 100644 --- a/src/test/java/dev/openfeature/sdk/DeveloperExperienceTest.java +++ b/src/test/java/dev/openfeature/sdk/DeveloperExperienceTest.java @@ -61,7 +61,7 @@ class DeveloperExperienceTest implements HookFixtures { /** * As an application author, you probably know special things about your users. You can communicate these to the - * provider via {@link EvaluationContext} + * provider via {@link MutableContext} */ @Test void providingContext() { @@ -69,7 +69,7 @@ class DeveloperExperienceTest implements HookFixtures { api.setProvider(new NoOpProvider()); Client client = api.getClient(); - EvaluationContext ctx = new EvaluationContext() + MutableContext ctx = new MutableContext() .add("int-val", 3) .add("double-val", 4.0) .add("str-val", "works") diff --git a/src/test/java/dev/openfeature/sdk/EvalContextTest.java b/src/test/java/dev/openfeature/sdk/EvalContextTest.java index 4a974e4e5..ba31b4b33 100644 --- a/src/test/java/dev/openfeature/sdk/EvalContextTest.java +++ b/src/test/java/dev/openfeature/sdk/EvalContextTest.java @@ -15,16 +15,16 @@ public class EvalContextTest { text="The `evaluation context` structure **MUST** define an optional `targeting key` field of " + "type string, identifying the subject of the flag evaluation.") @Test void requires_targeting_key() { - EvaluationContext ec = new EvaluationContext(); + MutableContext ec = new MutableContext(); ec.setTargetingKey("targeting-key"); assertEquals("targeting-key", ec.getTargetingKey()); } - @Specification(number="3.1.2", text="The evaluation context MUST support the inclusion of " + + @Specification(number="3.1.2", text= "The evaluation context MUST support the inclusion of " + "custom fields, having keys of type `string`, and " + "values of type `boolean | string | number | datetime | structure`.") @Test void eval_context() { - EvaluationContext ec = new EvaluationContext(); + MutableContext ec = new MutableContext(); ec.add("str", "test"); assertEquals("test", ec.getValue("str").asString()); @@ -44,8 +44,8 @@ public class EvalContextTest { "custom fields, having keys of type `string`, and " + "values of type `boolean | string | number | datetime | structure`.") @Test void eval_context_structure_array() { - EvaluationContext ec = new EvaluationContext(); - ec.add("obj", new Structure().add("val1", 1).add("val2", "2")); + MutableContext ec = new MutableContext(); + ec.add("obj", new MutableStructure().add("val1", 1).add("val2", "2")); ec.add("arr", new ArrayList(){{ add(new Value("one")); add(new Value("two")); @@ -62,7 +62,7 @@ public class EvalContextTest { @Specification(number="3.1.3", text="The evaluation context MUST support fetching the custom fields by key and also fetching all key value pairs.") @Test void fetch_all() { - EvaluationContext ec = new EvaluationContext(); + MutableContext ec = new MutableContext(); ec.add("str", "test"); ec.add("str2", "test2"); @@ -76,7 +76,7 @@ public class EvalContextTest { Instant dt = Instant.now(); ec.add("dt", dt); - ec.add("obj", new Structure().add("val1", 1).add("val2", "2")); + ec.add("obj", new MutableStructure().add("val1", 1).add("val2", "2")); Map foundStr = ec.asMap(); assertEquals(ec.getValue("str").asString(), foundStr.get("str").asString()); @@ -97,7 +97,7 @@ public class EvalContextTest { @Specification(number="3.1.4", text="The evaluation context fields MUST have an unique key.") @Test void unique_key_across_types() { - EvaluationContext ec = new EvaluationContext(); + MutableContext ec = new MutableContext(); ec.add("key", "val"); ec.add("key", "val2"); assertEquals("val2", ec.getValue("key").asString()); @@ -107,20 +107,20 @@ public class EvalContextTest { } @Test void can_chain_attribute_addition() { - EvaluationContext ec = new EvaluationContext(); - EvaluationContext out = ec.add("str", "test") + MutableContext ec = new MutableContext(); + MutableContext out = ec.add("str", "test") .add("int", 4) .add("bool", false) - .add("str", new Structure()); - assertEquals(EvaluationContext.class, out.getClass()); + .add("str", new MutableStructure()); + assertEquals(MutableContext.class, out.getClass()); } @Test void can_add_key_with_null() { - EvaluationContext ec = new EvaluationContext() + MutableContext ec = new MutableContext() .add("Boolean", (Boolean)null) .add("String", (String)null) .add("Double", (Double)null) - .add("Structure", (Structure)null) + .add("Structure", (MutableStructure)null) .add("List", (List)null) .add("Instant", (Instant)null); assertEquals(6, ec.asMap().size()); @@ -134,25 +134,25 @@ public class EvalContextTest { @Test void merge_targeting_key() { String key1 = "key1"; - EvaluationContext ctx1 = new EvaluationContext(key1); - EvaluationContext ctx2 = new EvaluationContext(); + EvaluationContext ctx1 = new MutableContext(key1); + EvaluationContext ctx2 = new MutableContext(); - EvaluationContext ctxMerged = EvaluationContext.merge(ctx1, ctx2); + EvaluationContext ctxMerged = ctx1.merge(ctx2); assertEquals(key1, ctxMerged.getTargetingKey()); String key2 = "key2"; ctx2.setTargetingKey(key2); - ctxMerged = EvaluationContext.merge(ctx1, ctx2); + ctxMerged = ctx1.merge(ctx2); assertEquals(key2, ctxMerged.getTargetingKey()); ctx2.setTargetingKey(" "); - ctxMerged = EvaluationContext.merge(ctx1, ctx2); + ctxMerged = ctx1.merge(ctx2); assertEquals(key1, ctxMerged.getTargetingKey()); } @Test void asObjectMap() { String key1 = "key1"; - EvaluationContext ctx = new EvaluationContext(key1); + MutableContext ctx = new MutableContext(key1); ctx.add("stringItem", "stringValue"); ctx.add("boolItem", false); ctx.add("integerItem", 1); @@ -172,7 +172,7 @@ public class EvalContextTest { structureValue.put("structIntegerItem", new Value(1)); structureValue.put("structDoubleItem", new Value(1.2)); structureValue.put("structInstantItem", new Value(Instant.ofEpochSecond(1663331342))); - Structure structure = new Structure(structureValue); + Structure structure = new MutableStructure(structureValue); ctx.add("structureItem", structure); diff --git a/src/test/java/dev/openfeature/sdk/FlagEvaluationSpecTest.java b/src/test/java/dev/openfeature/sdk/FlagEvaluationSpecTest.java index fda13ddfe..75b6e5bb2 100644 --- a/src/test/java/dev/openfeature/sdk/FlagEvaluationSpecTest.java +++ b/src/test/java/dev/openfeature/sdk/FlagEvaluationSpecTest.java @@ -100,24 +100,24 @@ private Client _client() { String key = "key"; assertEquals(true, c.getBooleanValue(key, false)); - assertEquals(true, c.getBooleanValue(key, false, new EvaluationContext())); - assertEquals(true, c.getBooleanValue(key, false, new EvaluationContext(), FlagEvaluationOptions.builder().build())); + assertEquals(true, c.getBooleanValue(key, false, new MutableContext())); + assertEquals(true, c.getBooleanValue(key, false, new MutableContext(), FlagEvaluationOptions.builder().build())); assertEquals("gnirts-ym", c.getStringValue(key, "my-string")); - assertEquals("gnirts-ym", c.getStringValue(key, "my-string", new EvaluationContext())); - assertEquals("gnirts-ym", c.getStringValue(key, "my-string", new EvaluationContext(), FlagEvaluationOptions.builder().build())); + assertEquals("gnirts-ym", c.getStringValue(key, "my-string", new MutableContext())); + assertEquals("gnirts-ym", c.getStringValue(key, "my-string", new MutableContext(), FlagEvaluationOptions.builder().build())); assertEquals(400, c.getIntegerValue(key, 4)); - assertEquals(400, c.getIntegerValue(key, 4, new EvaluationContext())); - assertEquals(400, c.getIntegerValue(key, 4, new EvaluationContext(), FlagEvaluationOptions.builder().build())); + assertEquals(400, c.getIntegerValue(key, 4, new MutableContext())); + assertEquals(400, c.getIntegerValue(key, 4, new MutableContext(), FlagEvaluationOptions.builder().build())); assertEquals(40.0, c.getDoubleValue(key, .4)); - assertEquals(40.0, c.getDoubleValue(key, .4, new EvaluationContext())); - assertEquals(40.0, c.getDoubleValue(key, .4, new EvaluationContext(), FlagEvaluationOptions.builder().build())); + assertEquals(40.0, c.getDoubleValue(key, .4, new MutableContext())); + assertEquals(40.0, c.getDoubleValue(key, .4, new MutableContext(), FlagEvaluationOptions.builder().build())); assertEquals(null, c.getObjectValue(key, new Value())); - assertEquals(null, c.getObjectValue(key, new Value(), new EvaluationContext())); - assertEquals(null, c.getObjectValue(key, new Value(), new EvaluationContext(), FlagEvaluationOptions.builder().build())); + assertEquals(null, c.getObjectValue(key, new Value(), new MutableContext())); + assertEquals(null, c.getObjectValue(key, new Value(), new MutableContext(), FlagEvaluationOptions.builder().build())); } @Specification(number="1.4.1", text="The client MUST provide methods for detailed flag value evaluation with parameters flag key (string, required), default value (boolean | number | string | structure, required), evaluation context (optional), and evaluation options (optional), which returns an evaluation details structure.") @@ -138,8 +138,8 @@ private Client _client() { .variant(null) .build(); assertEquals(bd, c.getBooleanDetails(key, true)); - assertEquals(bd, c.getBooleanDetails(key, true, new EvaluationContext())); - assertEquals(bd, c.getBooleanDetails(key, true, new EvaluationContext(), FlagEvaluationOptions.builder().build())); + assertEquals(bd, c.getBooleanDetails(key, true, new MutableContext())); + assertEquals(bd, c.getBooleanDetails(key, true, new MutableContext(), FlagEvaluationOptions.builder().build())); FlagEvaluationDetails sd = FlagEvaluationDetails.builder() .flagKey(key) @@ -147,24 +147,24 @@ private Client _client() { .variant(null) .build(); assertEquals(sd, c.getStringDetails(key, "test")); - assertEquals(sd, c.getStringDetails(key, "test", new EvaluationContext())); - assertEquals(sd, c.getStringDetails(key, "test", new EvaluationContext(), FlagEvaluationOptions.builder().build())); + assertEquals(sd, c.getStringDetails(key, "test", new MutableContext())); + assertEquals(sd, c.getStringDetails(key, "test", new MutableContext(), FlagEvaluationOptions.builder().build())); FlagEvaluationDetails id = FlagEvaluationDetails.builder() .flagKey(key) .value(400) .build(); assertEquals(id, c.getIntegerDetails(key, 4)); - assertEquals(id, c.getIntegerDetails(key, 4, new EvaluationContext())); - assertEquals(id, c.getIntegerDetails(key, 4, new EvaluationContext(), FlagEvaluationOptions.builder().build())); + assertEquals(id, c.getIntegerDetails(key, 4, new MutableContext())); + assertEquals(id, c.getIntegerDetails(key, 4, new MutableContext(), FlagEvaluationOptions.builder().build())); FlagEvaluationDetails dd = FlagEvaluationDetails.builder() .flagKey(key) .value(40.0) .build(); assertEquals(dd, c.getDoubleDetails(key, .4)); - assertEquals(dd, c.getDoubleDetails(key, .4, new EvaluationContext())); - assertEquals(dd, c.getDoubleDetails(key, .4, new EvaluationContext(), FlagEvaluationOptions.builder().build())); + assertEquals(dd, c.getDoubleDetails(key, .4, new MutableContext())); + assertEquals(dd, c.getDoubleDetails(key, .4, new MutableContext(), FlagEvaluationOptions.builder().build())); // TODO: Structure detail tests. } @@ -233,20 +233,20 @@ private Client _client() { DoSomethingProvider provider = new DoSomethingProvider(); api.setProvider(provider); - EvaluationContext apiCtx = new EvaluationContext(); + MutableContext apiCtx = new MutableContext(); apiCtx.add("common", "1"); apiCtx.add("common2", "1"); apiCtx.add("api", "2"); api.setEvaluationContext(apiCtx); Client c = api.getClient(); - EvaluationContext clientCtx = new EvaluationContext(); + MutableContext clientCtx = new MutableContext(); clientCtx.add("common", "3"); clientCtx.add("common2", "3"); clientCtx.add("client", "4"); c.setEvaluationContext(clientCtx); - EvaluationContext invocationCtx = new EvaluationContext(); + MutableContext invocationCtx = new MutableContext(); clientCtx.add("common", "5"); clientCtx.add("invocation", "6"); diff --git a/src/test/java/dev/openfeature/sdk/HookContextTest.java b/src/test/java/dev/openfeature/sdk/HookContextTest.java index 6b28c1dee..e27d0ab57 100644 --- a/src/test/java/dev/openfeature/sdk/HookContextTest.java +++ b/src/test/java/dev/openfeature/sdk/HookContextTest.java @@ -15,7 +15,7 @@ class HookContextTest { FlagValueType.BOOLEAN, meta, meta, - new EvaluationContext(), + new MutableContext(), false ); diff --git a/src/test/java/dev/openfeature/sdk/HookSpecTest.java b/src/test/java/dev/openfeature/sdk/HookSpecTest.java index 1c3e45ab5..d028cc6cb 100644 --- a/src/test/java/dev/openfeature/sdk/HookSpecTest.java +++ b/src/test/java/dev/openfeature/sdk/HookSpecTest.java @@ -103,7 +103,7 @@ void emptyApiHooks() { HookContext.builder() .flagKey("key") .type(FlagValueType.INTEGER) - .ctx(new EvaluationContext()) + .ctx(new MutableContext()) .build(); fail("Missing default value shouldn't be valid"); } catch (NullPointerException e) { @@ -115,7 +115,7 @@ void emptyApiHooks() { HookContext.builder() .flagKey("key") .type(FlagValueType.INTEGER) - .ctx(new EvaluationContext()) + .ctx(new MutableContext()) .defaultValue(1) .build(); } catch (NullPointerException e) { @@ -130,7 +130,7 @@ void emptyApiHooks() { HookContext.builder() .flagKey("key") .type(FlagValueType.INTEGER) - .ctx(new EvaluationContext()) + .ctx(new MutableContext()) .defaultValue(1) .build(); @@ -138,7 +138,7 @@ void emptyApiHooks() { HookContext.builder() .flagKey("key") .type(FlagValueType.INTEGER) - .ctx(new EvaluationContext()) + .ctx(new MutableContext()) .providerMetadata(new NoOpProvider().getMetadata()) .defaultValue(1) .build(); @@ -147,7 +147,7 @@ void emptyApiHooks() { HookContext.builder() .flagKey("key") .type(FlagValueType.INTEGER) - .ctx(new EvaluationContext()) + .ctx(new MutableContext()) .defaultValue(1) .clientMetadata(OpenFeatureAPI.getInstance().getClient().getMetadata()) .build(); @@ -160,7 +160,7 @@ void emptyApiHooks() { Client client = api.getClient(); Hook evalHook = mockBooleanHook(); - client.getBooleanValue("key", false, new EvaluationContext(), + client.getBooleanValue("key", false, new MutableContext(), FlagEvaluationOptions.builder().hook(evalHook).build()); verify(evalHook, times(1)).before(any(), any()); @@ -366,7 +366,7 @@ public void finallyAfter(HookContext ctx, Map hints) { hh.put(hintKey, "My hint value"); hh = Collections.unmodifiableMap(hh); - client.getBooleanValue("key", false, new EvaluationContext(), FlagEvaluationOptions.builder() + client.getBooleanValue("key", false, new MutableContext(), FlagEvaluationOptions.builder() .hook(mutatingHook) .hookHints(hh) .build()); @@ -391,7 +391,7 @@ public void finallyAfter(HookContext ctx, Map hints) { OpenFeatureAPI api = OpenFeatureAPI.getInstance(); api.setProvider(provider); Client client = api.getClient(); - client.getBooleanValue("key", false, new EvaluationContext(), + client.getBooleanValue("key", false, new MutableContext(), FlagEvaluationOptions.builder().hook(hook).build()); order.verify(hook).before(any(), any()); @@ -406,7 +406,7 @@ public void finallyAfter(HookContext ctx, Map hints) { Hook hook = mockBooleanHook(); doThrow(RuntimeException.class).when(hook).before(any(), any()); Client client = getClient(null); - Boolean value = client.getBooleanValue("key", false, new EvaluationContext(), + Boolean value = client.getBooleanValue("key", false, new MutableContext(), FlagEvaluationOptions.builder().hook(hook).build()); verify(hook, times(1)).before(any(), any()); verify(hook, times(1)).error(any(), any(), any()); @@ -418,7 +418,7 @@ public void finallyAfter(HookContext ctx, Map hints) { Hook hook = mockBooleanHook(); doThrow(RuntimeException.class).when(hook).after(any(), any(), any()); Client client = getClient(null); - client.getBooleanValue("key", false, new EvaluationContext(), + client.getBooleanValue("key", false, new MutableContext(), FlagEvaluationOptions.builder().hook(hook).build()); verify(hook, times(1)).after(any(), any(), any()); verify(hook, times(1)).error(any(), any(), any()); @@ -431,7 +431,7 @@ public void finallyAfter(HookContext ctx, Map hints) { Client client = getClient(null); - client.getBooleanValue("key", false, new EvaluationContext(), + client.getBooleanValue("key", false, new MutableContext(), FlagEvaluationOptions.builder() .hook(hook2) .hook(hook) @@ -447,7 +447,7 @@ public void finallyAfter(HookContext ctx, Map hints) { @Specification(number = "4.1.4", text = "The evaluation context MUST be mutable only within the before hook.") @Specification(number = "4.3.3", text = "Any evaluation context returned from a before hook MUST be passed to subsequent before hooks (via HookContext).") @Test void beforeContextUpdated() { - EvaluationContext ctx = new EvaluationContext(); + MutableContext ctx = new MutableContext(); Hook hook = mockBooleanHook(); when(hook.before(any(), any())).thenReturn(Optional.of(ctx)); Hook hook2 = mockBooleanHook(); @@ -472,11 +472,11 @@ public void finallyAfter(HookContext ctx, Map hints) { @Specification(number="4.3.4", text="When before hooks have finished executing, any resulting evaluation context MUST be merged with the existing evaluation context.") @Test void mergeHappensCorrectly() { - EvaluationContext hookCtx = new EvaluationContext(); + MutableContext hookCtx = new MutableContext(); hookCtx.add("test", "works"); hookCtx.add("another", "exists"); - EvaluationContext invocationCtx = new EvaluationContext(); + MutableContext invocationCtx = new MutableContext(); invocationCtx.add("something", "here"); invocationCtx.add("test", "broken"); @@ -496,9 +496,9 @@ public void finallyAfter(HookContext ctx, Map hints) { .hook(hook) .build()); - ArgumentCaptor captor = ArgumentCaptor.forClass(EvaluationContext.class); + ArgumentCaptor captor = ArgumentCaptor.forClass(MutableContext.class); verify(provider).getBooleanEvaluation(any(), any(), captor.capture()); - EvaluationContext ec = captor.getValue(); + MutableContext ec = captor.getValue(); assertEquals("works", ec.getValue("test").asString()); assertEquals("exists", ec.getValue("another").asString()); assertEquals("here", ec.getValue("something").asString()); @@ -513,7 +513,7 @@ public void finallyAfter(HookContext ctx, Map hints) { InOrder order = inOrder(hook, hook2); Client client = getClient(null); - client.getBooleanValue("key", false, new EvaluationContext(), + client.getBooleanValue("key", false, new MutableContext(), FlagEvaluationOptions.builder() .hook(hook2) .hook(hook) @@ -533,7 +533,7 @@ public void finallyAfter(HookContext ctx, Map hints) { InOrder order = inOrder(hook, hook2); Client client = getClient(null); - client.getBooleanValue("key", false, new EvaluationContext(), + client.getBooleanValue("key", false, new MutableContext(), FlagEvaluationOptions.builder() .hook(hook2) .hook(hook) diff --git a/src/test/java/dev/openfeature/sdk/HookSupportTest.java b/src/test/java/dev/openfeature/sdk/HookSupportTest.java index e846707af..d787bb0c6 100644 --- a/src/test/java/dev/openfeature/sdk/HookSupportTest.java +++ b/src/test/java/dev/openfeature/sdk/HookSupportTest.java @@ -21,7 +21,7 @@ class HookSupportTest implements HookFixtures { @Test @DisplayName("should merge EvaluationContexts on before hooks correctly") void shouldMergeEvaluationContextsOnBeforeHooksCorrectly() { - EvaluationContext baseContext = new EvaluationContext(); + MutableContext baseContext = new MutableContext(); baseContext.add("baseKey", "baseValue"); HookContext hookContext = new HookContext<>("flagKey", FlagValueType.STRING, "defaultValue", baseContext, () -> "client", () -> "provider"); Hook hook1 = mockStringHook(); @@ -43,7 +43,7 @@ void shouldMergeEvaluationContextsOnBeforeHooksCorrectly() { void shouldAlwaysCallGenericHook(FlagValueType flagValueType) { Hook genericHook = mockGenericHook(); HookSupport hookSupport = new HookSupport(); - EvaluationContext baseContext = new EvaluationContext(); + MutableContext baseContext = new MutableContext(); IllegalStateException expectedException = new IllegalStateException("All fine, just a test"); HookContext hookContext = new HookContext<>("flagKey", flagValueType, createDefaultValue(flagValueType), baseContext, () -> "client", () -> "provider"); @@ -75,8 +75,8 @@ private Object createDefaultValue(FlagValueType flagValueType) { } } - private EvaluationContext evaluationContextWithValue(String key, String value) { - EvaluationContext result = new EvaluationContext(); + private MutableContext evaluationContextWithValue(String key, String value) { + MutableContext result = new MutableContext(); result.add(key, value); return result; } diff --git a/src/test/java/dev/openfeature/sdk/ProviderSpecTest.java b/src/test/java/dev/openfeature/sdk/ProviderSpecTest.java index 5e642421c..27cc64e8f 100644 --- a/src/test/java/dev/openfeature/sdk/ProviderSpecTest.java +++ b/src/test/java/dev/openfeature/sdk/ProviderSpecTest.java @@ -24,33 +24,33 @@ public class ProviderSpecTest { @Specification(number="2.9.1", text="The flag resolution structure SHOULD accept a generic " + "argument (or use an equivalent language feature) which indicates the type of the wrapped value field.") @Test void flag_value_set() { - ProviderEvaluation int_result = p.getIntegerEvaluation("key", 4, new EvaluationContext()); + ProviderEvaluation int_result = p.getIntegerEvaluation("key", 4, new MutableContext()); assertNotNull(int_result.getValue()); - ProviderEvaluation double_result = p.getDoubleEvaluation("key", 0.4, new EvaluationContext()); + ProviderEvaluation double_result = p.getDoubleEvaluation("key", 0.4, new MutableContext()); assertNotNull(double_result.getValue()); - ProviderEvaluation string_result = p.getStringEvaluation("key", "works", new EvaluationContext()); + ProviderEvaluation string_result = p.getStringEvaluation("key", "works", new MutableContext()); assertNotNull(string_result.getValue()); - ProviderEvaluation boolean_result = p.getBooleanEvaluation("key", false, new EvaluationContext()); + ProviderEvaluation boolean_result = p.getBooleanEvaluation("key", false, new MutableContext()); assertNotNull(boolean_result.getValue()); - ProviderEvaluation object_result = p.getObjectEvaluation("key", new Value(), new EvaluationContext()); + ProviderEvaluation object_result = p.getObjectEvaluation("key", new Value(), new MutableContext()); assertNotNull(object_result.getValue()); } @Specification(number="2.6", text="The `provider` SHOULD populate the `flag resolution` structure's `reason` field with `\"DEFAULT\",` `\"TARGETING_MATCH\"`, `\"SPLIT\"`, `\"DISABLED\"`, `\"UNKNOWN\"`, `\"ERROR\"` or some other string indicating the semantic reason for the returned flag value.") @Test void has_reason() { - ProviderEvaluation result = p.getBooleanEvaluation("key", false, new EvaluationContext()); + ProviderEvaluation result = p.getBooleanEvaluation("key", false, new MutableContext()); assertEquals(Reason.DEFAULT.toString(), result.getReason()); } @Specification(number="2.7", text="In cases of normal execution, the provider MUST NOT populate " + "the flag resolution structure's error code field, or otherwise must populate it with a null or falsy value.") @Test void no_error_code_by_default() { - ProviderEvaluation result = p.getBooleanEvaluation("key", false, new EvaluationContext()); + ProviderEvaluation result = p.getBooleanEvaluation("key", false, new MutableContext()); assertNull(result.getErrorCode()); } @@ -62,16 +62,16 @@ public class ProviderSpecTest { @Specification(number="2.5", text="In cases of normal execution, the provider SHOULD populate the " + "flag resolution structure's variant field with a string identifier corresponding to the returned flag value.") @Test void variant_set() { - ProviderEvaluation int_result = p.getIntegerEvaluation("key", 4, new EvaluationContext()); + ProviderEvaluation int_result = p.getIntegerEvaluation("key", 4, new MutableContext()); assertNotNull(int_result.getReason()); - ProviderEvaluation double_result = p.getDoubleEvaluation("key", 0.4, new EvaluationContext()); + ProviderEvaluation double_result = p.getDoubleEvaluation("key", 0.4, new MutableContext()); assertNotNull(double_result.getReason()); - ProviderEvaluation string_result = p.getStringEvaluation("key", "works", new EvaluationContext()); + ProviderEvaluation string_result = p.getStringEvaluation("key", "works", new MutableContext()); assertNotNull(string_result.getReason()); - ProviderEvaluation boolean_result = p.getBooleanEvaluation("key", false, new EvaluationContext()); + ProviderEvaluation boolean_result = p.getBooleanEvaluation("key", false, new MutableContext()); assertNotNull(boolean_result.getReason()); } diff --git a/src/test/java/dev/openfeature/sdk/StructureTest.java b/src/test/java/dev/openfeature/sdk/StructureTest.java index 8170ea3d5..f05f93023 100644 --- a/src/test/java/dev/openfeature/sdk/StructureTest.java +++ b/src/test/java/dev/openfeature/sdk/StructureTest.java @@ -14,7 +14,7 @@ public class StructureTest { @Test public void noArgShouldContainEmptyAttributes() { - Structure structure = new Structure(); + MutableStructure structure = new MutableStructure(); assertEquals(0, structure.asMap().keySet().size()); } @@ -25,7 +25,7 @@ public class StructureTest { put(KEY, new Value(KEY)); } }; - Structure structure = new Structure(map); + MutableStructure structure = new MutableStructure(map); assertEquals(KEY, structure.asMap().get(KEY).asString()); assertNotSame(structure.asMap(), map); // should be a copy } @@ -45,11 +45,11 @@ public class StructureTest { int INT_VAL = 13; double DOUBLE_VAL = .5; Instant DATE_VAL = Instant.now(); - Structure STRUCT_VAL = new Structure(); + MutableStructure STRUCT_VAL = new MutableStructure(); List LIST_VAL = new ArrayList(); Value VALUE_VAL = new Value(); - Structure structure = new Structure(); + MutableStructure structure = new MutableStructure(); structure.add(BOOL_KEY, BOOL_VAL); structure.add(STRING_KEY, STRING_VAL); structure.add(INT_KEY, INT_VAL); diff --git a/src/test/java/dev/openfeature/sdk/ValueTest.java b/src/test/java/dev/openfeature/sdk/ValueTest.java index f8bebb21d..cf25e7b37 100644 --- a/src/test/java/dev/openfeature/sdk/ValueTest.java +++ b/src/test/java/dev/openfeature/sdk/ValueTest.java @@ -24,7 +24,7 @@ public class ValueTest { list.add(true); list.add("val"); list.add(.5); - list.add(new Structure()); + list.add(new MutableStructure()); list.add(new ArrayList()); list.add(Instant.now()); @@ -96,7 +96,7 @@ class Something {} @Test public void structureShouldContainStructure() { String INNER_KEY = "key"; String INNER_VALUE = "val"; - Structure innerValue = new Structure().add(INNER_KEY, INNER_VALUE); + MutableStructure innerValue = new MutableStructure().add(INNER_KEY, INNER_VALUE); Value value = new Value(innerValue); assertTrue(value.isStructure()); assertEquals(INNER_VALUE, value.asStructure().getValue(INNER_KEY).asString()); diff --git a/src/test/java/dev/openfeature/sdk/integration/StepDefinitions.java b/src/test/java/dev/openfeature/sdk/integration/StepDefinitions.java index 39147565a..3513bddc4 100644 --- a/src/test/java/dev/openfeature/sdk/integration/StepDefinitions.java +++ b/src/test/java/dev/openfeature/sdk/integration/StepDefinitions.java @@ -5,8 +5,9 @@ // import dev.openfeature.contrib.providers.flagd.FlagdProvider; import dev.openfeature.sdk.Client; -import dev.openfeature.sdk.EvaluationContext; import dev.openfeature.sdk.FlagEvaluationDetails; +import dev.openfeature.sdk.MutableStructure; +import dev.openfeature.sdk.MutableContext; import dev.openfeature.sdk.OpenFeatureAPI; import dev.openfeature.sdk.Reason; import dev.openfeature.sdk.Structure; @@ -32,7 +33,7 @@ public class StepDefinitions { private String contextAwareFlagKey; private String contextAwareDefaultValue; - private EvaluationContext context; + private MutableContext context; private String contextAwareValue; private String notFoundFlagKey; @@ -206,7 +207,7 @@ public void the_variant_should_be_and_the_reason_should_be(String expectedVarian @When("context contains keys {string}, {string}, {string}, {string} with values {string}, {string}, {int}, {string}") public void context_contains_keys_with_values(String field1, String field2, String field3, String field4, String value1, String value2, Integer value3, String value4) { - this.context = new EvaluationContext() + this.context = new MutableContext() .add(field1, value1) .add(field2, value2) .add(field3, value3) @@ -229,7 +230,7 @@ public void the_resolved_string_response_should_be(String expected) { @Then("the resolved flag value is {string} when the context is empty") public void the_resolved_flag_value_is_when_the_context_is_empty(String expected) { String emptyContextValue = client.getStringValue(contextAwareFlagKey, contextAwareDefaultValue, - new EvaluationContext()); + new MutableContext()); assertEquals(expected, emptyContextValue); }