Moving latest changes in develop and reverting them from master (#41)

* Revert "Increased coverage threshold to 95 for PR"

This reverts commit b545c5b4cf.

* Revert "Converted expressions to delegates and code cleanup (#34)"

This reverts commit 75baa6e358.
pull/43/head
Abbas Cyclewala 2020-08-28 07:44:00 +05:30 committed by GitHub
parent b545c5b4cf
commit c0488f1113
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
54 changed files with 567 additions and 346 deletions

View File

@ -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

27
.vscode/launch.json vendored
View File

@ -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}"
}
]
}

View File

@ -1,3 +0,0 @@
{
"dotnetCoreExplorer.searchpatterns": "test/**/bin/Debug/netcoreapp*/*.{dll,exe,json}"
}

42
.vscode/tasks.json vendored
View File

@ -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"
}
]
}

View File

@ -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>

View File

@ -1,7 +1,6 @@
{
"sdk": {
"version": "3.1.101",
"rollForward": "latestFeature",
"allowPrerelease": false
"rollForward": "latestFeature"
}
}

View File

@ -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);
}
}
}
}

View File

@ -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; }
}
}

View File

@ -1,8 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace RulesEngine.Models
{
public delegate T RuleFunc<T>(params object[] param);
}

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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);
}
}
}
}

View File

@ -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>

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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
{

View File

@ -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; }
}
}

View File

@ -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);
}
}
}
}
}

View File

@ -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);
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}

View File

@ -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>

View File

@ -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>

View File

@ -0,0 +1,5 @@
{
"sdk": {
"version": "3.1.301"
}
}

View File

@ -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);

View File

@ -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;

View File

@ -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]

View File

@ -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);
}
}
}

View File

@ -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]

View File

@ -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;
}
}
}

View File

@ -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]

View File

@ -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]

View File

@ -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; }
}
}

View File

@ -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>

View File

@ -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
{