Skip to content

Commit a329d38

Browse files
authored
feat: support IN operator for local evaluation (#128)
* feat: support `IN` operator for local evaluation * fix: coerce trait values to `Strings` for `NOT_CONTAINS`, `CONTAINS`, `IN` and `REGEX` operators * feat: implement delimiter logic for the `IN` operator * feat: add docs
1 parent b99ea7d commit a329d38

File tree

3 files changed

+40
-15
lines changed

3 files changed

+40
-15
lines changed

src/main/java/com/flagsmith/flagengine/segments/SegmentEvaluator.java

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -174,15 +174,24 @@ private static Boolean traitMatchesSegmentCondition(Optional<TraitModel> trait,
174174
*/
175175
public static Boolean conditionMatchesTraitValue(SegmentConditionModel condition, Object value) {
176176
SegmentConditions operator = condition.getOperator();
177-
if (operator.equals(SegmentConditions.NOT_CONTAINS)) {
178-
return ((String) value).indexOf(condition.getValue()) == -1;
179-
} else if (operator.equals(SegmentConditions.CONTAINS)) {
180-
return ((String) value).indexOf(condition.getValue()) > -1;
181-
} else if (operator.equals(SegmentConditions.REGEX)) {
182-
Pattern pattern = Pattern.compile(condition.getValue());
183-
return pattern.matcher((String) value).find();
184-
} else {
185-
return TypeCasting.compare(operator, value, condition.getValue());
177+
switch (operator) {
178+
case NOT_CONTAINS:
179+
return (String.valueOf(value)).indexOf(condition.getValue()) == -1;
180+
case CONTAINS:
181+
return (String.valueOf(value)).indexOf(condition.getValue()) > -1;
182+
case IN:
183+
if (value instanceof String) {
184+
return Arrays.asList(condition.getValue().split(",")).contains(value);
185+
}
186+
if (value instanceof Integer) {
187+
return Arrays.asList(condition.getValue().split(",")).contains(String.valueOf(value));
188+
}
189+
return false;
190+
case REGEX:
191+
Pattern pattern = Pattern.compile(condition.getValue());
192+
return pattern.matcher(String.valueOf(value)).find();
193+
default:
194+
return TypeCasting.compare(operator, value, condition.getValue());
186195
}
187196
}
188-
}
197+
}

src/main/java/com/flagsmith/flagengine/segments/constants/SegmentConditions.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
public enum SegmentConditions {
44
EQUAL, GREATER_THAN, LESS_THAN, LESS_THAN_INCLUSIVE, CONTAINS,
55
GREATER_THAN_INCLUSIVE, NOT_CONTAINS, NOT_EQUAL, REGEX, PERCENTAGE_SPLIT,
6-
MODULO, IS_SET, IS_NOT_SET;
6+
MODULO, IS_SET, IS_NOT_SET, IN;
77
}

src/test/java/com/flagsmith/flagengine/unit/segments/SegmentModelTest.java

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,17 +61,35 @@ private static Stream<Arguments> conditionTestData() {
6161
Arguments.of(SegmentConditions.CONTAINS, "bar", "b", true),
6262
Arguments.of(SegmentConditions.CONTAINS, "bar", "bar", true),
6363
Arguments.of(SegmentConditions.CONTAINS, "bar", "baz", false),
64+
Arguments.of(SegmentConditions.CONTAINS, 1, "2", false),
65+
Arguments.of(SegmentConditions.CONTAINS, 12, "1", true),
6466
Arguments.of(SegmentConditions.NOT_CONTAINS, "bar", "b", false),
6567
Arguments.of(SegmentConditions.NOT_CONTAINS, "bar", "bar", false),
6668
Arguments.of(SegmentConditions.NOT_CONTAINS, "bar", "baz", true),
69+
Arguments.of(SegmentConditions.NOT_CONTAINS, 1, "2", true),
70+
Arguments.of(SegmentConditions.NOT_CONTAINS, 12, "1", false),
6771
Arguments.of(SegmentConditions.REGEX, "foo", "[a-z]+", true),
6872
Arguments.of(SegmentConditions.REGEX, "FOO", "[a-z]+", false),
73+
Arguments.of(SegmentConditions.REGEX, 42, "[a-z]+", false),
74+
Arguments.of(SegmentConditions.REGEX, 42, "\\d+", true),
6975
Arguments.of(SegmentConditions.MODULO, 2, "2|0", true),
7076
Arguments.of(SegmentConditions.MODULO, 3, "2|0", false),
7177
Arguments.of(SegmentConditions.MODULO, 2.0, "2|0", true),
7278
Arguments.of(SegmentConditions.MODULO, 2.0, "2.0|0.0", true),
7379
Arguments.of(SegmentConditions.MODULO, "foo", "2|0", false),
74-
Arguments.of(SegmentConditions.MODULO, "foo", "foo|bar", false)
80+
Arguments.of(SegmentConditions.MODULO, "foo", "foo|bar", false),
81+
Arguments.of(SegmentConditions.IN, "foo", "", false),
82+
Arguments.of(SegmentConditions.IN, "foo", "foo,bar", true),
83+
Arguments.of(SegmentConditions.IN, "bar", "foo,bar", true),
84+
Arguments.of(SegmentConditions.IN, "ba", "foo,bar", false),
85+
Arguments.of(SegmentConditions.IN, "foo", "foo", true),
86+
Arguments.of(SegmentConditions.IN, 1, "1,2,3,4", true),
87+
Arguments.of(SegmentConditions.IN, 1, "", false),
88+
Arguments.of(SegmentConditions.IN, 1, "1", true),
89+
// Flagsmith's engine does not evaluate `IN` condition for floats/doubles and booleans
90+
// due to ambiguous serialization across supported platforms.
91+
Arguments.of(SegmentConditions.IN, 1.5, "1.5", false),
92+
Arguments.of(SegmentConditions.IN, false, "false", false)
7593
);
7694
}
7795

@@ -131,9 +149,7 @@ private static Stream<Arguments> semverTestData() {
131149
Arguments.of(SegmentConditions.GREATER_THAN_INCLUSIVE, "1.0.1", "1.0.1:semver", true),
132150
Arguments.of(SegmentConditions.LESS_THAN_INCLUSIVE, "1.0.0", "1.0.1:semver", true),
133151
Arguments.of(SegmentConditions.LESS_THAN_INCLUSIVE, "1.0.0", "1.0.0:semver", true),
134-
Arguments.of(SegmentConditions.LESS_THAN_INCLUSIVE, "1.0.1", "1.0.0:semver", false)
135-
);
152+
Arguments.of(SegmentConditions.LESS_THAN_INCLUSIVE, "1.0.1", "1.0.0:semver", false));
136153
}
137154

138-
139155
}

0 commit comments

Comments
 (0)