diff --git a/pom.xml b/pom.xml
index 37d0d829..39064bce 100644
--- a/pom.xml
+++ b/pom.xml
@@ -90,6 +90,11 @@
slf4j-api
${slf4j.version}
+
+ org.apache.maven
+ maven-artifact
+ 3.6.3
+
diff --git a/src/main/java/com/flagsmith/flagengine/segments/SegmentEvaluator.java b/src/main/java/com/flagsmith/flagengine/segments/SegmentEvaluator.java
index a7834262..18021791 100644
--- a/src/main/java/com/flagsmith/flagengine/segments/SegmentEvaluator.java
+++ b/src/main/java/com/flagsmith/flagengine/segments/SegmentEvaluator.java
@@ -154,9 +154,11 @@ private static Boolean traitsMatchSegmentCondition(List identityTrai
* @param value Trait value to compare with.
* @return
*/
- private static Boolean traitsMatchValue(SegmentConditionModel condition, Object value) {
+ public static Boolean traitsMatchValue(SegmentConditionModel condition, Object value) {
SegmentConditions operator = condition.getOperator();
if (operator.equals(SegmentConditions.NOT_CONTAINS)) {
+ return ((String) value).indexOf(condition.getValue()) == -1;
+ } else if (operator.equals(SegmentConditions.CONTAINS)) {
return ((String) value).indexOf(condition.getValue()) > -1;
} else if (operator.equals(SegmentConditions.REGEX)) {
Pattern pattern = Pattern.compile(condition.getValue());
diff --git a/src/main/java/com/flagsmith/flagengine/utils/SemanticVersioning.java b/src/main/java/com/flagsmith/flagengine/utils/SemanticVersioning.java
new file mode 100644
index 00000000..5fa6767a
--- /dev/null
+++ b/src/main/java/com/flagsmith/flagengine/utils/SemanticVersioning.java
@@ -0,0 +1,30 @@
+package com.flagsmith.flagengine.utils;
+
+public class SemanticVersioning {
+
+ /**
+ * Checks if the given string have `:semver` suffix or not
+ * >>> is_semver("2.1.41-beta:semver")
+ * True
+ * >>> is_semver("2.1.41-beta")
+ * False
+ * @param version The version string.
+ * @return
+ */
+ public static Boolean isSemver(String version) {
+ return version.endsWith(":semver");
+ }
+
+ /**
+ * Remove the semver suffix(i.e: last 7 characters) from the given value
+ * >>> remove_semver_suffix("2.1.41-beta:semver")
+ * '2.1.41-beta'
+ * >>> remove_semver_suffix("2.1.41:semver")
+ * '2.1.41'
+ * @param version the version string to strip version from.
+ * @return
+ */
+ public static String removeSemver(String version) {
+ return version.substring(0, version.length() - 7);
+ }
+}
diff --git a/src/main/java/com/flagsmith/flagengine/utils/types/TypeCasting.java b/src/main/java/com/flagsmith/flagengine/utils/types/TypeCasting.java
index 0645b607..32882f6d 100644
--- a/src/main/java/com/flagsmith/flagengine/utils/types/TypeCasting.java
+++ b/src/main/java/com/flagsmith/flagengine/utils/types/TypeCasting.java
@@ -1,6 +1,9 @@
package com.flagsmith.flagengine.utils.types;
import com.flagsmith.flagengine.segments.constants.SegmentConditions;
+import com.flagsmith.flagengine.utils.SemanticVersioning;
+import org.apache.commons.lang3.BooleanUtils;
+import org.apache.maven.artifact.versioning.ComparableVersion;
public class TypeCasting {
@@ -13,15 +16,19 @@ public class TypeCasting {
*/
public static Boolean compare(SegmentConditions condition, Object value1, Object value2) {
- if (TypeCasting.isInteger(value1) && TypeCasting.isInteger(value2)) {
- return compare(condition, TypeCasting.toInteger(value1), TypeCasting.toInteger(value2));
- } else if (TypeCasting.isFloat(value1) && TypeCasting.isFloat(value2)) {
- return compare(condition, TypeCasting.toFloat(value1), TypeCasting.toFloat(value2));
- } else if (TypeCasting.isBoolean(value1) && TypeCasting.isBoolean(value2)) {
- return compare(condition, TypeCasting.toBoolean(value1), TypeCasting.toBoolean(value2));
+ if (isInteger(value1) && isInteger(value2)) {
+ return compare(condition, toInteger(value1), toInteger(value2));
+ } else if (isFloat(value1) && isFloat(value2)) {
+ return compare(condition, toFloat(value1), toFloat(value2));
+ } else if (isDouble(value1) && isDouble(value2)) {
+ return compare(condition, toDouble(value1), toDouble(value2));
+ } else if (isBoolean(value1) && isBoolean(value2)) {
+ return compare(condition, toBoolean(value1), toBoolean(value2));
+ } else if (isSemver(value2)) {
+ return compare(condition, toSemver(value1), toSemver(value2));
}
- return value1.equals(value2);
+ return compare(condition, (String) value1, (String) value2);
}
/**
@@ -51,6 +58,28 @@ public static Boolean compare(SegmentConditions condition, Comparable value1, Co
return value1.compareTo(value2) == 0;
}
+ /**
+ * Convert the object to Double.
+ * @param number Object to convert to Double.
+ * @return
+ */
+ public static Double toDouble(Object number) {
+ try {
+ return number instanceof Double ? ((Double) number) : Double.parseDouble((String) number);
+ } catch (Exception nfe) {
+ return null;
+ }
+ }
+
+ /**
+ * Is the object of type Double?.
+ * @param number Object to type check.
+ * @return
+ */
+ public static Boolean isDouble(Object number) {
+ return number instanceof Float || toDouble(number) != null;
+ }
+
/**
* Convert the object to float.
* @param number Object to convert to Float.
@@ -59,7 +88,7 @@ public static Boolean compare(SegmentConditions condition, Comparable value1, Co
public static Float toFloat(Object number) {
try {
return number instanceof Float ? ((Float) number) : Float.parseFloat((String) number);
- } catch (NumberFormatException nfe) {
+ } catch (Exception nfe) {
return null;
}
}
@@ -81,7 +110,7 @@ public static Boolean isFloat(Object number) {
public static Integer toInteger(Object number) {
try {
return number instanceof Integer ? ((Integer) number) : Integer.valueOf((String) number);
- } catch (NumberFormatException nfe) {
+ } catch (Exception nfe) {
return null;
}
}
@@ -102,9 +131,9 @@ public static Boolean isInteger(Object number) {
*/
public static Boolean toBoolean(Object str) {
try {
- String value = ((String) str).toLowerCase();
- return Boolean.parseBoolean(value);
- } catch (NumberFormatException nfe) {
+ return str instanceof Boolean ? ((Boolean) str)
+ : BooleanUtils.toBoolean((String) str);
+ } catch (Exception nfe) {
return null;
}
}
@@ -115,8 +144,32 @@ public static Boolean toBoolean(Object str) {
* @return
*/
public static Boolean isBoolean(Object str) {
- String value = ((String) str).toLowerCase();
- return Boolean.TRUE.toString().toLowerCase().equals(value)
- || Boolean.FALSE.toString().toLowerCase().equals(value);
+ return str instanceof Boolean
+ || Boolean.TRUE.toString().equalsIgnoreCase(((String) str))
+ || Boolean.FALSE.toString().equalsIgnoreCase(((String) str));
+ }
+
+ /**
+ * Convert the object to Semver.
+ * @param str Object to convert to Semver.
+ * @return
+ */
+ public static ComparableVersion toSemver(Object str) {
+ try {
+ String value = SemanticVersioning.isSemver((String) str)
+ ? SemanticVersioning.removeSemver((String) str) : ((String) str);
+ return new ComparableVersion(value);
+ } catch (Exception nfe) {
+ return null;
+ }
+ }
+
+ /**
+ * Is the object of type Semver?.
+ * @param str Object to type check.
+ * @return
+ */
+ public static Boolean isSemver(Object str) {
+ return SemanticVersioning.isSemver((String) str);
}
}
diff --git a/src/test/java/com/flagsmith/flagengine/unit/segments/SegmentModelTest.java b/src/test/java/com/flagsmith/flagengine/unit/segments/SegmentModelTest.java
new file mode 100644
index 00000000..d5fb03d2
--- /dev/null
+++ b/src/test/java/com/flagsmith/flagengine/unit/segments/SegmentModelTest.java
@@ -0,0 +1,128 @@
+package com.flagsmith.flagengine.unit.segments;
+
+import com.flagsmith.flagengine.segments.SegmentConditionModel;
+import com.flagsmith.flagengine.segments.SegmentEvaluator;
+import com.flagsmith.flagengine.segments.constants.SegmentConditions;
+import org.testng.Assert;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class SegmentModelTest {
+
+ @DataProvider(name = "conditionTestData")
+ public Object[][] conditionTestData() {
+ return new Object[][] {
+ new Object[] {SegmentConditions.EQUAL, "bar", "bar", true},
+ new Object[] {SegmentConditions.EQUAL, "bar", "baz", false},
+ new Object[] {SegmentConditions.EQUAL, 1, "1", true},
+ new Object[] {SegmentConditions.EQUAL, 1, "2", false},
+ new Object[] {SegmentConditions.EQUAL, true, "true", true},
+ new Object[] {SegmentConditions.EQUAL, false, "false", true},
+ new Object[] {SegmentConditions.EQUAL, false, "true", false},
+ new Object[] {SegmentConditions.EQUAL, true, "false", false},
+ new Object[] {SegmentConditions.EQUAL, 1.23, "1.23", true},
+ new Object[] {SegmentConditions.EQUAL, 1.23, "4.56", false},
+ new Object[] {SegmentConditions.GREATER_THAN, 2, "1", true},
+ new Object[] {SegmentConditions.GREATER_THAN, 1, "1", false},
+ new Object[] {SegmentConditions.GREATER_THAN, 0, "1", false},
+ new Object[] {SegmentConditions.GREATER_THAN, 2.1, "2.0", true},
+ new Object[] {SegmentConditions.GREATER_THAN, 2.1, "2.1", false},
+ new Object[] {SegmentConditions.GREATER_THAN, 2.0, "2.1", false},
+ new Object[] {SegmentConditions.GREATER_THAN_INCLUSIVE, 2, "1", true},
+ new Object[] {SegmentConditions.GREATER_THAN_INCLUSIVE, 1, "1", true},
+ new Object[] {SegmentConditions.GREATER_THAN_INCLUSIVE, 0, "1", false},
+ new Object[] {SegmentConditions.GREATER_THAN_INCLUSIVE, 2.1, "2.0", true},
+ new Object[] {SegmentConditions.GREATER_THAN_INCLUSIVE, 2.1, "2.1", true},
+ new Object[] {SegmentConditions.GREATER_THAN_INCLUSIVE, 2.0, "2.1", false},
+ new Object[] {SegmentConditions.LESS_THAN, 1, "2", true},
+ new Object[] {SegmentConditions.LESS_THAN, 1, "1", false},
+ new Object[] {SegmentConditions.LESS_THAN, 1, "0", false},
+ new Object[] {SegmentConditions.LESS_THAN, 2.0, "2.1", true},
+ new Object[] {SegmentConditions.LESS_THAN, 2.1, "2.1", false},
+ new Object[] {SegmentConditions.LESS_THAN, 2.1, "2.0", false},
+ new Object[] {SegmentConditions.LESS_THAN_INCLUSIVE, 1, "2", true},
+ new Object[] {SegmentConditions.LESS_THAN_INCLUSIVE, 1, "1", true},
+ new Object[] {SegmentConditions.LESS_THAN_INCLUSIVE, 1, "0", false},
+ new Object[] {SegmentConditions.LESS_THAN_INCLUSIVE, 2.0, "2.1", true},
+ new Object[] {SegmentConditions.LESS_THAN_INCLUSIVE, 2.1, "2.1", true},
+ new Object[] {SegmentConditions.LESS_THAN_INCLUSIVE, 2.1, "2.0", false},
+ new Object[] {SegmentConditions.NOT_EQUAL, "bar", "baz", true},
+ new Object[] {SegmentConditions.NOT_EQUAL, "bar", "bar", false},
+ new Object[] {SegmentConditions.NOT_EQUAL, 1, "2", true},
+ new Object[] {SegmentConditions.NOT_EQUAL, 1, "1", false},
+ new Object[] {SegmentConditions.NOT_EQUAL, true, "false", true},
+ new Object[] {SegmentConditions.NOT_EQUAL, false, "true", true},
+ new Object[] {SegmentConditions.NOT_EQUAL, false, "false", false},
+ new Object[] {SegmentConditions.NOT_EQUAL, true, "true", false},
+ new Object[] {SegmentConditions.CONTAINS, "bar", "b", true},
+ new Object[] {SegmentConditions.CONTAINS, "bar", "bar", true},
+ new Object[] {SegmentConditions.CONTAINS, "bar", "baz", false},
+ new Object[] {SegmentConditions.NOT_CONTAINS, "bar", "b", false},
+ new Object[] {SegmentConditions.NOT_CONTAINS, "bar", "bar", false},
+ new Object[] {SegmentConditions.NOT_CONTAINS, "bar", "baz", true},
+ new Object[] {SegmentConditions.REGEX, "foo", "[a-z]+", true},
+ new Object[] {SegmentConditions.REGEX, "FOO", "[a-z]+", false},
+ };
+ }
+
+ @Test(dataProvider = "conditionTestData")
+ public void testSegmentConditionMatchesTraitValue(
+ SegmentConditions condition,
+ Object traitValue,
+ String conditionValue,
+ Boolean expectedResponse) {
+
+ SegmentConditionModel conditionModel = new SegmentConditionModel();
+ conditionModel.setValue(conditionValue);
+ conditionModel.setOperator(condition);
+ conditionModel.setProperty_("foo");
+
+ Boolean actualResult = SegmentEvaluator.traitsMatchValue(conditionModel, traitValue);
+
+ Assert.assertTrue(actualResult.equals(expectedResponse));
+ }
+
+ @DataProvider(name = "semverTestData")
+ public Object[][] semverTestData() {
+ return new Object[][] {
+ new Object[] {SegmentConditions.EQUAL, "1.0.0", "1.0.0:semver", true},
+ new Object[] {SegmentConditions.EQUAL, "1.0.0", "1.0.1:semver", false},
+ new Object[] {SegmentConditions.NOT_EQUAL, "1.0.0", "1.0.0:semver", false},
+ new Object[] {SegmentConditions.NOT_EQUAL, "1.0.0", "1.0.1:semver", true},
+ new Object[] {SegmentConditions.GREATER_THAN, "1.0.1", "1.0.0:semver", true},
+ new Object[] {SegmentConditions.GREATER_THAN, "1.0.0", "1.0.0-beta:semver", true},
+ new Object[] {SegmentConditions.GREATER_THAN, "1.0.1", "1.2.0:semver", false},
+ new Object[] {SegmentConditions.GREATER_THAN, "1.0.1", "1.0.1:semver", false},
+ new Object[] {SegmentConditions.GREATER_THAN, "1.2.4", "1.2.3-pre.2+build.4:semver", true},
+ new Object[] {SegmentConditions.LESS_THAN, "1.0.0", "1.0.1:semver", true},
+ new Object[] {SegmentConditions.LESS_THAN, "1.0.0", "1.0.0:semver", false},
+ new Object[] {SegmentConditions.LESS_THAN, "1.0.1", "1.0.0:semver", false},
+ new Object[] {SegmentConditions.LESS_THAN, "1.0.0-rc.2", "1.0.0-rc.3:semver", true},
+ new Object[] {SegmentConditions.GREATER_THAN_INCLUSIVE, "1.0.1", "1.0.0:semver", true},
+ new Object[] {SegmentConditions.GREATER_THAN_INCLUSIVE, "1.0.1", "1.2.0:semver", false},
+ new Object[] {SegmentConditions.GREATER_THAN_INCLUSIVE, "1.0.1", "1.0.1:semver", true},
+ new Object[] {SegmentConditions.LESS_THAN_INCLUSIVE, "1.0.0", "1.0.1:semver", true},
+ new Object[] {SegmentConditions.LESS_THAN_INCLUSIVE, "1.0.0", "1.0.0:semver", true},
+ new Object[] {SegmentConditions.LESS_THAN_INCLUSIVE, "1.0.1", "1.0.0:semver", false},
+ };
+ }
+
+ @Test(dataProvider = "semverTestData")
+ public void testSemverMatchesTraitValue(
+ SegmentConditions condition,
+ Object traitValue,
+ String conditionValue,
+ Boolean expectedResponse) {
+
+ SegmentConditionModel conditionModel = new SegmentConditionModel();
+ conditionModel.setValue(conditionValue);
+ conditionModel.setOperator(condition);
+ conditionModel.setProperty_("foo");
+
+ Boolean actualResult = SegmentEvaluator.traitsMatchValue(conditionModel, traitValue);
+
+ Assert.assertTrue(actualResult.equals(expectedResponse));
+ }
+
+
+}