diff --git a/src/main/java/dev/openfeature/javasdk/EvaluationContext.java b/src/main/java/dev/openfeature/javasdk/EvaluationContext.java index 7eb3b16db..41088cfdd 100644 --- a/src/main/java/dev/openfeature/javasdk/EvaluationContext.java +++ b/src/main/java/dev/openfeature/javasdk/EvaluationContext.java @@ -1,6 +1,8 @@ package dev.openfeature.javasdk; import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.ImmutableMap; +import java.util.stream.Collectors; import lombok.*; import java.time.ZonedDateTime; @@ -18,7 +20,7 @@ public class EvaluationContext { private final Map booleanAttributes; final Map jsonAttributes; - EvaluationContext() { + public EvaluationContext() { objMapper = new ObjectMapper(); this.targetingKey = ""; this.integerAttributes = new HashMap<>(); @@ -29,8 +31,9 @@ public class EvaluationContext { // TODO Not sure if I should have sneakythrows or checked exceptions here.. @SneakyThrows - public void addStructureAttribute(String key, T value) { + public EvaluationContext withStructureAttribute(String key, T value) { jsonAttributes.put(key, objMapper.writeValueAsString(value)); + return this; } @SneakyThrows @@ -39,16 +42,18 @@ public T getStructureAttribute(String key, Class klass) { return objMapper.readValue(val, klass); } - public void addStringAttribute(String key, String value) { + public EvaluationContext withStringAttribute(String key, String value) { stringAttributes.put(key, value); + return this; } public String getStringAttribute(String key) { return stringAttributes.get(key); } - public void addIntegerAttribute(String key, Integer value) { + public EvaluationContext withIntegerAttribute(String key, Integer value) { integerAttributes.put(key, value); + return this; } public Integer getIntegerAttribute(String key) { @@ -59,12 +64,14 @@ public Boolean getBooleanAttribute(String key) { return booleanAttributes.get(key); } - public void addBooleanAttribute(String key, Boolean b) { + public EvaluationContext withBooleanAttribute(String key, Boolean b) { booleanAttributes.put(key, b); + return this; } - public void addDatetimeAttribute(String key, ZonedDateTime value) { + public EvaluationContext withDatetimeAttribute(String key, ZonedDateTime value) { this.stringAttributes.put(key, value.format(DateTimeFormatter.ISO_ZONED_DATE_TIME)); + return this; } public ZonedDateTime getDatetimeAttribute(String key) { @@ -103,4 +110,49 @@ public static EvaluationContext merge(EvaluationContext ctx1, EvaluationContext return ec; } + + public EvaluationContext fromMap(Map map) { + EvaluationContext context = new EvaluationContext(); + context.integerAttributes.putAll( + map.entrySet() + .stream() + .filter(entry -> entry.getValue() instanceof Integer) + .collect(Collectors.toMap(Map.Entry::getKey, e -> (Integer) e.getValue())) + ); + context.stringAttributes.putAll( + map.entrySet() + .stream() + .filter(entry -> entry.getValue() instanceof String) + .collect(Collectors.toMap(Map.Entry::getKey, e -> (String)e.getValue())) + ); + context.booleanAttributes.putAll( + map.entrySet() + .stream() + .filter(entry -> entry.getValue() instanceof Boolean) + .collect(Collectors.toMap(Map.Entry::getKey, e -> (Boolean)e.getValue())) + ); +// Hmmm, this won't work as we already have a string type. +// I would recommend changing the type to Map from Jackson +// context.jsonAttributes.putAll( +// map.entrySet() +// .stream() +// .filter(entry -> entry.getValue() instanceof String) +// .collect(Collectors.toMap(e -> e.getKey(), e -> (String)e.getValue())) +// ); + + return null; + } + + /** + * Converts the Evaluation Context into a standard {@link Map} + */ + public Map toMap() { + Map map = new HashMap<>(); + // 🤔 This will fail if two different maps have the same key. + map.putAll(integerAttributes); + map.putAll(stringAttributes); + map.putAll(booleanAttributes); + map.putAll(jsonAttributes); + return ImmutableMap.copyOf(map); + } } diff --git a/src/test/java/dev/openfeature/javasdk/EvalContextTest.java b/src/test/java/dev/openfeature/javasdk/EvalContextTest.java index 4efcc3c80..eb410b1aa 100644 --- a/src/test/java/dev/openfeature/javasdk/EvalContextTest.java +++ b/src/test/java/dev/openfeature/javasdk/EvalContextTest.java @@ -22,17 +22,17 @@ public class EvalContextTest { @Test void eval_context() { EvaluationContext ec = new EvaluationContext(); - ec.addStringAttribute("str", "test"); + ec.withStringAttribute("str", "test"); assertEquals("test", ec.getStringAttribute("str")); - ec.addBooleanAttribute("bool", true); + ec.withBooleanAttribute("bool", true); assertEquals(true, ec.getBooleanAttribute("bool")); - ec.addIntegerAttribute("int", 4); + ec.withIntegerAttribute("int", 4); assertEquals(4, ec.getIntegerAttribute("int")); ZonedDateTime dt = ZonedDateTime.now(); - ec.addDatetimeAttribute("dt", dt); + ec.withDatetimeAttribute("dt", dt); assertEquals(dt, ec.getDatetimeAttribute("dt")); } @@ -47,7 +47,7 @@ public class EvalContextTest { n2.left = n1; EvaluationContext ec = new EvaluationContext(); - ec.addStructureAttribute("obj", n2); + ec.withStructureAttribute("obj", n2); String stringyObject = ec.jsonAttributes.get("obj"); diff --git a/src/test/java/dev/openfeature/javasdk/HookSpecTest.java b/src/test/java/dev/openfeature/javasdk/HookSpecTest.java index 9d38f03ed..2fcae5ac5 100644 --- a/src/test/java/dev/openfeature/javasdk/HookSpecTest.java +++ b/src/test/java/dev/openfeature/javasdk/HookSpecTest.java @@ -429,11 +429,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 invocation evaluation context with the invocation evaluation context taking precedence in the case of any conflicts.") @Test void mergeHappensCorrectly() { EvaluationContext hookCtx = new EvaluationContext(); - hookCtx.addStringAttribute("test", "broken"); - hookCtx.addStringAttribute("another", "exists"); + hookCtx.withStringAttribute("test", "broken"); + hookCtx.withStringAttribute("another", "exists"); EvaluationContext invocationCtx = new EvaluationContext(); - invocationCtx.addStringAttribute("test", "works"); + invocationCtx.withStringAttribute("test", "works"); Hook hook = mockBooleanHook(); when(hook.before(any(), any())).thenReturn(Optional.of(hookCtx)); diff --git a/src/test/java/dev/openfeature/javasdk/HookSupportTest.java b/src/test/java/dev/openfeature/javasdk/HookSupportTest.java index ece43553b..134d9493e 100644 --- a/src/test/java/dev/openfeature/javasdk/HookSupportTest.java +++ b/src/test/java/dev/openfeature/javasdk/HookSupportTest.java @@ -16,7 +16,7 @@ class HookSupportTest implements HookFixtures { @DisplayName("should merge EvaluationContexts on before hooks correctly") void shouldMergeEvaluationContextsOnBeforeHooksCorrectly() { EvaluationContext baseContext = new EvaluationContext(); - baseContext.addStringAttribute("baseKey", "baseValue"); + baseContext.withStringAttribute("baseKey", "baseValue"); HookContext hookContext = new HookContext<>("flagKey", FlagValueType.STRING, "defaultValue", baseContext, () -> "client", () -> "provider"); Hook hook1 = mockStringHook(); Hook hook2 = mockStringHook(); @@ -69,7 +69,7 @@ private Object createDefaultValue(FlagValueType flagValueType) { private EvaluationContext evaluationContextWithValue(String key, String value) { EvaluationContext result = new EvaluationContext(); - result.addStringAttribute(key, value); + result.withStringAttribute(key, value); return result; }