Skip to content

Commit 23e2cd0

Browse files
committed
Blocked waiting on EvalContext spec to be finalized.
1 parent f820fd6 commit 23e2cd0

File tree

6 files changed

+277
-13
lines changed

6 files changed

+277
-13
lines changed

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

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

3+
import lombok.Getter;
4+
5+
import java.time.ZonedDateTime;
6+
import java.time.format.DateTimeFormatter;
7+
import java.util.HashMap;
8+
import java.util.Map;
9+
310
public class EvaluationContext {
11+
@Getter private final String targetingKey;
12+
private final Map<String, Integer> integerAttributes;
13+
private final Map<String, String> stringAttributes;
14+
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+
31+
EvaluationContext() {
32+
this.targetingKey = "";
33+
this.integerAttributes = new HashMap<>();
34+
this.stringAttributes = new HashMap<>();
35+
}
36+
37+
public void addStringAttribute(String key, String value) {
38+
stringAttributes.put(key, value);
39+
}
40+
41+
public String getStringAttribute(String key) {
42+
return stringAttributes.get(key);
43+
}
44+
45+
public void addIntegerAttribute(String key, Integer value) {
46+
integerAttributes.put(key, value);
47+
}
48+
49+
public Integer getIntegerAttribute(String key) {
50+
return integerAttributes.get(key);
51+
}
52+
53+
public Boolean getBooleanAttribute(String key) {
54+
return Boolean.valueOf(stringAttributes.get(key));
55+
}
56+
57+
public void addBooleanAttribute(String key, Boolean b) {
58+
stringAttributes.put(key, b.toString());
59+
}
60+
61+
public void addDatetimeAttribute(String key, ZonedDateTime value) {
62+
this.stringAttributes.put(key, value.format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
63+
}
64+
65+
public ZonedDateTime getDatetimeAttribute(String key) {
66+
String attr = this.stringAttributes.get(key);
67+
if (attr == null) {
68+
return null;
69+
}
70+
return ZonedDateTime.parse(attr, DateTimeFormatter.ISO_ZONED_DATE_TIME);
71+
}
72+
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+
}
152+
153+
public void setEnvironment(String environment) {
154+
this.stringAttributes.put(KNOWN_KEYS.ENVIRONMENT.toString(), environment);
155+
}
156+
157+
public void setApplication(String application) {
158+
this.stringAttributes.put(KNOWN_KEYS.APPLICATION.toString(), application);
159+
}
160+
161+
public void setVersion(String version) {
162+
this.stringAttributes.put(KNOWN_KEYS.VERSION.toString(), version);
163+
}
164+
165+
public void setTimestamp(ZonedDateTime timestamp) {
166+
addDatetimeAttribute(KNOWN_KEYS.TIMESTAMP.toString(), timestamp);
167+
}
168+
4169
/**
5170
* Merges two EvaluationContext objects with the second overriding the first in case of conflict.
6171
*/

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

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

89
@Value @Builder @With
910
public class HookContext<T> {
1011
@NonNull String flagKey;
1112
@NonNull FlagValueType type;
1213
@NonNull T defaultValue;
13-
@NonNull EvaluationContext ctx;
14+
@Nullable EvaluationContext ctx;
1415
Client client;
1516
FeatureProvider provider;
1617

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

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,12 @@ public void registerHooks(Hook... hooks) {
3535

3636
<T> FlagEvaluationDetails<T> evaluateFlag(FlagValueType type, String key, T defaultValue, EvaluationContext ctx, FlagEvaluationOptions options) {
3737
FeatureProvider provider = this.openfeatureApi.getProvider();
38-
if (ctx == null) {
39-
ctx = new EvaluationContext();
40-
}
41-
4238
ImmutableMap<String, Object> hints = options.getHookHints();
4339

40+
// merge of: API.context, client.context, invocation.context
41+
4442
// TODO: Context transformation?
45-
HookContext hookCtx = HookContext.from(key, type, this, ctx, defaultValue);
43+
HookContext hookCtx = HookContext.from(key, type, this, null, defaultValue);
4644

4745
List<Hook> mergedHooks;
4846
if (options != null && options.getHooks() != null) {
@@ -139,7 +137,7 @@ public Boolean getBooleanValue(String key, Boolean defaultValue, EvaluationConte
139137

140138
@Override
141139
public FlagEvaluationDetails<Boolean> getBooleanDetails(String key, Boolean defaultValue) {
142-
return getBooleanDetails(key, defaultValue, new EvaluationContext());
140+
return getBooleanDetails(key, defaultValue, null);
143141
}
144142

145143
@Override
@@ -169,12 +167,12 @@ public String getStringValue(String key, String defaultValue, EvaluationContext
169167

170168
@Override
171169
public FlagEvaluationDetails<String> getStringDetails(String key, String defaultValue) {
172-
return getStringDetails(key, defaultValue, new EvaluationContext());
170+
return getStringDetails(key, defaultValue, null);
173171
}
174172

175173
@Override
176174
public FlagEvaluationDetails<String> getStringDetails(String key, String defaultValue, EvaluationContext ctx) {
177-
return getStringDetails(key, defaultValue, new EvaluationContext(), FlagEvaluationOptions.builder().build());
175+
return getStringDetails(key, defaultValue, ctx, FlagEvaluationOptions.builder().build());
178176
}
179177

180178
@Override
@@ -199,7 +197,7 @@ public Integer getIntegerValue(String key, Integer defaultValue, EvaluationConte
199197

200198
@Override
201199
public FlagEvaluationDetails<Integer> getIntegerDetails(String key, Integer defaultValue) {
202-
return getIntegerDetails(key, defaultValue, new EvaluationContext());
200+
return getIntegerDetails(key, defaultValue, null);
203201
}
204202

205203
@Override

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class DeveloperExperienceTest {
4141
api.setProvider(new NoOpProvider());
4242
Client client = api.getClient();
4343
client.registerHooks(clientHook);
44-
Boolean retval = client.getBooleanValue(flagKey, false, new EvaluationContext(),
44+
Boolean retval = client.getBooleanValue(flagKey, false, null,
4545
FlagEvaluationOptions.builder().hook(evalHook).build());
4646
verify(clientHook, times(1)).finallyAfter(any(), any());
4747
verify(evalHook, times(1)).finallyAfter(any(), any());
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package dev.openfeature.javasdk;
2+
3+
import org.junit.jupiter.api.Disabled;
4+
import org.junit.jupiter.api.Test;
5+
6+
import java.time.ZonedDateTime;
7+
8+
import static org.junit.jupiter.api.Assertions.assertEquals;
9+
import static org.junit.jupiter.api.Assertions.assertNull;
10+
11+
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")
16+
@Test void requires_targeting_key() {
17+
EvaluationContext ec = new EvaluationContext();
18+
assertEquals("targeting-key", ec.getTargetingKey());
19+
}
20+
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`.")
24+
@Test void eval_context() {
25+
EvaluationContext ec = new EvaluationContext();
26+
27+
ec.addStringAttribute("str", "test");
28+
assertEquals("test", ec.getStringAttribute("str"));
29+
30+
ec.addBooleanAttribute("bool", true);
31+
assertEquals(true, ec.getBooleanAttribute("bool"));
32+
33+
ec.addIntegerAttribute("int", 4);
34+
assertEquals(4, ec.getIntegerAttribute("int"));
35+
36+
ZonedDateTime dt = ZonedDateTime.now();
37+
ec.addDatetimeAttribute("dt", dt);
38+
assertEquals(dt, ec.getDatetimeAttribute("dt"));
39+
}
40+
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+
}
100+
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ void emptyApiHooks() {
6565
try {
6666
HookContext.<Integer>builder()
6767
.flagKey("key")
68-
.ctx(new EvaluationContext())
68+
.ctx(null)
6969
.defaultValue(1)
7070
.build();
7171
fail("Missing type shouldn't be valid");
@@ -77,7 +77,7 @@ void emptyApiHooks() {
7777
try {
7878
HookContext.<Integer>builder()
7979
.type(FlagValueType.INTEGER)
80-
.ctx(new EvaluationContext())
80+
.ctx(null)
8181
.defaultValue(1)
8282
.build();
8383
fail("Missing key shouldn't be valid");

0 commit comments

Comments
 (0)