Skip to content

Commit 11c33e3

Browse files
add support for semver rule evaluation (FF-1569) (#39)
* add support for semver rule evaluation (FF-1569) * azul builds * adjust unit tests for semver comparison * numeric first
1 parent f41ae19 commit 11c33e3

File tree

5 files changed

+93
-33
lines changed

5 files changed

+93
-33
lines changed

Makefile

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,6 @@ help: Makefile
2828
build: test-data
2929
mvn --batch-mode --update-snapshots package
3030

31-
docker-compile-java:
32-
@docker run --rm -v "$$(pwd)":/usr/src/myapp -w /usr/src/myapp amd64/maven:3.8.2-ibmjava-8-alpine mvn clean compile
33-
3431
## test-data
3532
testDataDir := src/test/resources/
3633
tempDir := ${testDataDir}temp/

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,11 @@
33
## Getting Started
44

55
Refer to our [SDK documentation](https://docs.geteppo.com/feature-flags/sdks/server-sdks/java) for how to install and use the SDK.
6+
7+
## Contributing
8+
9+
A version of Java 8 is required to locally compile the SDK.
10+
11+
### Apple M-Series
12+
13+
Download a `arm64` compatible build: https://www.azul.com/downloads/?version=java-8-lts&architecture=arm-64-bit&package=jdk#zulu

pom.xml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>cloud.eppo</groupId>
88
<artifactId>eppo-server-sdk</artifactId>
9-
<version>2.2.0</version>
9+
<version>2.3.0</version>
1010

1111
<name>${project.groupId}:${project.artifactId}</name>
1212
<description>Eppo Server-Side SDK for Java</description>
@@ -82,6 +82,11 @@
8282
</properties>
8383

8484
<dependencies>
85+
<dependency>
86+
<groupId>com.github.zafarkhaja</groupId>
87+
<artifactId>java-semver</artifactId>
88+
<version>0.10.2</version>
89+
</dependency>
8590
<dependency>
8691
<groupId>com.fasterxml.jackson.core</groupId>
8792
<artifactId>jackson-databind</artifactId>

src/main/java/com/eppo/sdk/helpers/RuleValidator.java

Lines changed: 48 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.eppo.sdk.dto.EppoValue;
44
import com.eppo.sdk.dto.SubjectAttributes;
55
import com.eppo.sdk.exception.InvalidSubjectAttribute;
6+
import com.github.zafarkhaja.semver.Version;
67
import com.eppo.sdk.dto.Condition;
78
import com.eppo.sdk.dto.Rule;
89

@@ -24,18 +25,6 @@ interface IConditionFunc<T> {
2425
* Compare Class
2526
*/
2627
class Compare {
27-
/**
28-
* This function is used to compare number
29-
*
30-
* @param a
31-
* @param b
32-
* @param conditionFunc
33-
* @return
34-
*/
35-
public static boolean compareNumber(double a, double b, IConditionFunc<Double> conditionFunc) {
36-
return conditionFunc.check(a, b);
37-
}
38-
3928
/**
4029
* This function is used to compare Regex
4130
*
@@ -75,8 +64,7 @@ public class RuleValidator {
7564
*/
7665
public static Optional<Rule> findMatchingRule(
7766
SubjectAttributes subjectAttributes,
78-
List<Rule> rules
79-
) {
67+
List<Rule> rules) {
8068
for (Rule rule : rules) {
8169
if (RuleValidator.matchesRule(subjectAttributes, rule)) {
8270
return Optional.of(rule);
@@ -95,9 +83,9 @@ public static Optional<Rule> findMatchingRule(
9583
*/
9684
private static boolean matchesRule(
9785
SubjectAttributes subjectAttributes,
98-
Rule rule
99-
) throws InvalidSubjectAttribute {
100-
List<Boolean> conditionEvaluations = RuleValidator.evaluateRuleConditions(subjectAttributes, rule.getConditions());
86+
Rule rule) throws InvalidSubjectAttribute {
87+
List<Boolean> conditionEvaluations = RuleValidator.evaluateRuleConditions(subjectAttributes,
88+
rule.getConditions());
10189
return !conditionEvaluations.contains(false);
10290
}
10391

@@ -111,23 +99,57 @@ private static boolean matchesRule(
11199
*/
112100
private static boolean evaluateCondition(
113101
SubjectAttributes subjectAttributes,
114-
Condition condition
115-
) throws InvalidSubjectAttribute {
102+
Condition condition) throws InvalidSubjectAttribute {
116103
if (subjectAttributes.containsKey(condition.attribute)) {
117104
EppoValue value = subjectAttributes.get(condition.attribute);
105+
Optional<Version> valueSemVer = Version.tryParse(value.stringValue());
106+
Optional<Version> conditionSemVer = Version.tryParse(condition.value.stringValue());
107+
118108
try {
119109
switch (condition.operator) {
120110
case GTE:
121-
return Compare.compareNumber(value.doubleValue(), condition.value.doubleValue()
122-
, (a, b) -> a >= b);
111+
if (value.isNumeric() && condition.value.isNumeric()) {
112+
return value.doubleValue() >= condition.value.doubleValue();
113+
}
114+
115+
if (valueSemVer.isPresent() && conditionSemVer.isPresent()) {
116+
return valueSemVer.get().isHigherThanOrEquivalentTo(conditionSemVer.get());
117+
}
118+
119+
return false;
123120
case GT:
124-
return Compare.compareNumber(value.doubleValue(), condition.value.doubleValue(), (a, b) -> a > b);
121+
if (value.isNumeric() && condition.value.isNumeric()) {
122+
return value.doubleValue() > condition.value.doubleValue();
123+
}
124+
125+
if (valueSemVer.isPresent() && conditionSemVer.isPresent()) {
126+
return valueSemVer.get().isHigherThan(conditionSemVer.get());
127+
}
128+
129+
return false;
125130
case LTE:
126-
return Compare.compareNumber(value.doubleValue(), condition.value.doubleValue(), (a, b) -> a <= b);
131+
if (value.isNumeric() && condition.value.isNumeric()) {
132+
return value.doubleValue() <= condition.value.doubleValue();
133+
}
134+
135+
if (valueSemVer.isPresent() && conditionSemVer.isPresent()) {
136+
return valueSemVer.get().isLowerThanOrEquivalentTo(conditionSemVer.get());
137+
}
138+
139+
return false;
127140
case LT:
128-
return Compare.compareNumber(value.doubleValue(), condition.value.doubleValue(), (a, b) -> a < b);
141+
if (value.isNumeric() && condition.value.isNumeric()) {
142+
return value.doubleValue() < condition.value.doubleValue();
143+
}
144+
145+
if (valueSemVer.isPresent() && conditionSemVer.isPresent()) {
146+
return valueSemVer.get().isLowerThan(conditionSemVer.get());
147+
}
148+
149+
return false;
129150
case MATCHES:
130-
return Compare.compareRegex(value.stringValue(), Pattern.compile(condition.value.stringValue()));
151+
return Compare.compareRegex(value.stringValue(),
152+
Pattern.compile(condition.value.stringValue()));
131153
case ONE_OF:
132154
return Compare.isOneOf(value.stringValue(), condition.value.arrayValue());
133155
case NOT_ONE_OF:
@@ -151,8 +173,7 @@ private static boolean evaluateCondition(
151173
*/
152174
private static List<Boolean> evaluateRuleConditions(
153175
SubjectAttributes subjectAttributes,
154-
List<Condition> conditions
155-
) throws InvalidSubjectAttribute {
176+
List<Condition> conditions) throws InvalidSubjectAttribute {
156177
return conditions.stream()
157178
.map((condition) -> {
158179
try {

src/test/java/com/eppo/sdk/helpers/RuleValidatorTest.java

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,21 @@ public void addNumericConditionToRule(Rule rule) {
3535
addConditionToRule(rule, condition2);
3636
}
3737

38+
public void addSemVerConditionToRule(Rule rule) {
39+
Condition condition1 = new Condition();
40+
condition1.value = EppoValue.valueOf("1.5.0");
41+
condition1.attribute = "version";
42+
condition1.operator = OperatorType.GTE;
43+
44+
Condition condition2 = new Condition();
45+
condition2.value = EppoValue.valueOf("2.2.0");
46+
condition2.attribute = "version";
47+
condition2.operator = OperatorType.LT;
48+
49+
addConditionToRule(rule, condition1);
50+
addConditionToRule(rule, condition2);
51+
}
52+
3853
public void addRegexConditionToRule(Rule rule) {
3954
Condition condition = new Condition();
4055
condition.value = EppoValue.valueOf("[a-z]+");
@@ -85,7 +100,8 @@ void testMatchesAnyRuleWithEmptyConditions() {
85100
SubjectAttributes subjectAttributes = new SubjectAttributes();
86101
addNameToSubjectAttribute(subjectAttributes);
87102

88-
Assertions.assertEquals(ruleWithEmptyConditions, RuleValidator.findMatchingRule(subjectAttributes, rules).get());
103+
Assertions.assertEquals(ruleWithEmptyConditions,
104+
RuleValidator.findMatchingRule(subjectAttributes, rules).get());
89105
}
90106

91107
@DisplayName("findMatchingRule() with empty rules")
@@ -126,6 +142,20 @@ void testMatchesAnyRuleWhenRuleMatches() {
126142
Assertions.assertEquals(rule, RuleValidator.findMatchingRule(subjectAttributes, rules).get());
127143
}
128144

145+
@DisplayName("findMatchingRule() when rule matches with semver")
146+
@Test
147+
void testMatchesAnyRuleWhenRuleMatchesWithSemVer() {
148+
List<Rule> rules = new ArrayList<>();
149+
Rule rule = createRule(new ArrayList<>());
150+
addSemVerConditionToRule(rule);
151+
rules.add(rule);
152+
153+
SubjectAttributes subjectAttributes = new SubjectAttributes();
154+
subjectAttributes.put("version", EppoValue.valueOf("1.15.5"));
155+
156+
Assertions.assertEquals(rule, RuleValidator.findMatchingRule(subjectAttributes, rules).get());
157+
}
158+
129159
@DisplayName("findMatchingRule() throw InvalidSubjectAttribute")
130160
@Test
131161
void testMatchesAnyRuleWhenThrowInvalidSubjectAttribute() {
@@ -137,7 +167,6 @@ void testMatchesAnyRuleWhenThrowInvalidSubjectAttribute() {
137167
SubjectAttributes subjectAttributes = new SubjectAttributes();
138168
subjectAttributes.put("price", EppoValue.valueOf("abcd"));
139169

140-
141170
Assertions.assertFalse(RuleValidator.findMatchingRule(subjectAttributes, rules).isPresent());
142171
}
143172

0 commit comments

Comments
 (0)