Rules Engine param Feature (#24)
* Feature Description Rules Engine has a param (like ‘var’ in c#) feature support now, it makes authoring and troubleshooting of issues very easy. Now you can breakdown your bigger statements into smaller logical expressions as parameters within a rule definition. * renaming param to localParam * adding change log for local param Co-authored-by: Deepak Joshi <dejosh@microsoft.com>pull/41/head
parent
170b494b66
commit
a0a8938892
|
@ -2,6 +2,9 @@
|
|||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [2.1.0] - 18-05-2020
|
||||
- Adding local param support to make expression authroing more intuitive.
|
||||
|
||||
## [2.0.0] - 18-05-2020
|
||||
### Changed
|
||||
- Interface simplified by removing redundant parameters in the IRulesEngine.
|
||||
|
|
|
@ -38,7 +38,20 @@ namespace RulesEngine.ExpressionBuilders
|
|||
var binaryExpression = Expression.And(Expression.Constant(true), Expression.Constant(false));
|
||||
var exceptionMessage = ex.Message;
|
||||
return Helpers.ToResultTreeExpression(rule, null, binaryExpression, typeParamExpressions, ruleInputExp, exceptionMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Builds the expression for rule parameter.</summary>
|
||||
/// <param name="param">The parameter.</param>
|
||||
/// <param name="typeParamExpressions">The type parameter expressions.</param>
|
||||
/// <param name="ruleInputExp">The rule input exp.</param>
|
||||
/// <returns>Expression.</returns>
|
||||
internal override Expression BuildExpressionForRuleParam(LocalParam param, IEnumerable<ParameterExpression> typeParamExpressions, ParameterExpression ruleInputExp)
|
||||
{
|
||||
var config = new ParsingConfig { CustomTypeProvider = new CustomTypeProvider(_reSettings.CustomTypes) };
|
||||
var e = DynamicExpressionParser.ParseLambda(config, typeParamExpressions.ToArray(), null, param.Expression);
|
||||
return e.Body;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,5 +21,12 @@ namespace RulesEngine.ExpressionBuilders
|
|||
/// <param name="ruleInputExp">The rule input exp.</param>
|
||||
/// <returns>Expression type</returns>
|
||||
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>
|
||||
/// <param name="typeParamExpressions">The type parameter expressions.</param>
|
||||
/// <param name="ruleInputExp">The rule input exp.</param>
|
||||
/// <returns>Expression.</returns>
|
||||
internal abstract Expression BuildExpressionForRuleParam(LocalParam rule, IEnumerable<ParameterExpression> typeParamExpressions, ParameterExpression ruleInputExp);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace RulesEngine.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// CompiledParam class.
|
||||
/// </summary>
|
||||
internal class CompiledParam
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the name.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The name.
|
||||
/// </value>
|
||||
internal string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The value.
|
||||
/// </value>
|
||||
internal Delegate Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the parameters.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The parameters.
|
||||
/// </value>
|
||||
internal IEnumerable<RuleParameter> Parameters { get; set; }
|
||||
}
|
||||
}
|
|
@ -16,7 +16,16 @@ namespace RulesEngine.Models
|
|||
/// <value>
|
||||
/// The compiled rules.
|
||||
/// </value>
|
||||
internal List<Delegate> CompiledRules { get; set; }
|
||||
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; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace RulesEngine.Models
|
||||
{
|
||||
/// <summary>Class Param.
|
||||
/// Implements the <see cref="RulesEngine.Models.Rule" /></summary>
|
||||
public class LocalParam
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the rule.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The name of the rule.
|
||||
/// </value>
|
||||
[JsonProperty, JsonRequired]
|
||||
public string Name { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or Sets the lambda expression.
|
||||
/// </summary>
|
||||
[JsonProperty, JsonRequired]
|
||||
public string Expression { get; private set; }
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ using Newtonsoft.Json;
|
|||
using Newtonsoft.Json.Converters;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
namespace RulesEngine.Models
|
||||
{
|
||||
|
@ -70,12 +71,27 @@ namespace RulesEngine.Models
|
|||
/// </value>
|
||||
public List<Rule> Rules { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parameters.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The parameters.
|
||||
/// </value>
|
||||
[JsonProperty]
|
||||
public IEnumerable<LocalParam> LocalParams { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or Sets the lambda expression.
|
||||
/// </summary>
|
||||
public string Expression { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the success event.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The success event.
|
||||
/// </value>
|
||||
public string SuccessEvent { get; set; }
|
||||
|
||||
}
|
||||
|
|
|
@ -47,6 +47,14 @@ namespace RulesEngine.Models
|
|||
/// </summary>
|
||||
public string ExceptionMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the rule evaluated parameters.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The rule evaluated parameters.
|
||||
/// </value>
|
||||
public IEnumerable<RuleParameter> RuleEvaluatedParams { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// This method will return all the error and warning messages to caller
|
||||
/// </summary>
|
||||
|
|
|
@ -17,10 +17,13 @@ namespace RulesEngine.Models
|
|||
/// </summary>
|
||||
public string WorkflowName { get; set; }
|
||||
|
||||
public List<string> WorkflowRulesToInject { get; set; }
|
||||
/// <summary>Gets or sets the workflow rules to inject.</summary>
|
||||
/// <value>The workflow rules to inject.</value>
|
||||
public IEnumerable<string> WorkflowRulesToInject { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// list of rules.
|
||||
/// </summary>
|
||||
public List<Rule> Rules { get; set; }
|
||||
public IEnumerable<Rule> Rules { 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
using Microsoft.Extensions.Logging;
|
||||
using RulesEngine.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text;
|
||||
using System.Linq;
|
||||
|
||||
namespace RulesEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Rule param compilers
|
||||
/// </summary>
|
||||
internal class ParamCompiler
|
||||
{
|
||||
/// <summary>
|
||||
/// The expression builder factory
|
||||
/// </summary>
|
||||
private readonly RuleExpressionBuilderFactory _expressionBuilderFactory;
|
||||
|
||||
/// <summary>
|
||||
/// The logger
|
||||
/// </summary>
|
||||
private readonly ILogger _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ParamCompiler"/> class.
|
||||
/// </summary>
|
||||
/// <param name="expressionBuilderFactory">The expression builder factory.</param>
|
||||
/// <exception cref="ArgumentNullException">expressionBuilderFactory</exception>
|
||||
internal ParamCompiler(RuleExpressionBuilderFactory expressionBuilderFactory, ILogger logger)
|
||||
{
|
||||
if (expressionBuilderFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException($"{nameof(expressionBuilderFactory)} can't be null.");
|
||||
}
|
||||
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException($"{nameof(logger)} can't be null.");
|
||||
}
|
||||
|
||||
_logger = logger;
|
||||
_expressionBuilderFactory = expressionBuilderFactory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compiles the and evaluate parameter expression.
|
||||
/// </summary>
|
||||
/// <param name="rule">The rule.</param>
|
||||
/// <param name="ruleParams">The rule parameters.</param>
|
||||
/// <returns>
|
||||
/// IEnumerable<RuleParameter>.
|
||||
/// </returns>
|
||||
public CompiledRuleParam CompileParamsExpression(Rule rule, IEnumerable<RuleParameter> ruleParams)
|
||||
{
|
||||
|
||||
CompiledRuleParam compiledRuleParam = null;
|
||||
|
||||
if (rule.LocalParams != null)
|
||||
{
|
||||
var compiledParameters = new List<CompiledParam>();
|
||||
var evaluatedParameters = new List<RuleParameter>();
|
||||
foreach (var param in rule.LocalParams)
|
||||
{
|
||||
IEnumerable<ParameterExpression> typeParameterExpressions = GetParameterExpression(ruleParams.ToArray()).ToList(); // calling ToList to avoid multiple calls this the method for nested rule scenario.
|
||||
ParameterExpression ruleInputExp = Expression.Parameter(typeof(RuleInput), nameof(RuleInput));
|
||||
var ruleParamExpression = GetExpressionForRuleParam(param, typeParameterExpressions, ruleInputExp);
|
||||
var lambdaParameterExps = new List<ParameterExpression>(typeParameterExpressions) { ruleInputExp };
|
||||
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);
|
||||
ruleParams = ruleParams.Concat(new List<RuleParameter> { evaluatedParam });
|
||||
evaluatedParameters.Add(evaluatedParam);
|
||||
}
|
||||
|
||||
compiledRuleParam = new CompiledRuleParam { Name = rule.RuleName, CompiledParameters = compiledParameters, RuleParameters = evaluatedParameters };
|
||||
}
|
||||
|
||||
return compiledRuleParam;
|
||||
}
|
||||
|
||||
/// <summary>Evaluates the compiled parameter.</summary>
|
||||
/// <param name="paramName">Name of the parameter.</param>
|
||||
/// <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<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);
|
||||
}
|
||||
|
||||
// <summary>
|
||||
/// Gets the parameter expression.
|
||||
/// </summary>
|
||||
/// <param name="ruleParams">The types.</param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// types
|
||||
/// or
|
||||
/// type
|
||||
/// </exception>
|
||||
private IEnumerable<ParameterExpression> GetParameterExpression(params RuleParameter[] ruleParams)
|
||||
{
|
||||
foreach (var ruleParam in ruleParams)
|
||||
{
|
||||
if (ruleParam == null)
|
||||
{
|
||||
throw new ArgumentException($"{nameof(ruleParam)} can't be null.");
|
||||
}
|
||||
|
||||
yield return Expression.Parameter(ruleParam.Type, ruleParam.Name);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the expression for rule.
|
||||
/// </summary>
|
||||
/// <param name="param">The rule.</param>
|
||||
/// <param name="typeParameterExpressions">The type parameter expressions.</param>
|
||||
/// <param name="ruleInputExp">The rule input exp.</param>
|
||||
/// <returns></returns>
|
||||
private Expression GetExpressionForRuleParam(LocalParam param, IEnumerable<ParameterExpression> typeParameterExpressions, ParameterExpression ruleInputExp)
|
||||
{
|
||||
return BuildExpression(param, typeParameterExpressions, ruleInputExp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the expression.
|
||||
/// </summary>
|
||||
/// <param name="param">The rule.</param>
|
||||
/// <param name="typeParameterExpressions">The type parameter expressions.</param>
|
||||
/// <param name="ruleInputExp">The rule input exp.</param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="InvalidOperationException"></exception>
|
||||
private Expression BuildExpression(LocalParam param, IEnumerable<ParameterExpression> typeParameterExpressions, ParameterExpression ruleInputExp)
|
||||
{
|
||||
var ruleExpressionBuilder = _expressionBuilderFactory.RuleGetExpressionBuilder(RuleExpressionType.LambdaExpression);
|
||||
|
||||
var expression = ruleExpressionBuilder.BuildExpressionForRuleParam(param, typeParameterExpressions, ruleInputExp);
|
||||
|
||||
return expression;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -26,6 +26,9 @@ namespace RulesEngine
|
|||
/// </summary>
|
||||
private readonly RuleExpressionBuilderFactory _expressionBuilderFactory;
|
||||
|
||||
/// <summary>
|
||||
/// The logger
|
||||
/// </summary>
|
||||
private readonly ILogger _logger;
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -9,42 +9,68 @@ using System.Linq;
|
|||
|
||||
namespace RulesEngine
|
||||
{
|
||||
/// <summary>Class RulesCache.</summary>
|
||||
internal class RulesCache
|
||||
{
|
||||
private ConcurrentDictionary<string, CompiledRule> _compileRules = new ConcurrentDictionary<string, CompiledRule>();
|
||||
/// <summary>The compile rules</summary>
|
||||
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>();
|
||||
|
||||
/// <summary>Determines whether [contains workflow rules] [the specified workflow name].</summary>
|
||||
/// <param name="workflowName">Name of the workflow.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if [contains workflow rules] [the specified workflow name]; otherwise, <c>false</c>.</returns>
|
||||
public bool ContainsWorkflowRules(string workflowName)
|
||||
{
|
||||
return _workflowRules.ContainsKey(workflowName);
|
||||
}
|
||||
|
||||
/// <summary>Determines whether [contains compiled rules] [the specified workflow name].</summary>
|
||||
/// <param name="workflowName">Name of the workflow.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if [contains compiled rules] [the specified workflow name]; otherwise, <c>false</c>.</returns>
|
||||
public bool ContainsCompiledRules(string workflowName)
|
||||
{
|
||||
return _compileRules.ContainsKey(workflowName);
|
||||
}
|
||||
|
||||
/// <summary>Adds the or update workflow rules.</summary>
|
||||
/// <param name="workflowName">Name of the workflow.</param>
|
||||
/// <param name="rules">The rules.</param>
|
||||
public void AddOrUpdateWorkflowRules(string workflowName, WorkflowRules rules)
|
||||
{
|
||||
_workflowRules.AddOrUpdate(workflowName, rules, (k, v) => rules);
|
||||
}
|
||||
|
||||
public void AddOrUpdateCompiledRule(string compiledRuleKey, CompiledRule compiledRule)
|
||||
/// <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<CompiledRule> compiledRule)
|
||||
{
|
||||
_compileRules.AddOrUpdate(compiledRuleKey, compiledRule, (k, v) => compiledRule);
|
||||
}
|
||||
|
||||
/// <summary>Clears this instance.</summary>
|
||||
public void Clear()
|
||||
{
|
||||
_workflowRules.Clear();
|
||||
_compileRules.Clear();
|
||||
}
|
||||
|
||||
/// <summary>Gets the rules.</summary>
|
||||
/// <param name="workflowName">Name of the workflow.</param>
|
||||
/// <returns>IEnumerable<Rule>.</returns>
|
||||
public IEnumerable<Rule> GetRules(string workflowName)
|
||||
{
|
||||
return _workflowRules[workflowName].Rules;
|
||||
}
|
||||
|
||||
/// <summary>Gets the work flow rules.</summary>
|
||||
/// <param name="workflowName">Name of the workflow.</param>
|
||||
/// <returns>WorkflowRules.</returns>
|
||||
/// <exception cref="Exception">Could not find injected Workflow: {wfname}</exception>
|
||||
public WorkflowRules GetWorkFlowRules(string workflowName)
|
||||
{
|
||||
_workflowRules.TryGetValue(workflowName, out var workflowRules);
|
||||
|
@ -64,7 +90,8 @@ namespace RulesEngine
|
|||
{
|
||||
throw new Exception($"Could not find injected Workflow: {wfname}");
|
||||
}
|
||||
workflowRules.Rules.AddRange(injectedWorkflow.Rules);
|
||||
|
||||
workflowRules.Rules.ToList().AddRange(injectedWorkflow.Rules);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,11 +99,57 @@ namespace RulesEngine
|
|||
}
|
||||
}
|
||||
|
||||
public CompiledRule GetCompiledRules(string compiledRulesKey)
|
||||
/// <summary>Gets the rules cache key.</summary>
|
||||
/// <param name="workflowName">Name of the workflow.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
/// <exception cref="Exception">Could not find injected Workflow: {wfname}</exception>
|
||||
public string GetRulesCacheKey(string workflowName)
|
||||
{
|
||||
_workflowRules.TryGetValue(workflowName, out var workflowRules);
|
||||
if (workflowRules == null) return string.Empty;
|
||||
else
|
||||
{
|
||||
var ruleCacheKey = workflowName + "_";
|
||||
if (workflowRules.WorkflowRulesToInject?.Any() == true)
|
||||
{
|
||||
if (workflowRules.Rules == null)
|
||||
{
|
||||
workflowRules.Rules = new List<Rule>();
|
||||
}
|
||||
foreach (string wfname in workflowRules.WorkflowRulesToInject)
|
||||
{
|
||||
var injectedWorkflow = GetWorkFlowRules(wfname);
|
||||
if (injectedWorkflow == null)
|
||||
{
|
||||
throw new Exception($"Could not find injected Workflow: {wfname}");
|
||||
}
|
||||
|
||||
var lstRuleNames = injectedWorkflow.Rules.Select(s => s.RuleName)?.ToList();
|
||||
lstRuleNames?.Add(workflowName);
|
||||
ruleCacheKey += string.Join("_", lstRuleNames);
|
||||
}
|
||||
}
|
||||
|
||||
if (workflowRules.Rules != null)
|
||||
{
|
||||
var lstRuleNames = workflowRules.Rules.Select(s => s.RuleName)?.ToList();
|
||||
ruleCacheKey += string.Join("_", lstRuleNames);
|
||||
}
|
||||
|
||||
return ruleCacheKey;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Gets the compiled rules.</summary>
|
||||
/// <param name="compiledRulesKey">The compiled rules key.</param>
|
||||
/// <returns>CompiledRule.</returns>
|
||||
public IEnumerable<CompiledRule> GetCompiledRules(string compiledRulesKey)
|
||||
{
|
||||
return _compileRules[compiledRulesKey];
|
||||
}
|
||||
|
||||
/// <summary>Removes the specified workflow name.</summary>
|
||||
/// <param name="workflowName">Name of the workflow.</param>
|
||||
public void Remove(string workflowName)
|
||||
{
|
||||
if (_workflowRules.TryRemove(workflowName, out WorkflowRules workflowObj))
|
||||
|
@ -84,7 +157,7 @@ namespace RulesEngine
|
|||
var compiledKeysToRemove = _compileRules.Keys.Where(key => key.StartsWith(workflowName));
|
||||
foreach (var key in compiledKeysToRemove)
|
||||
{
|
||||
_compileRules.TryRemove(key, out CompiledRule val);
|
||||
_compileRules.TryRemove(key, out IEnumerable<CompiledRule> val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,27 +1,60 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using FluentValidation;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using RulesEngine.Exceptions;
|
||||
using RulesEngine.HelperFunctions;
|
||||
using RulesEngine.Interfaces;
|
||||
using RulesEngine.Models;
|
||||
using RulesEngine.Validators;
|
||||
using RulesEngine.Exceptions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using FluentValidation;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace RulesEngine
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <seealso cref="RulesEngine.Interfaces.IRulesEngine" />
|
||||
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();
|
||||
|
||||
/// <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
|
||||
|
||||
#region Constructor
|
||||
|
@ -40,6 +73,7 @@ namespace RulesEngine
|
|||
{
|
||||
_logger = logger ?? new NullLogger<RulesEngine>();
|
||||
_reSettings = reSettings ?? new ReSettings();
|
||||
ruleParamCompiler = new ParamCompiler(new RuleExpressionBuilderFactory(_reSettings), _logger);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
@ -81,6 +115,11 @@ namespace RulesEngine
|
|||
|
||||
#region Private Methods
|
||||
|
||||
/// <summary>
|
||||
/// Adds the workflow.
|
||||
/// </summary>
|
||||
/// <param name="workflowRules">The workflow rules.</param>
|
||||
/// <exception cref="RuleValidationException"></exception>
|
||||
public void AddWorkflow(params WorkflowRules[] workflowRules)
|
||||
{
|
||||
try
|
||||
|
@ -98,11 +137,18 @@ namespace RulesEngine
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the workflows.
|
||||
/// </summary>
|
||||
public void ClearWorkflows()
|
||||
{
|
||||
_rulesCache.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the workflow.
|
||||
/// </summary>
|
||||
/// <param name="workflowNames">The workflow names.</param>
|
||||
public void RemoveWorkflow(params string[] workflowNames)
|
||||
{
|
||||
foreach (var workflowName in workflowNames)
|
||||
|
@ -122,7 +168,7 @@ namespace RulesEngine
|
|||
{
|
||||
List<RuleResultTree> result;
|
||||
|
||||
if (RegisterRule(workflowName, ruleParams))
|
||||
if (RegisterCompiledRule(workflowName, ruleParams))
|
||||
{
|
||||
result = ExecuteRuleByWorkflow(workflowName, ruleParams);
|
||||
}
|
||||
|
@ -135,31 +181,45 @@ namespace RulesEngine
|
|||
return result;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This will compile the rules and store them to dictionary
|
||||
/// </summary>
|
||||
/// <typeparam name="T">type of entity</typeparam>
|
||||
/// <param name="workflowName">workflow name</param>
|
||||
/// <returns>bool result</returns>
|
||||
private bool RegisterRule(string workflowName, params RuleParameter[] ruleParams)
|
||||
/// <param name="ruleParams">The rule parameters.</param>
|
||||
/// <returns>
|
||||
/// bool result
|
||||
/// </returns>
|
||||
private bool RegisterCompiledRule(string workflowName, params RuleParameter[] ruleParams)
|
||||
{
|
||||
string compileRulesKey = GetCompileRulesKey(workflowName, ruleParams);
|
||||
string compileRulesKey = _rulesCache.GetRulesCacheKey(workflowName);
|
||||
if (_rulesCache.ContainsCompiledRules(compileRulesKey))
|
||||
return true;
|
||||
|
||||
var workflowRules = _rulesCache.GetWorkFlowRules(workflowName);
|
||||
|
||||
if (workflowRules != null)
|
||||
{
|
||||
var lstFunc = new List<Delegate>();
|
||||
var lstFunc = new List<CompiledRule>();
|
||||
var ruleCompiler = new RuleCompiler(new RuleExpressionBuilderFactory(_reSettings), _logger);
|
||||
foreach (var rule in _rulesCache.GetRules(workflowName))
|
||||
{
|
||||
RuleCompiler ruleCompiler = new RuleCompiler(new RuleExpressionBuilderFactory(_reSettings), _logger);
|
||||
lstFunc.Add(ruleCompiler.CompileRule(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());
|
||||
lstFunc.Add(new CompiledRule { Rule = compiledRule, CompiledParameters = compiledRuleParam });
|
||||
}
|
||||
|
||||
_rulesCache.AddOrUpdateCompiledRule(compileRulesKey, new CompiledRule() { CompiledRules = lstFunc });
|
||||
_rulesCache.AddOrUpdateCompiledRule(compileRulesKey, lstFunc);
|
||||
_logger.LogTrace($"Rules has been compiled for the {workflowName} workflow and added to dictionary");
|
||||
return true;
|
||||
}
|
||||
|
@ -169,6 +229,7 @@ namespace RulesEngine
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private static string GetCompileRulesKey(string workflowName, RuleParameter[] ruleParams)
|
||||
{
|
||||
return $"{workflowName}-" + String.Join("-", ruleParams.Select(c => c.Type.Name));
|
||||
|
@ -180,20 +241,95 @@ namespace RulesEngine
|
|||
/// <param name="workflowName"></param>
|
||||
/// <param name="ruleParams"></param>
|
||||
/// <returns>list of rule result set</returns>
|
||||
private List<RuleResultTree> ExecuteRuleByWorkflow(string workflowName, RuleParameter[] ruleParams)
|
||||
private List<RuleResultTree> ExecuteRuleByWorkflow(string workflowName, RuleParameter[] ruleParameters)
|
||||
{
|
||||
_logger.LogTrace($"Compiled rules found for {workflowName} workflow and executed");
|
||||
|
||||
List<RuleResultTree> result = new List<RuleResultTree>();
|
||||
var compileRulesKey = GetCompileRulesKey(workflowName, ruleParams);
|
||||
var inputs = ruleParams.Select(c => c.Value);
|
||||
foreach (var compiledRule in _rulesCache.GetCompiledRules(compileRulesKey).CompiledRules)
|
||||
string compileRulesKey = _rulesCache.GetRulesCacheKey(workflowName);
|
||||
foreach (var compiledRule in _rulesCache.GetCompiledRules(compileRulesKey))
|
||||
{
|
||||
result.Add(compiledRule.DynamicInvoke(new List<object>(inputs) { new RuleInput() }.ToArray()) as RuleResultTree);
|
||||
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);
|
||||
}
|
||||
|
||||
FormatErrorMessages(result?.Where(r => !r.IsSuccess));
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The result
|
||||
/// </summary>
|
||||
/// <param name="result">The result.</param>
|
||||
/// <returns>Updated error message.</returns>
|
||||
private IEnumerable<RuleResultTree> FormatErrorMessages(IEnumerable<RuleResultTree> result)
|
||||
{
|
||||
foreach (var error in result)
|
||||
{
|
||||
var errorParameters = Regex.Matches(error?.Rule?.ErrorMessage, ParamParseRegex);
|
||||
var errorMessage = error?.Rule?.ErrorMessage;
|
||||
var evaluatedParams = error?.RuleEvaluatedParams;
|
||||
foreach (var param in errorParameters)
|
||||
{
|
||||
var paramVal = param?.ToString();
|
||||
var property = paramVal?.Substring(2, paramVal.Length - 3);
|
||||
if (property?.Split('.')?.Count() > 1)
|
||||
{
|
||||
var typeName = property?.Split('.')?[0];
|
||||
var propertyName = property?.Split('.')?[1];
|
||||
errorMessage = UpdateErrorMessage(errorMessage, evaluatedParams, property, typeName, propertyName);
|
||||
}
|
||||
else
|
||||
{
|
||||
var arrParams = evaluatedParams?.Select(c => new { c.Name, c.Value });
|
||||
var model = arrParams?.Where(a => string.Equals(a.Name, property))?.FirstOrDefault();
|
||||
var value = model?.Value != null ? JsonConvert.SerializeObject(model?.Value) : null;
|
||||
errorMessage = errorMessage?.Replace($"$({property})", value ?? $"$({property})");
|
||||
}
|
||||
}
|
||||
|
||||
error.Rule.ErrorMessage = errorMessage;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the error message.
|
||||
/// </summary>
|
||||
/// <param name="errorMessage">The error message.</param>
|
||||
/// <param name="evaluatedParams">The evaluated parameters.</param>
|
||||
/// <param name="property">The property.</param>
|
||||
/// <param name="typeName">Name of the type.</param>
|
||||
/// <param name="propertyName">Name of the property.</param>
|
||||
/// <returns>Updated error message.</returns>
|
||||
private static string UpdateErrorMessage(string errorMessage, IEnumerable<RuleParameter> evaluatedParams, string property, string typeName, string propertyName)
|
||||
{
|
||||
var arrParams = evaluatedParams?.Select(c => new { c.Name, c.Value });
|
||||
var model = arrParams?.Where(a => string.Equals(a.Name, typeName))?.FirstOrDefault();
|
||||
if (model != null)
|
||||
{
|
||||
var modelJson = JsonConvert.SerializeObject(model?.Value);
|
||||
var jObj = JObject.Parse(modelJson);
|
||||
JToken jToken = null;
|
||||
var val = jObj?.TryGetValue(propertyName, StringComparison.OrdinalIgnoreCase, out jToken);
|
||||
errorMessage = errorMessage.Replace($"$({property})", jToken != null ? jToken?.ToString() : $"({property})");
|
||||
}
|
||||
|
||||
return errorMessage;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,22 +2,35 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<Version>2.0.1</Version>
|
||||
<Version>2.1.0</Version>
|
||||
<Copyright>Copyright (c) Microsoft Corporation.</Copyright>
|
||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||
<PackageProjectUrl>https://github.com/microsoft/RulesEngine</PackageProjectUrl>
|
||||
<Authors>Dishant Munjal, Abbas Cyclewala, Yogesh Prajapati</Authors>
|
||||
<Authors>Dishant Munjal, Abbas Cyclewala, Yogesh Prajapati, Deepak Joshi, Pritam Deshmukh, Chetanya Kumar</Authors>
|
||||
<Description>Rules Engine is a package for abstracting business logic/rules/policies out of the system. This works in a very simple way by giving you an ability to put your rules in a store outside the core logic of the system thus ensuring that any change in rules doesn't affect the core system.</Description>
|
||||
<PackageTags>BRE, Rules Engine, Abstraction</PackageTags>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<Optimize>true</Optimize>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="SourceLink">
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<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.0.18" />
|
||||
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.1.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"sdk": {
|
||||
"version": "3.1.101"
|
||||
"version": "3.1.301"
|
||||
}
|
||||
}
|
|
@ -13,6 +13,7 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using Xunit;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using RulesEngine.HelperFunctions;
|
||||
|
||||
namespace RulesEngine.UnitTest
|
||||
{
|
||||
|
@ -32,7 +33,7 @@ namespace RulesEngine.UnitTest
|
|||
public void RulesEngine_InjectedRules_ReturnsListOfRuleResultTree(string ruleFileName)
|
||||
{
|
||||
var re = GetRulesEngine(ruleFileName);
|
||||
|
||||
|
||||
dynamic input1 = GetInput1();
|
||||
dynamic input2 = GetInput2();
|
||||
dynamic input3 = GetInput3();
|
||||
|
@ -67,7 +68,7 @@ namespace RulesEngine.UnitTest
|
|||
dynamic input2 = GetInput2();
|
||||
dynamic input3 = GetInput3();
|
||||
|
||||
var result = re.ExecuteRule("inputWorkflow",input1);
|
||||
var result = re.ExecuteRule("inputWorkflow", input1);
|
||||
Assert.NotNull(result);
|
||||
Assert.IsType<List<RuleResultTree>>(result);
|
||||
}
|
||||
|
@ -153,6 +154,37 @@ 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_Rull_Params_Returns_Success(string ruleFileName)
|
||||
{
|
||||
dynamic[] inputs = GetInputs4();
|
||||
|
||||
var ruleParams = new List<RuleParameter>();
|
||||
|
||||
for (int i = 0; i < inputs.Length; i++)
|
||||
{
|
||||
var input = inputs[i];
|
||||
var obj = Utils.GetTypedObject(input);
|
||||
ruleParams.Add(new RuleParameter($"input{i + 1}", obj));
|
||||
}
|
||||
|
||||
var files = Directory.GetFiles(Directory.GetCurrentDirectory(), ruleFileName, SearchOption.AllDirectories);
|
||||
if (files == null || files.Length == 0)
|
||||
throw new Exception("Rules not found.");
|
||||
|
||||
var fileData = File.ReadAllText(files[0]);
|
||||
var bre = new RulesEngine(JsonConvert.DeserializeObject<WorkflowRules[]>(fileData), null);
|
||||
var result = bre.ExecuteRule("inputWorkflow", ruleParams?.ToArray()); ;
|
||||
var ruleResult = result?.FirstOrDefault(r => string.Equals(r.Rule.RuleName, "GiveDiscount10", StringComparison.OrdinalIgnoreCase));
|
||||
Assert.True(ruleResult.IsSuccess);
|
||||
}
|
||||
|
||||
private RulesEngine CreateRulesEngine(WorkflowRules workflow)
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(workflow);
|
||||
|
@ -172,7 +204,7 @@ namespace RulesEngine.UnitTest
|
|||
|
||||
var injectWorkflowStr = JsonConvert.SerializeObject(injectWorkflow);
|
||||
var mockLogger = new Mock<ILogger>();
|
||||
return new RulesEngine(new string[] { data, injectWorkflowStr}, mockLogger.Object);
|
||||
return new RulesEngine(new string[] { data, injectWorkflowStr }, mockLogger.Object);
|
||||
}
|
||||
|
||||
|
||||
|
@ -196,6 +228,44 @@ namespace RulesEngine.UnitTest
|
|||
var telemetryInfo = "{\"noOfVisitsPerMonth\": 10,\"percentageOfBuyingToVisit\": 15}";
|
||||
return JsonConvert.DeserializeObject<ExpandoObject>(telemetryInfo, converter);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the inputs.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The inputs.
|
||||
/// </returns>
|
||||
private static dynamic[] GetInputs4()
|
||||
{
|
||||
var basicInfo = "{\"name\": \"Dishant\",\"email\": \"dishantmunjal@live.com\",\"creditHistory\": \"good\",\"country\": \"canada\",\"loyalityFactor\": 3,\"totalPurchasesToDate\": 70000}";
|
||||
var orderInfo = "{\"totalOrders\": 50,\"recurringItems\": 2}";
|
||||
var telemetryInfo = "{\"noOfVisitsPerMonth\": 10,\"percentageOfBuyingToVisit\": 15}";
|
||||
var laborCategoriesInput = "[{\"country\": \"india\", \"loyalityFactor\": 2, \"totalPurchasesToDate\": 20000}]";
|
||||
var currentLaborCategoryInput = "{\"CurrentLaborCategoryProp\":\"TestVal2\"}";
|
||||
|
||||
var converter = new ExpandoObjectConverter();
|
||||
var settings = new JsonSerializerSettings
|
||||
{
|
||||
ContractResolver = new PrivateSetterContractResolver()
|
||||
};
|
||||
|
||||
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);
|
||||
dynamic input5 = JsonConvert.DeserializeObject<ExpandoObject>(orderInfo, converter);
|
||||
|
||||
var inputs = new dynamic[]
|
||||
{
|
||||
input1,
|
||||
input2,
|
||||
input3,
|
||||
input4,
|
||||
input5
|
||||
};
|
||||
|
||||
return inputs;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace RulesEngine.UnitTest
|
||||
{
|
||||
/// <summary>
|
||||
/// Class RuleTestClass.
|
||||
/// </summary>
|
||||
public class RuleTestClass
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the country.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The country.
|
||||
/// </value>
|
||||
[JsonProperty("country")]
|
||||
public string Country { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the loyality factor.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The loyality factor.
|
||||
/// </value>
|
||||
[JsonProperty("loyalityFactor")]
|
||||
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; }
|
||||
}
|
||||
}
|
|
@ -24,6 +24,9 @@
|
|||
<None Update="TestData\rules1.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TestData\rules4.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TestData\rules3.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
[
|
||||
{
|
||||
"WorkflowName": "inputWorkflow",
|
||||
"Rules": [
|
||||
{
|
||||
"RuleName": "GiveDiscount10",
|
||||
"SuccessEvent": "10",
|
||||
"ErrorMessage": "One or more adjust rules failed, with loyalityFactor : $(model1.loyalityFactor), country : $(model1.country), totalPurchasesToDate : $(model1.totalPurchasesToDate), model2 : $(model2)",
|
||||
"ErrorType": "Error",
|
||||
"localParams": [
|
||||
{
|
||||
"Name": "model1",
|
||||
"Expression": "input1.FirstOrDefault(country.Equals(\"india\", StringComparison.OrdinalIgnoreCase))"
|
||||
},
|
||||
{
|
||||
"Name": "model2",
|
||||
"Expression": "model1.country == \"india\""
|
||||
}
|
||||
],
|
||||
"RuleExpressionType": "LambdaExpression",
|
||||
"Expression": "model1.country == \"india\" AND model1.loyalityFactor <= 2 AND model1.totalPurchasesToDate >= 5000 AND model2"
|
||||
},
|
||||
{
|
||||
"RuleName": "GiveDiscount100",
|
||||
"SuccessEvent": "10",
|
||||
"ErrorMessage": "One or more adjust rules failed, with loyalityFactor : $(model1.loyalityFactor), country : $(model1.country), totalPurchasesToDate : $(model1.totalPurchasesToDate), model2 : $(model2)",
|
||||
"ErrorType": "Error",
|
||||
"localParams": [
|
||||
{
|
||||
"Name": "model1",
|
||||
"Expression": "input1.FirstOrDefault(country.Equals(\"india\", StringComparison.OrdinalIgnoreCase))"
|
||||
},
|
||||
{
|
||||
"Name": "model2",
|
||||
"Expression": "model1.country == \"india\""
|
||||
}
|
||||
],
|
||||
"RuleExpressionType": "LambdaExpression",
|
||||
"Expression": "model1.country == \"india\" AND model1.loyalityFactor < 0 AND model1.totalPurchasesToDate >= 5000 AND model2"
|
||||
},
|
||||
{
|
||||
"RuleName": "GiveDiscount25",
|
||||
"SuccessEvent": "25",
|
||||
"ErrorMessage": "One or more adjust rules failed, country : $(input4.country), loyalityFactor : $(input4.loyalityFactor), totalPurchasesToDate : $(input4.totalPurchasesToDate), totalOrders : $(input5.totalOrders), noOfVisitsPerMonth : $(input30.noOfVisitsPerMonth)",
|
||||
"ErrorType": "Error",
|
||||
"RuleExpressionType": "LambdaExpression",
|
||||
"Expression": "input4.country == \"india\" AND input4.loyalityFactor >= 2 AND input4.totalPurchasesToDate <= 10 AND input5.totalOrders > 2 AND input3.noOfVisitsPerMonth > 5"
|
||||
},
|
||||
{
|
||||
"RuleName": "GiveDiscount30",
|
||||
"SuccessEvent": "30",
|
||||
"ErrorMessage": "One or more adjust rules failed.",
|
||||
"ErrorType": "Error",
|
||||
"RuleExpressionType": "LambdaExpression",
|
||||
"Expression": "input4.loyalityFactor > 30 AND input4.totalPurchasesToDate >= 50000 AND input4.totalPurchasesToDate <= 100000 AND input5.totalOrders > 5 AND input3.noOfVisitsPerMonth > 15"
|
||||
},
|
||||
{
|
||||
"RuleName": "GiveDiscount35",
|
||||
"SuccessEvent": "35",
|
||||
"ErrorMessage": "One or more adjust rules failed, totalPurchasesToDate : $(input4.totalPurchasesToDate), totalOrders : $(input5.totalOrders)",
|
||||
"ErrorType": "Error",
|
||||
"RuleExpressionType": "LambdaExpression",
|
||||
"Expression": "input4.loyalityFactor > 30 AND input4.totalPurchasesToDate >= 100000 AND input5.totalOrders > 15 AND input3.noOfVisitsPerMonth > 25"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
Loading…
Reference in New Issue