diff --git a/CHANGELOG.md b/CHANGELOG.md index 6122e62..f163cb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ All notable changes to this project will be documented in this file. ## [5.0.3] - Updated dependencies to latest - Fixed RulesEngine throwing exception when type name is same as input name +- Added config to disable FastCompile for expressions +- Added RuleParameter.Create method for better handling on types when value is null ## [5.0.2] - Fixed Scoped Params returning incorrect results in some corner case scenarios diff --git a/docs/index.md b/docs/index.md index 8f60212..4a9d9eb 100644 --- a/docs/index.md +++ b/docs/index.md @@ -37,6 +37,8 @@ RulesEngine is a highly extensible library to build rule based system using C# e - [Steps to use a custom Action](#steps-to-use-a-custom-action) - [Standalone Expression Evaluator](#standalone-expression-evaluator) - [Usage](#usage-2) +- [Settings](#settings) + - [NestedRuleExecutionMode](#nestedruleexecutionmode) @@ -555,3 +557,29 @@ This will output "Hello World" For more advanced usage, refer - https://dotnetfiddle.net/KSX8i0 +## Settings +RulesEngine allows you to pass optional `ReSettings` in constructor to specify certain configuration for RulesEngine. + +Here are the all the options available:- + + +| Property | Type | Default Value | Description | +| --- | --- | --- | --- | +| `CustomTypes` | `Type[]` | N/A | Custom types to be used in rule expressions. | +| `CustomActions` | `Dictionary>` | N/A | Custom actions that can be used in the rules. | +| `EnableExceptionAsErrorMessage` | `bool` | `true` | If `true`, returns any exception occurred while rule execution as an error message. Otherwise, throws an exception. This setting is only applicable if `IgnoreException` is set to `false`. | +| `IgnoreException` | `bool` | `false` | If `true`, it will ignore any exception thrown with rule compilation/execution. | +| `EnableFormattedErrorMessage` | `bool` | `true` | Enables error message formatting. | +| `EnableScopedParams` | `bool` | `true` | Enables global parameters and local parameters for rules. | +| `IsExpressionCaseSensitive` | `bool` | `false` | Sets whether expressions are case sensitive. | +| `AutoRegisterInputType` | `bool` | `true` | Auto registers input type in custom type to allow calling method on type. | +| `NestedRuleExecutionMode` | `NestedRuleExecutionMode` | `All` | Sets the mode for nested rule execution. | +| `CacheConfig` | `MemCacheConfig` | N/A | Configures the memory cache. | +| `UseFastExpressionCompiler` | `bool` | `true` | Whether to use FastExpressionCompiler for rule compilation. | + + +### NestedRuleExecutionMode +| Value | Description | +| --- | --- | +| `All` | Executes all nested rules. | +| `Performance` | Skips nested rules whose execution does not impact parent rule's result. | \ No newline at end of file diff --git a/src/RulesEngine/ExpressionBuilders/RuleExpressionParser.cs b/src/RulesEngine/ExpressionBuilders/RuleExpressionParser.cs index 9d18dc3..da2e823 100644 --- a/src/RulesEngine/ExpressionBuilders/RuleExpressionParser.cs +++ b/src/RulesEngine/ExpressionBuilders/RuleExpressionParser.cs @@ -57,10 +57,19 @@ namespace RulesEngine.ExpressionBuilders } var expressionBody = new List() { e }; var wrappedExpression = WrapExpression(expressionBody, parameterExpressions, new ParameterExpression[] { }); - return wrappedExpression.CompileFast(); + return CompileExpression(wrappedExpression); } + private Func CompileExpression(Expression> expression) + { + if(_reSettings.UseFastExpressionCompiler) + { + return expression.CompileFast(); + } + return expression.Compile(); + } + private Expression> WrapExpression(List expressionList, ParameterExpression[] parameters, ParameterExpression[] variables) { var argExp = Expression.Parameter(typeof(object[]), "args"); @@ -77,7 +86,7 @@ namespace RulesEngine.ExpressionBuilders { ruleExpParams = ruleExpParams ?? new RuleExpressionParameter[] { }; var expression = CreateDictionaryExpression(ruleParams, ruleExpParams); - return expression.CompileFast(); + return CompileExpression(expression); } public T Evaluate(string expression, RuleParameter[] ruleParams) diff --git a/src/RulesEngine/Models/ReSettings.cs b/src/RulesEngine/Models/ReSettings.cs index 7a133ea..5356533 100644 --- a/src/RulesEngine/Models/ReSettings.cs +++ b/src/RulesEngine/Models/ReSettings.cs @@ -27,7 +27,8 @@ namespace RulesEngine.Models CacheConfig = reSettings.CacheConfig; IsExpressionCaseSensitive = reSettings.IsExpressionCaseSensitive; AutoRegisterInputType = reSettings.AutoRegisterInputType; - } + UseFastExpressionCompiler = reSettings.UseFastExpressionCompiler; + } /// @@ -79,6 +80,10 @@ namespace RulesEngine.Models /// public NestedRuleExecutionMode NestedRuleExecutionMode { get; set; } = NestedRuleExecutionMode.All; public MemCacheConfig CacheConfig { get; set; } + /// + /// Whether to use FastExpressionCompiler for rule compilation + /// + public bool UseFastExpressionCompiler { get; set; } = true; } public enum NestedRuleExecutionMode diff --git a/src/RulesEngine/Models/RuleParameter.cs b/src/RulesEngine/Models/RuleParameter.cs index d638729..aafa44a 100644 --- a/src/RulesEngine/Models/RuleParameter.cs +++ b/src/RulesEngine/Models/RuleParameter.cs @@ -17,10 +17,13 @@ namespace RulesEngine.Models Init(name, Value?.GetType()); } - internal RuleParameter(string name, Type type) + + internal RuleParameter(string name, Type type,object value = null) { + Value = Utils.GetTypedObject(value); Init(name, type); } + public Type Type { get; private set; } public string Name { get; private set; } public object Value { get; private set; } @@ -33,5 +36,13 @@ namespace RulesEngine.Models ParameterExpression = Expression.Parameter(Type, Name); } + public static RuleParameter Create(string name, T value) + { + var typedValue = Utils.GetTypedObject(value); + var type = typedValue?.GetType() ?? typeof(T); + return new RuleParameter(name,type,value); + } + + } } diff --git a/test/RulesEngine.UnitTest/BusinessRuleEngineTest.cs b/test/RulesEngine.UnitTest/BusinessRuleEngineTest.cs index ca6a1eb..d27d6cd 100644 --- a/test/RulesEngine.UnitTest/BusinessRuleEngineTest.cs +++ b/test/RulesEngine.UnitTest/BusinessRuleEngineTest.cs @@ -338,8 +338,9 @@ namespace RulesEngine.UnitTest } [Theory] - [InlineData("rules4.json")] - public async Task RulesEngine_Execute_Rule_For_Nested_Rule_Params_Returns_Success(string ruleFileName) + [InlineData("rules4.json", true)] + [InlineData("rules4.json", false)] + public async Task RulesEngine_Execute_Rule_For_Nested_Rule_Params_Returns_Success(string ruleFileName,bool fastExpressionEnabled) { var inputs = GetInputs4(); @@ -359,7 +360,9 @@ namespace RulesEngine.UnitTest } var fileData = File.ReadAllText(files[0]); - var bre = new RulesEngine(JsonConvert.DeserializeObject(fileData), null); + var bre = new RulesEngine(JsonConvert.DeserializeObject(fileData), new ReSettings { + UseFastExpressionCompiler = fastExpressionEnabled + }); var result = await bre.ExecuteAllRulesAsync("inputWorkflow", ruleParams?.ToArray()); var ruleResult = result?.FirstOrDefault(r => string.Equals(r.Rule.RuleName, "GiveDiscount10", StringComparison.OrdinalIgnoreCase)); Assert.True(ruleResult.IsSuccess); diff --git a/test/RulesEngine.UnitTest/RuleExpressionParserTests/RuleExpressionParserTests.cs b/test/RulesEngine.UnitTest/RuleExpressionParserTests/RuleExpressionParserTests.cs index bda3ad2..c69b5be 100644 --- a/test/RulesEngine.UnitTest/RuleExpressionParserTests/RuleExpressionParserTests.cs +++ b/test/RulesEngine.UnitTest/RuleExpressionParserTests/RuleExpressionParserTests.cs @@ -3,14 +3,8 @@ using Newtonsoft.Json.Linq; using RulesEngine.ExpressionBuilders; -using System; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Xunit; -using System.Text.Json; namespace RulesEngine.UnitTest.RuleExpressionParserTests { @@ -59,5 +53,17 @@ namespace RulesEngine.UnitTest.RuleExpressionParserTests Assert.Equal("helloworld", value3); } + + [Theory] + [InlineData(false)] + public void TestExpressionWithDifferentCompilerSettings(bool fastExpressionEnabled){ + var ruleParser = new RuleExpressionParser(new Models.ReSettings() { UseFastExpressionCompiler = fastExpressionEnabled }); + + decimal? d1 = null; + var result = ruleParser.Evaluate("d1 < 20", new[] { Models.RuleParameter.Create("d1", d1) }); + Assert.False(result); + } } + + }