parent
07ed77042d
commit
63ecbe6a64
|
@ -8,6 +8,9 @@ EndProject
|
|||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RulesEngine.UnitTest", "..\..\test\RulesEngine.UnitTest\RulesEngine.UnitTest.csproj", "{50E0C2A5-E2C8-4B12-8C0E-B69F698A82BF}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DemoApp", "..\..\demo\DemoApp\DemoApp.csproj", "{57BB8C07-799A-4F87-A7CC-D3D3F694DD02}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{CD4DFE6A-083B-478E-8377-77F474833E30} = {CD4DFE6A-083B-478E-8377-77F474833E30}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
|
|
|
@ -25,11 +25,20 @@ namespace RulesEngine.ExpressionBuilders
|
|||
_reSettings = reSettings;
|
||||
}
|
||||
internal override Expression<Func<RuleInput, RuleResultTree>> BuildExpressionForRule(Rule rule, IEnumerable<ParameterExpression> typeParamExpressions, ParameterExpression ruleInputExp)
|
||||
{
|
||||
try
|
||||
{
|
||||
var config = new ParsingConfig { CustomTypeProvider = new CustomTypeProvider(_reSettings.CustomTypes) };
|
||||
var e = DynamicExpressionParser.ParseLambda(config, typeParamExpressions.ToArray(), null, rule.Expression);
|
||||
var body = (BinaryExpression)e.Body;
|
||||
return Helpers.ToResultTreeExpression(rule, null, body, typeParamExpressions, ruleInputExp);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var binaryExpression = Expression.And(Expression.Constant(true), Expression.Constant(false));
|
||||
var exceptionMessage = ex.Message;
|
||||
return Helpers.ToResultTreeExpression(rule, null, binaryExpression, typeParamExpressions, ruleInputExp, exceptionMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,9 +23,9 @@ namespace RulesEngine.HelperFunctions
|
|||
/// <param name="typeParamExpressions">The type parameter expressions.</param>
|
||||
/// <param name="ruleInputExp">The rule input exp.</param>
|
||||
/// <returns>Expression of func</returns>
|
||||
internal static Expression<Func<RuleInput, RuleResultTree>> ToResultTreeExpression(Rule rule, IEnumerable<MemberInitExpression> childRuleResults, BinaryExpression isSuccessExp, IEnumerable<ParameterExpression> typeParamExpressions, ParameterExpression ruleInputExp)
|
||||
internal static Expression<Func<RuleInput, RuleResultTree>> ToResultTreeExpression(Rule rule, IEnumerable<MemberInitExpression> childRuleResults, BinaryExpression isSuccessExp, IEnumerable<ParameterExpression> typeParamExpressions, ParameterExpression ruleInputExp, string exceptionMessage = "")
|
||||
{
|
||||
var memberInit = ToResultTree(rule, childRuleResults, isSuccessExp, typeParamExpressions, null);
|
||||
var memberInit = ToResultTree(rule, childRuleResults, isSuccessExp, typeParamExpressions, null, exceptionMessage);
|
||||
var lambda = Expression.Lambda<Func<RuleInput, RuleResultTree>>(memberInit, new[] { ruleInputExp });
|
||||
return lambda;
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ namespace RulesEngine.HelperFunctions
|
|||
/// <param name="isSuccessExp">The is success exp.</param>
|
||||
/// <param name="childRuleResultsblockexpr">The child rule results block expression.</param>
|
||||
/// <returns></returns>
|
||||
internal static MemberInitExpression ToResultTree(Rule rule, IEnumerable<MemberInitExpression> childRuleResults, BinaryExpression isSuccessExp, IEnumerable<ParameterExpression> typeParamExpressions, BlockExpression childRuleResultsblockexpr)
|
||||
internal static MemberInitExpression ToResultTree(Rule rule, IEnumerable<MemberInitExpression> childRuleResults, BinaryExpression isSuccessExp, IEnumerable<ParameterExpression> typeParamExpressions, BlockExpression childRuleResultsblockexpr, string exceptionMessage = "")
|
||||
{
|
||||
var createdType = typeof(RuleResultTree);
|
||||
var ctor = Expression.New(createdType);
|
||||
|
@ -47,10 +47,12 @@ namespace RulesEngine.HelperFunctions
|
|||
var isSuccessProp = createdType.GetProperty(nameof(RuleResultTree.IsSuccess));
|
||||
var childResultProp = createdType.GetProperty(nameof(RuleResultTree.ChildResults));
|
||||
var inputProp = createdType.GetProperty(nameof(RuleResultTree.Input));
|
||||
var exceptionProp = createdType.GetProperty(nameof(RuleResultTree.ExceptionMessage));
|
||||
|
||||
var rulePropBinding = Expression.Bind(ruleProp, Expression.Constant(rule));
|
||||
var isSuccessPropBinding = Expression.Bind(isSuccessProp, isSuccessExp);
|
||||
var inputBinding = Expression.Bind(inputProp, typeParamExpressions.FirstOrDefault());
|
||||
var exceptionBinding = Expression.Bind(exceptionProp, Expression.Constant(exceptionMessage));
|
||||
|
||||
MemberInitExpression memberInit;
|
||||
|
||||
|
@ -59,16 +61,16 @@ namespace RulesEngine.HelperFunctions
|
|||
var ruleResultTreeArr = Expression.NewArrayInit(typeof(RuleResultTree), childRuleResults);
|
||||
|
||||
var childResultPropBinding = Expression.Bind(childResultProp, ruleResultTreeArr);
|
||||
memberInit = Expression.MemberInit(ctor, new[] { rulePropBinding, isSuccessPropBinding, childResultPropBinding, inputBinding });
|
||||
memberInit = Expression.MemberInit(ctor, new[] { rulePropBinding, isSuccessPropBinding, childResultPropBinding, inputBinding, exceptionBinding });
|
||||
}
|
||||
else if (childRuleResultsblockexpr != null)
|
||||
{
|
||||
var childResultPropBinding = Expression.Bind(childResultProp, childRuleResultsblockexpr);
|
||||
memberInit = Expression.MemberInit(ctor, new[] { rulePropBinding, isSuccessPropBinding, childResultPropBinding, inputBinding });
|
||||
memberInit = Expression.MemberInit(ctor, new[] { rulePropBinding, isSuccessPropBinding, childResultPropBinding, inputBinding, exceptionBinding });
|
||||
}
|
||||
else
|
||||
{
|
||||
memberInit = Expression.MemberInit(ctor, new[] { rulePropBinding, isSuccessPropBinding, inputBinding });
|
||||
memberInit = Expression.MemberInit(ctor, new[] { rulePropBinding, isSuccessPropBinding, inputBinding, exceptionBinding });
|
||||
}
|
||||
|
||||
return memberInit;
|
||||
|
|
|
@ -40,6 +40,11 @@ namespace RulesEngine.Models
|
|||
/// </summary>
|
||||
public object Input { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the exception message in case an error is thrown during rules calculation.
|
||||
/// </summary>
|
||||
public string ExceptionMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// This method will return all the error and warning messages to caller
|
||||
/// </summary>
|
||||
|
|
|
@ -13,6 +13,7 @@ using System.Dynamic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using Xunit;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace RulesEngine.UnitTest
|
||||
{
|
||||
|
@ -32,12 +33,12 @@ namespace RulesEngine.UnitTest
|
|||
public void RulesEngine_InjectedRules_ReturnsListOfRuleResultTree(string ruleFileName)
|
||||
{
|
||||
var re = GetRulesEngine(ruleFileName);
|
||||
dynamic input = GetInput();
|
||||
|
||||
dynamic input2 = GetInput();
|
||||
input2.value1 = "val1";
|
||||
dynamic input1 = GetInput1();
|
||||
dynamic input2 = GetInput2();
|
||||
dynamic input3 = GetInput3();
|
||||
|
||||
var result = re.ExecuteRule("inputWorkflowReference", new List<dynamic>() { input, input2 }.AsEnumerable(), new object[] { });
|
||||
var result = re.ExecuteRule("inputWorkflowReference", new List<dynamic>() { input1, input2, input3 }.AsEnumerable(), new object[] { });
|
||||
Assert.NotNull(result);
|
||||
Assert.IsType<List<RuleResultTree>>(result);
|
||||
}
|
||||
|
@ -47,12 +48,12 @@ namespace RulesEngine.UnitTest
|
|||
public void ExecuteRule_ReturnsListOfRuleResultTree(string ruleFileName)
|
||||
{
|
||||
var re = GetRulesEngine(ruleFileName);
|
||||
dynamic input = GetInput();
|
||||
|
||||
dynamic input2 = GetInput();
|
||||
input2.value1 = "val1";
|
||||
dynamic input1 = GetInput1();
|
||||
dynamic input2 = GetInput2();
|
||||
dynamic input3 = GetInput3();
|
||||
|
||||
var result = re.ExecuteRule("inputWorkflow", new List<dynamic>() { input, input2 }.AsEnumerable(), new object[] { });
|
||||
var result = re.ExecuteRule("inputWorkflow", new List<dynamic>() { input1, input2, input3 }.AsEnumerable(), new object[] { });
|
||||
Assert.NotNull(result);
|
||||
Assert.IsType<List<RuleResultTree>>(result);
|
||||
}
|
||||
|
@ -62,27 +63,43 @@ namespace RulesEngine.UnitTest
|
|||
public void ExecuteRule_SingleObject_ReturnsListOfRuleResultTree(string ruleFileName)
|
||||
{
|
||||
var re = GetRulesEngine(ruleFileName);
|
||||
dynamic input = GetInput();
|
||||
|
||||
dynamic input2 = GetInput();
|
||||
input2.value1 = "val1";
|
||||
dynamic input1 = GetInput1();
|
||||
dynamic input2 = GetInput2();
|
||||
dynamic input3 = GetInput3();
|
||||
|
||||
var result = re.ExecuteRule("inputWorkflow",input);
|
||||
var result = re.ExecuteRule("inputWorkflow",input1);
|
||||
Assert.NotNull(result);
|
||||
Assert.IsType<List<RuleResultTree>>(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("rules3.json")]
|
||||
public void ExecuteRule_ExceptionScenario_RulesInvalid(string ruleFileName)
|
||||
{
|
||||
var re = GetRulesEngine(ruleFileName);
|
||||
|
||||
dynamic input1 = GetInput1();
|
||||
dynamic input2 = GetInput2();
|
||||
dynamic input3 = GetInput3();
|
||||
|
||||
var result = re.ExecuteRule("inputWorkflow", new List<dynamic>() { input1, input2, input3 }.AsEnumerable(), new object[] { });
|
||||
Assert.NotNull(result);
|
||||
Assert.IsType<List<RuleResultTree>>(result);
|
||||
Assert.False(string.IsNullOrEmpty(result[0].ExceptionMessage) || string.IsNullOrWhiteSpace(result[0].ExceptionMessage));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("rules2.json")]
|
||||
public void ExecuteRule_ReturnsListOfRuleResultTree_ResultMessage(string ruleFileName)
|
||||
{
|
||||
var re = GetRulesEngine(ruleFileName);
|
||||
dynamic input = GetInput();
|
||||
|
||||
dynamic input2 = GetInput();
|
||||
input2.value1 = "val1";
|
||||
dynamic input1 = GetInput1();
|
||||
dynamic input2 = GetInput2();
|
||||
dynamic input3 = GetInput3();
|
||||
|
||||
List<RuleResultTree> result = re.ExecuteRule("inputWorkflow", input);
|
||||
var result = re.ExecuteRule("inputWorkflow", new List<dynamic>() { input1, input2, input3 }.AsEnumerable(), new object[] { });
|
||||
Assert.NotNull(result);
|
||||
Assert.IsType<List<RuleResultTree>>(result);
|
||||
Assert.NotNull(result.First().GetMessages());
|
||||
|
@ -116,7 +133,7 @@ namespace RulesEngine.UnitTest
|
|||
public void ExecuteRule_InvalidWorkFlow_ThrowsException(string ruleFileName)
|
||||
{
|
||||
var re = GetRulesEngine(ruleFileName);
|
||||
dynamic input = GetInput();
|
||||
dynamic input = GetInput1();
|
||||
|
||||
Assert.Throws<ArgumentException>(() => { re.ExecuteRule("inputWorkflow1", new List<dynamic>() { input }.AsEnumerable(), new object[] { }); });
|
||||
}
|
||||
|
@ -128,11 +145,11 @@ namespace RulesEngine.UnitTest
|
|||
{
|
||||
var re = GetRulesEngine(ruleFileName);
|
||||
|
||||
dynamic input = GetInput();
|
||||
dynamic input2 = GetInput();
|
||||
input2.valueX = "hello";
|
||||
dynamic input1 = GetInput1();
|
||||
dynamic input2 = GetInput2();
|
||||
dynamic input3 = GetInput3();
|
||||
|
||||
var result = re.ExecuteRule("inputWorkflow", new List<dynamic>() { input, input2 }.AsEnumerable(), new object[] { });
|
||||
var result = re.ExecuteRule("inputWorkflow", new List<dynamic>() { input1, input2, input3 }.AsEnumerable(), new object[] { });
|
||||
Assert.NotNull(result);
|
||||
Assert.IsType<List<RuleResultTree>>(result);
|
||||
}
|
||||
|
@ -159,14 +176,27 @@ namespace RulesEngine.UnitTest
|
|||
return new RulesEngine(new string[] { data, injectWorkflowStr}, mockLogger.Object);
|
||||
}
|
||||
|
||||
private dynamic GetInput()
|
||||
|
||||
private dynamic GetInput1()
|
||||
{
|
||||
dynamic input = new ExpandoObject();
|
||||
input.value1 = "value1";
|
||||
input.value2 = "value2";
|
||||
input.value3 = 1;
|
||||
input.value4 = new { subValue = "subValue", nullValue = default(string) };
|
||||
return input;
|
||||
var converter = new ExpandoObjectConverter();
|
||||
var basicInfo = "{\"name\": \"Dishant\",\"email\": \"dishantmunjal@live.com\",\"creditHistory\": \"good\",\"country\": \"canada\",\"loyalityFactor\": 3,\"totalPurchasesToDate\": 10000}";
|
||||
return JsonConvert.DeserializeObject<ExpandoObject>(basicInfo, converter);
|
||||
}
|
||||
|
||||
private dynamic GetInput2()
|
||||
{
|
||||
var converter = new ExpandoObjectConverter();
|
||||
var orderInfo = "{\"totalOrders\": 5,\"recurringItems\": 2}";
|
||||
return JsonConvert.DeserializeObject<ExpandoObject>(orderInfo, converter);
|
||||
}
|
||||
|
||||
private dynamic GetInput3()
|
||||
{
|
||||
var converter = new ExpandoObjectConverter();
|
||||
var telemetryInfo = "{\"noOfVisitsPerMonth\": 10,\"percentageOfBuyingToVisit\": 15}";
|
||||
return JsonConvert.DeserializeObject<ExpandoObject>(telemetryInfo, converter);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -23,6 +23,9 @@
|
|||
<None Update="TestData\rules1.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TestData\rules3.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TestData\rules2.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
|
|
|
@ -2,19 +2,12 @@
|
|||
"WorkflowName": "inputWorkflow",
|
||||
"Rules": [
|
||||
{
|
||||
"RuleName": "Rule1",
|
||||
"Operator": "AndAlso",
|
||||
"RuleName": "GiveDiscount10",
|
||||
"SuccessEvent": "10",
|
||||
"ErrorMessage": "One or more adjust rules failed.",
|
||||
"ErrorType": "Error",
|
||||
"Rules": [
|
||||
{
|
||||
"RuleName": "SubRule1",
|
||||
"Expression": "input1.Request_RequestType == \"vod\" AND input1.Labor_BillingCode == \"billable\" AND ((input1.Request_RegistrationStatus == \"cancelled with t&e\" AND input1.Request_Status == \"cancelled\") OR (input1.Request_Status != \"cancelled\"))",
|
||||
"ErrorMessage": "SubError message 1",
|
||||
"ErrorType": "Error",
|
||||
"RuleExpressionType": "LambdaExpression"
|
||||
}
|
||||
]
|
||||
"RuleExpressionType": "LambdaExpression",
|
||||
"Expression": "input1.country == \"india\" AND input1.loyalityFactor <= 2 AND input1.totalPurchasesToDate >= 5000 AND input2.totalOrders > 2 AND input3.noOfVisitsPerMonth > 2"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -8,25 +8,28 @@
|
|||
"ErrorType": "Error",
|
||||
"Rules": [
|
||||
{
|
||||
"RuleName": "SubRule1",
|
||||
"Expression": "input1.Request_RequestType == \"vod\" AND input1.Labor_BillingCode == \"billable\" AND ((input1.Request_RegistrationStatus == \"cancelled with t&e\" AND input1.Request_Status == \"cancelled\") OR (input1.Request_Status != \"cancelled\"))",
|
||||
"ErrorMessage": "SubError message 1",
|
||||
"RuleName": "GiveDiscount10",
|
||||
"SuccessEvent": "10",
|
||||
"ErrorMessage": "One or more adjust rules failed.",
|
||||
"ErrorType": "Error",
|
||||
"RuleExpressionType": "LambdaExpression"
|
||||
"RuleExpressionType": "LambdaExpression",
|
||||
"Expression": "input1.country == \"india\" AND input1.loyalityFactor <= 2 AND input1.totalPurchasesToDate >= 5000 AND input2.totalOrders > 2 AND input3.noOfVisitsPerMonth > 2"
|
||||
},
|
||||
{
|
||||
"RuleName": "SubRule2",
|
||||
"Expression": "1 == 1",
|
||||
"ErrorMessage": "SubError message 2",
|
||||
"RuleName": "GiveDiscount20",
|
||||
"SuccessEvent": "20",
|
||||
"ErrorMessage": "One or more adjust rules failed.",
|
||||
"ErrorType": "Error",
|
||||
"RuleExpressionType": "LambdaExpression"
|
||||
"RuleExpressionType": "LambdaExpression",
|
||||
"Expression": "input1.country == \"india\" AND input1.loyalityFactor == 3 AND input1.totalPurchasesToDate >= 10000 AND input2.totalOrders > 2 AND input3.noOfVisitsPerMonth > 2"
|
||||
},
|
||||
{
|
||||
"RuleName": "SubRule3",
|
||||
"Expression": "input1.Request_RequestType == \"vod\" AND input1.Labor_BillingCode == \"billable\" AND ((input1.Request_RegistrationStatus == \"cancelled with t&e\" AND input1.Request_Status == \"cancelled\") OR (input1.Request_Status != \"cancelled\"))",
|
||||
"ErrorMessage": "SubError message 3",
|
||||
"RuleName": "GiveDiscount25",
|
||||
"SuccessEvent": "25",
|
||||
"ErrorMessage": "One or more adjust rules failed.",
|
||||
"ErrorType": "Error",
|
||||
"RuleExpressionType": "LambdaExpression"
|
||||
"RuleExpressionType": "LambdaExpression",
|
||||
"Expression": "input1.country != \"india\" AND input1.loyalityFactor >= 2 AND input1.totalPurchasesToDate >= 10000 AND input2.totalOrders > 2 AND input3.noOfVisitsPerMonth > 5"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"WorkflowName": "inputWorkflow",
|
||||
"Rules": [
|
||||
{
|
||||
"RuleName": "GiveDiscount10",
|
||||
"SuccessEvent": "10",
|
||||
"ErrorMessage": "One or more adjust rules failed.",
|
||||
"ErrorType": "Error",
|
||||
"RuleExpressionType": "LambdaExpression",
|
||||
"Expression": "input1.couy == \"india\" AND input1.loyalityFactor <= 2 AND input1.totalPurchasesToDate >= 5000 AND input2.totalOrders > 2 AND input3.noOfVisitsPerMonth > 2"
|
||||
}
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue