Added option to disable auto type registry (#501)

* - added option to disable auto type registry
- removed caching from RuleExpressionParser

* updated test cases

* added test cases
pull/504/head v5.0.1
Abbas Cyclewala 2023-07-12 18:18:11 +05:30 committed by GitHub
parent fe70cdad88
commit 103e817431
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 194 additions and 31 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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": [
{

View File

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