Skip to content

Commit aaa924f

Browse files
committed
Support for eval context & merging relevant thereof
1 parent 23e2cd0 commit aaa924f

File tree

7 files changed

+113
-189
lines changed

7 files changed

+113
-189
lines changed
Lines changed: 31 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,21 @@
11
package dev.openfeature.javasdk;
22

3+
import lombok.EqualsAndHashCode;
34
import lombok.Getter;
5+
import lombok.Setter;
6+
import lombok.ToString;
47

58
import java.time.ZonedDateTime;
69
import java.time.format.DateTimeFormatter;
710
import java.util.HashMap;
811
import java.util.Map;
912

13+
@ToString @EqualsAndHashCode
1014
public class EvaluationContext {
11-
@Getter private final String targetingKey;
15+
@Setter @Getter private String targetingKey;
1216
private final Map<String, Integer> integerAttributes;
1317
private final Map<String, String> stringAttributes;
1418

15-
16-
private enum KNOWN_KEYS {
17-
EMAIL,
18-
FIRST_NAME,
19-
LAST_NAME,
20-
NAME,
21-
IP,
22-
TZ,
23-
LOCALE,
24-
COUNTRY_CODE,
25-
ENVIRONMENT,
26-
APPLICATION,
27-
VERSION,
28-
TIMESTAMP,
29-
}
30-
3119
EvaluationContext() {
3220
this.targetingKey = "";
3321
this.integerAttributes = new HashMap<>();
@@ -62,6 +50,8 @@ public void addDatetimeAttribute(String key, ZonedDateTime value) {
6250
this.stringAttributes.put(key, value.format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
6351
}
6452

53+
// TODO: addStructure or similar.
54+
6555
public ZonedDateTime getDatetimeAttribute(String key) {
6656
String attr = this.stringAttributes.get(key);
6757
if (attr == null) {
@@ -70,107 +60,34 @@ public ZonedDateTime getDatetimeAttribute(String key) {
7060
return ZonedDateTime.parse(attr, DateTimeFormatter.ISO_ZONED_DATE_TIME);
7161
}
7262

73-
public String getEmail() {
74-
return this.stringAttributes.get(KNOWN_KEYS.EMAIL.toString());
75-
}
76-
77-
public String getFirstName() {
78-
return this.stringAttributes.get(KNOWN_KEYS.FIRST_NAME.toString());
79-
}
80-
81-
public String getLastName() {
82-
return this.stringAttributes.get(KNOWN_KEYS.LAST_NAME.toString());
83-
}
84-
85-
public String getName() {
86-
return this.stringAttributes.get(KNOWN_KEYS.NAME.toString());
87-
}
88-
89-
public String getIp() {
90-
return this.stringAttributes.get(KNOWN_KEYS.IP.toString());
91-
}
92-
93-
public String getTz() {
94-
return this.stringAttributes.get(KNOWN_KEYS.TZ.toString());
95-
}
96-
97-
public String getLocale() {
98-
return this.stringAttributes.get(KNOWN_KEYS.LOCALE.toString());
99-
}
100-
101-
public String getCountryCode() {
102-
return this.stringAttributes.get(KNOWN_KEYS.COUNTRY_CODE.toString());
103-
}
104-
105-
public String getEnvironment() {
106-
return this.stringAttributes.get(KNOWN_KEYS.ENVIRONMENT.toString());
107-
}
108-
109-
public String getApplication() {
110-
return this.stringAttributes.get(KNOWN_KEYS.APPLICATION.toString());
111-
}
112-
113-
public String getVersion() {
114-
return this.stringAttributes.get(KNOWN_KEYS.VERSION.toString());
115-
}
116-
117-
public ZonedDateTime getTimestamp() {
118-
return getDatetimeAttribute(KNOWN_KEYS.TIMESTAMP.toString());
119-
}
120-
121-
public void setEmail(String email) {
122-
this.stringAttributes.put(KNOWN_KEYS.EMAIL.toString(), email);
123-
}
124-
125-
public void setFirstName(String firstname) {
126-
this.stringAttributes.put(KNOWN_KEYS.FIRST_NAME.toString(), firstname);
127-
}
128-
129-
public void setLastName(String lastname) {
130-
this.stringAttributes.put(KNOWN_KEYS.LAST_NAME.toString(), lastname);
131-
}
132-
133-
public void setName(String name) {
134-
this.stringAttributes.put(KNOWN_KEYS.NAME.toString(), name);
135-
}
136-
137-
public void setIp(String ip) {
138-
this.stringAttributes.put(KNOWN_KEYS.IP.toString(), ip);
139-
}
140-
141-
public void setTz(String tz) {
142-
this.stringAttributes.put(KNOWN_KEYS.TZ.toString(), tz);
143-
}
144-
145-
public void setLocale(String locale) {
146-
this.stringAttributes.put(KNOWN_KEYS.LOCALE.toString(), locale);
147-
}
148-
149-
public void setCountryCode(String countryCode) {
150-
this.stringAttributes.put(KNOWN_KEYS.COUNTRY_CODE.toString(), countryCode);
151-
}
63+
/**
64+
* Merges two EvaluationContext objects with the second overriding the first in case of conflict.
65+
*/
66+
public static EvaluationContext merge(EvaluationContext ctx1, EvaluationContext ctx2) {
67+
EvaluationContext ec = new EvaluationContext();
68+
for (Map.Entry<String, Integer> e : ctx1.integerAttributes.entrySet()) {
69+
ec.addIntegerAttribute(e.getKey(), e.getValue());
70+
}
15271

153-
public void setEnvironment(String environment) {
154-
this.stringAttributes.put(KNOWN_KEYS.ENVIRONMENT.toString(), environment);
155-
}
72+
for (Map.Entry<String, Integer> e : ctx2.integerAttributes.entrySet()) {
73+
ec.addIntegerAttribute(e.getKey(), e.getValue());
74+
}
15675

157-
public void setApplication(String application) {
158-
this.stringAttributes.put(KNOWN_KEYS.APPLICATION.toString(), application);
159-
}
76+
for (Map.Entry<String, String> e : ctx1.stringAttributes.entrySet()) {
77+
ec.addStringAttribute(e.getKey(), e.getValue());
78+
}
16079

161-
public void setVersion(String version) {
162-
this.stringAttributes.put(KNOWN_KEYS.VERSION.toString(), version);
163-
}
80+
for (Map.Entry<String, String> e : ctx2.stringAttributes.entrySet()) {
81+
ec.addStringAttribute(e.getKey(), e.getValue());
82+
}
83+
if (ctx1.getTargetingKey() != null) {
84+
ec.setTargetingKey(ctx1.getTargetingKey());
85+
}
16486

165-
public void setTimestamp(ZonedDateTime timestamp) {
166-
addDatetimeAttribute(KNOWN_KEYS.TIMESTAMP.toString(), timestamp);
167-
}
87+
if (ctx2.getTargetingKey() != null) {
88+
ec.setTargetingKey(ctx2.getTargetingKey());
89+
}
16890

169-
/**
170-
* Merges two EvaluationContext objects with the second overriding the first in case of conflict.
171-
*/
172-
public static EvaluationContext merge(EvaluationContext ctx1, EvaluationContext ctx2) {
173-
// TODO(abrahms): Actually implement this when we know what the fields of EC are.
174-
return ctx1;
91+
return ec;
17592
}
17693
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
// TODO: interface? or abstract class?
88
public abstract class Hook<T> {
99
public Optional<EvaluationContext> before(HookContext<T> ctx, ImmutableMap<String, Object> hints) {
10-
return null;
10+
return Optional.empty();
1111
}
1212
public void after(HookContext<T> ctx, FlagEvaluationDetails<T> details, ImmutableMap<String, Object> hints) {}
1313
public void error(HookContext<T> ctx, Exception error, ImmutableMap<String, Object> hints) {}

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,13 @@
44
import lombok.NonNull;
55
import lombok.Value;
66
import lombok.With;
7-
import javax.annotation.Nullable;
87

98
@Value @Builder @With
109
public class HookContext<T> {
1110
@NonNull String flagKey;
1211
@NonNull FlagValueType type;
1312
@NonNull T defaultValue;
14-
@Nullable EvaluationContext ctx;
13+
@NonNull EvaluationContext ctx;
1514
Client client;
1615
FeatureProvider provider;
1716

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,14 @@ public void registerHooks(Hook... hooks) {
3636
<T> FlagEvaluationDetails<T> evaluateFlag(FlagValueType type, String key, T defaultValue, EvaluationContext ctx, FlagEvaluationOptions options) {
3737
FeatureProvider provider = this.openfeatureApi.getProvider();
3838
ImmutableMap<String, Object> hints = options.getHookHints();
39+
if (ctx == null) {
40+
ctx = new EvaluationContext();
41+
}
3942

4043
// merge of: API.context, client.context, invocation.context
4144

4245
// TODO: Context transformation?
43-
HookContext hookCtx = HookContext.from(key, type, this, null, defaultValue);
46+
HookContext hookCtx = HookContext.from(key, type, this, ctx, defaultValue);
4447

4548
List<Hook> mergedHooks;
4649
if (options != null && options.getHooks() != null) {
@@ -112,7 +115,7 @@ private EvaluationContext beforeHooks(HookContext hookCtx, List<Hook> hooks, Imm
112115
EvaluationContext ctx = hookCtx.getCtx();
113116
for (Hook hook : Lists.reverse(hooks)) {
114117
Optional<EvaluationContext> newCtx = hook.before(hookCtx, hints);
115-
if (newCtx.isPresent()) {
118+
if (newCtx != null && newCtx.isPresent()) {
116119
ctx = EvaluationContext.merge(ctx, newCtx.get());
117120
hookCtx = hookCtx.withCtx(ctx);
118121
}
@@ -137,7 +140,7 @@ public Boolean getBooleanValue(String key, Boolean defaultValue, EvaluationConte
137140

138141
@Override
139142
public FlagEvaluationDetails<Boolean> getBooleanDetails(String key, Boolean defaultValue) {
140-
return getBooleanDetails(key, defaultValue, null);
143+
return getBooleanDetails(key, defaultValue, new EvaluationContext());
141144
}
142145

143146
@Override
@@ -197,7 +200,7 @@ public Integer getIntegerValue(String key, Integer defaultValue, EvaluationConte
197200

198201
@Override
199202
public FlagEvaluationDetails<Integer> getIntegerDetails(String key, Integer defaultValue) {
200-
return getIntegerDetails(key, defaultValue, null);
203+
return getIntegerDetails(key, defaultValue, new EvaluationContext());
201204
}
202205

203206
@Override

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

Lines changed: 12 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,20 @@
66
import java.time.ZonedDateTime;
77

88
import static org.junit.jupiter.api.Assertions.assertEquals;
9-
import static org.junit.jupiter.api.Assertions.assertNull;
109

1110
public class EvalContextTests {
12-
@Specification(spec="flag evaluation", number="3.1",
13-
text="The `evaluation context` structure MUST define a required `targeting key` " +
14-
"field of type string, identifying the subject of the flag evaluation.")
15-
@Disabled("https://github.com/open-feature/spec/pull/60/files#r872827439")
11+
@Specification(spec="Evaluation Context", number="3.1",
12+
text="The `evaluation context` structure **MUST** define an optional `targeting key` field of " +
13+
"type string, identifying the subject of the flag evaluation.")
1614
@Test void requires_targeting_key() {
1715
EvaluationContext ec = new EvaluationContext();
16+
ec.setTargetingKey("targeting-key");
1817
assertEquals("targeting-key", ec.getTargetingKey());
1918
}
2019

21-
@Specification(spec="flag evaluation", number="3.3", text="The evaluation context MUST support the inclusion " +
22-
"of custom fields, having keys of type `string`, and values of " +
23-
"type `boolean | string | number | datetime`.")
20+
@Specification(spec="Evaluation Context", number="3.2", text="The evaluation context MUST support the inclusion of " +
21+
"custom fields, having keys of type `string`, and " +
22+
"values of type `boolean | string | number | datetime | structure`.")
2423
@Test void eval_context() {
2524
EvaluationContext ec = new EvaluationContext();
2625

@@ -38,63 +37,9 @@ public class EvalContextTests {
3837
assertEquals(dt, ec.getDatetimeAttribute("dt"));
3938
}
4039

41-
42-
@Specification(spec="flag evaluation", number="3.2", text="The evaluation context MUST define the " +
43-
"following optional fields: `email` (string), `first name` (string), `last name`(string), " +
44-
"`name`(string), `ip`(string), `tz`(string), `locale`(string), `country code` (string), " +
45-
"`timestamp`(date), `environment`(string), `application`(string), and `version`(string).")
46-
@Test void mandated_fields() {
47-
EvaluationContext ec = new EvaluationContext();
48-
49-
assertNull(ec.getEmail());
50-
ec.setEmail("Test");
51-
assertEquals("Test", ec.getEmail());
52-
53-
assertNull(ec.getFirstName());
54-
ec.setFirstName("Test");
55-
assertEquals("Test", ec.getFirstName());
56-
57-
assertNull(ec.getLastName());
58-
ec.setLastName("Test");
59-
assertEquals("Test", ec.getLastName());
60-
61-
assertNull(ec.getName());
62-
ec.setName("Test");
63-
assertEquals("Test", ec.getName());
64-
65-
assertNull(ec.getIp());
66-
ec.setIp("Test");
67-
assertEquals("Test", ec.getIp());
68-
69-
assertNull(ec.getTz());
70-
ec.setTz("Test");
71-
assertEquals("Test", ec.getTz());
72-
73-
assertNull(ec.getLocale());
74-
ec.setLocale("Test");
75-
assertEquals("Test", ec.getLocale());
76-
77-
assertNull(ec.getCountryCode());
78-
ec.setCountryCode("Test");
79-
assertEquals("Test", ec.getCountryCode());
80-
81-
assertNull(ec.getTimestamp());
82-
ZonedDateTime dt = ZonedDateTime.now();
83-
ec.setTimestamp(dt);
84-
assertEquals(dt, ec.getTimestamp());
85-
86-
assertNull(ec.getEnvironment());
87-
ec.setEnvironment("Test");
88-
assertEquals("Test", ec.getEnvironment());
89-
90-
91-
assertNull(ec.getApplication());
92-
ec.setApplication("Test");
93-
assertEquals("Test", ec.getApplication());
94-
95-
96-
assertNull(ec.getVersion());
97-
ec.setVersion("Test");
98-
assertEquals("Test", ec.getVersion());
99-
}
40+
@Specification(spec="Evaluation Context", number="3.2", text="The evaluation context MUST support the inclusion of " +
41+
"custom fields, having keys of type `string`, and " +
42+
"values of type `boolean | string | number | datetime | structure`.")
43+
@Disabled("Structure support")
44+
@Test void eval_context__structure() {}
10045
}

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

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

3-
import dev.openfeature.javasdk.*;
43
import org.junit.jupiter.api.Disabled;
54
import org.junit.jupiter.api.Test;
65

@@ -132,6 +131,7 @@ Client _client() {
132131
"unhandled error, etc) the reason field in the evaluation details SHOULD indicate an error.")
133132
@Disabled
134133
@Test void detail_flags() {
134+
// TODO: Add tests re: detail functions.
135135
throw new NotImplementedException();
136136
}
137137

0 commit comments

Comments
 (0)