Support for arbitrary classes in EvaluationContext

This commit is contained in:
Justin Abrahms 2022-05-27 22:31:53 -07:00
parent c9fae1b868
commit 9a8beadfd6
No known key found for this signature in database
GPG Key ID: 599E2E12011DC474
4 changed files with 66 additions and 23 deletions

View File

@ -26,6 +26,8 @@ repositories {
dependencies { dependencies {
implementation 'org.slf4j:slf4j-log4j12:1.7.29' implementation 'org.slf4j:slf4j-log4j12:1.7.29'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.0'
// This dependency is used internally, and not exposed to consumers on their own compile classpath. // This dependency is used internally, and not exposed to consumers on their own compile classpath.
implementation 'com.google.guava:guava:30.1.1-jre' implementation 'com.google.guava:guava:30.1.1-jre'

View File

@ -1,9 +1,7 @@
package dev.openfeature.javasdk; package dev.openfeature.javasdk;
import lombok.EqualsAndHashCode; import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Getter; import lombok.*;
import lombok.Setter;
import lombok.ToString;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
@ -13,14 +11,32 @@ import java.util.Map;
@ToString @EqualsAndHashCode @ToString @EqualsAndHashCode
@SuppressWarnings("PMD.BeanMembersShouldSerialize") @SuppressWarnings("PMD.BeanMembersShouldSerialize")
public class EvaluationContext { public class EvaluationContext {
@EqualsAndHashCode.Exclude private final ObjectMapper objMapper;
@Setter @Getter private String targetingKey; @Setter @Getter private String targetingKey;
private final Map<String, Integer> integerAttributes; private final Map<String, Integer> integerAttributes;
private final Map<String, String> stringAttributes; private final Map<String, String> stringAttributes;
private final Map<String, Boolean> booleanAttributes;
final Map<String, String> jsonAttributes;
EvaluationContext() { EvaluationContext() {
objMapper = new ObjectMapper();
this.targetingKey = ""; this.targetingKey = "";
this.integerAttributes = new HashMap<>(); this.integerAttributes = new HashMap<>();
this.stringAttributes = new HashMap<>(); this.stringAttributes = new HashMap<>();
booleanAttributes = new HashMap<>();
jsonAttributes = new HashMap<>();
}
// TODO Not sure if I should have sneakythrows or checked exceptions here..
@SneakyThrows
public <T> void addStructureAttribute(String key, T value) {
jsonAttributes.put(key, objMapper.writeValueAsString(value));
}
@SneakyThrows
public <T> T getStructureAttribute(String key, Class<T> klass) {
String val = jsonAttributes.get(key);
return objMapper.readValue(val, klass);
} }
public void addStringAttribute(String key, String value) { public void addStringAttribute(String key, String value) {
@ -40,19 +56,17 @@ public class EvaluationContext {
} }
public Boolean getBooleanAttribute(String key) { public Boolean getBooleanAttribute(String key) {
return Boolean.valueOf(stringAttributes.get(key)); return booleanAttributes.get(key);
} }
public void addBooleanAttribute(String key, Boolean b) { public void addBooleanAttribute(String key, Boolean b) {
stringAttributes.put(key, b.toString()); booleanAttributes.put(key, b);
} }
public void addDatetimeAttribute(String key, ZonedDateTime value) { public void addDatetimeAttribute(String key, ZonedDateTime value) {
this.stringAttributes.put(key, value.format(DateTimeFormatter.ISO_ZONED_DATE_TIME)); this.stringAttributes.put(key, value.format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
} }
// TODO: addStructure or similar.
public ZonedDateTime getDatetimeAttribute(String key) { public ZonedDateTime getDatetimeAttribute(String key) {
String attr = this.stringAttributes.get(key); String attr = this.stringAttributes.get(key);
if (attr == null) { if (attr == null) {
@ -66,21 +80,19 @@ public class EvaluationContext {
*/ */
public static EvaluationContext merge(EvaluationContext ctx1, EvaluationContext ctx2) { public static EvaluationContext merge(EvaluationContext ctx1, EvaluationContext ctx2) {
EvaluationContext ec = new EvaluationContext(); EvaluationContext ec = new EvaluationContext();
for (Map.Entry<String, Integer> e : ctx1.integerAttributes.entrySet()) {
ec.addIntegerAttribute(e.getKey(), e.getValue());
}
for (Map.Entry<String, Integer> e : ctx2.integerAttributes.entrySet()) { ec.stringAttributes.putAll(ctx1.stringAttributes);
ec.addIntegerAttribute(e.getKey(), e.getValue()); ec.stringAttributes.putAll(ctx2.stringAttributes);
}
for (Map.Entry<String, String> e : ctx1.stringAttributes.entrySet()) { ec.integerAttributes.putAll(ctx1.integerAttributes);
ec.addStringAttribute(e.getKey(), e.getValue()); ec.integerAttributes.putAll(ctx2.integerAttributes);
}
ec.booleanAttributes.putAll(ctx1.booleanAttributes);
ec.booleanAttributes.putAll(ctx2.booleanAttributes);
ec.jsonAttributes.putAll(ctx1.jsonAttributes);
ec.jsonAttributes.putAll(ctx2.jsonAttributes);
for (Map.Entry<String, String> e : ctx2.stringAttributes.entrySet()) {
ec.addStringAttribute(e.getKey(), e.getValue());
}
if (ctx1.getTargetingKey() != null) { if (ctx1.getTargetingKey() != null) {
ec.setTargetingKey(ctx1.getTargetingKey()); ec.setTargetingKey(ctx1.getTargetingKey());
} }

View File

@ -1,6 +1,5 @@
package dev.openfeature.javasdk; package dev.openfeature.javasdk;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
@ -40,6 +39,24 @@ public class EvalContextTests {
@Specification(spec="Evaluation Context", number="3.2", text="The evaluation context MUST support the inclusion of " + @Specification(spec="Evaluation Context", number="3.2", text="The evaluation context MUST support the inclusion of " +
"custom fields, having keys of type `string`, and " + "custom fields, having keys of type `string`, and " +
"values of type `boolean | string | number | datetime | structure`.") "values of type `boolean | string | number | datetime | structure`.")
@Disabled("Structure support") @Test void eval_context__structure() {
@Test void eval_context__structure() {} Node<Integer> n1 = new Node<>();
n1.value = 4;
Node<Integer> n2 = new Node<>();
n2.value = 2;
n2.left = n1;
EvaluationContext ec = new EvaluationContext();
ec.addStructureAttribute("obj", n2);
String stringyObject = ec.jsonAttributes.get("obj");
assertEquals("{\"left\":{\"left\":null,\"right\":null,\"value\":4},\"right\":null,\"value\":2}", stringyObject);
Node nodeFromString = ec.getStructureAttribute("obj", Node.class);
assertEquals(n2, nodeFromString);
assertEquals(n1, nodeFromString.left);
assertEquals(2, nodeFromString.value);
assertEquals(4, nodeFromString.left.value);
}
} }

View File

@ -0,0 +1,12 @@
package dev.openfeature.javasdk;
import lombok.Data;
@Data
/**
* This is only for testing that object serialization works with both generics and nested classes.
*/
class Node<T> {
Node<T> left, right;
T value;
}