Skip to content

Commit

Permalink
Merge branch 'fix/advanced-mongrule-operator-clobbering'
Browse files Browse the repository at this point in the history
  • Loading branch information
vazarkevych committed May 16, 2024
2 parents 0a72ee2 + a6cb303 commit 3947e21
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 57 deletions.
90 changes: 44 additions & 46 deletions lib/src/Evaluator/condition_evaluator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,53 +37,51 @@ enum GBAttributeType {

/// Evaluator class fro condition.
class GBConditionEvaluator {
/// This is the main function used to evaluate a condition.
/// This is the main function used to evaluate a condition. It loops through the condition key/value pairs and checks each entry:
/// - attributes : User Attributes
/// - condition : to be evaluated
bool evaluateCondition(Map<String, dynamic> attributes, dynamic conditionOBJ) {
if (conditionOBJ is List) {
bool isEvalCondition(Map<String, dynamic> attributes, dynamic conditionObj) {
if (conditionObj is List) {
return false;
}

/// If conditionObj has a key $or, return evalOr(attributes, condition["$or"])
var targetItems = conditionOBJ["\$or"];
if (targetItems != null) {
return evalOr(attributes, targetItems);
}

/// If conditionObj has a key $nor, return !evalOr(attributes, condition["$nor"])
targetItems = conditionOBJ["\$nor"];
if (targetItems != null) {
return !evalOr(attributes, targetItems);
}

/// If conditionObj has a key $and, return !evalAnd(attributes, condition["$and"])
targetItems = conditionOBJ["\$and"];
if (targetItems != null) {
return evalAnd(attributes, targetItems);
}

// If conditionObj has a key $not, return !evalCondition(attributes, condition["$not"])
var targetItem = conditionOBJ["\$not"];
if (targetItem != null) {
return !evaluateCondition(attributes, targetItem);
}
// Loop through the conditionObj key/value pairs
for (final key in conditionOBJ.keys) {
final element = getPath(attributes, key);
final value = conditionOBJ[key];

if (!evalConditionValue(value, element)) {
return false;
if (conditionObj is Map<String, dynamic>) {
for (var key in conditionObj.keys) {
var value = conditionObj[key];
switch (key) {
case "\$or":
if (!isEvalOr(attributes, value)) {
return false;
}
break;
case "\$nor":
if (isEvalOr(attributes, value)) {
return false;
}
break;
case "\$and":
if (!isEvalAnd(attributes, value)) {
return false;
}
break;
case "\$not":
if (isEvalCondition(attributes, value)) {
return false;
}
break;
default:
var element = getPath(attributes, key);
if (!isEvalConditionValue(value, element)) {
return false;
}
}
}
}

// If none of the entries failed their checks, `evalCondition` returns true
return true;
}

/// Evaluate OR conditions against given attributes
bool evalOr(Map<String, dynamic> attributes, List conditionObj) {
bool isEvalOr(Map<String, dynamic> attributes, List conditionObj) {
// If conditionObj is empty, return true
if (conditionObj.isEmpty) {
return true;
Expand All @@ -92,7 +90,7 @@ class GBConditionEvaluator {
for (var item in conditionObj) {
// If evalCondition(attributes, conditionObj[i]) is true, break out of
// the loop and return true
if (evaluateCondition(attributes, item)) {
if (isEvalCondition(attributes, item)) {
return true;
}
}
Expand All @@ -102,14 +100,14 @@ class GBConditionEvaluator {
}

/// Evaluate AND conditions against given attributes
bool evalAnd(dynamic attributes, List conditionObj) {
bool isEvalAnd(dynamic attributes, List conditionObj) {
// Loop through the conditionObjects

// Loop through the conditionObjects
for (var item in conditionObj) {
// If evalCondition(attributes, conditionObj[i]) is true, break out of
// the loop and return false
if (!evaluateCondition(attributes, item)) {
if (!isEvalCondition(attributes, item)) {
return false;
}
}
Expand Down Expand Up @@ -182,7 +180,7 @@ class GBConditionEvaluator {
}

///Evaluates Condition Value against given condition & attributes
bool evalConditionValue(dynamic conditionValue, dynamic attributeValue) {
bool isEvalConditionValue(dynamic conditionValue, dynamic attributeValue) {
// If conditionValue is a string, number, boolean, return true if it's
// "equal" to attributeValue and false if not.
if ((conditionValue as Object?).isPrimitive && (attributeValue as Object?).isPrimitive) {
Expand Down Expand Up @@ -242,13 +240,13 @@ class GBConditionEvaluator {
if (isOperatorObject(condition)) {
// If evalConditionValue(condition, item), break out of loop and
//return true
if (evalConditionValue(condition, item)) {
if (isEvalConditionValue(condition, item)) {
return true;
}
}
// Else if evalCondition(item, condition), break out of loop and
//return true
else if (evaluateCondition(item, condition)) {
else if (isEvalCondition(item, condition)) {
return true;
}
}
Expand All @@ -268,7 +266,7 @@ class GBConditionEvaluator {

/// Evaluate NOT operator - whether condition doesn't contain attribute
if (operator == "\$not") {
return !evalConditionValue(conditionValue, attributeValue);
return !isEvalConditionValue(conditionValue, attributeValue);
}

/// Evaluate EXISTS operator - whether condition contains attribute
Expand Down Expand Up @@ -301,7 +299,7 @@ class GBConditionEvaluator {
for (var con in conditionValue) {
var result = false;
for (var attr in attributeValue) {
if (evalConditionValue(con, attr)) {
if (isEvalConditionValue(con, attr)) {
result = true;
}
}
Expand All @@ -326,7 +324,7 @@ class GBConditionEvaluator {
/// Evaluate SIE operator - whether condition size is same as that
/// of attribute
case "\$size":
return evalConditionValue(conditionValue, attributeValue.length);
return isEvalConditionValue(conditionValue, attributeValue.length);

default:
}
Expand Down
4 changes: 2 additions & 2 deletions lib/src/Evaluator/experiment_evaluator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ class ExperimentEvaluator {

if (experiment.condition != null &&
!GBConditionEvaluator()
.evaluateCondition(context.attributes!, experiment.condition!)) {
.isEvalCondition(context.attributes!, experiment.condition!)) {
return _getExperimentResult(
featureId: featureId,
context: context,
Expand Down Expand Up @@ -162,7 +162,7 @@ class ExperimentEvaluator {
}

final evalObj = {'value': parentResult.value};
final evalCondition = GBConditionEvaluator().evaluateCondition(
final evalCondition = GBConditionEvaluator().isEvalCondition(
evalObj,
parentCondition.condition,
);
Expand Down
4 changes: 2 additions & 2 deletions lib/src/Evaluator/feature_evaluator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ class FeatureEvaluator {
var evalObj = {'value': parentResult.value};

// Evaluate the condition with the attributes and condition object
bool evalCondition = GBConditionEvaluator().evaluateCondition(
bool evalCondition = GBConditionEvaluator().isEvalCondition(
evalObj,
parentCondition.condition,
);
Expand Down Expand Up @@ -106,7 +106,7 @@ class FeatureEvaluator {
// Check if rule.force is set
if (rule.force != null) {
if (rule.condition != null &&
!GBConditionEvaluator().evaluateCondition(
!GBConditionEvaluator().isEvalCondition(
getAttributes(),
rule.condition!,
)) {
Expand Down
16 changes: 9 additions & 7 deletions test/common_test/gb_condition_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ void main() {
final passedScenarios = <String>[];
for (final item in evaluateCondition) {
final evaluator = GBConditionEvaluator();
final result = evaluator.evaluateCondition(item[2], item[1]);
final result = evaluator.isEvalCondition(item[2], item[1]);
final status = "${item[0]}\nExpected Result - ${item[3]}\nActual result - $result\n\n";
if (item[3].toString() == result.toString()) {
passedScenarios.add(status);
Expand All @@ -29,20 +29,22 @@ void main() {
}
index++;
}
print(failedScenarios);
// print(passedScenarios.length);
expect(failedScenarios.length, 0);
customLogger('Passed Test ${passedScenarios.length} out of ${evaluateCondition.length}');
});

test('Test valid condition obj', () {
final evaluator = GBConditionEvaluator();

expect(evaluator.evaluateCondition({}, []), false);
expect(evaluator.isEvalCondition({}, []), false);

expect(evaluator.isOperatorObject({}), false);

expect(evaluator.getPath('test', 'key'), null);

expect(evaluator.evalConditionValue(<String, dynamic>{}, null), false);
expect(evaluator.isEvalConditionValue(<String, dynamic>{}, null), false);

expect(evaluator.evalOperatorCondition("\$lte", "abc", "abc"), true);

Expand All @@ -66,7 +68,7 @@ void main() {
''';

expect(
GBConditionEvaluator().evaluateCondition(
GBConditionEvaluator().isEvalCondition(
jsonDecode(attributes),
jsonDecode(condition),
),
Expand All @@ -88,7 +90,7 @@ void main() {
''';

expect(
GBConditionEvaluator().evaluateCondition(
GBConditionEvaluator().isEvalCondition(
jsonDecode(attributes),
jsonDecode(condition),
),
Expand All @@ -110,7 +112,7 @@ void main() {
''';

expect(
GBConditionEvaluator().evaluateCondition(
GBConditionEvaluator().isEvalCondition(
jsonDecode(attributes),
jsonDecode(condition),
),
Expand All @@ -132,7 +134,7 @@ void main() {
''';

expect(
GBConditionEvaluator().evaluateCondition(
GBConditionEvaluator().isEvalCondition(
jsonDecode(attributes),
jsonDecode(condition),
),
Expand Down
53 changes: 53 additions & 0 deletions test/test_cases/test_case.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3002,6 +3002,59 @@ const String gbTestCases = r'''
"version": "1.2.3.4"
},
true
],
[
"$or pass but second condition fail",
{
"$or": [{ "foo": 1 }, { "bar": 1 }],
"baz": 2
},
{
"foo": 1,
"bar": 2,
"baz": 1
},
false
],
[
"$or and second condition both pass",
{
"$or": [{ "foo": 1 }, { "bar": 1 }],
"baz": 2
},
{
"foo": 1,
"bar": 2,
"baz": 2
},
true
],
[
"$and condition pass but $or fail",
{
"$and": [{ "foo": 1 }, { "bar": 1 }],
"$or": [{ "baz": 1 }, { "empty": 1 }]
},
{
"foo": 1,
"bar": 1,
"baz": 2
},
false
],
[
"$and and $or both pass",
{
"$and": [{ "foo": 1 }, { "bar": 1 }],
"$or": [{ "baz": 1 }, { "empty": 1 }]
},
{
"foo": 1,
"bar": 1,
"baz": 2,
"empty": 1
},
true
]
],
"hash": [
Expand Down

0 comments on commit 3947e21

Please sign in to comment.