diff --git a/.github/workflows/dotnetcore-build.yml b/.github/workflows/dotnetcore-build.yml index c4b0524..52de750 100644 --- a/.github/workflows/dotnetcore-build.yml +++ b/.github/workflows/dotnetcore-build.yml @@ -16,21 +16,21 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v1 with: - dotnet-version: 3.1 + dotnet-version: 3.1.101 - name: Install minicover run: dotnet tool install --global minicover --version 3.0.6 - name: Install dependencies - run: dotnet restore RulesEngine.sln + run: dotnet restore src/RulesEngine/RulesEngine.sln - name: Build - run: dotnet build RulesEngine.sln --configuration Release --no-restore + run: dotnet build src/RulesEngine/RulesEngine.sln --configuration Release --no-restore - name: Instrument run: minicover instrument - name: Test - run: dotnet test RulesEngine.sln --no-build --configuration Release --verbosity m + run: dotnet test src/RulesEngine/RulesEngine.sln --no-build --configuration Release --verbosity m - name: Uninstrument run: minicover uninstrument - name: Report - run: minicover report --threshold 95 + run: minicover report --threshold 80 if: ${{ github.event_name == 'pull_request' }} - name: Report coveralls run: minicover coverallsreport --repo-token ${{ secrets.COVERALLS_TOKEN }} --branch master diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 6f691ce..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - // Use IntelliSense to find out which attributes exist for C# debugging - // Use hover for the description of the existing attributes - // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md - "version": "0.2.0", - "configurations": [ - { - "name": ".NET Core Launch (console)", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceFolder}/demo/DemoApp/bin/Debug/netcoreapp3.1/DemoApp.dll", - "args": [], - "cwd": "${workspaceFolder}/demo/DemoApp", - // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console - "console": "internalConsole", - "stopAtEntry": false - }, - { - "name": ".NET Core Attach", - "type": "coreclr", - "request": "attach", - "processId": "${command:pickProcess}" - } - ] -} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 7870888..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "dotnetCoreExplorer.searchpatterns": "test/**/bin/Debug/netcoreapp*/*.{dll,exe,json}" -} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index 94770e3..0000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "label": "build", - "command": "dotnet", - "type": "process", - "args": [ - "build", - "${workspaceFolder}/demo/DemoApp/DemoApp.csproj", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary" - ], - "problemMatcher": "$msCompile" - }, - { - "label": "publish", - "command": "dotnet", - "type": "process", - "args": [ - "publish", - "${workspaceFolder}/demo/DemoApp/DemoApp.csproj", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary" - ], - "problemMatcher": "$msCompile" - }, - { - "label": "watch", - "command": "dotnet", - "type": "process", - "args": [ - "watch", - "run", - "${workspaceFolder}/demo/DemoApp/DemoApp.csproj", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary" - ], - "problemMatcher": "$msCompile" - } - ] -} \ No newline at end of file diff --git a/demo/DemoApp/DemoApp.csproj b/demo/DemoApp/DemoApp.csproj index fc7049e..37c2e70 100644 --- a/demo/DemoApp/DemoApp.csproj +++ b/demo/DemoApp/DemoApp.csproj @@ -8,7 +8,7 @@ - + diff --git a/global.json b/global.json index 1009c7f..0d92626 100644 --- a/global.json +++ b/global.json @@ -1,7 +1,6 @@ { "sdk": { "version": "3.1.101", - "rollForward": "latestFeature", - "allowPrerelease": false + "rollForward": "latestFeature" } } \ No newline at end of file diff --git a/src/RulesEngine/HelperFunctions/Helpers.cs b/src/RulesEngine/HelperFunctions/Helpers.cs deleted file mode 100644 index ecc30f7..0000000 --- a/src/RulesEngine/HelperFunctions/Helpers.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using RulesEngine.Models; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; - -namespace RulesEngine.HelperFunctions -{ - /// - /// Helpers - /// - internal static class Helpers - { - internal static RuleFunc ToResultTree(Rule rule, IEnumerable childRuleResults, RuleFunc isSuccessFunc, string exceptionMessage = "") - { - return (inputs) => new RuleResultTree - { - Rule = rule, - Input = inputs.FirstOrDefault(), - IsSuccess = isSuccessFunc(inputs), - ChildResults = childRuleResults, - ExceptionMessage = exceptionMessage - }; - } - - /// - /// To the result tree error messages - /// - /// ruleResultTree - /// ruleResultMessage - internal static void ToResultTreeMessages(RuleResultTree ruleResultTree, ref RuleResultMessage ruleResultMessage) - { - if (ruleResultTree.ChildResults != null) - { - GetChildRuleMessages(ruleResultTree.ChildResults, ref ruleResultMessage); - } - else - { - if (!ruleResultTree.IsSuccess) - { - string errMsg = ruleResultTree.Rule.ErrorMessage; - errMsg = string.IsNullOrEmpty(errMsg) ? $"Error message does not configured for {ruleResultTree.Rule.RuleName}" : errMsg; - - if (ruleResultTree.Rule.ErrorType == ErrorType.Error && !ruleResultMessage.ErrorMessages.Contains(errMsg)) - { - ruleResultMessage.ErrorMessages.Add(errMsg); - } - else if (ruleResultTree.Rule.ErrorType == ErrorType.Warning && !ruleResultMessage.WarningMessages.Contains(errMsg)) - { - ruleResultMessage.WarningMessages.Add(errMsg); - } - } - } - } - - /// - /// To get the child error message recersivly - /// - /// childResultTree - /// ruleResultMessage - private static void GetChildRuleMessages(IEnumerable childResultTree, ref RuleResultMessage ruleResultMessage) - { - foreach (var item in childResultTree) - { - ToResultTreeMessages(item, ref ruleResultMessage); - } - } - } -} diff --git a/src/RulesEngine/Models/CompiledRuleParam.cs b/src/RulesEngine/Models/CompiledRuleParam.cs deleted file mode 100644 index 4e470a0..0000000 --- a/src/RulesEngine/Models/CompiledRuleParam.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Text; - -namespace RulesEngine.Models -{ - [ExcludeFromCodeCoverage] - internal class CompiledRuleParam - { - internal string Name { get; set; } - internal IEnumerable CompiledParameters { get; set; } - internal IEnumerable RuleParameters { get; set; } - } -} diff --git a/src/RulesEngine/Models/RuleDelegate.cs b/src/RulesEngine/Models/RuleDelegate.cs deleted file mode 100644 index df6ebeb..0000000 --- a/src/RulesEngine/Models/RuleDelegate.cs +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace RulesEngine.Models -{ - public delegate T RuleFunc(params object[] param); - -} diff --git a/RulesEngine.sln b/src/RulesEngine/RulesEngine.sln similarity index 89% rename from RulesEngine.sln rename to src/RulesEngine/RulesEngine.sln index f984879..bb5e076 100644 --- a/RulesEngine.sln +++ b/src/RulesEngine/RulesEngine.sln @@ -3,11 +3,11 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.29123.89 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RulesEngine", "src\RulesEngine\\RulesEngine.csproj", "{CD4DFE6A-083B-478E-8377-77F474833E30}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RulesEngine", "RulesEngine\RulesEngine.csproj", "{CD4DFE6A-083B-478E-8377-77F474833E30}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RulesEngine.UnitTest", "test\RulesEngine.UnitTest\RulesEngine.UnitTest.csproj", "{50E0C2A5-E2C8-4B12-8C0E-B69F698A82BF}" +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}" +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 diff --git a/src/RulesEngine/CustomTypeProvider.cs b/src/RulesEngine/RulesEngine/CustomTypeProvider.cs similarity index 100% rename from src/RulesEngine/CustomTypeProvider.cs rename to src/RulesEngine/RulesEngine/CustomTypeProvider.cs diff --git a/src/RulesEngine/Exceptions/RuleValidationException.cs b/src/RulesEngine/RulesEngine/Exceptions/RuleValidationException.cs similarity index 100% rename from src/RulesEngine/Exceptions/RuleValidationException.cs rename to src/RulesEngine/RulesEngine/Exceptions/RuleValidationException.cs diff --git a/src/RulesEngine/ExpressionBuilders/LambdaExpressionBuilder.cs b/src/RulesEngine/RulesEngine/ExpressionBuilders/LambdaExpressionBuilder.cs similarity index 71% rename from src/RulesEngine/ExpressionBuilders/LambdaExpressionBuilder.cs rename to src/RulesEngine/RulesEngine/ExpressionBuilders/LambdaExpressionBuilder.cs index 64c1bb7..3a93b78 100644 --- a/src/RulesEngine/ExpressionBuilders/LambdaExpressionBuilder.cs +++ b/src/RulesEngine/RulesEngine/ExpressionBuilders/LambdaExpressionBuilder.cs @@ -24,22 +24,21 @@ namespace RulesEngine.ExpressionBuilders { _reSettings = reSettings; } - internal override RuleFunc BuildExpressionForRule(Rule rule, IEnumerable typeParamExpressions) + internal override Expression> BuildExpressionForRule(Rule rule, IEnumerable typeParamExpressions, ParameterExpression ruleInputExp) { try { var config = new ParsingConfig { CustomTypeProvider = new CustomTypeProvider(_reSettings.CustomTypes) }; - var e = DynamicExpressionParser.ParseLambda(config, true, typeParamExpressions.ToArray(),typeof(bool), rule.Expression); - var ruleDelegate = e.Compile(); - bool func(object[] paramList) => (bool)ruleDelegate.DynamicInvoke(paramList); - return Helpers.ToResultTree(rule, null, func); + 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) + catch (Exception ex) { - bool func(object[] param) => false; + var binaryExpression = Expression.And(Expression.Constant(true), Expression.Constant(false)); var exceptionMessage = ex.Message; - return Helpers.ToResultTree(rule, null, func, exceptionMessage); - } + return Helpers.ToResultTreeExpression(rule, null, binaryExpression, typeParamExpressions, ruleInputExp, exceptionMessage); + } } /// Builds the expression for rule parameter. diff --git a/src/RulesEngine/ExpressionBuilders/RuleExpressionBuilderBase.cs b/src/RulesEngine/RulesEngine/ExpressionBuilders/RuleExpressionBuilderBase.cs similarity index 86% rename from src/RulesEngine/ExpressionBuilders/RuleExpressionBuilderBase.cs rename to src/RulesEngine/RulesEngine/ExpressionBuilders/RuleExpressionBuilderBase.cs index ec1b05b..88a6da7 100644 --- a/src/RulesEngine/ExpressionBuilders/RuleExpressionBuilderBase.cs +++ b/src/RulesEngine/RulesEngine/ExpressionBuilders/RuleExpressionBuilderBase.cs @@ -20,7 +20,7 @@ namespace RulesEngine.ExpressionBuilders /// The type parameter expressions. /// The rule input exp. /// Expression type - internal abstract RuleFunc BuildExpressionForRule(Rule rule, IEnumerable typeParameterExpressions); + internal abstract Expression> BuildExpressionForRule(Rule rule, IEnumerable typeParamExpressions, ParameterExpression ruleInputExp); /// Builds the expression for rule parameter. /// The rule. diff --git a/src/RulesEngine/Extensions/ListofRuleResultTreeExtension.cs b/src/RulesEngine/RulesEngine/Extensions/ListofRuleResultTreeExtension.cs similarity index 100% rename from src/RulesEngine/Extensions/ListofRuleResultTreeExtension.cs rename to src/RulesEngine/RulesEngine/Extensions/ListofRuleResultTreeExtension.cs diff --git a/src/RulesEngine/HelperFunctions/Constants.cs b/src/RulesEngine/RulesEngine/HelperFunctions/Constants.cs similarity index 100% rename from src/RulesEngine/HelperFunctions/Constants.cs rename to src/RulesEngine/RulesEngine/HelperFunctions/Constants.cs diff --git a/src/RulesEngine/HelperFunctions/ExpressionUtils.cs b/src/RulesEngine/RulesEngine/HelperFunctions/ExpressionUtils.cs similarity index 100% rename from src/RulesEngine/HelperFunctions/ExpressionUtils.cs rename to src/RulesEngine/RulesEngine/HelperFunctions/ExpressionUtils.cs diff --git a/src/RulesEngine/RulesEngine/HelperFunctions/Helpers.cs b/src/RulesEngine/RulesEngine/HelperFunctions/Helpers.cs new file mode 100644 index 0000000..0446b44 --- /dev/null +++ b/src/RulesEngine/RulesEngine/HelperFunctions/Helpers.cs @@ -0,0 +1,122 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using RulesEngine.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; + +namespace RulesEngine.HelperFunctions +{ + /// + /// Helpers + /// + internal static class Helpers + { + /// + /// To the result tree expression. + /// + /// The rule. + /// The child rule results. + /// The is success exp. + /// The type parameter expressions. + /// The rule input exp. + /// Expression of func + internal static Expression> ToResultTreeExpression(Rule rule, IEnumerable childRuleResults, BinaryExpression isSuccessExp, IEnumerable typeParamExpressions, ParameterExpression ruleInputExp, string exceptionMessage = "") + { + var memberInit = ToResultTree(rule, childRuleResults, isSuccessExp, typeParamExpressions, null, exceptionMessage); + var lambda = Expression.Lambda>(memberInit, new[] { ruleInputExp }); + return lambda; + } + + /// + /// To the result tree member expression + /// + /// The rule. + /// The child rule results. + /// The is success exp. + /// The child rule results block expression. + /// + internal static MemberInitExpression ToResultTree(Rule rule, IEnumerable childRuleResults, BinaryExpression isSuccessExp, IEnumerable typeParamExpressions, BlockExpression childRuleResultsblockexpr, string exceptionMessage = "") + { + var createdType = typeof(RuleResultTree); + var ctor = Expression.New(createdType); + + var ruleProp = createdType.GetProperty(nameof(RuleResultTree.Rule)); + 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; + + if (childRuleResults != null) + { + var ruleResultTreeArr = Expression.NewArrayInit(typeof(RuleResultTree), childRuleResults); + + var childResultPropBinding = Expression.Bind(childResultProp, ruleResultTreeArr); + 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, exceptionBinding }); + } + else + { + memberInit = Expression.MemberInit(ctor, new[] { rulePropBinding, isSuccessPropBinding, inputBinding, exceptionBinding }); + } + + return memberInit; + } + + /// + /// To the result tree error messages + /// + /// ruleResultTree + /// ruleResultMessage + internal static void ToResultTreeMessages(RuleResultTree ruleResultTree, ref RuleResultMessage ruleResultMessage) + { + if (ruleResultTree.ChildResults != null) + { + GetChildRuleMessages(ruleResultTree.ChildResults, ref ruleResultMessage); + } + else + { + if (ruleResultTree.IsSuccess) + { + string errMsg = ruleResultTree.Rule.ErrorMessage; + errMsg = string.IsNullOrEmpty(errMsg) ? $"Error message does not configured for {ruleResultTree.Rule.RuleName}" : errMsg; + + if (ruleResultTree.Rule.ErrorType == ErrorType.Error && !ruleResultMessage.ErrorMessages.Contains(errMsg)) + { + ruleResultMessage.ErrorMessages.Add(errMsg); + } + else if (ruleResultTree.Rule.ErrorType == ErrorType.Warning && !ruleResultMessage.WarningMessages.Contains(errMsg)) + { + ruleResultMessage.WarningMessages.Add(errMsg); + } + } + } + } + + /// + /// To get the child error message recersivly + /// + /// childResultTree + /// ruleResultMessage + private static void GetChildRuleMessages(IEnumerable childResultTree, ref RuleResultMessage ruleResultMessage) + { + foreach (var item in childResultTree) + { + ToResultTreeMessages(item, ref ruleResultMessage); + } + } + } +} diff --git a/src/RulesEngine/HelperFunctions/Utils.cs b/src/RulesEngine/RulesEngine/HelperFunctions/Utils.cs similarity index 100% rename from src/RulesEngine/HelperFunctions/Utils.cs rename to src/RulesEngine/RulesEngine/HelperFunctions/Utils.cs diff --git a/src/RulesEngine/Interfaces/IRulesEngine.cs b/src/RulesEngine/RulesEngine/Interfaces/IRulesEngine.cs similarity index 100% rename from src/RulesEngine/Interfaces/IRulesEngine.cs rename to src/RulesEngine/RulesEngine/Interfaces/IRulesEngine.cs diff --git a/src/RulesEngine/Models/CompiledParam.cs b/src/RulesEngine/RulesEngine/Models/CompiledParam.cs similarity index 92% rename from src/RulesEngine/Models/CompiledParam.cs rename to src/RulesEngine/RulesEngine/Models/CompiledParam.cs index dc55578..c05b968 100644 --- a/src/RulesEngine/Models/CompiledParam.cs +++ b/src/RulesEngine/RulesEngine/Models/CompiledParam.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Text; namespace RulesEngine.Models @@ -8,7 +7,6 @@ namespace RulesEngine.Models /// /// CompiledParam class. /// - [ExcludeFromCodeCoverage] internal class CompiledParam { /// diff --git a/src/RulesEngine/RulesEngine/Models/CompiledRule.cs b/src/RulesEngine/RulesEngine/Models/CompiledRule.cs new file mode 100644 index 0000000..f1d7959 --- /dev/null +++ b/src/RulesEngine/RulesEngine/Models/CompiledRule.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace RulesEngine.Models +{ + [ExcludeFromCodeCoverage] + internal class CompiledRule + { + /// + /// Gets or sets the compiled rules. + /// + /// + /// The compiled rules. + /// + internal Delegate Rule { get; set; } + + + /// + /// Gets or sets the rule parameters. + /// + /// + /// The rule parameters. + /// + internal CompiledRuleParam CompiledParameters { get; set; } + } + +} diff --git a/src/RulesEngine/RulesEngine/Models/CompiledRuleParam.cs b/src/RulesEngine/RulesEngine/Models/CompiledRuleParam.cs new file mode 100644 index 0000000..cecd2d5 --- /dev/null +++ b/src/RulesEngine/RulesEngine/Models/CompiledRuleParam.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace RulesEngine.Models +{ + /// Class CompiledRule. + internal class CompiledRuleParam + { + /// + /// Gets or sets the compiled rules. + /// + /// + /// The compiled rules. + /// + internal string Name { get; set; } + + /// Gets or sets the rule parameters. + /// The rule parameters. + internal IEnumerable CompiledParameters { get; set; } + + /// + /// Gets or sets the rule parameters. + /// + /// + /// The rule parameters. + /// + internal IEnumerable RuleParameters { get; set; } + } +} diff --git a/src/RulesEngine/Models/LocalParam.cs b/src/RulesEngine/RulesEngine/Models/LocalParam.cs similarity index 81% rename from src/RulesEngine/Models/LocalParam.cs rename to src/RulesEngine/RulesEngine/Models/LocalParam.cs index 302472b..754e0be 100644 --- a/src/RulesEngine/Models/LocalParam.cs +++ b/src/RulesEngine/RulesEngine/Models/LocalParam.cs @@ -1,11 +1,9 @@ using Newtonsoft.Json; -using System.Diagnostics.CodeAnalysis; namespace RulesEngine.Models { - /// Class LocalParam. - /// - [ExcludeFromCodeCoverage] + /// Class Param. + /// Implements the public class LocalParam { diff --git a/src/RulesEngine/Models/ReSettings.cs b/src/RulesEngine/RulesEngine/Models/ReSettings.cs similarity index 100% rename from src/RulesEngine/Models/ReSettings.cs rename to src/RulesEngine/RulesEngine/Models/ReSettings.cs diff --git a/src/RulesEngine/Models/Rule.cs b/src/RulesEngine/RulesEngine/Models/Rule.cs similarity index 99% rename from src/RulesEngine/Models/Rule.cs rename to src/RulesEngine/RulesEngine/Models/Rule.cs index 10423ec..f0178fa 100644 --- a/src/RulesEngine/Models/Rule.cs +++ b/src/RulesEngine/RulesEngine/Models/Rule.cs @@ -85,6 +85,7 @@ namespace RulesEngine.Models /// public string Expression { get; set; } + /// /// Gets or sets the success event. /// @@ -94,4 +95,5 @@ namespace RulesEngine.Models public string SuccessEvent { get; set; } } + } diff --git a/src/RulesEngine/Models/RuleErrorType.cs b/src/RulesEngine/RulesEngine/Models/RuleErrorType.cs similarity index 100% rename from src/RulesEngine/Models/RuleErrorType.cs rename to src/RulesEngine/RulesEngine/Models/RuleErrorType.cs diff --git a/src/RulesEngine/Models/RuleExpressionType.cs b/src/RulesEngine/RulesEngine/Models/RuleExpressionType.cs similarity index 100% rename from src/RulesEngine/Models/RuleExpressionType.cs rename to src/RulesEngine/RulesEngine/Models/RuleExpressionType.cs diff --git a/src/RulesEngine/Models/RuleInput.cs b/src/RulesEngine/RulesEngine/Models/RuleInput.cs similarity index 100% rename from src/RulesEngine/Models/RuleInput.cs rename to src/RulesEngine/RulesEngine/Models/RuleInput.cs diff --git a/src/RulesEngine/Models/RuleParameter.cs b/src/RulesEngine/RulesEngine/Models/RuleParameter.cs similarity index 100% rename from src/RulesEngine/Models/RuleParameter.cs rename to src/RulesEngine/RulesEngine/Models/RuleParameter.cs diff --git a/src/RulesEngine/Models/RuleResultTree.cs b/src/RulesEngine/RulesEngine/Models/RuleResultTree.cs similarity index 100% rename from src/RulesEngine/Models/RuleResultTree.cs rename to src/RulesEngine/RulesEngine/Models/RuleResultTree.cs diff --git a/src/RulesEngine/Models/WorkflowRules.cs b/src/RulesEngine/RulesEngine/Models/WorkflowRules.cs similarity index 100% rename from src/RulesEngine/Models/WorkflowRules.cs rename to src/RulesEngine/RulesEngine/Models/WorkflowRules.cs diff --git a/src/RulesEngine/RulesEngine/ParamCache.cs b/src/RulesEngine/RulesEngine/ParamCache.cs new file mode 100644 index 0000000..84c7e97 --- /dev/null +++ b/src/RulesEngine/RulesEngine/ParamCache.cs @@ -0,0 +1,86 @@ +using RulesEngine.Models; +using System; +using System.Collections.Concurrent; +using System.Linq; + +namespace RulesEngine +{ + /// Maintains the cache of evaludated param. + internal class ParamCache where T : class + { + /// + /// The compile rules + /// + private readonly ConcurrentDictionary _evaluatedParams = new ConcurrentDictionary(); + + /// + /// + /// Determines whether the specified parameter key name contains parameters. + /// + /// + /// Name of the parameter key. + /// + /// true if the specified parameter key name contains parameters; otherwise, false. + public bool ContainsParams(string paramKeyName) + { + return _evaluatedParams.ContainsKey(paramKeyName); + } + + /// Adds the or update evaluated parameter. + /// Name of the parameter key. + /// The rule parameters. + public void AddOrUpdateParams(string paramKeyName, T ruleParameters) + { + _evaluatedParams.AddOrUpdate(paramKeyName, ruleParameters, (k, v) => v); + } + + /// Clears this instance. + public void Clear() + { + _evaluatedParams.Clear(); + } + + /// Gets the evaluated parameters. + /// Name of the parameter key. + /// Delegate[]. + public T GetParams(string paramKeyName) + { + return _evaluatedParams[paramKeyName]; + } + + /// Gets the evaluated parameters cache key. + /// Name of the workflow. + /// The rule. + /// Cache key. + public string GetCompiledParamsCacheKey(string workflowName, Rule rule) + { + if (rule == null) + { + return string.Empty; + } + else + { + if (rule?.LocalParams == null) + { + return $"Compiled_{workflowName}_{rule.RuleName}"; + } + + return $"Compiled_{workflowName}_{rule.RuleName}_{string.Join("_", rule?.LocalParams.Select(r => r?.Name))}"; + } + } + + /// Removes the specified workflow name. + /// Name of the workflow. + public void RemoveCompiledParams(string paramKeyName) + { + if (_evaluatedParams.TryRemove(paramKeyName, out T ruleParameters)) + { + var compiledKeysToRemove = _evaluatedParams.Keys.Where(key => key.StartsWith(paramKeyName)); + foreach (var key in compiledKeysToRemove) + { + _evaluatedParams.TryRemove(key, out T val); + } + } + } + } +} diff --git a/src/RulesEngine/ParamCompiler.cs b/src/RulesEngine/RulesEngine/ParamCompiler.cs similarity index 95% rename from src/RulesEngine/ParamCompiler.cs rename to src/RulesEngine/RulesEngine/ParamCompiler.cs index 09f1462..03e7689 100644 --- a/src/RulesEngine/ParamCompiler.cs +++ b/src/RulesEngine/RulesEngine/ParamCompiler.cs @@ -70,8 +70,8 @@ namespace RulesEngine var expression = Expression.Lambda(ruleParamExpression, lambdaParameterExps); var compiledParam = expression.Compile(); compiledParameters.Add(new CompiledParam { Name = param.Name, Value = compiledParam, Parameters = evaluatedParameters }); - var evaluatedParam = this.EvaluateCompiledParam(param.Name, compiledParam, ruleParams.Select(c => c.Value).ToArray()); - ruleParams = ruleParams.Append(evaluatedParam); + var evaluatedParam = this.EvaluateCompiledParam(param.Name, compiledParam, ruleParams); + ruleParams = ruleParams.Concat(new List { evaluatedParam }); evaluatedParameters.Add(evaluatedParam); } @@ -86,8 +86,9 @@ namespace RulesEngine /// The compiled parameter. /// The rule parameters. /// RuleParameter. - public RuleParameter EvaluateCompiledParam(string paramName, Delegate compiledParam, IEnumerable inputs) + public RuleParameter EvaluateCompiledParam(string paramName, Delegate compiledParam, IEnumerable ruleParams) { + var inputs = ruleParams.Select(c => c.Value); var result = compiledParam.DynamicInvoke(new List(inputs) { new RuleInput() }.ToArray()); return new RuleParameter(paramName, result); } diff --git a/src/RulesEngine/Properties/AssemblyInfo.cs b/src/RulesEngine/RulesEngine/Properties/AssemblyInfo.cs similarity index 100% rename from src/RulesEngine/Properties/AssemblyInfo.cs rename to src/RulesEngine/RulesEngine/Properties/AssemblyInfo.cs diff --git a/src/RulesEngine/RuleCompiler.cs b/src/RulesEngine/RulesEngine/RuleCompiler.cs similarity index 50% rename from src/RulesEngine/RuleCompiler.cs rename to src/RulesEngine/RulesEngine/RuleCompiler.cs index bb8c162..5912647 100644 --- a/src/RulesEngine/RuleCompiler.cs +++ b/src/RulesEngine/RulesEngine/RuleCompiler.cs @@ -60,14 +60,23 @@ namespace RulesEngine /// /// /// Compiled func delegate - public RuleFunc CompileRule(Rule rule,params RuleParameter[] ruleParams) + public Delegate CompileRule(Rule rule,params RuleParameter[] ruleParams) { try { - IEnumerable typeParameterExpressions = GetParameterExpression(ruleParams).ToList(); - RuleFunc ruleExpression = GetExpressionForRule(rule, typeParameterExpressions,ruleParams); + IEnumerable typeParameterExpressions = GetParameterExpression(ruleParams).ToList(); // calling ToList to avoid multiple calls this the method for nested rule scenario. - return ruleExpression; + ParameterExpression ruleInputExp = Expression.Parameter(typeof(RuleInput), nameof(RuleInput)); + + Expression> ruleExpression = GetExpressionForRule(rule, typeParameterExpressions, ruleInputExp); + + var lambdaParameterExps = new List(typeParameterExpressions) { ruleInputExp }; + + + var expression = Expression.Lambda(ruleExpression.Body, lambdaParameterExps); + + + return expression.Compile(); } catch (Exception ex) { @@ -111,18 +120,18 @@ namespace RulesEngine /// The type parameter expressions. /// The rule input exp. /// - private RuleFunc GetExpressionForRule(Rule rule, IEnumerable typeParameterExpressions, RuleParameter[] ruleParams) + private Expression> GetExpressionForRule(Rule rule, IEnumerable typeParameterExpressions, ParameterExpression ruleInputExp) { ExpressionType nestedOperator; if (Enum.TryParse(rule.Operator, out nestedOperator) && nestedOperators.Contains(nestedOperator) && rule.Rules != null && rule.Rules.Any()) { - return BuildNestedExpression(rule, nestedOperator, typeParameterExpressions, ruleParams); + return BuildNestedExpression(rule, nestedOperator, typeParameterExpressions, ruleInputExp); } else { - return BuildExpression(rule, typeParameterExpressions,ruleParams); + return BuildExpression(rule, typeParameterExpressions, ruleInputExp); } } @@ -134,7 +143,7 @@ namespace RulesEngine /// The rule input exp. /// /// - private RuleFunc BuildExpression(Rule rule, IEnumerable typeParameterExpressions, RuleParameter[] ruleParams) + private Expression> BuildExpression(Rule rule, IEnumerable typeParameterExpressions, ParameterExpression ruleInputExp) { if (!rule.RuleExpressionType.HasValue) { @@ -143,7 +152,7 @@ namespace RulesEngine var ruleExpressionBuilder = _expressionBuilderFactory.RuleGetExpressionBuilder(rule.RuleExpressionType.Value); - var expression = ruleExpressionBuilder.BuildExpressionForRule(rule, typeParameterExpressions); + var expression = ruleExpressionBuilder.BuildExpressionForRule(rule, typeParameterExpressions, ruleInputExp); return expression; } @@ -158,38 +167,104 @@ namespace RulesEngine /// The rule input exp. /// Expression of func delegate /// - private RuleFunc BuildNestedExpression(Rule parentRule, ExpressionType operation, IEnumerable typeParameterExpressions, RuleParameter[] ruleParams) + private Expression> BuildNestedExpression(Rule parentRule, ExpressionType operation, IEnumerable typeParameterExpressions, ParameterExpression ruleInputExp) { - var expressions = new List>(); + List>> expressions = new List>>(); foreach (var r in parentRule.Rules) { - expressions.Add(GetExpressionForRule(r, typeParameterExpressions, ruleParams)); + expressions.Add(GetExpressionForRule(r, typeParameterExpressions, ruleInputExp)); } - return (paramArray) => - { - var resultList = expressions.Select(fn => fn(paramArray)); - RuleFunc isSuccess = (p) => ApplyOperation(resultList, operation); - RuleFunc result = Helpers.ToResultTree(parentRule, resultList,isSuccess); - return result(paramArray); - }; - } + List childRuleResultTree = new List(); - - private bool ApplyOperation(IEnumerable ruleResults, ExpressionType operation) - { - switch (operation) + foreach (var exp in expressions) { - case ExpressionType.And: - case ExpressionType.AndAlso: - return ruleResults.All(r => r.IsSuccess); + var resultMemberInitExpression = exp.Body as MemberInitExpression; - case ExpressionType.Or: - case ExpressionType.OrElse: - return ruleResults.Any(r => r.IsSuccess); - default: - return false; + if (resultMemberInitExpression == null)// assert is a MemberInitExpression + { + throw new InvalidCastException($"expression.Body '{exp.Body}' is not of MemberInitExpression type."); + } + + childRuleResultTree.Add(resultMemberInitExpression); } + + Expression> nestedExpression = Helpers.ToResultTreeExpression(parentRule, childRuleResultTree, BinaryExpression(expressions, operation), typeParameterExpressions, ruleInputExp); + + return nestedExpression; } + + /// + /// Binaries the expression. + /// + /// The expressions. + /// Type of the operation. + /// Binary Expression + private BinaryExpression BinaryExpression(IList>> expressions, ExpressionType operationType) + { + if (expressions.Count == 1) + { + return ResolveIsSuccessBinding(expressions.First()); + } + + BinaryExpression nestedBinaryExp = Expression.MakeBinary(operationType, ResolveIsSuccessBinding(expressions[0]), ResolveIsSuccessBinding(expressions[1])); + + for (int i = 2; expressions.Count > i; i++) + { + nestedBinaryExp = Expression.MakeBinary(operationType, nestedBinaryExp, ResolveIsSuccessBinding(expressions[i])); + } + + return nestedBinaryExp; + } + + /// + /// Resolves the is success binding. + /// + /// The expression. + /// Binary expression of IsSuccess prop + /// expression + /// + /// + /// IsSuccess + /// or + /// IsSuccess + /// or + /// IsSuccess + /// + private BinaryExpression ResolveIsSuccessBinding(Expression> expression) + { + if (expression == null) + { + throw new ArgumentNullException($"{nameof(expression)} should not be null."); + } + + var memberInitExpression = expression.Body as MemberInitExpression; + + if (memberInitExpression == null)// assert it's a MemberInitExpression + { + throw new InvalidCastException($"expression.Body '{expression.Body}' is not of MemberInitExpression type."); + } + + MemberAssignment isSuccessBinding = (MemberAssignment)memberInitExpression.Bindings.FirstOrDefault(f => f.Member.Name == nameof(RuleResultTree.IsSuccess)); + + if (isSuccessBinding == null) + { + throw new NullReferenceException($"Expected {nameof(RuleResultTree.IsSuccess)} property binding not found in {memberInitExpression}."); + } + + if (isSuccessBinding.Expression == null) + { + throw new NullReferenceException($"{nameof(RuleResultTree.IsSuccess)} assignment expression can not be null."); + } + + BinaryExpression isSuccessExpression = isSuccessBinding.Expression as BinaryExpression; + + if (isSuccessExpression == null) + { + throw new NullReferenceException($"Expected {nameof(RuleResultTree.IsSuccess)} assignment expression to be of {typeof(BinaryExpression)} and not {isSuccessBinding.Expression.GetType()}"); + } + + return isSuccessExpression; + } } } diff --git a/src/RulesEngine/RuleExpressionBuilderFactory.cs b/src/RulesEngine/RulesEngine/RuleExpressionBuilderFactory.cs similarity index 100% rename from src/RulesEngine/RuleExpressionBuilderFactory.cs rename to src/RulesEngine/RulesEngine/RuleExpressionBuilderFactory.cs diff --git a/src/RulesEngine/RulesCache.cs b/src/RulesEngine/RulesEngine/RulesCache.cs similarity index 94% rename from src/RulesEngine/RulesCache.cs rename to src/RulesEngine/RulesEngine/RulesCache.cs index 80d2f43..4b8b1ba 100644 --- a/src/RulesEngine/RulesCache.cs +++ b/src/RulesEngine/RulesEngine/RulesCache.cs @@ -13,7 +13,7 @@ namespace RulesEngine internal class RulesCache { /// The compile rules - private ConcurrentDictionary>> _compileRules = new ConcurrentDictionary>>(); + private ConcurrentDictionary> _compileRules = new ConcurrentDictionary>(); /// The workflow rules private ConcurrentDictionary _workflowRules = new ConcurrentDictionary(); @@ -47,7 +47,7 @@ namespace RulesEngine /// Adds the or update compiled rule. /// The compiled rule key. /// The compiled rule. - public void AddOrUpdateCompiledRule(string compiledRuleKey, IEnumerable> compiledRule) + public void AddOrUpdateCompiledRule(string compiledRuleKey, IEnumerable compiledRule) { _compileRules.AddOrUpdate(compiledRuleKey, compiledRule, (k, v) => compiledRule); } @@ -143,7 +143,7 @@ namespace RulesEngine /// Gets the compiled rules. /// The compiled rules key. /// CompiledRule. - public IEnumerable> GetCompiledRules(string compiledRulesKey) + public IEnumerable GetCompiledRules(string compiledRulesKey) { return _compileRules[compiledRulesKey]; } @@ -157,7 +157,7 @@ namespace RulesEngine var compiledKeysToRemove = _compileRules.Keys.Where(key => key.StartsWith(workflowName)); foreach (var key in compiledKeysToRemove) { - _compileRules.TryRemove(key, out IEnumerable> val); + _compileRules.TryRemove(key, out IEnumerable val); } } } diff --git a/src/RulesEngine/RulesEngine.cs b/src/RulesEngine/RulesEngine/RulesEngine.cs similarity index 79% rename from src/RulesEngine/RulesEngine.cs rename to src/RulesEngine/RulesEngine/RulesEngine.cs index 60d76bf..19dc2a2 100644 --- a/src/RulesEngine/RulesEngine.cs +++ b/src/RulesEngine/RulesEngine/RulesEngine.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using FluentValidation; -using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Newtonsoft.Json; @@ -26,11 +25,35 @@ namespace RulesEngine public class RulesEngine : IRulesEngine { #region Variables + + /// + /// The logger + /// private readonly ILogger _logger; + + /// + /// The re settings + /// private readonly ReSettings _reSettings; + + /// + /// The rules cache + /// private readonly RulesCache _rulesCache = new RulesCache(); - private readonly MemoryCache _compiledParamsCache = new MemoryCache(new MemoryCacheOptions()); + + /// + /// The parameters cache + /// + private readonly ParamCache _compiledParamsCache = new ParamCache(); + + /// + /// The rule parameter compiler + /// private readonly ParamCompiler ruleParamCompiler; + + /// + /// The parameter parse regex + /// private const string ParamParseRegex = "(\\$\\(.*?\\))"; #endregion @@ -145,7 +168,7 @@ namespace RulesEngine { List result; - if (RegisterRule(workflowName, ruleParams)) + if (RegisterCompiledRule(workflowName, ruleParams)) { result = ExecuteRuleByWorkflow(workflowName, ruleParams); } @@ -166,7 +189,7 @@ namespace RulesEngine /// /// bool result /// - private bool RegisterRule(string workflowName, params RuleParameter[] ruleParams) + private bool RegisterCompiledRule(string workflowName, params RuleParameter[] ruleParams) { string compileRulesKey = _rulesCache.GetRulesCacheKey(workflowName); if (_rulesCache.ContainsCompiledRules(compileRulesKey)) @@ -175,31 +198,28 @@ namespace RulesEngine var workflowRules = _rulesCache.GetWorkFlowRules(workflowName); if (workflowRules != null) { - var lstFunc = new List>(); + var lstFunc = new List(); var ruleCompiler = new RuleCompiler(new RuleExpressionBuilderFactory(_reSettings), _logger); foreach (var rule in _rulesCache.GetRules(workflowName)) { - var compiledParamsKey = GetCompiledParamsCacheKey(workflowName, rule); - CompiledRuleParam compiledRuleParam = _compiledParamsCache.GetOrCreate(compiledParamsKey, (entry) => ruleParamCompiler.CompileParamsExpression(rule, ruleParams)); + var compiledParamsKey = _compiledParamsCache.GetCompiledParamsCacheKey(workflowName, rule); + CompiledRuleParam compiledRuleParam; + if (_compiledParamsCache.ContainsParams(compiledParamsKey)) + { + compiledRuleParam = _compiledParamsCache.GetParams(compiledParamsKey); + } + else + { + compiledRuleParam = ruleParamCompiler.CompileParamsExpression(rule, ruleParams); + _compiledParamsCache.AddOrUpdateParams(compiledParamsKey, compiledRuleParam); + } var updatedRuleParams = compiledRuleParam != null ? ruleParams?.Concat(compiledRuleParam?.RuleParameters) : ruleParams; var compiledRule = ruleCompiler.CompileRule(rule, updatedRuleParams?.ToArray()); - - RuleFunc updatedRule = (object[] paramList) => { - var inputs = paramList.AsEnumerable(); - IEnumerable localParams = compiledRuleParam?.CompiledParameters ?? new List(); - foreach(var localParam in localParams){ - var evaluatedLocalParam = ruleParamCompiler.EvaluateCompiledParam(localParam.Name,localParam.Value,inputs); - inputs = inputs.Append(evaluatedLocalParam.Value); - } - var result = compiledRule(inputs.ToArray()); - result.RuleEvaluatedParams = compiledRuleParam?.RuleParameters; - return result; - }; - lstFunc.Add(updatedRule); + lstFunc.Add(new CompiledRule { Rule = compiledRule, CompiledParameters = compiledRuleParam }); } - _rulesCache.AddOrUpdateCompiledRule(compileRulesKey,lstFunc ); + _rulesCache.AddOrUpdateCompiledRule(compileRulesKey, lstFunc); _logger.LogTrace($"Rules has been compiled for the {workflowName} workflow and added to dictionary"); return true; } @@ -209,24 +229,39 @@ namespace RulesEngine } } + + private static string GetCompileRulesKey(string workflowName, RuleParameter[] ruleParams) + { + return $"{workflowName}-" + String.Join("-", ruleParams.Select(c => c.Type.Name)); + } + /// /// This will execute the compiled rules /// /// /// /// list of rule result set - - // TODO: Cleanup and fix this private List ExecuteRuleByWorkflow(string workflowName, RuleParameter[] ruleParameters) { _logger.LogTrace($"Compiled rules found for {workflowName} workflow and executed"); List result = new List(); - string compiledRulesCacheKey = _rulesCache.GetRulesCacheKey(workflowName); - foreach (var compiledRule in _rulesCache.GetCompiledRules(compiledRulesCacheKey)) + string compileRulesKey = _rulesCache.GetRulesCacheKey(workflowName); + foreach (var compiledRule in _rulesCache.GetCompiledRules(compileRulesKey)) { - var inputs = ruleParameters.Select(c => c.Value).ToArray(); - var resultTree = compiledRule(inputs); + IEnumerable evaluatedRuleParams = new List(ruleParameters); + if (compiledRule?.CompiledParameters?.CompiledParameters != null) + { + foreach (var compiledParam in compiledRule?.CompiledParameters?.CompiledParameters) + { + var evaluatedParam = ruleParamCompiler.EvaluateCompiledParam(compiledParam.Name, compiledParam.Value, evaluatedRuleParams); + evaluatedRuleParams = evaluatedRuleParams.Concat(new List { evaluatedParam }); + } + } + + var inputs = evaluatedRuleParams.Select(c => c.Value); + var resultTree = compiledRule.Rule.DynamicInvoke(new List(inputs) { new RuleInput() }.ToArray()) as RuleResultTree; + resultTree.RuleEvaluatedParams = evaluatedRuleParams; result.Add(resultTree); } @@ -234,23 +269,6 @@ namespace RulesEngine return result; } - private string GetCompiledParamsCacheKey(string workflowName, Rule rule) - { - if (rule == null) - { - return string.Empty; - } - else - { - if (rule?.LocalParams == null) - { - return $"Compiled_{workflowName}_{rule.RuleName}"; - } - - return $"Compiled_{workflowName}_{rule.RuleName}_{string.Join("_", rule?.LocalParams.Select(r => r?.Name))}"; - } - } - /// /// The result /// diff --git a/src/RulesEngine/RulesEngine.csproj b/src/RulesEngine/RulesEngine/RulesEngine.csproj similarity index 81% rename from src/RulesEngine/RulesEngine.csproj rename to src/RulesEngine/RulesEngine/RulesEngine.csproj index 469ec04..17617ee 100644 --- a/src/RulesEngine/RulesEngine.csproj +++ b/src/RulesEngine/RulesEngine/RulesEngine.csproj @@ -21,17 +21,23 @@ snupkg - - - - - + + + + - + + + + + + True + + diff --git a/src/RulesEngine/Validators/RuleValidator.cs b/src/RulesEngine/RulesEngine/Validators/RuleValidator.cs similarity index 100% rename from src/RulesEngine/Validators/RuleValidator.cs rename to src/RulesEngine/RulesEngine/Validators/RuleValidator.cs diff --git a/src/RulesEngine/Validators/WorkflowRulesValidator.cs b/src/RulesEngine/RulesEngine/Validators/WorkflowRulesValidator.cs similarity index 100% rename from src/RulesEngine/Validators/WorkflowRulesValidator.cs rename to src/RulesEngine/RulesEngine/Validators/WorkflowRulesValidator.cs diff --git a/src/RulesEngine/global.json b/src/RulesEngine/global.json new file mode 100644 index 0000000..c120c81 --- /dev/null +++ b/src/RulesEngine/global.json @@ -0,0 +1,5 @@ +{ + "sdk": { + "version": "3.1.301" + } +} \ No newline at end of file diff --git a/test/RulesEngine.UnitTest/BusinessRuleEngineTest.cs b/test/RulesEngine.UnitTest/BusinessRuleEngineTest.cs index bffaeae..9f0f29b 100644 --- a/test/RulesEngine.UnitTest/BusinessRuleEngineTest.cs +++ b/test/RulesEngine.UnitTest/BusinessRuleEngineTest.cs @@ -14,12 +14,10 @@ using System.Linq; using Xunit; using Newtonsoft.Json.Converters; using RulesEngine.HelperFunctions; -using System.Diagnostics.CodeAnalysis; namespace RulesEngine.UnitTest { [Trait("Category", "Unit")] - [ExcludeFromCodeCoverage] public class RulesEngineTest { [Theory] @@ -140,37 +138,6 @@ namespace RulesEngine.UnitTest Assert.Throws(() => { re.ExecuteRule("inputWorkflow1", new List() { input }.AsEnumerable(), new object[] { }); }); } - [Theory] - [InlineData("rules1.json")] - public void RemoveWorkflow_RemovesWorkflow(string ruleFileName) - { - var re = GetRulesEngine(ruleFileName); - re.RemoveWorkflow("inputWorkflow"); - - dynamic input1 = GetInput1(); - dynamic input2 = GetInput2(); - dynamic input3 = GetInput3(); - - Assert.Throws(() => re.ExecuteRule("inputWorkflow", new List() { input1, input2, input3 }.AsEnumerable(), new object[] { })); - } - - - [Theory] - [InlineData("rules1.json")] - public void ClearWorkflow_RemovesAllWorkflow(string ruleFileName) - { - var re = GetRulesEngine(ruleFileName); - re.ClearWorkflows(); - - dynamic input1 = GetInput1(); - dynamic input2 = GetInput2(); - dynamic input3 = GetInput3(); - - Assert.Throws(() => re.ExecuteRule("inputWorkflow", new List() { input1, input2, input3 }.AsEnumerable(), new object[] { })); - Assert.Throws(() => re.ExecuteRule("inputWorkflowReference", new List() { input1, input2, input3 }.AsEnumerable(), new object[] { })); - } - - [Theory] [InlineData("rules1.json")] [InlineData("rules2.json")] @@ -187,10 +154,14 @@ namespace RulesEngine.UnitTest Assert.IsType>(result); } - + /// + /// Ruleses the engine execute rule for nested rull parameters returns success. + /// + /// Name of the rule file. + /// Rules not found. [Theory] [InlineData("rules4.json")] - public void RulesEngine_Execute_Rule_For_Nested_Rule_Params_Returns_Success(string ruleFileName) + public void RulesEngine_Execute_Rule_For_Nested_Rull_Params_Returns_Success(string ruleFileName) { dynamic[] inputs = GetInputs4(); @@ -273,8 +244,12 @@ namespace RulesEngine.UnitTest var currentLaborCategoryInput = "{\"CurrentLaborCategoryProp\":\"TestVal2\"}"; var converter = new ExpandoObjectConverter(); + var settings = new JsonSerializerSettings + { + ContractResolver = new PrivateSetterContractResolver() + }; - dynamic input1 = JsonConvert.DeserializeObject>(laborCategoriesInput); + dynamic input1 = JsonConvert.DeserializeObject>(laborCategoriesInput, settings); dynamic input2 = JsonConvert.DeserializeObject(currentLaborCategoryInput, converter); dynamic input3 = JsonConvert.DeserializeObject(telemetryInfo, converter); dynamic input4 = JsonConvert.DeserializeObject(basicInfo, converter); diff --git a/test/RulesEngine.UnitTest/CustomTypeProviderTests.cs b/test/RulesEngine.UnitTest/CustomTypeProviderTests.cs index bcd6adc..170b3c7 100644 --- a/test/RulesEngine.UnitTest/CustomTypeProviderTests.cs +++ b/test/RulesEngine.UnitTest/CustomTypeProviderTests.cs @@ -5,12 +5,10 @@ using RulesEngine; using Moq; using System; using Xunit; -using System.Diagnostics.CodeAnalysis; namespace RulesEngine.UnitTest { [Trait("Category", "Unit")] - [ExcludeFromCodeCoverage] public class CustomTypeProviderTests : IDisposable { private MockRepository mockRepository; diff --git a/test/RulesEngine.UnitTest/ExpressionUtilsTest.cs b/test/RulesEngine.UnitTest/ExpressionUtilsTest.cs index d8a9c25..3872e56 100644 --- a/test/RulesEngine.UnitTest/ExpressionUtilsTest.cs +++ b/test/RulesEngine.UnitTest/ExpressionUtilsTest.cs @@ -2,13 +2,11 @@ // Licensed under the MIT License. using RulesEngine.HelperFunctions; -using System.Diagnostics.CodeAnalysis; using Xunit; namespace RulesEngine.UnitTest { [Trait("Category", "Unit")] - [ExcludeFromCodeCoverage] public class ExpressionUtilsTest { [Fact] diff --git a/test/RulesEngine.UnitTest/LambdaExpressionBuilderTest.cs b/test/RulesEngine.UnitTest/LambdaExpressionBuilderTest.cs index 83a3f18..e73ae77 100644 --- a/test/RulesEngine.UnitTest/LambdaExpressionBuilderTest.cs +++ b/test/RulesEngine.UnitTest/LambdaExpressionBuilderTest.cs @@ -4,14 +4,12 @@ using RulesEngine; using RulesEngine.Models; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; using Xunit; namespace RulesEngine.UnitTest { [Trait("Category", "Unit")] - [ExcludeFromCodeCoverage] public class LambdaExpressionBuilderTest { [Fact] @@ -36,10 +34,13 @@ namespace RulesEngine.UnitTest dummyRule.Expression = "RequestType == \"vod\""; mainRule.Rules.Add(dummyRule); - var func = builder.BuildExpressionForRule(dummyRule, parameterExpressions); - Assert.NotNull(func); - Assert.Equal(typeof(RuleResultTree), func.Method.ReturnType); + ParameterExpression ruleInputExp = Expression.Parameter(typeof(RuleInput), nameof(RuleInput)); + + var expression = builder.BuildExpressionForRule(dummyRule, parameterExpressions, ruleInputExp); + + Assert.NotNull(expression); + Assert.Equal(typeof(RuleResultTree), expression.ReturnType); } } } diff --git a/test/RulesEngine.UnitTest/ListofRuleResultTreeExtensionTest.cs b/test/RulesEngine.UnitTest/ListofRuleResultTreeExtensionTest.cs index eacfffc..acf1f82 100644 --- a/test/RulesEngine.UnitTest/ListofRuleResultTreeExtensionTest.cs +++ b/test/RulesEngine.UnitTest/ListofRuleResultTreeExtensionTest.cs @@ -2,14 +2,12 @@ using RulesEngine.Models; using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Text; using Xunit; namespace RulesEngine.UnitTest { [Trait("Category", "Unit")] - [ExcludeFromCodeCoverage] public class ListofRuleResultTreeExtensionTest { [Fact] diff --git a/test/RulesEngine.UnitTest/PrivateSetterContractResolver.cs b/test/RulesEngine.UnitTest/PrivateSetterContractResolver.cs new file mode 100644 index 0000000..3782a28 --- /dev/null +++ b/test/RulesEngine.UnitTest/PrivateSetterContractResolver.cs @@ -0,0 +1,32 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; + +namespace RulesEngine.UnitTest +{ + /// Class PrivateSetterContractResolver. + /// Implements the + public class PrivateSetterContractResolver : DefaultContractResolver + { + /// Creates a for the given MemberInfo. + /// The member to create a for. + /// The member's parent . + /// A created for the given MemberInfo. + protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) + { + var jsonProperty = base.CreateProperty(member, memberSerialization); + if (!jsonProperty.Writable) + { + if (member is PropertyInfo propertyInfo) + { + jsonProperty.Writable = propertyInfo.GetSetMethod(true) != null; + } + } + + return jsonProperty; + } + } +} diff --git a/test/RulesEngine.UnitTest/RuleCompilerTest.cs b/test/RulesEngine.UnitTest/RuleCompilerTest.cs index c2895b5..0faf4ac 100644 --- a/test/RulesEngine.UnitTest/RuleCompilerTest.cs +++ b/test/RulesEngine.UnitTest/RuleCompilerTest.cs @@ -4,13 +4,11 @@ using Microsoft.Extensions.Logging.Abstractions; using RulesEngine.Models; using System; -using System.Diagnostics.CodeAnalysis; using Xunit; namespace RulesEngine.UnitTest { [Trait("Category","Unit")] - [ExcludeFromCodeCoverage] public class RuleCompilerTest { [Fact] diff --git a/test/RulesEngine.UnitTest/RuleExpressionBuilderFactoryTest.cs b/test/RulesEngine.UnitTest/RuleExpressionBuilderFactoryTest.cs index 663735c..dde2e2c 100644 --- a/test/RulesEngine.UnitTest/RuleExpressionBuilderFactoryTest.cs +++ b/test/RulesEngine.UnitTest/RuleExpressionBuilderFactoryTest.cs @@ -5,13 +5,11 @@ using RulesEngine; using RulesEngine.ExpressionBuilders; using RulesEngine.Models; using System; -using System.Diagnostics.CodeAnalysis; using Xunit; namespace RulesEngine.UnitTest { [Trait("Category", "Unit")] - [ExcludeFromCodeCoverage] public class RuleExpressionBuilderFactoryTest { [Theory] diff --git a/test/RulesEngine.UnitTest/RuleTestClass.cs b/test/RulesEngine.UnitTest/RuleTestClass.cs index 40c0ece..77407f3 100644 --- a/test/RulesEngine.UnitTest/RuleTestClass.cs +++ b/test/RulesEngine.UnitTest/RuleTestClass.cs @@ -1,19 +1,40 @@ using Newtonsoft.Json; using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Text; namespace RulesEngine.UnitTest { - [ExcludeFromCodeCoverage] + /// + /// Class RuleTestClass. + /// public class RuleTestClass { + /// + /// Gets the country. + /// + /// + /// The country. + /// [JsonProperty("country")] - public string Country { get; set; } + public string Country { get; private set; } + /// + /// Gets the loyality factor. + /// + /// + /// The loyality factor. + /// [JsonProperty("loyalityFactor")] - public int LoyalityFactor { get; set; } - public int TotalPurchasesToDate { get; set; } + public int LoyalityFactor { get; private set; } + + /// + /// Gets the total purchases to date. + /// + /// + /// The total purchases to date. + /// + [JsonProperty("totalPurchasesToDate")] + public int TotalPurchasesToDate { get; private set; } } } diff --git a/test/RulesEngine.UnitTest/RulesEngine.UnitTest.csproj b/test/RulesEngine.UnitTest/RulesEngine.UnitTest.csproj index c6491c6..9d95788 100644 --- a/test/RulesEngine.UnitTest/RulesEngine.UnitTest.csproj +++ b/test/RulesEngine.UnitTest/RulesEngine.UnitTest.csproj @@ -1,21 +1,23 @@  + Exe netcoreapp3.1 - - + + + - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/test/RulesEngine.UnitTest/UtilsTests.cs b/test/RulesEngine.UnitTest/UtilsTests.cs index 50dccf7..b0d3625 100644 --- a/test/RulesEngine.UnitTest/UtilsTests.cs +++ b/test/RulesEngine.UnitTest/UtilsTests.cs @@ -4,14 +4,12 @@ using RulesEngine.HelperFunctions; using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Dynamic; using System.Text; using Xunit; namespace RulesEngine.UnitTest { - [ExcludeFromCodeCoverage] public class TestClass { public string test { get; set; } @@ -19,7 +17,6 @@ namespace RulesEngine.UnitTest } [Trait("Category","Unit")] - [ExcludeFromCodeCoverage] public class UtilsTests {