Added option to disable auto type registry (#501)
* - added option to disable auto type registry - removed caching from RuleExpressionParser * updated test cases * added test casespull/504/head v5.0.1
parent
fe70cdad88
commit
103e817431
|
@ -2,6 +2,10 @@
|
|||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [5.0.1]
|
||||
- Added option to disable automatic type registry for input parameters in reSettings
|
||||
- Added option to make expression case sensitive in reSettings
|
||||
|
||||
## [5.0.0]
|
||||
- Fixed security bug related to System.Dynamic.Linq.Core
|
||||
|
||||
|
|
|
@ -17,15 +17,11 @@ namespace RulesEngine.ExpressionBuilders
|
|||
public class RuleExpressionParser
|
||||
{
|
||||
private readonly ReSettings _reSettings;
|
||||
private static MemCache _memoryCache;
|
||||
private readonly IDictionary<string, MethodInfo> _methodInfo;
|
||||
|
||||
public RuleExpressionParser(ReSettings reSettings)
|
||||
{
|
||||
_reSettings = reSettings;
|
||||
_memoryCache = _memoryCache ?? new MemCache(new MemCacheConfig {
|
||||
SizeLimit = 1000
|
||||
});
|
||||
_methodInfo = new Dictionary<string, MethodInfo>();
|
||||
PopulateMethodInfo();
|
||||
}
|
||||
|
@ -38,7 +34,8 @@ namespace RulesEngine.ExpressionBuilders
|
|||
public Expression Parse(string expression, ParameterExpression[] parameters, Type returnType)
|
||||
{
|
||||
var config = new ParsingConfig {
|
||||
CustomTypeProvider = new CustomTypeProvider(_reSettings.CustomTypes)
|
||||
CustomTypeProvider = new CustomTypeProvider(_reSettings.CustomTypes),
|
||||
IsCaseSensitive = _reSettings.IsExpressionCaseSensitive
|
||||
};
|
||||
return new ExpressionParser(parameters, expression, new object[] { }, config).Parse(returnType);
|
||||
|
||||
|
@ -51,19 +48,17 @@ namespace RulesEngine.ExpressionBuilders
|
|||
{
|
||||
rtype = null;
|
||||
}
|
||||
var cacheKey = GetCacheKey(expression, ruleParams, typeof(T));
|
||||
return _memoryCache.GetOrCreate(cacheKey, () => {
|
||||
var parameterExpressions = GetParameterExpression(ruleParams).ToArray();
|
||||
var parameterExpressions = GetParameterExpression(ruleParams).ToArray();
|
||||
|
||||
var e = Parse(expression, parameterExpressions, rtype);
|
||||
if(rtype == null)
|
||||
{
|
||||
e = Expression.Convert(e, typeof(T));
|
||||
}
|
||||
var expressionBody = new List<Expression>() { e };
|
||||
var wrappedExpression = WrapExpression<T>(expressionBody, parameterExpressions, new ParameterExpression[] { });
|
||||
return wrappedExpression.CompileFast();
|
||||
});
|
||||
var e = Parse(expression, parameterExpressions, rtype);
|
||||
if(rtype == null)
|
||||
{
|
||||
e = Expression.Convert(e, typeof(T));
|
||||
}
|
||||
var expressionBody = new List<Expression>() { e };
|
||||
var wrappedExpression = WrapExpression<T>(expressionBody, parameterExpressions, new ParameterExpression[] { });
|
||||
return wrappedExpression.CompileFast();
|
||||
|
||||
}
|
||||
|
||||
private Expression<Func<object[], T>> WrapExpression<T>(List<Expression> expressionList, ParameterExpression[] parameters, ParameterExpression[] variables)
|
||||
|
@ -155,13 +150,5 @@ namespace RulesEngine.ExpressionBuilders
|
|||
|
||||
return WrapExpression<Dictionary<string,object>>(body, paramExp.ToArray(), variableExp.ToArray());
|
||||
}
|
||||
|
||||
private string GetCacheKey(string expression, RuleParameter[] ruleParameters, Type returnType)
|
||||
{
|
||||
var paramKey = string.Join("|", ruleParameters.Select(c => c.Name + "_" + c.Type.ToString()));
|
||||
var returnTypeKey = returnType?.ToString() ?? "null";
|
||||
var combined = $"Expression:{expression}-Params:{paramKey}-ReturnType:{returnTypeKey}";
|
||||
return combined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,6 @@ namespace RulesEngine.Models
|
|||
[ExcludeFromCodeCoverage]
|
||||
public class ReSettings
|
||||
{
|
||||
|
||||
public ReSettings() { }
|
||||
|
||||
// create a copy of settings
|
||||
|
@ -26,7 +25,9 @@ namespace RulesEngine.Models
|
|||
EnableScopedParams = reSettings.EnableScopedParams;
|
||||
NestedRuleExecutionMode = reSettings.NestedRuleExecutionMode;
|
||||
CacheConfig = reSettings.CacheConfig;
|
||||
}
|
||||
IsExpressionCaseSensitive = reSettings.IsExpressionCaseSensitive;
|
||||
AutoRegisterInputType = reSettings.AutoRegisterInputType;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
|
@ -62,6 +63,17 @@ namespace RulesEngine.Models
|
|||
/// </summary>
|
||||
public bool EnableScopedParams { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether expression are case sensitive
|
||||
/// </summary>
|
||||
public bool IsExpressionCaseSensitive { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Auto Registers input type in Custom Type to allow calling method on type.
|
||||
/// Default : true
|
||||
/// </summary>
|
||||
public bool AutoRegisterInputType { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the mode for Nested rule execution, Default: All
|
||||
/// </summary>
|
||||
|
|
|
@ -241,7 +241,7 @@ namespace RulesEngine
|
|||
{
|
||||
return ruleFunc;
|
||||
}
|
||||
var paramDelegate = GetExpressionBuilder(rule.RuleExpressionType).CompileScopedParams(ruleParameters, ruleExpParams);
|
||||
var paramDelegate = CompileScopedParams(rule.RuleExpressionType,ruleParameters, ruleExpParams);
|
||||
|
||||
return (ruleParams) => {
|
||||
var inputs = ruleParams.Select(c => c.Value).ToArray();
|
||||
|
|
|
@ -287,7 +287,10 @@ namespace RulesEngine
|
|||
if (workflow != null)
|
||||
{
|
||||
var dictFunc = new Dictionary<string, RuleFunc<RuleResultTree>>();
|
||||
_reSettings.CustomTypes = _reSettings.CustomTypes.Safe().Union(ruleParams.Select(c => c.Type)).ToArray();
|
||||
if (_reSettings.AutoRegisterInputType)
|
||||
{
|
||||
_reSettings.CustomTypes = _reSettings.CustomTypes.Safe().Union(ruleParams.Select(c => c.Type)).ToArray();
|
||||
}
|
||||
// add separate compilation for global params
|
||||
|
||||
var globalParamExp = new Lazy<RuleExpressionParameter[]>(
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
|
||||
<Version>5.0.0</Version>
|
||||
<Version>5.0.1</Version>
|
||||
<Copyright>Copyright (c) Microsoft Corporation.</Copyright>
|
||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||
<PackageProjectUrl>https://github.com/microsoft/RulesEngine</PackageProjectUrl>
|
||||
|
|
|
@ -5,11 +5,13 @@ using RulesEngine.Actions;
|
|||
using RulesEngine.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace RulesEngine.UnitTest.ActionTests.MockClass
|
||||
{
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class ReturnContextAction : ActionBase
|
||||
{
|
||||
public override ValueTask<object> Run(ActionContext context, RuleParameter[] ruleParameters)
|
||||
|
|
|
@ -254,6 +254,23 @@ namespace RulesEngine.UnitTest
|
|||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void RulesEngine_AddOrUpdate_IncorrectJSON_ThrowsException()
|
||||
{
|
||||
Assert.Throws<RuleValidationException>(() => {
|
||||
var workflow = new Workflow();
|
||||
var re = new RulesEngine();
|
||||
re.AddOrUpdateWorkflow(workflow);
|
||||
});
|
||||
|
||||
Assert.Throws<RuleValidationException>(() => {
|
||||
var workflow = new Workflow() { WorkflowName = "test" };
|
||||
var re = new RulesEngine();
|
||||
re.AddOrUpdateWorkflow(workflow);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
[Theory]
|
||||
[InlineData("rules1.json")]
|
||||
public async Task ExecuteRule_InvalidWorkFlow_ThrowsException(string ruleFileName)
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using RulesEngine.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace RulesEngine.UnitTest
|
||||
{
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class CaseSensitiveTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(true,true,false)]
|
||||
[InlineData(false,true,true)]
|
||||
|
||||
public async Task CaseSensitiveTest(bool caseSensitive, bool expected1, bool expected2)
|
||||
{
|
||||
var reSettings = new ReSettings {
|
||||
IsExpressionCaseSensitive = caseSensitive
|
||||
};
|
||||
|
||||
|
||||
var worflow = new Workflow {
|
||||
WorkflowName = "CaseSensitivityTest",
|
||||
Rules = new[] {
|
||||
new Rule {
|
||||
RuleName = "check same case1",
|
||||
Expression = "input1 == \"hello\""
|
||||
},
|
||||
new Rule {
|
||||
RuleName = "check same case2",
|
||||
Expression = "INPUT1 == \"hello\""
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var re = new RulesEngine(new[] { worflow }, reSettings);
|
||||
var result = await re.ExecuteAllRulesAsync("CaseSensitivityTest", "hello");
|
||||
|
||||
Assert.Equal(expected1, result[0].IsSuccess);
|
||||
Assert.Equal(expected2, result[1].IsSuccess);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -40,7 +40,7 @@
|
|||
{
|
||||
"RuleName": "GiveDiscount25",
|
||||
"SuccessEvent": "25",
|
||||
"ErrorMessage": "One or more adjust rules failed, country : $(input4.country), loyaltyFactor : $(input4.loyaltyFactor), totalPurchasesToDate : $(input4.totalPurchasesToDate), totalOrders : $(input5.totalOrders), noOfVisitsPerMonth : $(input30.noOfVisitsPerMonth)",
|
||||
"ErrorMessage": "One or more adjust rules failed, country : $(input4.country), loyaltyFactor : $(input4.loyaltyFactor), totalPurchasesToDate : $(input4.totalPurchasesToDate), totalOrders : $(input5.totalOrders), noOfVisitsPerMonth : $(input30.noOfVisitsPerMonth), $(model2)",
|
||||
"ErrorType": "Error",
|
||||
"localParams": [
|
||||
{
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using RulesEngine.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace RulesEngine.UnitTest
|
||||
{
|
||||
[Trait("Category", "Unit")]
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class TypedClassTests
|
||||
{
|
||||
public class Transazione
|
||||
{
|
||||
public List<Attore> Attori { get; set; } = new();
|
||||
}
|
||||
public class Attore
|
||||
{
|
||||
public Guid Id { get; internal set; }
|
||||
public string Nome { get; internal set; }
|
||||
public RuoloAttore RuoloAttore { get; internal set; }
|
||||
}
|
||||
|
||||
public enum RuoloAttore
|
||||
{
|
||||
A,
|
||||
B,
|
||||
C
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TypedClassTest()
|
||||
{
|
||||
Workflow workflow = new() {
|
||||
WorkflowName = "Conferimento",
|
||||
Rules = new Rule[] {
|
||||
new() {
|
||||
RuleName = "Attore Da",
|
||||
Enabled = true,
|
||||
ErrorMessage = "Attore Da Id must be defined",
|
||||
SuccessEvent = "10",
|
||||
RuleExpressionType = RuleExpressionType.LambdaExpression,
|
||||
Expression = "transazione.Attori.Any(a => a.RuoloAttore == 1)",
|
||||
},
|
||||
new() {
|
||||
RuleName = "Attore A",
|
||||
Enabled = true,
|
||||
ErrorMessage = "Attore A must be defined",
|
||||
SuccessEvent = "10",
|
||||
RuleExpressionType = RuleExpressionType.LambdaExpression,
|
||||
Expression = "transazione.Attori != null",
|
||||
},
|
||||
}
|
||||
};
|
||||
var reSettings = new ReSettings() {
|
||||
CustomTypes = new Type[] {
|
||||
},
|
||||
AutoRegisterInputType = false
|
||||
};
|
||||
var re = new RulesEngine(reSettings);
|
||||
re.AddWorkflow(workflow);
|
||||
|
||||
var param = new Transazione {
|
||||
Attori = new List<Attore>{
|
||||
new Attore{
|
||||
RuoloAttore = RuoloAttore.B,
|
||||
|
||||
},
|
||||
new Attore {
|
||||
RuoloAttore = RuoloAttore.C
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
var result = await re.ExecuteAllRulesAsync("Conferimento", new RuleParameter("transazione", param));
|
||||
|
||||
Assert.All(result, (res) => Assert.True(res.IsSuccess));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue