Skip to content

Commit 9a8bead

Browse files
committed
Support for arbitrary classes in EvaluationContext
1 parent c9fae1b commit 9a8bead

File tree

4 files changed

+66
-23
lines changed

4 files changed

+66
-23
lines changed

lib/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ repositories {
2626

2727
dependencies {
2828
implementation 'org.slf4j:slf4j-log4j12:1.7.29'
29+
implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.0'
30+
2931
// This dependency is used internally, and not exposed to consumers on their own compile classpath.
3032
implementation 'com.google.guava:guava:30.1.1-jre'
3133

lib/src/main/java/dev/openfeature/javasdk/EvaluationContext.java

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
package dev.openfeature.javasdk;
22

3-
import lombok.EqualsAndHashCode;
4-
import lombok.Getter;
5-
import lombok.Setter;
6-
import lombok.ToString;
3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import lombok.*;
75

86
import java.time.ZonedDateTime;
97
import java.time.format.DateTimeFormatter;
@@ -13,14 +11,32 @@
1311
@ToString @EqualsAndHashCode
1412
@SuppressWarnings("PMD.BeanMembersShouldSerialize")
1513
public class EvaluationContext {
14+
@EqualsAndHashCode.Exclude private final ObjectMapper objMapper;
1615
@Setter @Getter private String targetingKey;
1716
private final Map<String, Integer> integerAttributes;
1817
private final Map<String, String> stringAttributes;
18+
private final Map<String, Boolean> booleanAttributes;
19+
final Map<String, String> jsonAttributes;
1920

2021
EvaluationContext() {
22+
objMapper = new ObjectMapper();
2123
this.targetingKey = "";
2224
this.integerAttributes = new HashMap<>();
2325
this.stringAttributes = new HashMap<>();
26+
booleanAttributes = new HashMap<>();
27+
jsonAttributes = new HashMap<>();
28+
}
29+
30+
// TODO Not sure if I should have sneakythrows or checked exceptions here..
31+
@SneakyThrows
32+
public <T> void addStructureAttribute(String key, T value) {
33+
jsonAttributes.put(key, objMapper.writeValueAsString(value));
34+
}
35+
36+
@SneakyThrows
37+
public <T> T getStructureAttribute(String key, Class<T> klass) {
38+
String val = jsonAttributes.get(key);
39+
return objMapper.readValue(val, klass);
2440
}
2541

2642
public void addStringAttribute(String key, String value) {
@@ -40,19 +56,17 @@ public Integer getIntegerAttribute(String key) {
4056
}
4157

4258
public Boolean getBooleanAttribute(String key) {
43-
return Boolean.valueOf(stringAttributes.get(key));
59+
return booleanAttributes.get(key);
4460
}
4561

4662
public void addBooleanAttribute(String key, Boolean b) {
47-
stringAttributes.put(key, b.toString());
63+
booleanAttributes.put(key, b);
4864
}
4965

5066
public void addDatetimeAttribute(String key, ZonedDateTime value) {
5167
this.stringAttributes.put(key, value.format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
5268
}
5369

54-
// TODO: addStructure or similar.
55-
5670
public ZonedDateTime getDatetimeAttribute(String key) {
5771
String attr = this.stringAttributes.get(key);
5872
if (attr == null) {
@@ -66,21 +80,19 @@ public ZonedDateTime getDatetimeAttribute(String key) {
6680
*/
6781
public static EvaluationContext merge(EvaluationContext ctx1, EvaluationContext ctx2) {
6882
EvaluationContext ec = new EvaluationContext();
69-
for (Map.Entry<String, Integer> e : ctx1.integerAttributes.entrySet()) {
70-
ec.addIntegerAttribute(e.getKey(), e.getValue());
71-
}
7283

73-
for (Map.Entry<String, Integer> e : ctx2.integerAttributes.entrySet()) {
74-
ec.addIntegerAttribute(e.getKey(), e.getValue());
75-
}
84+
ec.stringAttributes.putAll(ctx1.stringAttributes);
85+
ec.stringAttributes.putAll(ctx2.stringAttributes);
7686

77-
for (Map.Entry<String, String> e : ctx1.stringAttributes.entrySet()) {
78-
ec.addStringAttribute(e.getKey(), e.getValue());
79-
}
87+
ec.integerAttributes.putAll(ctx1.integerAttributes);
88+
ec.integerAttributes.putAll(ctx2.integerAttributes);
89+
90+
ec.booleanAttributes.putAll(ctx1.booleanAttributes);
91+
ec.booleanAttributes.putAll(ctx2.booleanAttributes);
92+
93+
ec.jsonAttributes.putAll(ctx1.jsonAttributes);
94+
ec.jsonAttributes.putAll(ctx2.jsonAttributes);
8095

81-
for (Map.Entry<String, String> e : ctx2.stringAttributes.entrySet()) {
82-
ec.addStringAttribute(e.getKey(), e.getValue());
83-
}
8496
if (ctx1.getTargetingKey() != null) {
8597
ec.setTargetingKey(ctx1.getTargetingKey());
8698
}

lib/src/test/java/dev/openfeature/javasdk/EvalContextTests.java

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package dev.openfeature.javasdk;
22

3-
import org.junit.jupiter.api.Disabled;
43
import org.junit.jupiter.api.Test;
54

65
import java.time.ZonedDateTime;
@@ -40,6 +39,24 @@ public class EvalContextTests {
4039
@Specification(spec="Evaluation Context", number="3.2", text="The evaluation context MUST support the inclusion of " +
4140
"custom fields, having keys of type `string`, and " +
4241
"values of type `boolean | string | number | datetime | structure`.")
43-
@Disabled("Structure support")
44-
@Test void eval_context__structure() {}
42+
@Test void eval_context__structure() {
43+
Node<Integer> n1 = new Node<>();
44+
n1.value = 4;
45+
Node<Integer> n2 = new Node<>();
46+
n2.value = 2;
47+
n2.left = n1;
48+
49+
EvaluationContext ec = new EvaluationContext();
50+
ec.addStructureAttribute("obj", n2);
51+
52+
53+
String stringyObject = ec.jsonAttributes.get("obj");
54+
assertEquals("{\"left\":{\"left\":null,\"right\":null,\"value\":4},\"right\":null,\"value\":2}", stringyObject);
55+
56+
Node nodeFromString = ec.getStructureAttribute("obj", Node.class);
57+
assertEquals(n2, nodeFromString);
58+
assertEquals(n1, nodeFromString.left);
59+
assertEquals(2, nodeFromString.value);
60+
assertEquals(4, nodeFromString.left.value);
61+
}
4562
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package dev.openfeature.javasdk;
2+
3+
import lombok.Data;
4+
5+
@Data
6+
/**
7+
* This is only for testing that object serialization works with both generics and nested classes.
8+
*/
9+
class Node<T> {
10+
Node<T> left, right;
11+
T value;
12+
}

0 commit comments

Comments
 (0)