Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancements #282

Merged
merged 3 commits into from
Nov 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion demo/DemoApp.EFDataExample/RulesEngineContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)

modelBuilder.Entity<Workflow>(entity => {
entity.HasKey(k => k.WorkflowName);
entity.Ignore(b => b.WorkflowRulesToInject);
entity.Ignore(b => b.WorkflowsToInject);
});

Expand Down
15 changes: 15 additions & 0 deletions src/RulesEngine/Actions/ActionContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,21 @@ public RuleResultTree GetParentRuleResult()
{
return _parentResult;
}

public bool TryGetContext<T>(string name,out T output)
{
try
{
output = GetContext<T>(name);
return true;
}
catch(ArgumentException)
{
output = default(T);
return false;
}
}

public T GetContext<T>(string name)
{
try
Expand Down
28 changes: 24 additions & 4 deletions src/RulesEngine/Actions/EvaluateRuleAction.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using RulesEngine.ExpressionBuilders;
using RulesEngine.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace RulesEngine.Actions
{
public class EvaluateRuleAction : ActionBase
{
private readonly RulesEngine _ruleEngine;
private readonly RuleExpressionParser _ruleExpressionParser;

public EvaluateRuleAction(RulesEngine ruleEngine)
public EvaluateRuleAction(RulesEngine ruleEngine, RuleExpressionParser ruleExpressionParser)
{
_ruleEngine = ruleEngine;
_ruleExpressionParser = ruleExpressionParser;
}

internal async override ValueTask<ActionRuleResult> ExecuteAndReturnResultAsync(ActionContext context, RuleParameter[] ruleParameters, bool includeRuleResults = false)
Expand All @@ -23,11 +28,11 @@ internal async override ValueTask<ActionRuleResult> ExecuteAndReturnResultAsync(
List<RuleResultTree> resultList = null;
if (includeRuleResults)
{
resultList = new List<RuleResultTree>(output.Results);
resultList = new List<RuleResultTree>(output?.Results ?? new List<RuleResultTree>() { });
resultList.AddRange(innerResult.Results);
}
return new ActionRuleResult {
Output = output.Output,
Output = output?.Output,
Exception = innerResult.Exception,
Results = resultList
};
Expand All @@ -37,7 +42,22 @@ public async override ValueTask<object> Run(ActionContext context, RuleParameter
{
var workflowName = context.GetContext<string>("workflowName");
var ruleName = context.GetContext<string>("ruleName");
var ruleResult = await _ruleEngine.ExecuteActionWorkflowAsync(workflowName, ruleName, ruleParameters);
var filteredRuleParameters = new List<RuleParameter>(ruleParameters);
if(context.TryGetContext<List<string>>("inputFilter",out var inputFilter))
{
filteredRuleParameters = ruleParameters.Where(c => inputFilter.Contains(c.Name)).ToList();
}
if (context.TryGetContext<List<ScopedParam>>("additionalInputs", out var additionalInputs))
{
foreach(var additionalInput in additionalInputs)
{
dynamic value = _ruleExpressionParser.Evaluate<object>(additionalInput.Expression, ruleParameters);
filteredRuleParameters.Add(new RuleParameter(additionalInput.Name, value));

}
}

var ruleResult = await _ruleEngine.ExecuteActionWorkflowAsync(workflowName, ruleName, filteredRuleParameters.ToArray());
return ruleResult;
}
}
Expand Down
1 change: 0 additions & 1 deletion src/RulesEngine/Models/Workflow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ public class Workflow
/// <value>The workflow rules to inject.</value>
[Obsolete("WorkflowRulesToInject is deprecated. Use WorkflowsToInject instead.")]
public IEnumerable<string> WorkflowRulesToInject {
get { return WorkflowsToInject; }
set { WorkflowsToInject = value; }
}
public IEnumerable<string> WorkflowsToInject { get; set; }
Expand Down
2 changes: 1 addition & 1 deletion src/RulesEngine/RulesCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ public Workflow GetWorkflow(string workflowName)
throw new Exception($"Could not find injected Workflow: {wfname}");
}

workflow.Rules.ToList().AddRange(injectedWorkflow.Rules);
workflow.Rules = workflow.Rules.Concat(injectedWorkflow.Rules).ToList();
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/RulesEngine/RulesEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ private IDictionary<string, Func<ActionBase>> GetDefaultActionRegistry()
{
return new Dictionary<string, Func<ActionBase>>{
{"OutputExpression",() => new OutputExpressionAction(_ruleExpressionParser) },
{"EvaluateRule", () => new EvaluateRuleAction(this) }
{"EvaluateRule", () => new EvaluateRuleAction(this,_ruleExpressionParser) }
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,26 @@ public async Task ExecuteActionWorkflowAsync_CalledWithNoActionsInWorkflow_Execu
}


[Fact]
public async Task ExecuteActionWorkflowAsync_SelfReferencingAction_NoFilter_ExecutesSuccessfully()
{

var engine = new RulesEngine(GetWorkflowWithActions());
var result = await engine.ExecuteActionWorkflowAsync("WorkflowWithGlobalsAndSelfRefActions", "RuleReferencingSameWorkflow", new RuleParameter[0]);
Assert.NotNull(result);
Assert.Null(result.Output);
}

[Fact]
public async Task ExecuteActionWorkflowAsync_SelfReferencingAction_WithFilter_ExecutesSuccessfully()
{

var engine = new RulesEngine(GetWorkflowWithActions());
var result = await engine.ExecuteActionWorkflowAsync("WorkflowWithGlobalsAndSelfRefActions", "RuleReferencingSameWorkflowWithInputFilter", new RuleParameter[0]);
Assert.NotNull(result);
Assert.Equal(4,result.Output);
}

private Workflow[] GetWorkflowsWithoutActions()
{
var workflow1 = new Workflow {
Expand Down Expand Up @@ -104,8 +124,74 @@ private Workflow[] GetWorkflowWithActions()
}

}


};
return new[] { workflow1 };

var workflow2 = new Workflow {
WorkflowName = "WorkflowWithGlobalsAndSelfRefActions",
GlobalParams = new[] {
new ScopedParam {
Name = "global1",
Expression = "\"Hello\""
}
},
Rules = new[] {

new Rule{
RuleName = "RuleReferencingSameWorkflow",
Expression = "1 == 1",
Actions = new RuleActions {
OnSuccess = new ActionInfo{
Name = "EvaluateRule",
Context = new Dictionary<string, object>{
{"workflowName", "WorkflowWithGlobalsAndSelfRefActions"},
{"ruleName","OtherRule"}
}
}
}
},new Rule{
RuleName = "RuleReferencingSameWorkflowWithInputFilter",
Expression = "1 == 1",
Actions = new RuleActions {
OnSuccess = new ActionInfo{
Name = "EvaluateRule",
Context = new Dictionary<string, object>{
{"workflowName", "WorkflowWithGlobalsAndSelfRefActions"},
{"ruleName","OtherRule"},
{"inputFilter",new string[] { } },
{"additionalInputs", new [] {
new ScopedParam(){
Name = "additionalValue",
Expression = "1"
}

} }
}

}
}
}


, new Rule{
RuleName = "OtherRule",
Expression = "additionalValue == 1",
Actions = new RuleActions {
OnSuccess = new ActionInfo{
Name = "OutputExpression",
Context = new Dictionary<string, object>{
{"expression", "2*2"}
}
}
}

}

}

};
return new[] { workflow1, workflow2 };
}
}
}
6 changes: 3 additions & 3 deletions test/RulesEngine.UnitTest/BusinessRuleEngineTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,17 @@ public void RulesEngine_New_ReturnsNotNull(string ruleFileName)

[Theory]
[InlineData("rules2.json")]
public async Task RulesEngine_InjectedRules_ReturnsListOfRuleResultTree(string ruleFileName)
public async Task RulesEngine_InjectedRules_ContainsInjectedRules(string ruleFileName)
{
var re = GetRulesEngine(ruleFileName);

dynamic input1 = GetInput1();
dynamic input2 = GetInput2();
dynamic input3 = GetInput3();

var result = await re.ExecuteAllRulesAsync("inputWorkflowReference", input1, input2, input3);
List<RuleResultTree> result = await re.ExecuteAllRulesAsync("inputWorkflowReference", input1, input2, input3);
Assert.NotNull(result);
Assert.IsType<List<RuleResultTree>>(result);
Assert.True(result.Any());
}

[Theory]
Expand Down