Skip to content

Commit

Permalink
Added option to disable auto type registry (#501)
Browse files Browse the repository at this point in the history
* - added option to disable auto type registry
- removed caching from RuleExpressionParser

* updated test cases

* added test cases
  • Loading branch information
abbasc52 committed Jul 12, 2023
1 parent fe70cda commit 103e817
Show file tree
Hide file tree
Showing 11 changed files with 194 additions and 31 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
37 changes: 12 additions & 25 deletions src/RulesEngine/ExpressionBuilders/RuleExpressionParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand All @@ -38,7 +34,8 @@ private void PopulateMethodInfo()
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);

Expand All @@ -51,19 +48,17 @@ public Expression Parse(string expression, ParameterExpression[] parameters, Typ
{
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)
Expand Down Expand Up @@ -155,13 +150,5 @@ private IEnumerable<ParameterExpression> GetParameterExpression(RuleParameter[]

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;
}
}
}
16 changes: 14 additions & 2 deletions src/RulesEngine/Models/ReSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ namespace RulesEngine.Models
[ExcludeFromCodeCoverage]
public class ReSettings
{

public ReSettings() { }

// create a copy of settings
Expand All @@ -26,7 +25,9 @@ internal ReSettings(ReSettings reSettings)
EnableScopedParams = reSettings.EnableScopedParams;
NestedRuleExecutionMode = reSettings.NestedRuleExecutionMode;
CacheConfig = reSettings.CacheConfig;
}
IsExpressionCaseSensitive = reSettings.IsExpressionCaseSensitive;
AutoRegisterInputType = reSettings.AutoRegisterInputType;
}


/// <summary>
Expand Down Expand Up @@ -62,6 +63,17 @@ internal ReSettings(ReSettings reSettings)
/// </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>
Expand Down
2 changes: 1 addition & 1 deletion src/RulesEngine/RuleCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ private RuleFunc<RuleResultTree> GetWrappedRuleFunc(Rule rule, RuleFunc<RuleResu
{
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();
Expand Down
5 changes: 4 additions & 1 deletion src/RulesEngine/RulesEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,10 @@ private bool RegisterRule(string workflowName, params RuleParameter[] ruleParams
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[]>(
Expand Down
2 changes: 1 addition & 1 deletion src/RulesEngine/RulesEngine.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
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)
Expand Down
17 changes: 17 additions & 0 deletions test/RulesEngine.UnitTest/BusinessRuleEngineTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,23 @@ public void RulesEngine_New_IncorrectJSON_ThrowsException()
}


[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)
Expand Down
50 changes: 50 additions & 0 deletions test/RulesEngine.UnitTest/CaseSensitiveTests.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
2 changes: 1 addition & 1 deletion test/RulesEngine.UnitTest/TestData/rules4.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
{
Expand Down
88 changes: 88 additions & 0 deletions test/RulesEngine.UnitTest/TypedClassTests.cs
Original file line number Diff line number Diff line change
@@ -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));

}
}
}

0 comments on commit 103e817

Please sign in to comment.