Skip to content

Commit 28eea59

Browse files
committed
sdk parity
1 parent 543defa commit 28eea59

File tree

12 files changed

+1073
-57
lines changed

12 files changed

+1073
-57
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,5 +68,6 @@ dist/
6868
example-1/
6969
monorepo
7070
featurevisor-go
71+
specs
7172

7273
.flattened-pom.xml

README.md

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,36 @@ f.<Map<String, Object>>getVariableJSON(featureKey, variableKey, context);
305305
f.<MyCustomClass>getVariableJSON(featureKey, variableKey, context);
306306
```
307307

308+
For strongly typed decoding, additional overloads are available:
309+
310+
```java
311+
import com.fasterxml.jackson.core.type.TypeReference;
312+
313+
// Array decoding using Class<T>
314+
List<MyItem> items = f.getVariableArray(featureKey, variableKey, context, MyItem.class);
315+
316+
// Array decoding using TypeReference
317+
List<Map<String, Object>> rows = f.getVariableArray(
318+
featureKey,
319+
variableKey,
320+
context,
321+
new TypeReference<List<Map<String, Object>>>() {}
322+
);
323+
324+
// Object decoding using Class<T>
325+
MyConfig config = f.getVariableObject(featureKey, variableKey, context, MyConfig.class);
326+
327+
// Object decoding using TypeReference
328+
Map<String, List<MyConfig>> nested = f.getVariableObject(
329+
featureKey,
330+
variableKey,
331+
context,
332+
new TypeReference<Map<String, List<MyConfig>>>() {}
333+
);
334+
```
335+
336+
Typed overloads are additive and non-breaking. If decoding fails for the requested target type, these methods return `null`.
337+
308338
## Getting all evaluations
309339

310340
You can get evaluations of all features available in the SDK instance:
@@ -717,9 +747,16 @@ $ mvn exec:java -Dexec.mainClass="com.featurevisor.cli.CLI" -Dexec.args="test --
717747
Additional options that are available:
718748

719749
```bash
720-
$ mvn exec:java -Dexec.mainClass="com.featurevisor.cli.CLI" -Dexec.args="test --projectDirectoryPath=/absolute/path/to/your/featurevisor/project --quiet --onlyFailures --keyPattern=myFeatureKey --assertionPattern=#1"
750+
$ mvn exec:java -Dexec.mainClass="com.featurevisor.cli.CLI" -Dexec.args="test --projectDirectoryPath=/absolute/path/to/your/featurevisor/project --quiet --onlyFailures --keyPattern=myFeatureKey --assertionPattern=#1 --with-tags --with-scopes --showDatafile --schemaVersion=2 --inflate=1"
721751
```
722752

753+
Scoped and tagged test behavior mirrors the JavaScript tester:
754+
755+
- `--with-tags`: builds and tests assertions against tagged datafiles.
756+
- `--with-scopes`: builds scoped datafiles and tests scoped assertions against those scoped files.
757+
- without `--with-scopes`: scoped assertions still run by merging scope context into assertion context (fallback behavior).
758+
- if both `scope` and `tag` are present in an assertion, scope datafile takes precedence.
759+
723760
### Benchmark
724761

725762
Learn more about benchmarking [here](https://featurevisor.com/docs/cli/#benchmarking).

src/main/java/com/featurevisor/cli/CLI.java

Lines changed: 238 additions & 42 deletions
Large diffs are not rendered by default.

src/main/java/com/featurevisor/sdk/ChildInstance.java

Lines changed: 121 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.featurevisor.sdk;
22

3+
import com.fasterxml.jackson.core.type.TypeReference;
34
import java.util.Map;
45
import java.util.HashMap;
56
import java.util.List;
@@ -236,11 +237,69 @@ public List<String> getVariableArray(String featureKey, String variableKey, Map<
236237
}
237238

238239
public List<String> getVariableArray(String featureKey, String variableKey, Map<String, Object> context) {
239-
return getVariableArray(featureKey, variableKey, context, null);
240+
return getVariableArray(featureKey, variableKey, context, (Featurevisor.OverrideOptions) null);
240241
}
241242

242243
public List<String> getVariableArray(String featureKey, String variableKey) {
243-
return getVariableArray(featureKey, variableKey, null, null);
244+
return getVariableArray(featureKey, variableKey, null, (Featurevisor.OverrideOptions) null);
245+
}
246+
247+
public <T> List<T> getVariableArray(
248+
String featureKey,
249+
String variableKey,
250+
Map<String, Object> context,
251+
Featurevisor.OverrideOptions options,
252+
Class<T> itemType
253+
) {
254+
return this.parent.getVariableArray(
255+
featureKey,
256+
variableKey,
257+
mergeContexts(this.context, context),
258+
mergeOverrideOptions(options),
259+
itemType
260+
);
261+
}
262+
263+
public <T> List<T> getVariableArray(
264+
String featureKey,
265+
String variableKey,
266+
Map<String, Object> context,
267+
Class<T> itemType
268+
) {
269+
return getVariableArray(featureKey, variableKey, context, null, itemType);
270+
}
271+
272+
public <T> List<T> getVariableArray(String featureKey, String variableKey, Class<T> itemType) {
273+
return getVariableArray(featureKey, variableKey, null, null, itemType);
274+
}
275+
276+
public <T> T getVariableArray(
277+
String featureKey,
278+
String variableKey,
279+
Map<String, Object> context,
280+
Featurevisor.OverrideOptions options,
281+
TypeReference<T> typeRef
282+
) {
283+
return this.parent.getVariableArray(
284+
featureKey,
285+
variableKey,
286+
mergeContexts(this.context, context),
287+
mergeOverrideOptions(options),
288+
typeRef
289+
);
290+
}
291+
292+
public <T> T getVariableArray(
293+
String featureKey,
294+
String variableKey,
295+
Map<String, Object> context,
296+
TypeReference<T> typeRef
297+
) {
298+
return getVariableArray(featureKey, variableKey, context, null, typeRef);
299+
}
300+
301+
public <T> T getVariableArray(String featureKey, String variableKey, TypeReference<T> typeRef) {
302+
return getVariableArray(featureKey, variableKey, null, null, typeRef);
244303
}
245304

246305
public <T> T getVariableObject(String featureKey, String variableKey, Map<String, Object> context, Featurevisor.OverrideOptions options) {
@@ -253,11 +312,69 @@ public <T> T getVariableObject(String featureKey, String variableKey, Map<String
253312
}
254313

255314
public <T> T getVariableObject(String featureKey, String variableKey, Map<String, Object> context) {
256-
return getVariableObject(featureKey, variableKey, context, null);
315+
return getVariableObject(featureKey, variableKey, context, (Featurevisor.OverrideOptions) null);
257316
}
258317

259318
public <T> T getVariableObject(String featureKey, String variableKey) {
260-
return getVariableObject(featureKey, variableKey, null, null);
319+
return getVariableObject(featureKey, variableKey, null, (Featurevisor.OverrideOptions) null);
320+
}
321+
322+
public <T> T getVariableObject(
323+
String featureKey,
324+
String variableKey,
325+
Map<String, Object> context,
326+
Featurevisor.OverrideOptions options,
327+
Class<T> type
328+
) {
329+
return this.parent.getVariableObject(
330+
featureKey,
331+
variableKey,
332+
mergeContexts(this.context, context),
333+
mergeOverrideOptions(options),
334+
type
335+
);
336+
}
337+
338+
public <T> T getVariableObject(
339+
String featureKey,
340+
String variableKey,
341+
Map<String, Object> context,
342+
Class<T> type
343+
) {
344+
return getVariableObject(featureKey, variableKey, context, null, type);
345+
}
346+
347+
public <T> T getVariableObject(String featureKey, String variableKey, Class<T> type) {
348+
return getVariableObject(featureKey, variableKey, null, null, type);
349+
}
350+
351+
public <T> T getVariableObject(
352+
String featureKey,
353+
String variableKey,
354+
Map<String, Object> context,
355+
Featurevisor.OverrideOptions options,
356+
TypeReference<T> typeRef
357+
) {
358+
return this.parent.getVariableObject(
359+
featureKey,
360+
variableKey,
361+
mergeContexts(this.context, context),
362+
mergeOverrideOptions(options),
363+
typeRef
364+
);
365+
}
366+
367+
public <T> T getVariableObject(
368+
String featureKey,
369+
String variableKey,
370+
Map<String, Object> context,
371+
TypeReference<T> typeRef
372+
) {
373+
return getVariableObject(featureKey, variableKey, context, null, typeRef);
374+
}
375+
376+
public <T> T getVariableObject(String featureKey, String variableKey, TypeReference<T> typeRef) {
377+
return getVariableObject(featureKey, variableKey, null, null, typeRef);
261378
}
262379

263380
public <T> T getVariableJSON(String featureKey, String variableKey, Map<String, Object> context, Featurevisor.OverrideOptions options) {

src/main/java/com/featurevisor/sdk/EvaluateByBucketing.java

Lines changed: 68 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -292,12 +292,62 @@ public static EvaluateByBucketingResult evaluateByBucketing(
292292

293293
// variable
294294
if (Evaluation.TYPE_VARIABLE.equals(type) && variableKey != null) {
295-
// override from rule
295+
// override from rule via variableOverrides (higher precedence than variables)
296+
if (matchedTraffic.getVariableOverrides() != null &&
297+
matchedTraffic.getVariableOverrides().containsKey(variableKey)) {
298+
299+
List<VariableOverride> overrides = matchedTraffic.getVariableOverrides().get(variableKey);
300+
for (int overrideIndex = 0; overrideIndex < overrides.size(); overrideIndex++) {
301+
VariableOverride override = overrides.get(overrideIndex);
302+
boolean matches = false;
303+
304+
if (override.getConditions() != null) {
305+
Object conditions = override.getConditions();
306+
if (conditions instanceof String && !"*".equals(conditions)) {
307+
try {
308+
conditions = new com.fasterxml.jackson.databind.ObjectMapper()
309+
.readValue((String) conditions, Object.class);
310+
} catch (Exception ignored) {
311+
conditions = override.getConditions();
312+
}
313+
}
314+
315+
matches = datafileReader.allConditionsAreMatched(conditions, context);
316+
} else if (override.getSegments() != null) {
317+
Object parsedSegments = datafileReader.parseSegmentsIfStringified(override.getSegments());
318+
matches = datafileReader.allSegmentsAreMatched(parsedSegments, context);
319+
}
320+
321+
if (matches) {
322+
Evaluation evaluation = new Evaluation(type, featureKey, variableKey)
323+
.reason(Evaluation.REASON_VARIABLE_OVERRIDE_RULE)
324+
.bucketKey(bucketKey)
325+
.bucketValue(bucketValue)
326+
.ruleKey(matchedTraffic.getKey())
327+
.traffic(convertTrafficToMap(matchedTraffic))
328+
.variableValue(override.getValue())
329+
.variableSchema(variableSchema)
330+
.variableOverrideIndex(overrideIndex);
331+
332+
Map<String, Object> details = new HashMap<>();
333+
details.put("featureKey", featureKey);
334+
details.put("variableKey", variableKey);
335+
details.put("bucketKey", bucketKey);
336+
details.put("bucketValue", bucketValue);
337+
logger.debug("variable override from rule", details);
338+
339+
result.setEvaluation(evaluation);
340+
return result;
341+
}
342+
}
343+
}
344+
345+
// override from rule via direct variables map
296346
if (matchedTraffic.getVariables() != null && matchedTraffic.getVariables().containsKey(variableKey)) {
297347
Object variableValue = matchedTraffic.getVariables().get(variableKey);
298348

299349
Evaluation evaluation = new Evaluation(type, featureKey, variableKey)
300-
.reason(Evaluation.REASON_VARIABLE_OVERRIDE)
350+
.reason(Evaluation.REASON_RULE)
301351
.bucketKey(bucketKey)
302352
.bucketValue(bucketValue)
303353
.ruleKey(matchedTraffic.getKey())
@@ -342,12 +392,22 @@ public static EvaluateByBucketingResult evaluateByBucketing(
342392
variation.getVariableOverrides().containsKey(variableKey)) {
343393

344394
List<VariableOverride> overrides = variation.getVariableOverrides().get(variableKey);
345-
for (VariableOverride override : overrides) {
395+
for (int overrideIndex = 0; overrideIndex < overrides.size(); overrideIndex++) {
396+
VariableOverride override = overrides.get(overrideIndex);
346397
boolean matches = false;
347398

348399
// Check conditions
349400
if (override.getConditions() != null) {
350-
matches = datafileReader.allConditionsAreMatched(override.getConditions(), context);
401+
Object conditions = override.getConditions();
402+
if (conditions instanceof String && !"*".equals(conditions)) {
403+
try {
404+
conditions = new com.fasterxml.jackson.databind.ObjectMapper()
405+
.readValue((String) conditions, Object.class);
406+
} catch (Exception ignored) {
407+
conditions = override.getConditions();
408+
}
409+
}
410+
matches = datafileReader.allConditionsAreMatched(conditions, context);
351411
}
352412
// Check segments
353413
else if (override.getSegments() != null) {
@@ -357,13 +417,14 @@ else if (override.getSegments() != null) {
357417

358418
if (matches) {
359419
Evaluation evaluation = new Evaluation(type, featureKey, variableKey)
360-
.reason(Evaluation.REASON_VARIABLE_OVERRIDE)
420+
.reason(Evaluation.REASON_VARIABLE_OVERRIDE_VARIATION)
361421
.bucketKey(bucketKey)
362422
.bucketValue(bucketValue)
363423
.ruleKey(matchedTraffic != null ? matchedTraffic.getKey() : null)
364424
.traffic(matchedTraffic != null ? convertTrafficToMap(matchedTraffic) : null)
365425
.variableValue(override.getValue())
366-
.variableSchema(variableSchema);
426+
.variableSchema(variableSchema)
427+
.variableOverrideIndex(overrideIndex);
367428

368429
Map<String, Object> details = new HashMap<>();
369430
details.put("featureKey", featureKey);
@@ -477,6 +538,7 @@ private static Map<String, Object> convertTrafficToMap(Traffic traffic) {
477538
map.put("enabled", traffic.getEnabled());
478539
map.put("variation", traffic.getVariation());
479540
map.put("variables", traffic.getVariables());
541+
map.put("variableOverrides", traffic.getVariableOverrides());
480542
map.put("variationWeights", traffic.getVariationWeights());
481543
map.put("allocation", traffic.getAllocation());
482544
}

src/main/java/com/featurevisor/sdk/Evaluation.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ public class Evaluation {
2727
public static final String REASON_VARIABLE_NOT_FOUND = "variable_not_found";
2828
public static final String REASON_VARIABLE_DEFAULT = "variable_default";
2929
public static final String REASON_VARIABLE_DISABLED = "variable_disabled";
30+
@Deprecated
3031
public static final String REASON_VARIABLE_OVERRIDE = "variable_override";
32+
public static final String REASON_VARIABLE_OVERRIDE_RULE = "variable_override_rule";
33+
public static final String REASON_VARIABLE_OVERRIDE_VARIATION = "variable_override_variation";
3134
public static final String REASON_NO_MATCH = "no_match";
3235
public static final String REASON_FORCED = "forced";
3336
public static final String REASON_STICKY = "sticky";
@@ -60,6 +63,7 @@ public class Evaluation {
6063
private String variableKey;
6164
private Object variableValue;
6265
private VariableSchema variableSchema;
66+
private Integer variableOverrideIndex;
6367

6468
// Required feature fields
6569
private String requiredFeatureKey;
@@ -94,6 +98,7 @@ public Evaluation(String type, String featureKey, String reason) {
9498
public String getVariableKey() { return variableKey; }
9599
public Object getVariableValue() { return variableValue; }
96100
public VariableSchema getVariableSchema() { return variableSchema; }
101+
public Integer getVariableOverrideIndex() { return variableOverrideIndex; }
97102
public String getRequiredFeatureKey() { return requiredFeatureKey; }
98103
public String getRequiredVariation() { return requiredVariation; }
99104
public String getActualVariation() { return actualVariation; }
@@ -117,6 +122,7 @@ public Evaluation(String type, String featureKey, String reason) {
117122
public void setVariableKey(String variableKey) { this.variableKey = variableKey; }
118123
public void setVariableValue(Object variableValue) { this.variableValue = variableValue; }
119124
public void setVariableSchema(VariableSchema variableSchema) { this.variableSchema = variableSchema; }
125+
public void setVariableOverrideIndex(Integer variableOverrideIndex) { this.variableOverrideIndex = variableOverrideIndex; }
120126
public void setRequiredFeatureKey(String requiredFeatureKey) { this.requiredFeatureKey = requiredFeatureKey; }
121127
public void setRequiredVariation(String requiredVariation) { this.requiredVariation = requiredVariation; }
122128
public void setActualVariation(String actualVariation) { this.actualVariation = actualVariation; }
@@ -212,6 +218,11 @@ public Evaluation variableSchema(VariableSchema variableSchema) {
212218
return this;
213219
}
214220

221+
public Evaluation variableOverrideIndex(Integer variableOverrideIndex) {
222+
this.variableOverrideIndex = variableOverrideIndex;
223+
return this;
224+
}
225+
215226
public Evaluation requiredFeatureKey(String requiredFeatureKey) {
216227
this.requiredFeatureKey = requiredFeatureKey;
217228
return this;
@@ -248,6 +259,7 @@ public Evaluation copy() {
248259
copy.variableKey = this.variableKey;
249260
copy.variableValue = this.variableValue;
250261
copy.variableSchema = this.variableSchema;
262+
copy.variableOverrideIndex = this.variableOverrideIndex;
251263
copy.requiredFeatureKey = this.requiredFeatureKey;
252264
copy.requiredVariation = this.requiredVariation;
253265
copy.actualVariation = this.actualVariation;

0 commit comments

Comments
 (0)