Skip to content

Commit cf1b41b

Browse files
Merge pull request #9 from splitio/fme-9600
Add track and evaluate with details
2 parents 9291ce2 + d0545c1 commit cf1b41b

File tree

4 files changed

+208
-104
lines changed

4 files changed

+208
-104
lines changed

pom.xml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@
7474
<dependency>
7575
<groupId>dev.openfeature</groupId>
7676
<artifactId>sdk</artifactId>
77-
<version>1.16.0</version>
77+
<version>1.17.0</version>
7878
</dependency>
7979
</dependencies>
8080

@@ -126,6 +126,7 @@
126126
<plugin>
127127
<groupId>org.apache.maven.plugins</groupId>
128128
<artifactId>maven-compiler-plugin</artifactId>
129+
<version>3.13.0</version>
129130
<configuration>
130131
<source>11</source>
131132
<target>11</target>
@@ -134,6 +135,7 @@
134135
<plugin>
135136
<groupId>org.apache.maven.plugins</groupId>
136137
<artifactId>maven-javadoc-plugin</artifactId>
138+
<version>3.11.3</version>
137139
<executions>
138140
<execution>
139141
<id>attach-javadocs</id>
@@ -159,6 +161,7 @@
159161
<plugin>
160162
<groupId>org.apache.maven.plugins</groupId>
161163
<artifactId>maven-source-plugin</artifactId>
164+
<version>3.3.1</version>
162165
<executions>
163166
<execution>
164167
<id>attach-sources</id>

src/main/java/io/split/openfeature/SplitProvider.java

Lines changed: 88 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,26 @@
33
import dev.openfeature.sdk.ErrorCode;
44
import dev.openfeature.sdk.EvaluationContext;
55
import dev.openfeature.sdk.FeatureProvider;
6+
import dev.openfeature.sdk.ImmutableMetadata;
67
import dev.openfeature.sdk.Metadata;
78
import dev.openfeature.sdk.MutableStructure;
89
import dev.openfeature.sdk.ProviderEvaluation;
910
import dev.openfeature.sdk.Reason;
11+
import dev.openfeature.sdk.TrackingEventDetails;
1012
import dev.openfeature.sdk.Value;
1113
import dev.openfeature.sdk.exceptions.GeneralError;
1214
import dev.openfeature.sdk.exceptions.OpenFeatureError;
1315
import dev.openfeature.sdk.exceptions.ParseError;
1416
import dev.openfeature.sdk.exceptions.TargetingKeyMissingError;
1517
import io.split.client.SplitClient;
18+
import io.split.client.api.SplitResult;
1619
import io.split.openfeature.utils.Serialization;
1720
import java.time.Instant;
1821
import java.time.format.DateTimeParseException;
22+
import java.util.HashMap;
1923
import java.util.List;
2024
import java.util.Map;
25+
import java.util.Optional;
2126
import java.util.stream.Collectors;
2227

2328
public class SplitProvider implements FeatureProvider {
@@ -44,126 +49,138 @@ public Metadata getMetadata() {
4449
}
4550

4651
@Override
47-
public ProviderEvaluation<Boolean> getBooleanEvaluation(String key, Boolean defaultTreatment, EvaluationContext evaluationContext) {
48-
try {
49-
String evaluated = evaluateTreatment(key, evaluationContext);
50-
if (noTreatment(evaluated)) {
51-
return constructProviderEvaluation(defaultTreatment, evaluated, Reason.DEFAULT, ErrorCode.FLAG_NOT_FOUND);
52-
}
52+
public ProviderEvaluation<Boolean> getBooleanEvaluation(
53+
String key, Boolean defaultVal, EvaluationContext ctx) {
54+
return getEvaluation(key, defaultVal, ctx, s -> {
5355
// if treatment is "on" or "true" we treat that as true
5456
// if it is "off" or "false" we treat it as false
5557
// if it is some other value we throw an error (sdk will catch it and throw default treatment)
56-
boolean value;
57-
if (Boolean.parseBoolean(evaluated) || evaluated.equals("on")) {
58-
value = true;
59-
} else if (evaluated.equalsIgnoreCase("false") || evaluated.equals("off")) {
60-
value = false;
58+
if (Boolean.parseBoolean(s) || s.equals("on")) {
59+
return true;
60+
} else if (s.equalsIgnoreCase("false") || s.equals("off")) {
61+
return false;
6162
} else {
6263
throw new ParseError();
6364
}
64-
return constructProviderEvaluation(value, evaluated);
65-
} catch (OpenFeatureError e) {
66-
throw e;
67-
} catch (Exception e) {
68-
throw new GeneralError("Error getting boolean evaluation", e);
69-
}
65+
}, "Boolean");
66+
}
7067

68+
@Override
69+
public ProviderEvaluation<String> getStringEvaluation(
70+
String key, String defaultVal, EvaluationContext ctx) {
71+
return getEvaluation(key, defaultVal, ctx, s -> s, "String");
7172
}
7273

7374
@Override
74-
public ProviderEvaluation<String> getStringEvaluation(String key, String defaultTreatment, EvaluationContext evaluationContext) {
75-
try {
76-
String evaluated = evaluateTreatment(key, evaluationContext);
77-
if (noTreatment(evaluated)) {
78-
return constructProviderEvaluation(defaultTreatment, evaluated, Reason.DEFAULT, ErrorCode.FLAG_NOT_FOUND);
79-
}
80-
return constructProviderEvaluation(evaluated, evaluated);
81-
} catch (OpenFeatureError e) {
82-
throw e;
83-
} catch (Exception e) {
84-
throw new GeneralError("Error getting String evaluation", e);
85-
}
75+
public ProviderEvaluation<Integer> getIntegerEvaluation(
76+
String key, Integer defaultVal, EvaluationContext ctx) {
77+
return getEvaluation(key, defaultVal, ctx, Integer::valueOf, "Integer");
8678
}
8779

8880
@Override
89-
public ProviderEvaluation<Integer> getIntegerEvaluation(String key, Integer defaultTreatment, EvaluationContext evaluationContext) {
90-
try {
91-
String evaluated = evaluateTreatment(key, evaluationContext);
92-
if (noTreatment(evaluated)) {
93-
return constructProviderEvaluation(defaultTreatment, evaluated, Reason.DEFAULT, ErrorCode.FLAG_NOT_FOUND);
94-
}
95-
Integer value = Integer.valueOf(evaluated);
96-
return constructProviderEvaluation(value, evaluated);
97-
} catch (OpenFeatureError e) {
98-
throw e;
99-
} catch (NumberFormatException e) {
100-
throw new ParseError();
101-
} catch (Exception e) {
102-
throw new GeneralError("Error getting Integer evaluation", e);
103-
}
81+
public ProviderEvaluation<Double> getDoubleEvaluation(
82+
String key, Double defaultTreatment, EvaluationContext ctx) {
83+
84+
return getEvaluation(key, defaultTreatment, ctx, s -> {
85+
if (s == null) throw new NumberFormatException("null");
86+
return Double.valueOf(s.trim());
87+
}, "Double");
10488
}
10589

10690
@Override
107-
public ProviderEvaluation<Double> getDoubleEvaluation(String key, Double defaultTreatment, EvaluationContext evaluationContext) {
91+
public ProviderEvaluation<Value> getObjectEvaluation(
92+
String key, Value defaultVal, EvaluationContext ctx) {
93+
return getEvaluation(key, defaultVal, ctx, s -> {
94+
Map<String, Object> rawMap = Serialization.stringToMap(s);
95+
return mapToValue(rawMap);
96+
}, "Object");
97+
}
98+
99+
@FunctionalInterface
100+
interface Mapper<T> {
101+
T map(String s) throws Exception;
102+
}
103+
104+
private <T> ProviderEvaluation<T> getEvaluation(
105+
String key,
106+
T defaultValue,
107+
EvaluationContext ctx,
108+
Mapper<T> mapper,
109+
String typeLabel
110+
) {
108111
try {
109-
String evaluated = evaluateTreatment(key, evaluationContext);
110-
if (noTreatment(evaluated)) {
111-
return constructProviderEvaluation(defaultTreatment, evaluated, Reason.DEFAULT, ErrorCode.FLAG_NOT_FOUND);
112+
SplitResult evaluated = evaluateTreatment(key, ctx);
113+
String treatment = evaluated.treatment();
114+
String config = evaluated.config();
115+
ImmutableMetadata metadata = ImmutableMetadata.builder().addString("config", config).build();
116+
System.out.println(metadata.getString("config"));
117+
if (noTreatment(treatment)) {
118+
return constructProviderEvaluation(
119+
defaultValue, treatment, Reason.DEFAULT, ErrorCode.FLAG_NOT_FOUND, metadata);
112120
}
113-
Double value = Double.valueOf(evaluated);
114-
return constructProviderEvaluation(value, evaluated);
121+
T mapped = mapper.map(treatment);
122+
return constructProviderEvaluation(mapped, treatment, metadata);
123+
115124
} catch (OpenFeatureError e) {
116125
throw e;
117-
} catch (NumberFormatException e) {
118-
throw new ParseError();
119126
} catch (Exception e) {
120-
throw new GeneralError("Error getting Double evaluation", e);
127+
throw new GeneralError(String.format("Error getting %s evaluation", typeLabel), e);
121128
}
122129
}
123130

124131
@Override
125-
public ProviderEvaluation<Value> getObjectEvaluation(String key, Value defaultTreatment, EvaluationContext evaluationContext) {
126-
try {
127-
String evaluated = evaluateTreatment(key, evaluationContext);
128-
if (noTreatment(evaluated)) {
129-
return constructProviderEvaluation(defaultTreatment, evaluated, Reason.DEFAULT, ErrorCode.FLAG_NOT_FOUND);
130-
}
131-
Map<String, Object> rawMap = Serialization.stringToMap(evaluated);
132-
Value value = mapToValue(rawMap);
133-
return constructProviderEvaluation(value, evaluated);
134-
} catch (OpenFeatureError e) {
135-
throw e;
136-
} catch (Exception e) {
137-
throw new GeneralError("Error getting Object evaluation", e);
132+
public void track(String eventName, EvaluationContext context, TrackingEventDetails details) {
133+
134+
// targetingKey is always required
135+
String key = context.getTargetingKey();
136+
if (key == null || key.isEmpty()) throw new TargetingKeyMissingError();
137+
138+
// eventName is always required
139+
if (eventName == null || eventName.isBlank()) throw new GeneralError("Missing eventName, required to track");
140+
141+
// trafficType is always required
142+
Value ttVal = context.getValue("trafficType");
143+
String trafficType = (ttVal != null && !ttVal.isNull() && ttVal.isString()) ? ttVal.asString() : null;
144+
if (trafficType == null || trafficType.isBlank()) throw new GeneralError("Missing trafficType variable, required to track");
145+
146+
double value = 0;
147+
Map<String, Object> attributes = new HashMap<>();
148+
if (details != null) {
149+
Optional<Number> optionalValue = details.getValue();
150+
value = optionalValue.orElse(0).doubleValue();
151+
attributes = details.asObjectMap();
138152
}
153+
154+
client.track(key, trafficType, eventName, value, attributes);
139155
}
140156

141157
public Map<String, Object> transformContext(EvaluationContext context) {
142158
return context.asObjectMap();
143159
}
144160

145-
private String evaluateTreatment(String key, EvaluationContext evaluationContext) {
161+
private SplitResult evaluateTreatment(String key, EvaluationContext evaluationContext) {
146162
String id = evaluationContext.getTargetingKey();
147163
if (id == null || id.isEmpty()) {
148164
// targeting key is always required
149165
throw new TargetingKeyMissingError();
150166
}
151167
Map<String, Object> attributes = transformContext(evaluationContext);
152-
return client.getTreatment(id, key, attributes);
168+
return client.getTreatmentWithConfig(id, key, attributes);
153169
}
154170

155171
private boolean noTreatment(String treatment) {
156172
return treatment == null || treatment.isEmpty() || treatment.equals("control");
157173
}
158174

159-
private <T> ProviderEvaluation<T> constructProviderEvaluation(T value, String variant) {
160-
return constructProviderEvaluation(value, variant, Reason.TARGETING_MATCH, null);
175+
private <T> ProviderEvaluation<T> constructProviderEvaluation(T value, String variant, ImmutableMetadata metadata) {
176+
return constructProviderEvaluation(value, variant, Reason.TARGETING_MATCH, null, metadata);
161177
}
162178

163-
private <T> ProviderEvaluation<T> constructProviderEvaluation(T value, String variant, Reason reason, ErrorCode errorCode) {
179+
private <T> ProviderEvaluation<T> constructProviderEvaluation(T value, String variant, Reason reason, ErrorCode errorCode, ImmutableMetadata metadata) {
164180
ProviderEvaluation.ProviderEvaluationBuilder<T> builder = ProviderEvaluation.builder();
165181
return builder
166182
.value(value)
183+
.flagMetadata(metadata)
167184
.reason(reason.name())
168185
.variant(variant)
169186
.errorCode(errorCode)

src/test/java/io/split/openfeature/ClientTest.java

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ public void getBooleanDetailsTest() {
148148
assertFalse(details.getValue());
149149
// the flag has a treatment of "off", this is returned as a value of false but the variant is still "off"
150150
assertEquals("off", details.getVariant());
151+
assertNull(details.getFlagMetadata().getString("config"));
151152
assertNull(details.getErrorCode());
152153
}
153154

@@ -159,17 +160,30 @@ public void getIntegerDetailsTest() {
159160
assertEquals(32, details.getValue());
160161
// the flag has a treatment of "32", this is resolved to an integer but the variant is still "32"
161162
assertEquals("32", details.getVariant());
163+
assertNull(details.getFlagMetadata().getString("config"));
162164
assertNull(details.getErrorCode());
163165
}
164166

165167
@Test
166-
public void getStringDetailsTest() {
168+
public void getStringWithDetailsTest() {
169+
FlagEvaluationDetails<String> details = client.getStringDetails("my_feature", "key");
170+
assertEquals("my_feature", details.getFlagKey());
171+
assertEquals(Reason.TARGETING_MATCH.name(), details.getReason());
172+
assertEquals("on", details.getValue());
173+
assertEquals("on", details.getVariant());
174+
assertEquals("{\"desc\" : \"this applies only to ON treatment\"}", details.getFlagMetadata().getString("config"));
175+
assertNull(details.getErrorCode());
176+
}
177+
178+
@Test
179+
public void getStringWithoutDetailsTest() {
167180
FlagEvaluationDetails<String> details = client.getStringDetails("some_other_feature", "blah");
168181
assertEquals("some_other_feature", details.getFlagKey());
169182
assertEquals(Reason.TARGETING_MATCH.name(), details.getReason());
170183
assertEquals("off", details.getValue());
171184
// the flag has a treatment of "off", since this is a string the variant is the same as the value
172185
assertEquals("off", details.getVariant());
186+
assertNull(details.getFlagMetadata().getString("config"));
173187
assertNull(details.getErrorCode());
174188
}
175189

@@ -181,6 +195,7 @@ public void getObjectDetailsTest() {
181195
assertEquals(mapToValue(Map.of("key", new Value("value"))), details.getValue());
182196
// the flag's treatment is stored as a string, and the variant is that raw string
183197
assertEquals("{\"key\": \"value\"}", details.getVariant());
198+
assertNull(details.getFlagMetadata().getString("config"));
184199
assertNull(details.getErrorCode());
185200
}
186201

@@ -192,6 +207,7 @@ public void getDoubleDetailsTest() {
192207
assertEquals(32D, details.getValue());
193208
// the flag has a treatment of "32", this is resolved to a double but the variant is still "32"
194209
assertEquals("32", details.getVariant());
210+
assertNull(details.getFlagMetadata().getString("config"));
195211
assertNull(details.getErrorCode());
196212
}
197213

@@ -205,6 +221,7 @@ public void getBooleanFailTest() {
205221
assertFalse(details.getValue());
206222
assertEquals(ErrorCode.PARSE_ERROR, details.getErrorCode());
207223
assertEquals(Reason.ERROR.name(), details.getReason());
224+
assertNull(details.getFlagMetadata().getString("config"));
208225
assertNull(details.getVariant());
209226
}
210227

@@ -216,8 +233,9 @@ public void getIntegerFailTest() {
216233

217234
FlagEvaluationDetails<Integer> details = client.getIntegerDetails("obj_feature", 10);
218235
assertEquals(10, details.getValue());
219-
assertEquals(ErrorCode.PARSE_ERROR, details.getErrorCode());
236+
assertEquals(ErrorCode.GENERAL, details.getErrorCode());
220237
assertEquals(Reason.ERROR.name(), details.getReason());
238+
assertNull(details.getFlagMetadata().getString("config"));
221239
assertNull(details.getVariant());
222240
}
223241

@@ -229,8 +247,9 @@ public void getDoubleFailTest() {
229247

230248
FlagEvaluationDetails<Double> details = client.getDoubleDetails("obj_feature", 10D);
231249
assertEquals(10D, details.getValue());
232-
assertEquals(ErrorCode.PARSE_ERROR, details.getErrorCode());
250+
assertEquals(ErrorCode.GENERAL, details.getErrorCode());
233251
assertEquals(Reason.ERROR.name(), details.getReason());
252+
assertNull(details.getFlagMetadata().getString("config"));
234253
assertNull(details.getVariant());
235254
}
236255

0 commit comments

Comments
 (0)