diff --git a/lib/src/Evaluator/condition_evaluator.dart b/lib/src/Evaluator/condition_evaluator.dart index d54554a..9e0e425 100644 --- a/lib/src/Evaluator/condition_evaluator.dart +++ b/lib/src/Evaluator/condition_evaluator.dart @@ -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 attributes, dynamic conditionOBJ) { - if (conditionOBJ is List) { + bool isEvalCondition(Map 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) { + 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 attributes, List conditionObj) { + bool isEvalOr(Map attributes, List conditionObj) { // If conditionObj is empty, return true if (conditionObj.isEmpty) { return true; @@ -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; } } @@ -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; } } @@ -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) { @@ -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; } } @@ -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 @@ -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; } } @@ -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: } diff --git a/lib/src/Evaluator/experiment_evaluator.dart b/lib/src/Evaluator/experiment_evaluator.dart index 726087e..44f14ce 100644 --- a/lib/src/Evaluator/experiment_evaluator.dart +++ b/lib/src/Evaluator/experiment_evaluator.dart @@ -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, @@ -162,7 +162,7 @@ class ExperimentEvaluator { } final evalObj = {'value': parentResult.value}; - final evalCondition = GBConditionEvaluator().evaluateCondition( + final evalCondition = GBConditionEvaluator().isEvalCondition( evalObj, parentCondition.condition, ); diff --git a/lib/src/Evaluator/feature_evaluator.dart b/lib/src/Evaluator/feature_evaluator.dart index 2fa0d96..c7c8023 100644 --- a/lib/src/Evaluator/feature_evaluator.dart +++ b/lib/src/Evaluator/feature_evaluator.dart @@ -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, ); @@ -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!, )) { diff --git a/test/common_test/gb_condition_test.dart b/test/common_test/gb_condition_test.dart index 314ee11..69a4f6f 100644 --- a/test/common_test/gb_condition_test.dart +++ b/test/common_test/gb_condition_test.dart @@ -19,7 +19,7 @@ void main() { final passedScenarios = []; 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); @@ -29,6 +29,8 @@ void main() { } index++; } + print(failedScenarios); + // print(passedScenarios.length); expect(failedScenarios.length, 0); customLogger('Passed Test ${passedScenarios.length} out of ${evaluateCondition.length}'); }); @@ -36,13 +38,13 @@ void main() { 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({}, null), false); + expect(evaluator.isEvalConditionValue({}, null), false); expect(evaluator.evalOperatorCondition("\$lte", "abc", "abc"), true); @@ -66,7 +68,7 @@ void main() { '''; expect( - GBConditionEvaluator().evaluateCondition( + GBConditionEvaluator().isEvalCondition( jsonDecode(attributes), jsonDecode(condition), ), @@ -88,7 +90,7 @@ void main() { '''; expect( - GBConditionEvaluator().evaluateCondition( + GBConditionEvaluator().isEvalCondition( jsonDecode(attributes), jsonDecode(condition), ), @@ -110,7 +112,7 @@ void main() { '''; expect( - GBConditionEvaluator().evaluateCondition( + GBConditionEvaluator().isEvalCondition( jsonDecode(attributes), jsonDecode(condition), ), @@ -132,7 +134,7 @@ void main() { '''; expect( - GBConditionEvaluator().evaluateCondition( + GBConditionEvaluator().isEvalCondition( jsonDecode(attributes), jsonDecode(condition), ), diff --git a/test/test_cases/test_case.dart b/test/test_cases/test_case.dart index c1e39b3..8178133 100644 --- a/test/test_cases/test_case.dart +++ b/test/test_cases/test_case.dart @@ -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": [