Moving latest changes in develop and reverting them from master (#41)
* Revert "Increased coverage threshold to 95 for PR" This reverts commitpull/43/headb545c5b4cf
. * Revert "Converted expressions to delegates and code cleanup (#34)" This reverts commit75baa6e358
.
parent
b545c5b4cf
commit
c0488f1113
|
@ -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
|
||||
|
|
|
@ -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}"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"dotnetCoreExplorer.searchpatterns": "test/**/bin/Debug/netcoreapp*/*.{dll,exe,json}"
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<ProjectReference Include="../../src/RulesEngine/RulesEngine.csproj" />
|
||||
<ProjectReference Include="../../src/RulesEngine/RulesEngine/RulesEngine.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
{
|
||||
"sdk": {
|
||||
"version": "3.1.101",
|
||||
"rollForward": "latestFeature",
|
||||
"allowPrerelease": false
|
||||
"rollForward": "latestFeature"
|
||||
}
|
||||
}
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers
|
||||
/// </summary>
|
||||
internal static class Helpers
|
||||
{
|
||||
internal static RuleFunc<RuleResultTree> ToResultTree(Rule rule, IEnumerable<RuleResultTree> childRuleResults, RuleFunc<bool> isSuccessFunc, string exceptionMessage = "")
|
||||
{
|
||||
return (inputs) => new RuleResultTree
|
||||
{
|
||||
Rule = rule,
|
||||
Input = inputs.FirstOrDefault(),
|
||||
IsSuccess = isSuccessFunc(inputs),
|
||||
ChildResults = childRuleResults,
|
||||
ExceptionMessage = exceptionMessage
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To the result tree error messages
|
||||
/// </summary>
|
||||
/// <param name="ruleResultTree">ruleResultTree</param>
|
||||
/// <param name="ruleResultMessage">ruleResultMessage</param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To get the child error message recersivly
|
||||
/// </summary>
|
||||
/// <param name="childResultTree">childResultTree</param>
|
||||
/// <param name="ruleResultMessage">ruleResultMessage</param>
|
||||
private static void GetChildRuleMessages(IEnumerable<RuleResultTree> childResultTree, ref RuleResultMessage ruleResultMessage)
|
||||
{
|
||||
foreach (var item in childResultTree)
|
||||
{
|
||||
ToResultTreeMessages(item, ref ruleResultMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<CompiledParam> CompiledParameters { get; set; }
|
||||
internal IEnumerable<RuleParameter> RuleParameters { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
namespace RulesEngine.Models
|
||||
{
|
||||
public delegate T RuleFunc<T>(params object[] param);
|
||||
|
||||
}
|
|
@ -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
|
|
@ -24,22 +24,21 @@ namespace RulesEngine.ExpressionBuilders
|
|||
{
|
||||
_reSettings = reSettings;
|
||||
}
|
||||
internal override RuleFunc<RuleResultTree> BuildExpressionForRule(Rule rule, IEnumerable<ParameterExpression> typeParamExpressions)
|
||||
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, 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Builds the expression for rule parameter.</summary>
|
|
@ -20,7 +20,7 @@ namespace RulesEngine.ExpressionBuilders
|
|||
/// <param name="typeParamExpressions">The type parameter expressions.</param>
|
||||
/// <param name="ruleInputExp">The rule input exp.</param>
|
||||
/// <returns>Expression type</returns>
|
||||
internal abstract RuleFunc<RuleResultTree> BuildExpressionForRule(Rule rule, IEnumerable<ParameterExpression> typeParameterExpressions);
|
||||
internal abstract Expression<Func<RuleInput, RuleResultTree>> BuildExpressionForRule(Rule rule, IEnumerable<ParameterExpression> typeParamExpressions, ParameterExpression ruleInputExp);
|
||||
|
||||
/// <summary>Builds the expression for rule parameter.</summary>
|
||||
/// <param name="rule">The rule.</param>
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers
|
||||
/// </summary>
|
||||
internal static class Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// To the result tree expression.
|
||||
/// </summary>
|
||||
/// <param name="rule">The rule.</param>
|
||||
/// <param name="childRuleResults">The child rule results.</param>
|
||||
/// <param name="isSuccessExp">The is success exp.</param>
|
||||
/// <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, string exceptionMessage = "")
|
||||
{
|
||||
var memberInit = ToResultTree(rule, childRuleResults, isSuccessExp, typeParamExpressions, null, exceptionMessage);
|
||||
var lambda = Expression.Lambda<Func<RuleInput, RuleResultTree>>(memberInit, new[] { ruleInputExp });
|
||||
return lambda;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To the result tree member expression
|
||||
/// </summary>
|
||||
/// <param name="rule">The rule.</param>
|
||||
/// <param name="childRuleResults">The child rule results.</param>
|
||||
/// <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, 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To the result tree error messages
|
||||
/// </summary>
|
||||
/// <param name="ruleResultTree">ruleResultTree</param>
|
||||
/// <param name="ruleResultMessage">ruleResultMessage</param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To get the child error message recersivly
|
||||
/// </summary>
|
||||
/// <param name="childResultTree">childResultTree</param>
|
||||
/// <param name="ruleResultMessage">ruleResultMessage</param>
|
||||
private static void GetChildRuleMessages(IEnumerable<RuleResultTree> childResultTree, ref RuleResultMessage ruleResultMessage)
|
||||
{
|
||||
foreach (var item in childResultTree)
|
||||
{
|
||||
ToResultTreeMessages(item, ref ruleResultMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
|||
/// <summary>
|
||||
/// CompiledParam class.
|
||||
/// </summary>
|
||||
[ExcludeFromCodeCoverage]
|
||||
internal class CompiledParam
|
||||
{
|
||||
/// <summary>
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the compiled rules.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The compiled rules.
|
||||
/// </value>
|
||||
internal Delegate Rule { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the rule parameters.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The rule parameters.
|
||||
/// </value>
|
||||
internal CompiledRuleParam CompiledParameters { get; set; }
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace RulesEngine.Models
|
||||
{
|
||||
/// <summary>Class CompiledRule.</summary>
|
||||
internal class CompiledRuleParam
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the compiled rules.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The compiled rules.
|
||||
/// </value>
|
||||
internal string Name { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the rule parameters.</summary>
|
||||
/// <value>The rule parameters.</value>
|
||||
internal IEnumerable<CompiledParam> CompiledParameters { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the rule parameters.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The rule parameters.
|
||||
/// </value>
|
||||
internal IEnumerable<RuleParameter> RuleParameters { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,11 +1,9 @@
|
|||
using Newtonsoft.Json;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace RulesEngine.Models
|
||||
{
|
||||
/// <summary>Class LocalParam.
|
||||
/// </summary>
|
||||
[ExcludeFromCodeCoverage]
|
||||
/// <summary>Class Param.
|
||||
/// Implements the <see cref="RulesEngine.Models.Rule" /></summary>
|
||||
public class LocalParam
|
||||
{
|
||||
|
|
@ -85,6 +85,7 @@ namespace RulesEngine.Models
|
|||
/// </summary>
|
||||
public string Expression { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the success event.
|
||||
/// </summary>
|
||||
|
@ -94,4 +95,5 @@ namespace RulesEngine.Models
|
|||
public string SuccessEvent { get; set; }
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
using RulesEngine.Models;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
|
||||
namespace RulesEngine
|
||||
{
|
||||
/// <summary>Maintains the cache of evaludated param.</summary>
|
||||
internal class ParamCache<T> where T : class
|
||||
{
|
||||
/// <summary>
|
||||
/// The compile rules
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<string, T> _evaluatedParams = new ConcurrentDictionary<string, T>();
|
||||
|
||||
/// <summary>
|
||||
/// <para></para>
|
||||
/// <para>Determines whether the specified parameter key name contains parameters.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="paramKeyName">Name of the parameter key.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the specified parameter key name contains parameters; otherwise, <c>false</c>.</returns>
|
||||
public bool ContainsParams(string paramKeyName)
|
||||
{
|
||||
return _evaluatedParams.ContainsKey(paramKeyName);
|
||||
}
|
||||
|
||||
/// <summary>Adds the or update evaluated parameter.</summary>
|
||||
/// <param name="paramKeyName">Name of the parameter key.</param>
|
||||
/// <param name="ruleParameters">The rule parameters.</param>
|
||||
public void AddOrUpdateParams(string paramKeyName, T ruleParameters)
|
||||
{
|
||||
_evaluatedParams.AddOrUpdate(paramKeyName, ruleParameters, (k, v) => v);
|
||||
}
|
||||
|
||||
/// <summary>Clears this instance.</summary>
|
||||
public void Clear()
|
||||
{
|
||||
_evaluatedParams.Clear();
|
||||
}
|
||||
|
||||
/// <summary>Gets the evaluated parameters.</summary>
|
||||
/// <param name="paramKeyName">Name of the parameter key.</param>
|
||||
/// <returns>Delegate[].</returns>
|
||||
public T GetParams(string paramKeyName)
|
||||
{
|
||||
return _evaluatedParams[paramKeyName];
|
||||
}
|
||||
|
||||
/// <summary>Gets the evaluated parameters cache key.</summary>
|
||||
/// <param name="workflowName">Name of the workflow.</param>
|
||||
/// <param name="rule">The rule.</param>
|
||||
/// <returns>Cache key.</returns>
|
||||
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))}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Removes the specified workflow name.</summary>
|
||||
/// <param name="workflowName">Name of the workflow.</param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<RuleParameter> { evaluatedParam });
|
||||
evaluatedParameters.Add(evaluatedParam);
|
||||
}
|
||||
|
||||
|
@ -86,8 +86,9 @@ namespace RulesEngine
|
|||
/// <param name="compiledParam">The compiled parameter.</param>
|
||||
/// <param name="ruleParams">The rule parameters.</param>
|
||||
/// <returns>RuleParameter.</returns>
|
||||
public RuleParameter EvaluateCompiledParam(string paramName, Delegate compiledParam, IEnumerable<object> inputs)
|
||||
public RuleParameter EvaluateCompiledParam(string paramName, Delegate compiledParam, IEnumerable<RuleParameter> ruleParams)
|
||||
{
|
||||
var inputs = ruleParams.Select(c => c.Value);
|
||||
var result = compiledParam.DynamicInvoke(new List<object>(inputs) { new RuleInput() }.ToArray());
|
||||
return new RuleParameter(paramName, result);
|
||||
}
|
|
@ -60,14 +60,23 @@ namespace RulesEngine
|
|||
/// <param name="input"></param>
|
||||
/// <param name="ruleParam"></param>
|
||||
/// <returns>Compiled func delegate</returns>
|
||||
public RuleFunc<RuleResultTree> CompileRule(Rule rule,params RuleParameter[] ruleParams)
|
||||
public Delegate CompileRule(Rule rule,params RuleParameter[] ruleParams)
|
||||
{
|
||||
try
|
||||
{
|
||||
IEnumerable<ParameterExpression> typeParameterExpressions = GetParameterExpression(ruleParams).ToList();
|
||||
RuleFunc<RuleResultTree> ruleExpression = GetExpressionForRule(rule, typeParameterExpressions,ruleParams);
|
||||
IEnumerable<ParameterExpression> 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<Func<RuleInput, RuleResultTree>> ruleExpression = GetExpressionForRule(rule, typeParameterExpressions, ruleInputExp);
|
||||
|
||||
var lambdaParameterExps = new List<ParameterExpression>(typeParameterExpressions) { ruleInputExp };
|
||||
|
||||
|
||||
var expression = Expression.Lambda(ruleExpression.Body, lambdaParameterExps);
|
||||
|
||||
|
||||
return expression.Compile();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -111,18 +120,18 @@ namespace RulesEngine
|
|||
/// <param name="typeParameterExpressions">The type parameter expressions.</param>
|
||||
/// <param name="ruleInputExp">The rule input exp.</param>
|
||||
/// <returns></returns>
|
||||
private RuleFunc<RuleResultTree> GetExpressionForRule(Rule rule, IEnumerable<ParameterExpression> typeParameterExpressions, RuleParameter[] ruleParams)
|
||||
private Expression<Func<RuleInput, RuleResultTree>> GetExpressionForRule(Rule rule, IEnumerable<ParameterExpression> 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
|
|||
/// <param name="ruleInputExp">The rule input exp.</param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="InvalidOperationException"></exception>
|
||||
private RuleFunc<RuleResultTree> BuildExpression(Rule rule, IEnumerable<ParameterExpression> typeParameterExpressions, RuleParameter[] ruleParams)
|
||||
private Expression<Func<RuleInput, RuleResultTree>> BuildExpression(Rule rule, IEnumerable<ParameterExpression> 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
|
|||
/// <param name="ruleInputExp">The rule input exp.</param>
|
||||
/// <returns>Expression of func delegate</returns>
|
||||
/// <exception cref="InvalidCastException"></exception>
|
||||
private RuleFunc<RuleResultTree> BuildNestedExpression(Rule parentRule, ExpressionType operation, IEnumerable<ParameterExpression> typeParameterExpressions, RuleParameter[] ruleParams)
|
||||
private Expression<Func<RuleInput, RuleResultTree>> BuildNestedExpression(Rule parentRule, ExpressionType operation, IEnumerable<ParameterExpression> typeParameterExpressions, ParameterExpression ruleInputExp)
|
||||
{
|
||||
var expressions = new List<RuleFunc<RuleResultTree>>();
|
||||
List<Expression<Func<RuleInput, RuleResultTree>>> expressions = new List<Expression<Func<RuleInput, RuleResultTree>>>();
|
||||
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<bool> isSuccess = (p) => ApplyOperation(resultList, operation);
|
||||
RuleFunc<RuleResultTree> result = Helpers.ToResultTree(parentRule, resultList,isSuccess);
|
||||
return result(paramArray);
|
||||
};
|
||||
}
|
||||
List<MemberInitExpression> childRuleResultTree = new List<MemberInitExpression>();
|
||||
|
||||
|
||||
private bool ApplyOperation(IEnumerable<RuleResultTree> 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<Func<RuleInput, RuleResultTree>> nestedExpression = Helpers.ToResultTreeExpression(parentRule, childRuleResultTree, BinaryExpression(expressions, operation), typeParameterExpressions, ruleInputExp);
|
||||
|
||||
return nestedExpression;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binaries the expression.
|
||||
/// </summary>
|
||||
/// <param name="expressions">The expressions.</param>
|
||||
/// <param name="operationType">Type of the operation.</param>
|
||||
/// <returns>Binary Expression</returns>
|
||||
private BinaryExpression BinaryExpression(IList<Expression<Func<RuleInput, RuleResultTree>>> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the is success binding.
|
||||
/// </summary>
|
||||
/// <param name="expression">The expression.</param>
|
||||
/// <returns>Binary expression of IsSuccess prop</returns>
|
||||
/// <exception cref="ArgumentNullException">expression</exception>
|
||||
/// <exception cref="InvalidCastException"></exception>
|
||||
/// <exception cref="NullReferenceException">
|
||||
/// IsSuccess
|
||||
/// or
|
||||
/// IsSuccess
|
||||
/// or
|
||||
/// IsSuccess
|
||||
/// </exception>
|
||||
private BinaryExpression ResolveIsSuccessBinding(Expression<Func<RuleInput, RuleResultTree>> 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,7 +13,7 @@ namespace RulesEngine
|
|||
internal class RulesCache
|
||||
{
|
||||
/// <summary>The compile rules</summary>
|
||||
private ConcurrentDictionary<string, IEnumerable<RuleFunc<RuleResultTree>>> _compileRules = new ConcurrentDictionary<string, IEnumerable<RuleFunc<RuleResultTree>>>();
|
||||
private ConcurrentDictionary<string, IEnumerable<CompiledRule>> _compileRules = new ConcurrentDictionary<string, IEnumerable<CompiledRule>>();
|
||||
|
||||
/// <summary>The workflow rules</summary>
|
||||
private ConcurrentDictionary<string, WorkflowRules> _workflowRules = new ConcurrentDictionary<string, WorkflowRules>();
|
||||
|
@ -47,7 +47,7 @@ namespace RulesEngine
|
|||
/// <summary>Adds the or update compiled rule.</summary>
|
||||
/// <param name="compiledRuleKey">The compiled rule key.</param>
|
||||
/// <param name="compiledRule">The compiled rule.</param>
|
||||
public void AddOrUpdateCompiledRule(string compiledRuleKey, IEnumerable<RuleFunc<RuleResultTree>> compiledRule)
|
||||
public void AddOrUpdateCompiledRule(string compiledRuleKey, IEnumerable<CompiledRule> compiledRule)
|
||||
{
|
||||
_compileRules.AddOrUpdate(compiledRuleKey, compiledRule, (k, v) => compiledRule);
|
||||
}
|
||||
|
@ -143,7 +143,7 @@ namespace RulesEngine
|
|||
/// <summary>Gets the compiled rules.</summary>
|
||||
/// <param name="compiledRulesKey">The compiled rules key.</param>
|
||||
/// <returns>CompiledRule.</returns>
|
||||
public IEnumerable<RuleFunc<RuleResultTree>> GetCompiledRules(string compiledRulesKey)
|
||||
public IEnumerable<CompiledRule> 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<RuleFunc<RuleResultTree>> val);
|
||||
_compileRules.TryRemove(key, out IEnumerable<CompiledRule> val);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
||||
/// <summary>
|
||||
/// The logger
|
||||
/// </summary>
|
||||
private readonly ILogger _logger;
|
||||
|
||||
/// <summary>
|
||||
/// The re settings
|
||||
/// </summary>
|
||||
private readonly ReSettings _reSettings;
|
||||
|
||||
/// <summary>
|
||||
/// The rules cache
|
||||
/// </summary>
|
||||
private readonly RulesCache _rulesCache = new RulesCache();
|
||||
private readonly MemoryCache _compiledParamsCache = new MemoryCache(new MemoryCacheOptions());
|
||||
|
||||
/// <summary>
|
||||
/// The parameters cache
|
||||
/// </summary>
|
||||
private readonly ParamCache<CompiledRuleParam> _compiledParamsCache = new ParamCache<CompiledRuleParam>();
|
||||
|
||||
/// <summary>
|
||||
/// The rule parameter compiler
|
||||
/// </summary>
|
||||
private readonly ParamCompiler ruleParamCompiler;
|
||||
|
||||
/// <summary>
|
||||
/// The parameter parse regex
|
||||
/// </summary>
|
||||
private const string ParamParseRegex = "(\\$\\(.*?\\))";
|
||||
#endregion
|
||||
|
||||
|
@ -145,7 +168,7 @@ namespace RulesEngine
|
|||
{
|
||||
List<RuleResultTree> result;
|
||||
|
||||
if (RegisterRule(workflowName, ruleParams))
|
||||
if (RegisterCompiledRule(workflowName, ruleParams))
|
||||
{
|
||||
result = ExecuteRuleByWorkflow(workflowName, ruleParams);
|
||||
}
|
||||
|
@ -166,7 +189,7 @@ namespace RulesEngine
|
|||
/// <returns>
|
||||
/// bool result
|
||||
/// </returns>
|
||||
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<RuleFunc<RuleResultTree>>();
|
||||
var lstFunc = new List<CompiledRule>();
|
||||
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<RuleResultTree> updatedRule = (object[] paramList) => {
|
||||
var inputs = paramList.AsEnumerable();
|
||||
IEnumerable<CompiledParam> localParams = compiledRuleParam?.CompiledParameters ?? new List<CompiledParam>();
|
||||
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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will execute the compiled rules
|
||||
/// </summary>
|
||||
/// <param name="workflowName"></param>
|
||||
/// <param name="ruleParams"></param>
|
||||
/// <returns>list of rule result set</returns>
|
||||
|
||||
// TODO: Cleanup and fix this
|
||||
private List<RuleResultTree> ExecuteRuleByWorkflow(string workflowName, RuleParameter[] ruleParameters)
|
||||
{
|
||||
_logger.LogTrace($"Compiled rules found for {workflowName} workflow and executed");
|
||||
|
||||
List<RuleResultTree> result = new List<RuleResultTree>();
|
||||
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<RuleParameter> evaluatedRuleParams = new List<RuleParameter>(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<RuleParameter> { evaluatedParam });
|
||||
}
|
||||
}
|
||||
|
||||
var inputs = evaluatedRuleParams.Select(c => c.Value);
|
||||
var resultTree = compiledRule.Rule.DynamicInvoke(new List<object>(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))}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The result
|
||||
/// </summary>
|
|
@ -21,17 +21,23 @@
|
|||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.6" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentValidation" Version="9.0.1" />
|
||||
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.1.6" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="FluentValidation" Version="8.4.0" />
|
||||
<PackageReference Include="Microsoft.CSharp" Version="4.5.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.1.3" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
|
||||
<PackageReference Include="System.Linq" Version="4.3.0" />
|
||||
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.2.0" />
|
||||
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.1.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\..\..\LICENSE">
|
||||
<Pack>True</Pack>
|
||||
<PackagePath></PackagePath>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"sdk": {
|
||||
"version": "3.1.301"
|
||||
}
|
||||
}
|
|
@ -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<ArgumentException>(() => { re.ExecuteRule("inputWorkflow1", new List<dynamic>() { 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<ArgumentException>(() => re.ExecuteRule("inputWorkflow", new List<dynamic>() { 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<ArgumentException>(() => re.ExecuteRule("inputWorkflow", new List<dynamic>() { input1, input2, input3 }.AsEnumerable(), new object[] { }));
|
||||
Assert.Throws<ArgumentException>(() => re.ExecuteRule("inputWorkflowReference", new List<dynamic>() { input1, input2, input3 }.AsEnumerable(), new object[] { }));
|
||||
}
|
||||
|
||||
|
||||
[Theory]
|
||||
[InlineData("rules1.json")]
|
||||
[InlineData("rules2.json")]
|
||||
|
@ -187,10 +154,14 @@ namespace RulesEngine.UnitTest
|
|||
Assert.IsType<List<RuleResultTree>>(result);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Ruleses the engine execute rule for nested rull parameters returns success.
|
||||
/// </summary>
|
||||
/// <param name="ruleFileName">Name of the rule file.</param>
|
||||
/// <exception cref="Exception">Rules not found.</exception>
|
||||
[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<List<RuleTestClass>>(laborCategoriesInput);
|
||||
dynamic input1 = JsonConvert.DeserializeObject<List<RuleTestClass>>(laborCategoriesInput, settings);
|
||||
dynamic input2 = JsonConvert.DeserializeObject<ExpandoObject>(currentLaborCategoryInput, converter);
|
||||
dynamic input3 = JsonConvert.DeserializeObject<ExpandoObject>(telemetryInfo, converter);
|
||||
dynamic input4 = JsonConvert.DeserializeObject<ExpandoObject>(basicInfo, converter);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
{
|
||||
/// <summary>Class PrivateSetterContractResolver.
|
||||
/// Implements the <see cref="Newtonsoft.Json.Serialization.DefaultContractResolver" /></summary>
|
||||
public class PrivateSetterContractResolver : DefaultContractResolver
|
||||
{
|
||||
/// <summary>Creates a <see cref="T:Newtonsoft.Json.Serialization.JsonProperty" /> for the given <see cref="T:System.Reflection.MemberInfo">MemberInfo</see>.</summary>
|
||||
/// <param name="member">The member to create a <see cref="T:Newtonsoft.Json.Serialization.JsonProperty" /> for.</param>
|
||||
/// <param name="memberSerialization">The member's parent <see cref="T:Newtonsoft.Json.MemberSerialization" />.</param>
|
||||
/// <returns>A created <see cref="T:Newtonsoft.Json.Serialization.JsonProperty" /> for the given <see cref="T:System.Reflection.MemberInfo">MemberInfo</see>.</returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -1,19 +1,40 @@
|
|||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text;
|
||||
|
||||
namespace RulesEngine.UnitTest
|
||||
{
|
||||
[ExcludeFromCodeCoverage]
|
||||
/// <summary>
|
||||
/// Class RuleTestClass.
|
||||
/// </summary>
|
||||
public class RuleTestClass
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the country.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The country.
|
||||
/// </value>
|
||||
[JsonProperty("country")]
|
||||
public string Country { get; set; }
|
||||
public string Country { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the loyality factor.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The loyality factor.
|
||||
/// </value>
|
||||
[JsonProperty("loyalityFactor")]
|
||||
public int LoyalityFactor { get; set; }
|
||||
public int TotalPurchasesToDate { get; set; }
|
||||
public int LoyalityFactor { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total purchases to date.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The total purchases to date.
|
||||
/// </value>
|
||||
[JsonProperty("totalPurchasesToDate")]
|
||||
public int TotalPurchasesToDate { get; private set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,21 +1,23 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
|
||||
<PackageReference Include="Moq" Version="4.14.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.1.3" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
|
||||
<PackageReference Include="Moq" Version="4.12.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.2">
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\RulesEngine\RulesEngine.csproj" />
|
||||
<ProjectReference Include="..\..\src\RulesEngine\RulesEngine\RulesEngine.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
||||
|
|
Loading…
Reference in New Issue