Added MethodCallExpression support (#53)
* MemberAccessExpression / UnaryExpression / MethodCallExpression support as long as it evaluatues to a boolean value.pull/55/head
parent
6b66162e56
commit
845e92c6e1
|
@ -23,14 +23,17 @@ namespace RulesEngine.ExpressionBuilders
|
||||||
internal LambdaExpressionBuilder(ReSettings reSettings)
|
internal LambdaExpressionBuilder(ReSettings reSettings)
|
||||||
{
|
{
|
||||||
_reSettings = reSettings;
|
_reSettings = reSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override Expression<Func<RuleInput, RuleResultTree>> BuildExpressionForRule(Rule rule, IEnumerable<ParameterExpression> typeParamExpressions, ParameterExpression ruleInputExp)
|
internal override Expression<Func<RuleInput, RuleResultTree>> BuildExpressionForRule(Rule rule, IEnumerable<ParameterExpression> typeParamExpressions, ParameterExpression ruleInputExp)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var config = new ParsingConfig { CustomTypeProvider = new CustomTypeProvider(_reSettings.CustomTypes) };
|
var config = new ParsingConfig { CustomTypeProvider = new CustomTypeProvider(_reSettings.CustomTypes) };
|
||||||
var e = DynamicExpressionParser.ParseLambda(config, typeParamExpressions.ToArray(), null, rule.Expression);
|
var e = DynamicExpressionParser.ParseLambda(config, typeParamExpressions.ToArray(), null, rule.Expression);
|
||||||
var body = (BinaryExpression)e.Body;
|
var body = e.Body is BinaryExpression binaryExpression
|
||||||
|
? binaryExpression
|
||||||
|
: Expression.MakeBinary(ExpressionType.And, e.Body, Expression.Constant(true));
|
||||||
return Helpers.ToResultTreeExpression(rule, null, body, typeParamExpressions, ruleInputExp);
|
return Helpers.ToResultTreeExpression(rule, null, body, typeParamExpressions, ruleInputExp);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
@ -52,6 +55,5 @@ namespace RulesEngine.ExpressionBuilders
|
||||||
var e = DynamicExpressionParser.ParseLambda(config, typeParamExpressions.ToArray(), null, param.Expression);
|
var e = DynamicExpressionParser.ParseLambda(config, typeParamExpressions.ToArray(), null, param.Expression);
|
||||||
return e.Body;
|
return e.Body;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -40,7 +40,7 @@ namespace RulesEngine.UnitTest
|
||||||
dynamic input2 = GetInput2();
|
dynamic input2 = GetInput2();
|
||||||
dynamic input3 = GetInput3();
|
dynamic input3 = GetInput3();
|
||||||
|
|
||||||
var result = re.ExecuteRule("inputWorkflowReference",input1, input2, input3);
|
var result = re.ExecuteRule("inputWorkflowReference", input1, input2, input3);
|
||||||
Assert.NotNull(result);
|
Assert.NotNull(result);
|
||||||
Assert.IsType<List<RuleResultTree>>(result);
|
Assert.IsType<List<RuleResultTree>>(result);
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,7 @@ namespace RulesEngine.UnitTest
|
||||||
List<RuleResultTree> result = re.ExecuteRule("inputWorkflow", input1, input2, input3);
|
List<RuleResultTree> result = re.ExecuteRule("inputWorkflow", input1, input2, input3);
|
||||||
Assert.NotNull(result);
|
Assert.NotNull(result);
|
||||||
Assert.IsType<List<RuleResultTree>>(result);
|
Assert.IsType<List<RuleResultTree>>(result);
|
||||||
Assert.Contains(result,c => c.IsSuccess);
|
Assert.Contains(result, c => c.IsSuccess);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
@ -74,7 +74,7 @@ namespace RulesEngine.UnitTest
|
||||||
List<RuleResultTree> result = re.ExecuteRule("inputWorkflow", input1);
|
List<RuleResultTree> result = re.ExecuteRule("inputWorkflow", input1);
|
||||||
Assert.NotNull(result);
|
Assert.NotNull(result);
|
||||||
Assert.IsType<List<RuleResultTree>>(result);
|
Assert.IsType<List<RuleResultTree>>(result);
|
||||||
Assert.DoesNotContain(result,c => c.IsSuccess);
|
Assert.DoesNotContain(result, c => c.IsSuccess);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
@ -102,7 +102,7 @@ namespace RulesEngine.UnitTest
|
||||||
dynamic input2 = GetInput2();
|
dynamic input2 = GetInput2();
|
||||||
dynamic input3 = GetInput3();
|
dynamic input3 = GetInput3();
|
||||||
|
|
||||||
List<RuleResultTree> result = re.ExecuteRule("inputWorkflow",input1, input2, input3);
|
List<RuleResultTree> result = re.ExecuteRule("inputWorkflow", input1, input2, input3);
|
||||||
Assert.NotNull(result);
|
Assert.NotNull(result);
|
||||||
Assert.NotNull(result.First().GetMessages());
|
Assert.NotNull(result.First().GetMessages());
|
||||||
Assert.NotNull(result.First().GetMessages().WarningMessages);
|
Assert.NotNull(result.First().GetMessages().WarningMessages);
|
||||||
|
@ -124,7 +124,6 @@ namespace RulesEngine.UnitTest
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("rules1.json")]
|
[InlineData("rules1.json")]
|
||||||
public void ExecuteRule_InvalidWorkFlow_ThrowsException(string ruleFileName)
|
public void ExecuteRule_InvalidWorkFlow_ThrowsException(string ruleFileName)
|
||||||
|
@ -132,7 +131,7 @@ namespace RulesEngine.UnitTest
|
||||||
var re = GetRulesEngine(ruleFileName);
|
var re = GetRulesEngine(ruleFileName);
|
||||||
dynamic input = GetInput1();
|
dynamic input = GetInput1();
|
||||||
|
|
||||||
Assert.Throws<ArgumentException>(() => { re.ExecuteRule("inputWorkflow1", input); });
|
Assert.Throws<ArgumentException>(() => { re.ExecuteRule("inputWorkflow1", input); });
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
@ -146,10 +145,9 @@ namespace RulesEngine.UnitTest
|
||||||
dynamic input2 = GetInput2();
|
dynamic input2 = GetInput2();
|
||||||
dynamic input3 = GetInput3();
|
dynamic input3 = GetInput3();
|
||||||
|
|
||||||
Assert.Throws<ArgumentException>(() => re.ExecuteRule("inputWorkflow",input1, input2, input3 ));
|
Assert.Throws<ArgumentException>(() => re.ExecuteRule("inputWorkflow", input1, input2, input3));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("rules1.json")]
|
[InlineData("rules1.json")]
|
||||||
public void ClearWorkflow_RemovesAllWorkflow(string ruleFileName)
|
public void ClearWorkflow_RemovesAllWorkflow(string ruleFileName)
|
||||||
|
@ -165,7 +163,6 @@ namespace RulesEngine.UnitTest
|
||||||
Assert.Throws<ArgumentException>(() => re.ExecuteRule("inputWorkflowReference", input1, input2, input3));
|
Assert.Throws<ArgumentException>(() => re.ExecuteRule("inputWorkflowReference", input1, input2, input3));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("rules1.json")]
|
[InlineData("rules1.json")]
|
||||||
[InlineData("rules2.json")]
|
[InlineData("rules2.json")]
|
||||||
|
@ -180,17 +177,16 @@ namespace RulesEngine.UnitTest
|
||||||
List<RuleResultTree> result = re.ExecuteRule("inputWorkflow", input1, input2, input3);
|
List<RuleResultTree> result = re.ExecuteRule("inputWorkflow", input1, input2, input3);
|
||||||
Assert.NotNull(result);
|
Assert.NotNull(result);
|
||||||
Assert.IsType<List<RuleResultTree>>(result);
|
Assert.IsType<List<RuleResultTree>>(result);
|
||||||
Assert.Contains(result,c => c.IsSuccess);
|
Assert.Contains(result, c => c.IsSuccess);
|
||||||
|
|
||||||
input3.hello = "world";
|
input3.hello = "world";
|
||||||
|
|
||||||
result = re.ExecuteRule("inputWorkflow", input1, input2, input3);
|
result = re.ExecuteRule("inputWorkflow", input1, input2, input3);
|
||||||
Assert.NotNull(result);
|
Assert.NotNull(result);
|
||||||
Assert.IsType<List<RuleResultTree>>(result);
|
Assert.IsType<List<RuleResultTree>>(result);
|
||||||
Assert.Contains(result,c => c.IsSuccess);
|
Assert.Contains(result, c => c.IsSuccess);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("rules4.json")]
|
[InlineData("rules4.json")]
|
||||||
public void RulesEngine_Execute_Rule_For_Nested_Rule_Params_Returns_Success(string ruleFileName)
|
public void RulesEngine_Execute_Rule_For_Nested_Rule_Params_Returns_Success(string ruleFileName)
|
||||||
|
@ -223,36 +219,91 @@ namespace RulesEngine.UnitTest
|
||||||
{
|
{
|
||||||
var re = GetRulesEngine(ruleFileName);
|
var re = GetRulesEngine(ruleFileName);
|
||||||
|
|
||||||
var input1 = new RuleParameter("customName",GetInput1());
|
var input1 = new RuleParameter("customName", GetInput1());
|
||||||
var input2 = new RuleParameter("input2",GetInput2());
|
var input2 = new RuleParameter("input2", GetInput2());
|
||||||
var input3 = new RuleParameter("input3",GetInput3());
|
var input3 = new RuleParameter("input3", GetInput3());
|
||||||
|
|
||||||
List<RuleResultTree> result = re.ExecuteRule("inputWorkflow", input1,input2, input3);
|
List<RuleResultTree> result = re.ExecuteRule("inputWorkflow", input1, input2, input3);
|
||||||
Assert.NotNull(result);
|
Assert.NotNull(result);
|
||||||
Assert.IsType<List<RuleResultTree>>(result);
|
Assert.IsType<List<RuleResultTree>>(result);
|
||||||
Assert.Contains(result.First().ChildResults, c => c.ExceptionMessage.Contains("Unknown identifier 'input1'"));
|
Assert.Contains(result.First().ChildResults, c => c.ExceptionMessage.Contains("Unknown identifier 'input1'"));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("rules5.json","hello",true)]
|
[InlineData("rules5.json", "hello", true)]
|
||||||
[InlineData("rules5.json",null,false)]
|
[InlineData("rules5.json", null, false)]
|
||||||
public void ExecuteRule_WithInjectedUtils_ReturnsListOfRuleResultTree(string ruleFileName,string propValue,bool expectedResult)
|
public void ExecuteRule_WithInjectedUtils_ReturnsListOfRuleResultTree(string ruleFileName, string propValue, bool expectedResult)
|
||||||
{
|
{
|
||||||
var re = GetRulesEngine(ruleFileName);
|
var re = GetRulesEngine(ruleFileName);
|
||||||
|
|
||||||
dynamic input1 = new ExpandoObject();
|
dynamic input1 = new ExpandoObject();
|
||||||
if(propValue != null)
|
if (propValue != null)
|
||||||
input1.Property1 = propValue;
|
input1.Property1 = propValue;
|
||||||
|
|
||||||
if(propValue == null)
|
if (propValue == null)
|
||||||
input1.Property1 = null;
|
input1.Property1 = null;
|
||||||
|
|
||||||
var utils = new TestInstanceUtils();
|
var utils = new TestInstanceUtils();
|
||||||
|
|
||||||
List<RuleResultTree> result = re.ExecuteRule("inputWorkflow", new RuleParameter("input1",input1),new RuleParameter("utils",utils));
|
List<RuleResultTree> result = re.ExecuteRule("inputWorkflow", new RuleParameter("input1", input1), new RuleParameter("utils", utils));
|
||||||
Assert.NotNull(result);
|
Assert.NotNull(result);
|
||||||
Assert.IsType<List<RuleResultTree>>(result);
|
Assert.IsType<List<RuleResultTree>>(result);
|
||||||
Assert.All(result,c => Assert.Equal(expectedResult,c.IsSuccess));
|
Assert.All(result, c => Assert.Equal(expectedResult, c.IsSuccess));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("rules6.json")]
|
||||||
|
public void ExecuteRule_RuleWithMethodExpression_ReturnsSucess(string ruleFileName)
|
||||||
|
{
|
||||||
|
var re = GetRulesEngine(ruleFileName);
|
||||||
|
|
||||||
|
Func<bool> func = () => true;
|
||||||
|
|
||||||
|
dynamic input1 = new ExpandoObject();
|
||||||
|
input1.Property1 = "hello";
|
||||||
|
input1.Boolean = false;
|
||||||
|
input1.Method = func;
|
||||||
|
|
||||||
|
var utils = new TestInstanceUtils();
|
||||||
|
|
||||||
|
List<RuleResultTree> result = re.ExecuteRule("inputWorkflow", new RuleParameter("input1", input1));
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.IsType<List<RuleResultTree>>(result);
|
||||||
|
Assert.All(result, c => Assert.True(c.IsSuccess));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("rules7.json")]
|
||||||
|
public void ExecuteRule_RuleWithUnaryExpression_ReturnsSucess(string ruleFileName)
|
||||||
|
{
|
||||||
|
var re = GetRulesEngine(ruleFileName);
|
||||||
|
|
||||||
|
dynamic input1 = new ExpandoObject();
|
||||||
|
input1.Boolean = false;
|
||||||
|
|
||||||
|
var utils = new TestInstanceUtils();
|
||||||
|
|
||||||
|
List<RuleResultTree> result = re.ExecuteRule("inputWorkflow", new RuleParameter("input1", input1));
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.IsType<List<RuleResultTree>>(result);
|
||||||
|
Assert.All(result, c => Assert.True(c.IsSuccess));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("rules8.json")]
|
||||||
|
public void ExecuteRule_RuleWithMemberAccessExpression_ReturnsSucess(string ruleFileName)
|
||||||
|
{
|
||||||
|
var re = GetRulesEngine(ruleFileName);
|
||||||
|
|
||||||
|
dynamic input1 = new ExpandoObject();
|
||||||
|
input1.Boolean = false;
|
||||||
|
|
||||||
|
var utils = new TestInstanceUtils();
|
||||||
|
|
||||||
|
List<RuleResultTree> result = re.ExecuteRule("inputWorkflow", new RuleParameter("input1", input1));
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.IsType<List<RuleResultTree>>(result);
|
||||||
|
Assert.All(result, c => Assert.False(c.IsSuccess));
|
||||||
}
|
}
|
||||||
|
|
||||||
private RulesEngine CreateRulesEngine(WorkflowRules workflow)
|
private RulesEngine CreateRulesEngine(WorkflowRules workflow)
|
||||||
|
@ -277,7 +328,6 @@ namespace RulesEngine.UnitTest
|
||||||
return new RulesEngine(new string[] { data, injectWorkflowStr }, mockLogger.Object);
|
return new RulesEngine(new string[] { data, injectWorkflowStr }, mockLogger.Object);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private dynamic GetInput1()
|
private dynamic GetInput1()
|
||||||
{
|
{
|
||||||
var converter = new ExpandoObjectConverter();
|
var converter = new ExpandoObjectConverter();
|
||||||
|
@ -334,14 +384,14 @@ namespace RulesEngine.UnitTest
|
||||||
}
|
}
|
||||||
|
|
||||||
[ExcludeFromCodeCoverage]
|
[ExcludeFromCodeCoverage]
|
||||||
private class TestInstanceUtils{
|
private class TestInstanceUtils
|
||||||
public bool CheckExists(string str){
|
{
|
||||||
if(str != null && str.Length > 0)
|
public bool CheckExists(string str)
|
||||||
|
{
|
||||||
|
if (str != null && str.Length > 0)
|
||||||
return true;
|
return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -33,6 +33,15 @@
|
||||||
<None Update="TestData\rules2.json">
|
<None Update="TestData\rules2.json">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
|
<None Update="TestData\rules8.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="TestData\rules7.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="TestData\rules6.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
<None Update="TestData\rules5.json">
|
<None Update="TestData\rules5.json">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"WorkflowName": "inputWorkflow",
|
||||||
|
"Rules": [
|
||||||
|
{
|
||||||
|
"RuleName": "GiveDiscount10",
|
||||||
|
"SuccessEvent": "10",
|
||||||
|
"ErrorMessage": "One or more adjust rules failed.",
|
||||||
|
"ErrorType": "Error",
|
||||||
|
"RuleExpressionType": "LambdaExpression",
|
||||||
|
"Expression": "input1.Property1.Contains(\"hell\")"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"RuleName": "GiveDiscount20",
|
||||||
|
"SuccessEvent": "20",
|
||||||
|
"ErrorMessage": "One or more adjust rules failed.",
|
||||||
|
"ErrorType": "Error",
|
||||||
|
"RuleExpressionType": "LambdaExpression",
|
||||||
|
"Expression": "input1.Property1.Contains(\"hell\") && !input1.Boolean"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"RuleName": "GiveDiscount30",
|
||||||
|
"SuccessEvent": "30",
|
||||||
|
"ErrorMessage": "One or more adjust rules failed.",
|
||||||
|
"ErrorType": "Error",
|
||||||
|
"RuleExpressionType": "LambdaExpression",
|
||||||
|
"Expression": "input1.Method.Invoke()"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"WorkflowName": "inputWorkflow",
|
||||||
|
"Rules": [
|
||||||
|
{
|
||||||
|
"RuleName": "GiveDiscount10",
|
||||||
|
"SuccessEvent": "10",
|
||||||
|
"ErrorMessage": "One or more adjust rules failed.",
|
||||||
|
"ErrorType": "Error",
|
||||||
|
"RuleExpressionType": "LambdaExpression",
|
||||||
|
"Expression": "!input1.Boolean"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"RuleName": "GiveDiscount20",
|
||||||
|
"SuccessEvent": "20",
|
||||||
|
"ErrorMessage": "One or more adjust rules failed.",
|
||||||
|
"ErrorType": "Error",
|
||||||
|
"RuleExpressionType": "LambdaExpression",
|
||||||
|
"Expression": "!input1.Boolean && true"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"WorkflowName": "inputWorkflow",
|
||||||
|
"Rules": [
|
||||||
|
{
|
||||||
|
"RuleName": "GiveDiscount10",
|
||||||
|
"SuccessEvent": "10",
|
||||||
|
"ErrorMessage": "One or more adjust rules failed.",
|
||||||
|
"ErrorType": "Error",
|
||||||
|
"RuleExpressionType": "LambdaExpression",
|
||||||
|
"Expression": "input1.Boolean"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"RuleName": "GiveDiscount20",
|
||||||
|
"SuccessEvent": "20",
|
||||||
|
"ErrorMessage": "One or more adjust rules failed.",
|
||||||
|
"ErrorType": "Error",
|
||||||
|
"RuleExpressionType": "LambdaExpression",
|
||||||
|
"Expression": "input1.Boolean && true || (input1.Boolean)"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
Loading…
Reference in New Issue