Skip to content

Commit 3a074b2

Browse files
DBlanchard88toddbaertKavindu-Dodan
authored
feat: $flagd.timestamp added to in-process evaluator (#512)
Signed-off-by: DBlanchard88 <[email protected]> Co-authored-by: Todd Baert <[email protected]> Co-authored-by: Kavindu Dodanduwa <[email protected]>
1 parent 4e293bf commit 3a074b2

File tree

2 files changed

+64
-21
lines changed
  • providers/flagd/src
    • main/java/dev/openfeature/contrib/providers/flagd/resolver/process/targeting
    • test/java/dev/openfeature/contrib/providers/flagd/resolver/process/targeting

2 files changed

+64
-21
lines changed

Diff for: providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/targeting/Operator.java

+23-15
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,24 @@
33
import java.util.HashMap;
44
import java.util.Map;
55
import java.util.Optional;
6+
import java.time.Instant;
67

78
import dev.openfeature.sdk.EvaluationContext;
89
import io.github.jamsesso.jsonlogic.JsonLogic;
910
import io.github.jamsesso.jsonlogic.JsonLogicException;
1011
import lombok.Getter;
1112

12-
1313
/**
14-
* Targeting operator wraps JsonLogic handlers and expose a simple API for external layers.
14+
* Targeting operator wraps JsonLogic handlers and expose a simple API for
15+
* external layers.
1516
* This helps to isolate external dependencies to this package.
1617
*/
1718
public class Operator {
1819

1920
static final String FLAGD_PROPS_KEY = "$flagd";
2021
static final String FLAG_KEY = "flagKey";
2122
static final String TARGET_KEY = "targetingKey";
23+
static final String TIME_STAMP = "timestamp";
2224

2325
private final JsonLogic jsonLogicHandler;
2426

@@ -38,8 +40,12 @@ public Operator() {
3840
*/
3941
public Object apply(final String flagKey, final String targetingRule, final EvaluationContext ctx)
4042
throws TargetingRuleException {
41-
final Map<String, String> flagdProperties = new HashMap<>();
43+
final Map<String, Object> flagdProperties = new HashMap<>();
4244
flagdProperties.put(FLAG_KEY, flagKey);
45+
46+
long unixTimestamp = Instant.now().getEpochSecond();
47+
flagdProperties.put(TIME_STAMP, unixTimestamp);
48+
4349
final Map<String, Object> valueMap = ctx.asObjectMap();
4450
valueMap.put(FLAGD_PROPS_KEY, flagdProperties);
4551

@@ -52,35 +58,37 @@ public Object apply(final String flagKey, final String targetingRule, final Eval
5258

5359
@Getter
5460
static class FlagProperties {
55-
private final String flagKey;
61+
private final Object flagKey;
62+
private final Object timestamp;
5663
private final String targetingKey;
5764

5865
FlagProperties(Object from) {
5966
if (from instanceof Map) {
6067
Map<?, ?> dataMap = (Map<?, ?>) from;
6168

62-
final Object flagKey = Optional.ofNullable(dataMap.get(FLAGD_PROPS_KEY))
63-
.filter(flagdProps -> flagdProps instanceof Map)
64-
.map(flagdProps -> ((Map<?, ?>)flagdProps).get(FLAG_KEY))
65-
.orElse(null);
66-
67-
if (flagKey instanceof String) {
68-
this.flagKey = (String) flagKey;
69-
} else {
70-
this.flagKey = null;
71-
}
69+
this.flagKey = extractSubPropertyFromFlagd(dataMap, FLAG_KEY);
70+
this.timestamp = extractSubPropertyFromFlagd(dataMap, TIME_STAMP);
7271

7372
final Object targetKey = dataMap.get(TARGET_KEY);
74-
73+
7574
if (targetKey instanceof String) {
7675
targetingKey = (String) targetKey;
7776
} else {
7877
targetingKey = null;
7978
}
79+
8080
} else {
8181
flagKey = null;
82+
timestamp = null;
8283
targetingKey = null;
8384
}
8485
}
86+
87+
private static Object extractSubPropertyFromFlagd(Map<?, ?> dataMap, String propertyName) {
88+
return Optional.ofNullable(dataMap.get(FLAGD_PROPS_KEY))
89+
.filter(flagdProps -> flagdProps instanceof Map)
90+
.map(flagdProps -> ((Map<?, ?>) flagdProps).get(propertyName))
91+
.orElse(null);
92+
}
8593
}
8694
}

Diff for: providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/targeting/OperatorTest.java

+41-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package dev.openfeature.contrib.providers.flagd.resolver.process.targeting;
22

33
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertTrue;
45

56
import java.util.HashMap;
67
import java.util.Map;
8+
import java.time.Instant;
79

810
import org.junit.jupiter.api.BeforeAll;
911
import org.junit.jupiter.api.Test;
@@ -33,6 +35,45 @@ void flagKeyPresent() throws TargetingRuleException {
3335
assertEquals(true, evalVariant);
3436
}
3537

38+
@Test
39+
void timestampPresent() throws TargetingRuleException {
40+
// given
41+
42+
// rule asserting $flagd.timestamp is a number (i.e., a Unix timestamp)
43+
final String targetingRule = "{\"var\":[\"$flagd.timestamp\"]}";
44+
45+
// when
46+
Object timestampString = OPERATOR.apply("some-key", targetingRule, new ImmutableContext());
47+
48+
long timestamp = (long) Double.parseDouble(timestampString.toString());
49+
50+
// generating current unix timestamp & 5 minute threshold
51+
long currentTimestamp = Instant.now().getEpochSecond();
52+
long thresholdPast = currentTimestamp - (5);
53+
long thresholdFuture = currentTimestamp + (5);
54+
55+
// checks if the timestamp is within 5 minutes of the current time
56+
assertTrue(timestamp >= thresholdPast && timestamp <= thresholdFuture);
57+
}
58+
59+
@Test
60+
void testFlagPropertiesConstructor() {
61+
// Given
62+
Map<String, Object> flagdProperties = new HashMap<>();
63+
flagdProperties.put(Operator.FLAG_KEY, "some-key");
64+
flagdProperties.put(Operator.TIME_STAMP, 1634000000L);
65+
66+
Map<String, Object> dataMap = new HashMap<>();
67+
dataMap.put(Operator.FLAGD_PROPS_KEY, flagdProperties);
68+
69+
// When
70+
Operator.FlagProperties flagProperties = new Operator.FlagProperties(dataMap);
71+
72+
// Then
73+
assertEquals("some-key", flagProperties.getFlagKey());
74+
assertEquals(1634000000L, flagProperties.getTimestamp());
75+
}
76+
3677
@Test
3778
void fractionalTestA() throws TargetingRuleException {
3879
// given
@@ -64,7 +105,6 @@ void fractionalTestA() throws TargetingRuleException {
64105
Map<String, Value> ctxData = new HashMap<>();
65106
ctxData.put("email", new Value("[email protected]"));
66107

67-
68108
// when
69109
Object evalVariant = OPERATOR.apply("headerColor", targetingRule, new ImmutableContext(ctxData));
70110

@@ -103,7 +143,6 @@ void fractionalTestB() throws TargetingRuleException {
103143
Map<String, Value> ctxData = new HashMap<>();
104144
ctxData.put("email", new Value("[email protected]"));
105145

106-
107146
// when
108147
Object evalVariant = OPERATOR.apply("headerColor", targetingRule, new ImmutableContext(ctxData));
109148

@@ -142,7 +181,6 @@ void fractionalTestC() throws TargetingRuleException {
142181
Map<String, Value> ctxData = new HashMap<>();
143182
ctxData.put("email", new Value("[email protected]"));
144183

145-
146184
// when
147185
Object evalVariant = OPERATOR.apply("headerColor", targetingRule, new ImmutableContext(ctxData));
148186

@@ -166,7 +204,6 @@ void stringCompStartsWith() throws TargetingRuleException {
166204
Map<String, Value> ctxData = new HashMap<>();
167205
ctxData.put("email", new Value("[email protected]"));
168206

169-
170207
// when
171208
Object evalVariant = OPERATOR.apply("adminRule", targetingRule, new ImmutableContext(ctxData));
172209

@@ -190,7 +227,6 @@ void stringCompEndsWith() throws TargetingRuleException {
190227
Map<String, Value> ctxData = new HashMap<>();
191228
ctxData.put("email", new Value("[email protected]"));
192229

193-
194230
// when
195231
Object evalVariant = OPERATOR.apply("isFaas", targetingRule, new ImmutableContext(ctxData));
196232

@@ -215,7 +251,6 @@ void semVerA() throws TargetingRuleException {
215251
Map<String, Value> ctxData = new HashMap<>();
216252
ctxData.put("version", new Value("1.1.0"));
217253

218-
219254
// when
220255
Object evalVariant = OPERATOR.apply("versionFlag", targetingRule, new ImmutableContext(ctxData));
221256

0 commit comments

Comments
 (0)