Abbasc52/null rule param fix (#119)

* Fixed scoped parameter throwing exception on compilation error
* Fixed null RuleParameter throwing exception
* Replaced thrown Exceptions with RuleException
pull/129/head v3.1.0-preview.4
Abbas Cyclewala 2021-04-19 11:21:33 +05:30 committed by GitHub
parent 65d2abbd8b
commit f3ac4316df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 186 additions and 23 deletions

View File

@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Text;
namespace RulesEngine.Exceptions
{
public class ExpressionParserException: Exception
{
public ExpressionParserException(string message, string expression) : base(message)
{
Data.Add("Expression", expression);
}
}
}

View File

@ -0,0 +1,20 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Text;
namespace RulesEngine.Exceptions
{
public class RuleException : Exception
{
public RuleException(string message) : base(message)
{
}
public RuleException(string message, Exception innerException) : base(message, innerException)
{
}
}
}

View File

@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Text;
namespace RulesEngine.Exceptions
{
public class ScopedParamException: Exception
{
public ScopedParamException(string message, Exception innerException, string scopedParamName): base(message,innerException)
{
Data.Add("ScopedParamName", scopedParamName);
}
}
}

View File

@ -1,11 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using RulesEngine.Exceptions;
using RulesEngine.HelperFunctions;
using RulesEngine.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core.Exceptions;
using System.Linq.Expressions;
namespace RulesEngine.ExpressionBuilders
@ -43,7 +45,15 @@ namespace RulesEngine.ExpressionBuilders
internal override LambdaExpression Parse(string expression, ParameterExpression[] parameters, Type returnType)
{
return _ruleExpressionParser.Parse(expression, parameters, returnType);
try
{
return _ruleExpressionParser.Parse(expression, parameters, returnType);
}
catch(ParseException ex)
{
throw new ExpressionParserException(ex.Message, expression);
}
}
internal override Func<object[],Dictionary<string,object>> CompileScopedParams(RuleParameter[] ruleParameters, RuleExpressionParameter[] scopedParameters)

View File

@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using RulesEngine.Exceptions;
using RulesEngine.Models;
using System;
using System.Collections.Generic;
@ -27,9 +28,9 @@ namespace RulesEngine.HelperFunctions
}
catch (Exception ex)
{
HandleRuleException(ex, rule, reSettings);
isSuccess = false;
exceptionMessage = GetExceptionMessage($"Error while executing rule : {rule?.RuleName} - {ex.Message}", reSettings);
HandleRuleException(new RuleException(exceptionMessage,ex), rule, reSettings);
isSuccess = false;
}
return new RuleResultTree {
@ -44,6 +45,11 @@ namespace RulesEngine.HelperFunctions
}
internal static RuleFunc<RuleResultTree> ToRuleExceptionResult(ReSettings reSettings, Rule rule,Exception ex)
{
HandleRuleException(ex, rule, reSettings);
return ToResultTree(reSettings, rule, null, (args) => false, ex.Message);
}
internal static void HandleRuleException(Exception ex, Rule rule, ReSettings reSettings)
{

View File

@ -14,7 +14,7 @@ namespace RulesEngine.Models
public RuleParameter(string name, object value)
{
Value = Utils.GetTypedObject(value);
Init(name, Value.GetType());
Init(name, Value?.GetType());
}
internal RuleParameter(string name, Type type)
@ -29,7 +29,7 @@ namespace RulesEngine.Models
private void Init(string name, Type type)
{
Name = name;
Type = type;
Type = type ?? typeof(object);
ParameterExpression = Expression.Parameter(Type, Name);
}

View File

@ -2,6 +2,7 @@
// Licensed under the MIT License.
using Microsoft.Extensions.Logging;
using RulesEngine.Exceptions;
using RulesEngine.ExpressionBuilders;
using RulesEngine.HelperFunctions;
using RulesEngine.Models;
@ -57,12 +58,14 @@ namespace RulesEngine
/// <returns>Compiled func delegate</returns>
internal RuleFunc<RuleResultTree> CompileRule(Rule rule, RuleParameter[] ruleParams, ScopedParam[] globalParams)
{
if (rule == null)
{
var ex = new ArgumentNullException(nameof(rule));
_logger.LogError(ex.Message);
throw ex;
}
try
{
if (rule == null)
{
throw new ArgumentNullException(nameof(rule));
}
var globalParamExp = GetRuleExpressionParameters(rule.RuleExpressionType,globalParams, ruleParams);
var extendedRuleParams = ruleParams.Concat(globalParamExp.Select(c => new RuleParameter(c.ParameterExpression.Name,c.ParameterExpression.Type)))
.ToArray();
@ -71,8 +74,9 @@ namespace RulesEngine
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
throw;
var message = $"Error while compiling rule `{rule.RuleName}`: {ex.Message}";
_logger.LogError(message);
return Helpers.ToRuleExceptionResult(_reSettings, rule, new RuleException(message, ex));
}
}
@ -125,13 +129,21 @@ namespace RulesEngine
foreach (var lp in localParams)
{
var lpExpression = expressionBuilder.Parse(lp.Expression, parameters.ToArray(), null).Body;
var ruleExpParam = new RuleExpressionParameter() {
ParameterExpression = Expression.Parameter(lpExpression.Type, lp.Name),
ValueExpression = lpExpression
};
parameters.Add(ruleExpParam.ParameterExpression);
ruleExpParams.Add(ruleExpParam);
try
{
var lpExpression = expressionBuilder.Parse(lp.Expression, parameters.ToArray(), null).Body;
var ruleExpParam = new RuleExpressionParameter() {
ParameterExpression = Expression.Parameter(lpExpression.Type, lp.Name),
ValueExpression = lpExpression
};
parameters.Add(ruleExpParam.ParameterExpression);
ruleExpParams.Add(ruleExpParam);
}
catch(Exception ex)
{
var message = $"{ex.Message}, in ScopedParam: {lp.Name}";
throw new RuleException(message);
}
}
}
return ruleExpParams.ToArray();

View File

@ -39,7 +39,7 @@ namespace RulesEngine
#endregion
#region Constructor
public RulesEngine(string[] jsonConfig, ILogger logger, ReSettings reSettings = null) : this(logger, reSettings)
public RulesEngine(string[] jsonConfig, ILogger logger = null, ReSettings reSettings = null) : this(logger, reSettings)
{
var workflowRules = jsonConfig.Select(item => JsonConvert.DeserializeObject<WorkflowRules>(item)).ToArray();
AddWorkflow(workflowRules);

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Version>3.1.0-preview.3</Version>
<Version>3.1.0-preview.4</Version>
<Copyright>Copyright (c) Microsoft Corporation.</Copyright>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageProjectUrl>https://github.com/microsoft/RulesEngine</PackageProjectUrl>

View File

@ -279,7 +279,6 @@ namespace RulesEngine.UnitTest
Assert.Contains(result, c => c.IsSuccess);
}
[Theory]
[InlineData("rules4.json")]
public async Task RulesEngine_Execute_Rule_For_Nested_Rule_Params_Returns_Success(string ruleFileName)
@ -417,7 +416,7 @@ namespace RulesEngine.UnitTest
var utils = new TestInstanceUtils();
await Assert.ThrowsAsync<System.Linq.Dynamic.Core.Exceptions.ParseException>(async () => {
await Assert.ThrowsAsync<RuleException>(async () => {
var result = await re.ExecuteAllRulesAsync("inputWorkflow", new RuleParameter("input1", input1));
});
}
@ -505,7 +504,7 @@ namespace RulesEngine.UnitTest
Country = null
};
_ = await Assert.ThrowsAsync<ArgumentNullException>(async () => await re.ExecuteAllRulesAsync("TestWorkflow", input));
_ = await Assert.ThrowsAsync<RuleException>(async () => await re.ExecuteAllRulesAsync("TestWorkflow", input));
}
@ -606,6 +605,71 @@ namespace RulesEngine.UnitTest
Assert.True(result2.All(c => c.IsSuccess == false));
}
[Fact]
public async Task ExecuteRule_WithNullInput_ShouldNotThrowException()
{
var workflow = new WorkflowRules {
WorkflowName = "Test",
Rules = new Rule[]{
new Rule {
RuleName = "RuleWithLocalParam",
RuleExpressionType = RuleExpressionType.LambdaExpression,
Expression = "input1 == null || input1.hello.world = \"wow\""
}
}
};
var re = new RulesEngine();
re.AddWorkflow(workflow);
var result1 = await re.ExecuteAllRulesAsync("Test", new RuleParameter("input1", value:null));
Assert.True(result1.All(c => c.IsSuccess));
var result2 = await re.ExecuteAllRulesAsync("Test",new object[] { null });
Assert.True(result2.All(c => c.IsSuccess));
dynamic input1 = new ExpandoObject();
input1.hello = new ExpandoObject();
input1.hello.world = "wow";
List<RuleResultTree> result3 = await re.ExecuteAllRulesAsync("Test", input1);
Assert.True(result3.All(c => c.IsSuccess));
}
[Fact]
public async Task ExecuteRule_SpecialCharInWorkflowName_RunsSuccessfully()
{
var workflow = new WorkflowRules {
WorkflowName = "Exámple",
Rules = new Rule[]{
new Rule {
RuleName = "RuleWithLocalParam",
RuleExpressionType = RuleExpressionType.LambdaExpression,
Expression = "input1 == null || input1.hello.world = \"wow\""
}
}
};
var workflowStr = "{\"WorkflowName\":\"Exámple\",\"WorkflowRulesToInject\":null,\"GlobalParams\":null,\"Rules\":[{\"RuleName\":\"RuleWithLocalParam\",\"Properties\":null,\"Operator\":null,\"ErrorMessage\":null,\"Enabled\":true,\"ErrorType\":\"Warning\",\"RuleExpressionType\":\"LambdaExpression\",\"WorkflowRulesToInject\":null,\"Rules\":null,\"LocalParams\":null,\"Expression\":\"input1 == null || input1.hello.world = \\\"wow\\\"\",\"Actions\":null,\"SuccessEvent\":null}]}";
var re = new RulesEngine(new string[] { workflowStr },null,null);
// re.AddWorkflow(workflowStr);
dynamic input1 = new ExpandoObject();
input1.hello = new ExpandoObject();
input1.hello.world = "wow";
List<RuleResultTree> result3 = await re.ExecuteAllRulesAsync("Exámple", input1);
Assert.True(result3.All(c => c.IsSuccess));
}
private RulesEngine CreateRulesEngine(WorkflowRules workflow)
{
var json = JsonConvert.SerializeObject(workflow);

View File

@ -98,6 +98,23 @@ namespace RulesEngine.UnitTest
}
}
[Theory]
[InlineData("GlobalParamsOnly")]
[InlineData("LocalParamsOnly")]
public async Task ErrorInScopedParam_ShouldAppearAsErrorMessage(string workflowName)
{
var workflows = GetWorkflowRulesList();
var engine = new RulesEngine(new string[] { }, null);
engine.AddWorkflow(workflows);
var input = new { };
var result = await engine.ExecuteAllRulesAsync(workflowName, input);
Assert.All(result, c => Assert.False(c.IsSuccess));
}
private void CheckResultTreeContainsAllInputs(string workflowName, List<RuleResultTree> result)
{
var workflow = GetWorkflowRulesList().Single(c => c.WorkflowName == workflowName);