Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-artifact</artifactId>
<version>3.6.3</version>
</dependency>

<!-- Test Dependencies-->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,11 @@ private static Boolean traitsMatchSegmentCondition(List<TraitModel> 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());
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
83 changes: 68 additions & 15 deletions src/main/java/com/flagsmith/flagengine/utils/types/TypeCasting.java
Original file line number Diff line number Diff line change
@@ -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 {

Expand All @@ -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);
}

/**
Expand Down Expand Up @@ -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.
Expand All @@ -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;
}
}
Expand All @@ -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;
}
}
Expand All @@ -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;
}
}
Expand All @@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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));
}


}