Abbasc52/combined fixes (#341)
* Fixed actions not working with complex objects * removed Microsoft.Extensions.Caching dependency * * updated nuget packages * update dotnet version to 6 for demo/test proj * improved integration with DynamicLinq * added test case for jsonElement getProperty method * updated test cases to cover case insensitivity * updated workflow schema * added schema for list of workflows * fixed schema name and added to solution * Update dotnetcore-build.yml * Update dotnetcore-build.yml * updated unit test proj to point to dotnet 6 * removed iloggerpull/342/head
parent
99bad9ffff
commit
571490455c
|
@ -3,7 +3,7 @@ updates:
|
||||||
- package-ecosystem: "github-actions"
|
- package-ecosystem: "github-actions"
|
||||||
# default location of `.github/workflows`
|
# default location of `.github/workflows`
|
||||||
directory: "/"
|
directory: "/"
|
||||||
open-pull-requests-limit: 10
|
open-pull-requests-limit: 3
|
||||||
schedule:
|
schedule:
|
||||||
interval: "weekly"
|
interval: "weekly"
|
||||||
# assignees:
|
# assignees:
|
||||||
|
@ -14,9 +14,9 @@ updates:
|
||||||
- package-ecosystem: "nuget"
|
- package-ecosystem: "nuget"
|
||||||
# location of package manifests
|
# location of package manifests
|
||||||
directory: "/"
|
directory: "/"
|
||||||
open-pull-requests-limit: 10
|
open-pull-requests-limit: 3
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "weekly"
|
||||||
ignore:
|
ignore:
|
||||||
- dependency-name: "*"
|
- dependency-name: "*"
|
||||||
update-types: ["version-update:semver-minor"]
|
update-types: ["version-update:semver-minor"]
|
||||||
|
|
|
@ -31,12 +31,12 @@ jobs:
|
||||||
|
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Setup .NET Core
|
- name: Setup .NET Core
|
||||||
uses: actions/setup-dotnet@v1
|
uses: actions/setup-dotnet@v2
|
||||||
with:
|
with:
|
||||||
dotnet-version: 3.1
|
dotnet-version: 6.0.x
|
||||||
|
|
||||||
- name: Install minicover
|
- name: Install minicover
|
||||||
run: dotnet tool install --global minicover --version 3.0.6
|
run: dotnet tool install --global minicover --version 3.4.4
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: dotnet restore RulesEngine.sln
|
run: dotnet restore RulesEngine.sln
|
||||||
|
|
|
@ -18,6 +18,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
||||||
CHANGELOG.md = CHANGELOG.md
|
CHANGELOG.md = CHANGELOG.md
|
||||||
global.json = global.json
|
global.json = global.json
|
||||||
README.md = README.md
|
README.md = README.md
|
||||||
|
schema\workflow-list-schema.json = schema\workflow-list-schema.json
|
||||||
schema\workflow-schema.json = schema\workflow-schema.json
|
schema\workflow-schema.json = schema\workflow-schema.json
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
|
|
|
@ -36,7 +36,7 @@ namespace RulesEngineBenchmark
|
||||||
var fileData = File.ReadAllText(files[0]);
|
var fileData = File.ReadAllText(files[0]);
|
||||||
workflow = JsonConvert.DeserializeObject<List<Workflow>>(fileData);
|
workflow = JsonConvert.DeserializeObject<List<Workflow>>(fileData);
|
||||||
|
|
||||||
rulesEngine = new RulesEngine.RulesEngine(workflow.ToArray(), null, new ReSettings {
|
rulesEngine = new RulesEngine.RulesEngine(workflow.ToArray(), new ReSettings {
|
||||||
EnableFormattedErrorMessage = false,
|
EnableFormattedErrorMessage = false,
|
||||||
EnableScopedParams = false
|
EnableScopedParams = false
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<RootNamespace>DemoApp.EFDataExample</RootNamespace>
|
<RootNamespace>DemoApp.EFDataExample</RootNamespace>
|
||||||
<AssemblyName>DemoApp.EFDataExample</AssemblyName>
|
<AssemblyName>DemoApp.EFDataExample</AssemblyName>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.8" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.3" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.8" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -24,18 +24,20 @@ namespace RulesEngine.Data
|
||||||
entity.Ignore(b => b.WorkflowsToInject);
|
entity.Ignore(b => b.WorkflowsToInject);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var serializationOptions = new JsonSerializerOptions(JsonSerializerDefaults.General);
|
||||||
|
|
||||||
modelBuilder.Entity<Rule>(entity => {
|
modelBuilder.Entity<Rule>(entity => {
|
||||||
entity.HasKey(k => k.RuleName);
|
entity.HasKey(k => k.RuleName);
|
||||||
|
|
||||||
entity.Property(b => b.Properties)
|
entity.Property(b => b.Properties)
|
||||||
.HasConversion(
|
.HasConversion(
|
||||||
v => JsonSerializer.Serialize(v, null),
|
v => JsonSerializer.Serialize(v, serializationOptions),
|
||||||
v => JsonSerializer.Deserialize<Dictionary<string, object>>(v, null));
|
v => JsonSerializer.Deserialize<Dictionary<string, object>>(v, serializationOptions)); ;
|
||||||
|
|
||||||
entity.Property(p => p.Actions)
|
entity.Property(p => p.Actions)
|
||||||
.HasConversion(
|
.HasConversion(
|
||||||
v => JsonSerializer.Serialize(v, null),
|
v => JsonSerializer.Serialize(v, serializationOptions),
|
||||||
v => JsonSerializer.Deserialize<RuleActions>(v, null));
|
v => JsonSerializer.Deserialize<RuleActions>(v, serializationOptions));
|
||||||
|
|
||||||
entity.Ignore(b => b.WorkflowsToInject);
|
entity.Ignore(b => b.WorkflowsToInject);
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<StartupObject>DemoApp.Program</StartupObject>
|
<StartupObject>DemoApp.Program</StartupObject>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"sdk": {
|
"sdk": {
|
||||||
"version": "3.1",
|
"version": "6.0",
|
||||||
"rollForward": "latestFeature",
|
"rollForward": "latestFeature",
|
||||||
"allowPrerelease": false
|
"allowPrerelease": false
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "https://raw.githubusercontent.com/microsoft/RulesEngine/main/schema/workflow-schema.json"
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,24 @@
|
||||||
{
|
{
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
"definitions": {
|
"definitions": {
|
||||||
|
"ScopedParam": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"Name": { "type": "string" },
|
||||||
|
"Expression": { "type": "string" }
|
||||||
|
},
|
||||||
|
"required": [ "Name", "Expression" ]
|
||||||
|
},
|
||||||
"Rule": {
|
"Rule": {
|
||||||
"title": "Rule",
|
"title": "Rule",
|
||||||
"properties": {
|
"properties": {
|
||||||
"RuleName": {
|
"RuleName": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"LocalParams": {
|
||||||
|
"type": "array",
|
||||||
|
"items": { "$ref": "#/definitions/ScopedParam" }
|
||||||
|
},
|
||||||
"Operator": {
|
"Operator": {
|
||||||
"enum": [
|
"enum": [
|
||||||
"And",
|
"And",
|
||||||
|
@ -18,12 +30,6 @@
|
||||||
"ErrorMessage": {
|
"ErrorMessage": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"ErrorType": {
|
|
||||||
"enum": [
|
|
||||||
"Warning",
|
|
||||||
"Error"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"SuccessEvent": {
|
"SuccessEvent": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
@ -45,6 +51,10 @@
|
||||||
},
|
},
|
||||||
"Actions": {
|
"Actions": {
|
||||||
"$ref": "#/definitions/RuleActions"
|
"$ref": "#/definitions/RuleActions"
|
||||||
|
},
|
||||||
|
"Enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
|
@ -65,6 +75,10 @@
|
||||||
"RuleName": {
|
"RuleName": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"LocalParams": {
|
||||||
|
"type": "array",
|
||||||
|
"items": { "$ref": "#/definitions/ScopedParam" }
|
||||||
|
},
|
||||||
"Expression": {
|
"Expression": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
@ -76,12 +90,6 @@
|
||||||
"ErrorMessage": {
|
"ErrorMessage": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"ErrorType": {
|
|
||||||
"enum": [
|
|
||||||
"Warning",
|
|
||||||
"Error"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"SuccessEvent": {
|
"SuccessEvent": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
@ -90,6 +98,10 @@
|
||||||
},
|
},
|
||||||
"Actions": {
|
"Actions": {
|
||||||
"$ref": "#/definitions/RuleActions"
|
"$ref": "#/definitions/RuleActions"
|
||||||
|
},
|
||||||
|
"Enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -116,11 +128,20 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
"properties": {
|
"properties": {
|
||||||
"WorkFlowName": {
|
"WorkflowName": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"WorkflowsToInject": {
|
||||||
|
"type": "array",
|
||||||
|
"items": { "type": "string" }
|
||||||
|
},
|
||||||
|
"GlobalParams": {
|
||||||
|
"type": "array",
|
||||||
|
"items": { "$ref": "#/definitions/ScopedParam" }
|
||||||
|
},
|
||||||
"Rules": {
|
"Rules": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
|
@ -136,7 +157,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"WorkFlowName",
|
"WorkflowName",
|
||||||
"Rules"
|
"Rules"
|
||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
|
|
|
@ -43,7 +43,7 @@ namespace RulesEngine.ExpressionBuilders
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override LambdaExpression Parse(string expression, ParameterExpression[] parameters, Type returnType)
|
internal override Expression Parse(string expression, ParameterExpression[] parameters, Type returnType)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
|
@ -22,7 +22,7 @@ namespace RulesEngine.ExpressionBuilders
|
||||||
/// <returns>Expression type</returns>
|
/// <returns>Expression type</returns>
|
||||||
internal abstract RuleFunc<RuleResultTree> BuildDelegateForRule(Rule rule, RuleParameter[] ruleParams);
|
internal abstract RuleFunc<RuleResultTree> BuildDelegateForRule(Rule rule, RuleParameter[] ruleParams);
|
||||||
|
|
||||||
internal abstract LambdaExpression Parse(string expression, ParameterExpression[] parameters, Type returnType);
|
internal abstract Expression Parse(string expression, ParameterExpression[] parameters, Type returnType);
|
||||||
|
|
||||||
internal abstract Func<object[], Dictionary<string, object>> CompileScopedParams(RuleParameter[] ruleParameters, RuleExpressionParameter[] scopedParameters);
|
internal abstract Func<object[], Dictionary<string, object>> CompileScopedParams(RuleParameter[] ruleParameters, RuleExpressionParameter[] scopedParameters);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,13 @@
|
||||||
// Licensed under the MIT License.
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
using FastExpressionCompiler;
|
using FastExpressionCompiler;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using RulesEngine.HelperFunctions;
|
||||||
using RulesEngine.Models;
|
using RulesEngine.Models;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Linq.Dynamic.Core;
|
using System.Linq.Dynamic.Core;
|
||||||
|
using System.Linq.Dynamic.Core.Parser;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
|
@ -16,13 +17,13 @@ namespace RulesEngine.ExpressionBuilders
|
||||||
public class RuleExpressionParser
|
public class RuleExpressionParser
|
||||||
{
|
{
|
||||||
private readonly ReSettings _reSettings;
|
private readonly ReSettings _reSettings;
|
||||||
private static IMemoryCache _memoryCache;
|
private static MemCache _memoryCache;
|
||||||
private readonly IDictionary<string, MethodInfo> _methodInfo;
|
private readonly IDictionary<string, MethodInfo> _methodInfo;
|
||||||
|
|
||||||
public RuleExpressionParser(ReSettings reSettings)
|
public RuleExpressionParser(ReSettings reSettings)
|
||||||
{
|
{
|
||||||
_reSettings = reSettings;
|
_reSettings = reSettings;
|
||||||
_memoryCache = _memoryCache ?? new MemoryCache(new MemoryCacheOptions {
|
_memoryCache = _memoryCache ?? new MemCache(new MemCacheConfig {
|
||||||
SizeLimit = 1000
|
SizeLimit = 1000
|
||||||
});
|
});
|
||||||
_methodInfo = new Dictionary<string, MethodInfo>();
|
_methodInfo = new Dictionary<string, MethodInfo>();
|
||||||
|
@ -34,22 +35,30 @@ namespace RulesEngine.ExpressionBuilders
|
||||||
var dict_add = typeof(Dictionary<string, object>).GetMethod("Add", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof(string), typeof(object) }, null);
|
var dict_add = typeof(Dictionary<string, object>).GetMethod("Add", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof(string), typeof(object) }, null);
|
||||||
_methodInfo.Add("dict_add", dict_add);
|
_methodInfo.Add("dict_add", dict_add);
|
||||||
}
|
}
|
||||||
public LambdaExpression Parse(string expression, ParameterExpression[] parameters, Type returnType)
|
public Expression Parse(string expression, ParameterExpression[] parameters, Type returnType)
|
||||||
{
|
{
|
||||||
var config = new ParsingConfig { CustomTypeProvider = new CustomTypeProvider(_reSettings.CustomTypes) };
|
var config = new ParsingConfig { CustomTypeProvider = new CustomTypeProvider(_reSettings.CustomTypes) };
|
||||||
|
return new ExpressionParser(parameters, expression, new object[] { }, config).Parse(returnType);
|
||||||
|
|
||||||
return DynamicExpressionParser.ParseLambda(config, false, parameters, returnType, expression);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Func<object[], T> Compile<T>(string expression, RuleParameter[] ruleParams)
|
public Func<object[], T> Compile<T>(string expression, RuleParameter[] ruleParams)
|
||||||
{
|
{
|
||||||
|
var rtype = typeof(T);
|
||||||
|
if(rtype == typeof(object))
|
||||||
|
{
|
||||||
|
rtype = null;
|
||||||
|
}
|
||||||
var cacheKey = GetCacheKey(expression, ruleParams, typeof(T));
|
var cacheKey = GetCacheKey(expression, ruleParams, typeof(T));
|
||||||
return _memoryCache.GetOrCreate(cacheKey, (entry) => {
|
return _memoryCache.GetOrCreate(cacheKey, () => {
|
||||||
entry.SetSize(1);
|
|
||||||
var parameterExpressions = GetParameterExpression(ruleParams).ToArray();
|
var parameterExpressions = GetParameterExpression(ruleParams).ToArray();
|
||||||
|
|
||||||
var e = Parse(expression, parameterExpressions, typeof(T));
|
var e = Parse(expression, parameterExpressions, rtype);
|
||||||
var expressionBody = new List<Expression>() { e.Body };
|
if(rtype == null)
|
||||||
|
{
|
||||||
|
e = Expression.Convert(e, typeof(T));
|
||||||
|
}
|
||||||
|
var expressionBody = new List<Expression>() { e };
|
||||||
var wrappedExpression = WrapExpression<T>(expressionBody, parameterExpressions, new ParameterExpression[] { });
|
var wrappedExpression = WrapExpression<T>(expressionBody, parameterExpressions, new ParameterExpression[] { });
|
||||||
return wrappedExpression.CompileFast();
|
return wrappedExpression.CompileFast();
|
||||||
});
|
});
|
||||||
|
@ -75,7 +84,7 @@ namespace RulesEngine.ExpressionBuilders
|
||||||
}
|
}
|
||||||
|
|
||||||
public T Evaluate<T>(string expression, RuleParameter[] ruleParams)
|
public T Evaluate<T>(string expression, RuleParameter[] ruleParams)
|
||||||
{
|
{
|
||||||
var func = Compile<T>(expression, ruleParams);
|
var func = Compile<T>(expression, ruleParams);
|
||||||
return func(ruleParams.Select(c => c.Value).ToArray());
|
return func(ruleParams.Select(c => c.Value).ToArray());
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ namespace RulesEngine.HelperFunctions
|
||||||
{
|
{
|
||||||
public static bool CheckContains(string check, string valList)
|
public static bool CheckContains(string check, string valList)
|
||||||
{
|
{
|
||||||
if (String.IsNullOrEmpty(check) || String.IsNullOrEmpty(valList))
|
if (string.IsNullOrEmpty(check) || string.IsNullOrEmpty(valList))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var list = valList.Split(',').ToList();
|
var list = valList.Split(',').ToList();
|
||||||
|
|
|
@ -0,0 +1,115 @@
|
||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace RulesEngine.HelperFunctions
|
||||||
|
{
|
||||||
|
public class MemCacheConfig {
|
||||||
|
public int SizeLimit { get; set; } = 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal class MemCache
|
||||||
|
{
|
||||||
|
private readonly MemCacheConfig _config;
|
||||||
|
private ConcurrentDictionary<string, (object value, DateTimeOffset expiry)> _cacheDictionary;
|
||||||
|
private ConcurrentQueue<(string key, DateTimeOffset expiry)> _cacheEvictionQueue;
|
||||||
|
|
||||||
|
public MemCache(MemCacheConfig config)
|
||||||
|
{
|
||||||
|
if(config == null)
|
||||||
|
{
|
||||||
|
config = new MemCacheConfig();
|
||||||
|
}
|
||||||
|
_config = config;
|
||||||
|
_cacheDictionary = new ConcurrentDictionary<string, (object value, DateTimeOffset expiry)>();
|
||||||
|
_cacheEvictionQueue = new ConcurrentQueue<(string key, DateTimeOffset expiry)>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetValue<T>(string key,out T value)
|
||||||
|
{
|
||||||
|
value = default;
|
||||||
|
if (_cacheDictionary.TryGetValue(key, out var cacheItem))
|
||||||
|
{
|
||||||
|
if(cacheItem.expiry < DateTimeOffset.UtcNow)
|
||||||
|
{
|
||||||
|
_cacheDictionary.TryRemove(key, out _);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
value = (T)cacheItem.value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public T Get<T>(string key)
|
||||||
|
{
|
||||||
|
TryGetValue<T>(key, out var value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns all known keys. May return keys for expired data as well
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public IEnumerable<string> GetKeys()
|
||||||
|
{
|
||||||
|
return _cacheDictionary.Keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T GetOrCreate<T>(string key, Func<T> createFn, DateTimeOffset? expiry = null)
|
||||||
|
{
|
||||||
|
if(!TryGetValue<T>(key,out var value))
|
||||||
|
{
|
||||||
|
value = createFn();
|
||||||
|
return Set<T>(key,value,expiry);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T Set<T>(string key, T value, DateTimeOffset? expiry = null)
|
||||||
|
{
|
||||||
|
var fixedExpiry = expiry ?? DateTimeOffset.MaxValue;
|
||||||
|
|
||||||
|
while (_cacheDictionary.Count > _config.SizeLimit)
|
||||||
|
{
|
||||||
|
if (_cacheEvictionQueue.IsEmpty)
|
||||||
|
{
|
||||||
|
_cacheDictionary.Clear();
|
||||||
|
}
|
||||||
|
if(_cacheEvictionQueue.TryDequeue(out var result)
|
||||||
|
&& _cacheDictionary.TryGetValue(result.key,out var dictionaryValue)
|
||||||
|
&& dictionaryValue.expiry == result.expiry)
|
||||||
|
{
|
||||||
|
_cacheDictionary.TryRemove(result.key, out _);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
_cacheDictionary.AddOrUpdate(key, (value, fixedExpiry), (k, v) => (value, fixedExpiry));
|
||||||
|
_cacheEvictionQueue.Enqueue((key, fixedExpiry));
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Remove(string key)
|
||||||
|
{
|
||||||
|
_cacheDictionary.TryRemove(key, out _);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
_cacheDictionary.Clear();
|
||||||
|
_cacheEvictionQueue = new ConcurrentQueue<(string key, DateTimeOffset expiry)>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under the MIT License.
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
using RulesEngine.Actions;
|
using RulesEngine.Actions;
|
||||||
|
using RulesEngine.HelperFunctions;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
@ -57,6 +58,8 @@ namespace RulesEngine.Models
|
||||||
get { return EnableScopedParams; }
|
get { return EnableScopedParams; }
|
||||||
set { EnableScopedParams = value; }
|
set { EnableScopedParams = value; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MemCacheConfig CacheConfig { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum NestedRuleExecutionMode
|
public enum NestedRuleExecutionMode
|
||||||
|
|
|
@ -23,13 +23,13 @@ namespace RulesEngine.Models
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string WorkflowName { get; set; }
|
public string WorkflowName { get; set; }
|
||||||
|
|
||||||
/// <summary>Gets or sets the workflow rules to inject.</summary>
|
/// <summary>Gets or sets the workflow rules to inject.</summary>
|
||||||
/// <value>The workflow rules to inject.</value>
|
/// <value>The workflow rules to inject.</value>
|
||||||
[Obsolete("WorkflowRulesToInject is deprecated. Use WorkflowsToInject instead.")]
|
[Obsolete("WorkflowRulesToInject is deprecated. Use WorkflowsToInject instead.")]
|
||||||
public IEnumerable<string> WorkflowRulesToInject {
|
public IEnumerable<string> WorkflowRulesToInject {
|
||||||
set { WorkflowsToInject = value; }
|
set { WorkflowsToInject = value; }
|
||||||
}
|
}
|
||||||
public IEnumerable<string> WorkflowsToInject { get; set; }
|
public IEnumerable<string> WorkflowsToInject { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or Sets the global params which will be applicable to all rules
|
/// Gets or Sets the global params which will be applicable to all rules
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
// Copyright (c) Microsoft Corporation.
|
// Copyright (c) Microsoft Corporation.
|
||||||
// Licensed under the MIT License.
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using RulesEngine.Exceptions;
|
using RulesEngine.Exceptions;
|
||||||
using RulesEngine.ExpressionBuilders;
|
using RulesEngine.ExpressionBuilders;
|
||||||
using RulesEngine.HelperFunctions;
|
using RulesEngine.HelperFunctions;
|
||||||
|
@ -10,7 +9,6 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace RulesEngine
|
namespace RulesEngine
|
||||||
{
|
{
|
||||||
|
@ -30,20 +28,13 @@ namespace RulesEngine
|
||||||
private readonly RuleExpressionBuilderFactory _expressionBuilderFactory;
|
private readonly RuleExpressionBuilderFactory _expressionBuilderFactory;
|
||||||
private readonly ReSettings _reSettings;
|
private readonly ReSettings _reSettings;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The logger
|
|
||||||
/// </summary>
|
|
||||||
private readonly ILogger _logger;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="RuleCompiler"/> class.
|
/// Initializes a new instance of the <see cref="RuleCompiler"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="expressionBuilderFactory">The expression builder factory.</param>
|
/// <param name="expressionBuilderFactory">The expression builder factory.</param>
|
||||||
/// <exception cref="ArgumentNullException">expressionBuilderFactory</exception>
|
/// <exception cref="ArgumentNullException">expressionBuilderFactory</exception>
|
||||||
internal RuleCompiler(RuleExpressionBuilderFactory expressionBuilderFactory, ReSettings reSettings, ILogger logger)
|
internal RuleCompiler(RuleExpressionBuilderFactory expressionBuilderFactory, ReSettings reSettings)
|
||||||
{
|
{
|
||||||
_logger = logger ?? throw new ArgumentNullException($"{nameof(logger)} can't be null.");
|
|
||||||
|
|
||||||
_expressionBuilderFactory = expressionBuilderFactory ?? throw new ArgumentNullException($"{nameof(expressionBuilderFactory)} can't be null.");
|
_expressionBuilderFactory = expressionBuilderFactory ?? throw new ArgumentNullException($"{nameof(expressionBuilderFactory)} can't be null.");
|
||||||
_reSettings = reSettings;
|
_reSettings = reSettings;
|
||||||
}
|
}
|
||||||
|
@ -61,7 +52,6 @@ namespace RulesEngine
|
||||||
if (rule == null)
|
if (rule == null)
|
||||||
{
|
{
|
||||||
var ex = new ArgumentNullException(nameof(rule));
|
var ex = new ArgumentNullException(nameof(rule));
|
||||||
_logger.LogError(ex.Message);
|
|
||||||
throw ex;
|
throw ex;
|
||||||
}
|
}
|
||||||
try
|
try
|
||||||
|
@ -75,7 +65,6 @@ namespace RulesEngine
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
var message = $"Error while compiling rule `{rule.RuleName}`: {ex.Message}";
|
var message = $"Error while compiling rule `{rule.RuleName}`: {ex.Message}";
|
||||||
_logger.LogError(message);
|
|
||||||
return Helpers.ToRuleExceptionResult(_reSettings, rule, new RuleException(message, ex));
|
return Helpers.ToRuleExceptionResult(_reSettings, rule, new RuleException(message, ex));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -131,7 +120,7 @@ namespace RulesEngine
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var lpExpression = expressionBuilder.Parse(lp.Expression, parameters.ToArray(), null).Body;
|
var lpExpression = expressionBuilder.Parse(lp.Expression, parameters.ToArray(), null);
|
||||||
var ruleExpParam = new RuleExpressionParameter() {
|
var ruleExpParam = new RuleExpressionParameter() {
|
||||||
ParameterExpression = Expression.Parameter(lpExpression.Type, lp.Name),
|
ParameterExpression = Expression.Parameter(lpExpression.Type, lp.Name),
|
||||||
ValueExpression = lpExpression
|
ValueExpression = lpExpression
|
||||||
|
@ -187,7 +176,7 @@ namespace RulesEngine
|
||||||
|
|
||||||
return (paramArray) => {
|
return (paramArray) => {
|
||||||
var (isSuccess, resultList) = ApplyOperation(paramArray, ruleFuncList, operation);
|
var (isSuccess, resultList) = ApplyOperation(paramArray, ruleFuncList, operation);
|
||||||
Func<object[], bool> isSuccessFn = (p) => isSuccess;
|
bool isSuccessFn(object[] p) => isSuccess;
|
||||||
var result = Helpers.ToResultTree(_reSettings, parentRule, resultList, isSuccessFn);
|
var result = Helpers.ToResultTree(_reSettings, parentRule, resultList, isSuccessFn);
|
||||||
return result(paramArray);
|
return result(paramArray);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright (c) Microsoft Corporation.
|
// Copyright (c) Microsoft Corporation.
|
||||||
// Licensed under the MIT License.
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
using RulesEngine.HelperFunctions;
|
||||||
using RulesEngine.Models;
|
using RulesEngine.Models;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
@ -13,10 +14,17 @@ namespace RulesEngine
|
||||||
internal class RulesCache
|
internal class RulesCache
|
||||||
{
|
{
|
||||||
/// <summary>The compile rules</summary>
|
/// <summary>The compile rules</summary>
|
||||||
private ConcurrentDictionary<string, (IDictionary<string, RuleFunc<RuleResultTree>>, Int64)> _compileRules = new ConcurrentDictionary<string, (IDictionary<string, RuleFunc<RuleResultTree>>, Int64)>();
|
private readonly MemCache _compileRules;
|
||||||
|
|
||||||
/// <summary>The workflow rules</summary>
|
/// <summary>The workflow rules</summary>
|
||||||
private ConcurrentDictionary<string, (Workflow, Int64)> _workflow = new ConcurrentDictionary<string, (Workflow, Int64)>();
|
private readonly ConcurrentDictionary<string, (Workflow, long)> _workflow = new ConcurrentDictionary<string, (Workflow, long)>();
|
||||||
|
|
||||||
|
|
||||||
|
public RulesCache(ReSettings reSettings)
|
||||||
|
{
|
||||||
|
_compileRules = new MemCache(reSettings.CacheConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>Determines whether [contains workflow rules] [the specified workflow name].</summary>
|
/// <summary>Determines whether [contains workflow rules] [the specified workflow name].</summary>
|
||||||
/// <param name="workflowName">Name of the workflow.</param>
|
/// <param name="workflowName">Name of the workflow.</param>
|
||||||
|
@ -32,21 +40,12 @@ namespace RulesEngine
|
||||||
return _workflow.Keys.ToList();
|
return _workflow.Keys.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Determines whether [contains compiled rules] [the specified workflow name].</summary>
|
|
||||||
/// <param name="workflowName">Name of the workflow.</param>
|
|
||||||
/// <returns>
|
|
||||||
/// <c>true</c> if [contains compiled rules] [the specified workflow name]; otherwise, <c>false</c>.</returns>
|
|
||||||
public bool ContainsCompiledRules(string workflowName)
|
|
||||||
{
|
|
||||||
return _compileRules.ContainsKey(workflowName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Adds the or update workflow rules.</summary>
|
/// <summary>Adds the or update workflow rules.</summary>
|
||||||
/// <param name="workflowName">Name of the workflow.</param>
|
/// <param name="workflowName">Name of the workflow.</param>
|
||||||
/// <param name="rules">The rules.</param>
|
/// <param name="rules">The rules.</param>
|
||||||
public void AddOrUpdateWorkflows(string workflowName, Workflow rules)
|
public void AddOrUpdateWorkflows(string workflowName, Workflow rules)
|
||||||
{
|
{
|
||||||
Int64 ticks = DateTime.UtcNow.Ticks;
|
long ticks = DateTime.UtcNow.Ticks;
|
||||||
_workflow.AddOrUpdate(workflowName, (rules, ticks), (k, v) => (rules, ticks));
|
_workflow.AddOrUpdate(workflowName, (rules, ticks), (k, v) => (rules, ticks));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,8 +54,8 @@ namespace RulesEngine
|
||||||
/// <param name="compiledRule">The compiled rule.</param>
|
/// <param name="compiledRule">The compiled rule.</param>
|
||||||
public void AddOrUpdateCompiledRule(string compiledRuleKey, IDictionary<string, RuleFunc<RuleResultTree>> compiledRule)
|
public void AddOrUpdateCompiledRule(string compiledRuleKey, IDictionary<string, RuleFunc<RuleResultTree>> compiledRule)
|
||||||
{
|
{
|
||||||
Int64 ticks = DateTime.UtcNow.Ticks;
|
long ticks = DateTime.UtcNow.Ticks;
|
||||||
_compileRules.AddOrUpdate(compiledRuleKey, (compiledRule, ticks), (k, v) => (compiledRule, ticks));
|
_compileRules.Set(compiledRuleKey,(compiledRule, ticks));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Checks if the compiled rules are up-to-date.</summary>
|
/// <summary>Checks if the compiled rules are up-to-date.</summary>
|
||||||
|
@ -66,9 +65,9 @@ namespace RulesEngine
|
||||||
/// <c>true</c> if [compiled rules] is newer than the [workflow rules]; otherwise, <c>false</c>.</returns>
|
/// <c>true</c> if [compiled rules] is newer than the [workflow rules]; otherwise, <c>false</c>.</returns>
|
||||||
public bool AreCompiledRulesUpToDate(string compiledRuleKey, string workflowName)
|
public bool AreCompiledRulesUpToDate(string compiledRuleKey, string workflowName)
|
||||||
{
|
{
|
||||||
if (_compileRules.TryGetValue(compiledRuleKey, out (IDictionary<string, RuleFunc<RuleResultTree>> rules, Int64 tick) compiledRulesObj))
|
if (_compileRules.TryGetValue(compiledRuleKey, out (IDictionary<string, RuleFunc<RuleResultTree>> rules, long tick) compiledRulesObj))
|
||||||
{
|
{
|
||||||
if (_workflow.TryGetValue(workflowName, out (Workflow rules, Int64 tick) WorkflowsObj))
|
if (_workflow.TryGetValue(workflowName, out (Workflow rules, long tick) WorkflowsObj))
|
||||||
{
|
{
|
||||||
return compiledRulesObj.tick >= WorkflowsObj.tick;
|
return compiledRulesObj.tick >= WorkflowsObj.tick;
|
||||||
}
|
}
|
||||||
|
@ -90,7 +89,7 @@ namespace RulesEngine
|
||||||
/// <exception cref="Exception">Could not find injected Workflow: {wfname}</exception>
|
/// <exception cref="Exception">Could not find injected Workflow: {wfname}</exception>
|
||||||
public Workflow GetWorkflow(string workflowName)
|
public Workflow GetWorkflow(string workflowName)
|
||||||
{
|
{
|
||||||
if (_workflow.TryGetValue(workflowName, out (Workflow rules, Int64 tick) WorkflowsObj))
|
if (_workflow.TryGetValue(workflowName, out (Workflow rules, long tick) WorkflowsObj))
|
||||||
{
|
{
|
||||||
var workflow = WorkflowsObj.rules;
|
var workflow = WorkflowsObj.rules;
|
||||||
if (workflow.WorkflowsToInject?.Any() == true)
|
if (workflow.WorkflowsToInject?.Any() == true)
|
||||||
|
@ -125,19 +124,19 @@ namespace RulesEngine
|
||||||
/// <returns>CompiledRule.</returns>
|
/// <returns>CompiledRule.</returns>
|
||||||
public IDictionary<string, RuleFunc<RuleResultTree>> GetCompiledRules(string compiledRulesKey)
|
public IDictionary<string, RuleFunc<RuleResultTree>> GetCompiledRules(string compiledRulesKey)
|
||||||
{
|
{
|
||||||
return _compileRules[compiledRulesKey].Item1;
|
return _compileRules.Get<(IDictionary<string, RuleFunc<RuleResultTree>> rules, long tick)>(compiledRulesKey).rules;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Removes the specified workflow name.</summary>
|
/// <summary>Removes the specified workflow name.</summary>
|
||||||
/// <param name="workflowName">Name of the workflow.</param>
|
/// <param name="workflowName">Name of the workflow.</param>
|
||||||
public void Remove(string workflowName)
|
public void Remove(string workflowName)
|
||||||
{
|
{
|
||||||
if (_workflow.TryRemove(workflowName, out (Workflow, Int64) workflowObj))
|
if (_workflow.TryRemove(workflowName, out var workflowObj))
|
||||||
{
|
{
|
||||||
var compiledKeysToRemove = _compileRules.Keys.Where(key => key.StartsWith(workflowName));
|
var compiledKeysToRemove = _compileRules.GetKeys().Where(key => key.StartsWith(workflowName));
|
||||||
foreach (var key in compiledKeysToRemove)
|
foreach (var key in compiledKeysToRemove)
|
||||||
{
|
{
|
||||||
_compileRules.TryRemove(key, out (IDictionary<string, RuleFunc<RuleResultTree>>, Int64) val);
|
_compileRules.Remove(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,431 +1,427 @@
|
||||||
// Copyright (c) Microsoft Corporation.
|
// Copyright (c) Microsoft Corporation.
|
||||||
// Licensed under the MIT License.
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
using Microsoft.Extensions.Logging;
|
using Newtonsoft.Json;
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
using Newtonsoft.Json.Linq;
|
||||||
using Newtonsoft.Json;
|
using RulesEngine.Actions;
|
||||||
using Newtonsoft.Json.Linq;
|
using RulesEngine.Exceptions;
|
||||||
using RulesEngine.Actions;
|
using RulesEngine.ExpressionBuilders;
|
||||||
using RulesEngine.Exceptions;
|
using RulesEngine.HelperFunctions;
|
||||||
using RulesEngine.ExpressionBuilders;
|
using RulesEngine.Interfaces;
|
||||||
using RulesEngine.Interfaces;
|
using RulesEngine.Models;
|
||||||
using RulesEngine.Models;
|
using RulesEngine.Validators;
|
||||||
using RulesEngine.Validators;
|
using System;
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
using System.Linq;
|
||||||
using System.Linq;
|
using System.Text.RegularExpressions;
|
||||||
using System.Text.RegularExpressions;
|
using System.Threading.Tasks;
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
namespace RulesEngine
|
||||||
namespace RulesEngine
|
{
|
||||||
{
|
/// <summary>
|
||||||
/// <summary>
|
///
|
||||||
///
|
/// </summary>
|
||||||
/// </summary>
|
/// <seealso cref="RulesEngine.Interfaces.IRulesEngine" />
|
||||||
/// <seealso cref="RulesEngine.Interfaces.IRulesEngine" />
|
public class RulesEngine : IRulesEngine
|
||||||
public class RulesEngine : IRulesEngine
|
{
|
||||||
{
|
#region Variables
|
||||||
#region Variables
|
private readonly ReSettings _reSettings;
|
||||||
private readonly ILogger _logger;
|
private readonly RulesCache _rulesCache;
|
||||||
private readonly ReSettings _reSettings;
|
private readonly RuleExpressionParser _ruleExpressionParser;
|
||||||
private readonly RulesCache _rulesCache = new RulesCache();
|
private readonly RuleCompiler _ruleCompiler;
|
||||||
private readonly RuleExpressionParser _ruleExpressionParser;
|
private readonly ActionFactory _actionFactory;
|
||||||
private readonly RuleCompiler _ruleCompiler;
|
private const string ParamParseRegex = "(\\$\\(.*?\\))";
|
||||||
private readonly ActionFactory _actionFactory;
|
#endregion
|
||||||
private const string ParamParseRegex = "(\\$\\(.*?\\))";
|
|
||||||
#endregion
|
#region Constructor
|
||||||
|
public RulesEngine(string[] jsonConfig, ReSettings reSettings = null) : this(reSettings)
|
||||||
#region Constructor
|
{
|
||||||
public RulesEngine(string[] jsonConfig, ILogger logger = null, ReSettings reSettings = null) : this(logger, reSettings)
|
var workflow = jsonConfig.Select(item => JsonConvert.DeserializeObject<Workflow>(item)).ToArray();
|
||||||
{
|
AddWorkflow(workflow);
|
||||||
var workflow = jsonConfig.Select(item => JsonConvert.DeserializeObject<Workflow>(item)).ToArray();
|
|
||||||
AddWorkflow(workflow);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RulesEngine(Workflow[] Workflows, ILogger logger = null, ReSettings reSettings = null) : this(logger, reSettings)
|
|
||||||
{
|
|
||||||
AddWorkflow(Workflows);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RulesEngine(ILogger logger = null, ReSettings reSettings = null)
|
|
||||||
{
|
|
||||||
_logger = logger ?? new NullLogger<RulesEngine>();
|
|
||||||
_reSettings = reSettings ?? new ReSettings();
|
|
||||||
_ruleExpressionParser = new RuleExpressionParser(_reSettings);
|
|
||||||
_ruleCompiler = new RuleCompiler(new RuleExpressionBuilderFactory(_reSettings, _ruleExpressionParser),_reSettings, _logger);
|
|
||||||
_actionFactory = new ActionFactory(GetActionRegistry(_reSettings));
|
|
||||||
}
|
|
||||||
|
|
||||||
private IDictionary<string, Func<ActionBase>> GetActionRegistry(ReSettings reSettings)
|
|
||||||
{
|
|
||||||
var actionDictionary = GetDefaultActionRegistry();
|
|
||||||
var customActions = reSettings.CustomActions ?? new Dictionary<string, Func<ActionBase>>();
|
|
||||||
foreach (var customAction in customActions)
|
|
||||||
{
|
|
||||||
actionDictionary.Add(customAction);
|
|
||||||
}
|
|
||||||
return actionDictionary;
|
|
||||||
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Public Methods
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This will execute all the rules of the specified workflow
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="workflowName">The name of the workflow with rules to execute against the inputs</param>
|
|
||||||
/// <param name="inputs">A variable number of inputs</param>
|
|
||||||
/// <returns>List of rule results</returns>
|
|
||||||
public async ValueTask<List<RuleResultTree>> ExecuteAllRulesAsync(string workflowName, params object[] inputs)
|
|
||||||
{
|
|
||||||
_logger.LogTrace($"Called {nameof(ExecuteAllRulesAsync)} for workflow {workflowName} and count of input {inputs.Count()}");
|
|
||||||
|
|
||||||
var ruleParams = new List<RuleParameter>();
|
|
||||||
|
|
||||||
for (var i = 0; i < inputs.Length; i++)
|
|
||||||
{
|
|
||||||
var input = inputs[i];
|
|
||||||
ruleParams.Add(new RuleParameter($"input{i + 1}", input));
|
|
||||||
}
|
|
||||||
|
|
||||||
return await ExecuteAllRulesAsync(workflowName, ruleParams.ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This will execute all the rules of the specified workflow
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="workflowName">The name of the workflow with rules to execute against the inputs</param>
|
|
||||||
/// <param name="ruleParams">A variable number of rule parameters</param>
|
|
||||||
/// <returns>List of rule results</returns>
|
|
||||||
public async ValueTask<List<RuleResultTree>> ExecuteAllRulesAsync(string workflowName, params RuleParameter[] ruleParams)
|
|
||||||
{
|
|
||||||
var ruleResultList = ValidateWorkflowAndExecuteRule(workflowName, ruleParams);
|
|
||||||
await ExecuteActionAsync(ruleResultList);
|
|
||||||
return ruleResultList;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async ValueTask ExecuteActionAsync(IEnumerable<RuleResultTree> ruleResultList)
|
|
||||||
{
|
|
||||||
foreach (var ruleResult in ruleResultList)
|
|
||||||
{
|
|
||||||
if(ruleResult.ChildResults != null)
|
|
||||||
{
|
|
||||||
await ExecuteActionAsync(ruleResult.ChildResults);
|
|
||||||
}
|
|
||||||
var actionResult = await ExecuteActionForRuleResult(ruleResult, false);
|
|
||||||
ruleResult.ActionResult = new ActionResult {
|
|
||||||
Output = actionResult.Output,
|
|
||||||
Exception = actionResult.Exception
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<ActionRuleResult> ExecuteActionWorkflowAsync(string workflowName, string ruleName, RuleParameter[] ruleParameters)
|
|
||||||
{
|
|
||||||
var compiledRule = CompileRule(workflowName, ruleName, ruleParameters);
|
|
||||||
var resultTree = compiledRule(ruleParameters);
|
|
||||||
return await ExecuteActionForRuleResult(resultTree, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async ValueTask<ActionRuleResult> ExecuteActionForRuleResult(RuleResultTree resultTree, bool includeRuleResults = false)
|
|
||||||
{
|
|
||||||
var ruleActions = resultTree?.Rule?.Actions;
|
|
||||||
var actionInfo = resultTree?.IsSuccess == true ? ruleActions?.OnSuccess : ruleActions?.OnFailure;
|
|
||||||
|
|
||||||
if (actionInfo != null)
|
|
||||||
{
|
|
||||||
var action = _actionFactory.Get(actionInfo.Name);
|
|
||||||
var ruleParameters = resultTree.Inputs.Select(kv => new RuleParameter(kv.Key, kv.Value)).ToArray();
|
|
||||||
return await action.ExecuteAndReturnResultAsync(new ActionContext(actionInfo.Context, resultTree), ruleParameters, includeRuleResults);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//If there is no action,return output as null and return the result for rule
|
|
||||||
return new ActionRuleResult {
|
|
||||||
Output = null,
|
|
||||||
Results = includeRuleResults ? new List<RuleResultTree>() { resultTree } : null
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Private Methods
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds the workflow if the workflow name is not already added. Ignores the rest.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="workflows">The workflow rules.</param>
|
|
||||||
/// <exception cref="RuleValidationException"></exception>
|
|
||||||
public void AddWorkflow(params Workflow[] workflows)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
foreach (var workflow in workflows)
|
|
||||||
{
|
|
||||||
var validator = new WorkflowsValidator();
|
|
||||||
validator.ValidateAndThrow(workflow);
|
|
||||||
if (!_rulesCache.ContainsWorkflows(workflow.WorkflowName))
|
|
||||||
{
|
|
||||||
_rulesCache.AddOrUpdateWorkflows(workflow.WorkflowName, workflow);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new ValidationException($"Cannot add workflow `{workflow.WorkflowName}` as it already exists. Use `AddOrUpdateWorkflow` to update existing workflow");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (ValidationException ex)
|
|
||||||
{
|
|
||||||
throw new RuleValidationException(ex.Message, ex.Errors);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds new workflow rules if not previously added.
|
|
||||||
/// Or updates the rules for an existing workflow.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="workflows">The workflow rules.</param>
|
|
||||||
/// <exception cref="RuleValidationException"></exception>
|
|
||||||
public void AddOrUpdateWorkflow(params Workflow[] workflows)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
foreach (var workflow in workflows)
|
|
||||||
{
|
|
||||||
var validator = new WorkflowsValidator();
|
|
||||||
validator.ValidateAndThrow(workflow);
|
|
||||||
_rulesCache.AddOrUpdateWorkflows(workflow.WorkflowName, workflow);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (ValidationException ex)
|
|
||||||
{
|
|
||||||
throw new RuleValidationException(ex.Message, ex.Errors);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<string> GetAllRegisteredWorkflowNames()
|
|
||||||
{
|
|
||||||
return _rulesCache.GetAllWorkflowNames();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public RulesEngine(Workflow[] Workflows, ReSettings reSettings = null) : this(reSettings)
|
||||||
/// Checks is workflow exist.
|
{
|
||||||
/// </summary>
|
AddWorkflow(Workflows);
|
||||||
/// <param name="workflowName">The workflow name.</param>
|
}
|
||||||
/// <returns> <c>true</c> if contains the specified workflow name; otherwise, <c>false</c>.</returns>
|
|
||||||
public bool ContainsWorkflow(string workflowName)
|
public RulesEngine(ReSettings reSettings = null)
|
||||||
{
|
{
|
||||||
return _rulesCache.ContainsWorkflows(workflowName);
|
_reSettings = reSettings ?? new ReSettings();
|
||||||
}
|
if(_reSettings.CacheConfig == null)
|
||||||
|
{
|
||||||
/// <summary>
|
_reSettings.CacheConfig = new MemCacheConfig();
|
||||||
/// Clears the workflow.
|
}
|
||||||
/// </summary>
|
_rulesCache = new RulesCache(_reSettings);
|
||||||
public void ClearWorkflows()
|
_ruleExpressionParser = new RuleExpressionParser(_reSettings);
|
||||||
{
|
_ruleCompiler = new RuleCompiler(new RuleExpressionBuilderFactory(_reSettings, _ruleExpressionParser),_reSettings);
|
||||||
_rulesCache.Clear();
|
_actionFactory = new ActionFactory(GetActionRegistry(_reSettings));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private IDictionary<string, Func<ActionBase>> GetActionRegistry(ReSettings reSettings)
|
||||||
/// Removes the workflows.
|
{
|
||||||
/// </summary>
|
var actionDictionary = GetDefaultActionRegistry();
|
||||||
/// <param name="workflowNames">The workflow names.</param>
|
var customActions = reSettings.CustomActions ?? new Dictionary<string, Func<ActionBase>>();
|
||||||
public void RemoveWorkflow(params string[] workflowNames)
|
foreach (var customAction in customActions)
|
||||||
{
|
{
|
||||||
foreach (var workflowName in workflowNames)
|
actionDictionary.Add(customAction);
|
||||||
{
|
}
|
||||||
_rulesCache.Remove(workflowName);
|
return actionDictionary;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
#endregion
|
||||||
/// <summary>
|
|
||||||
/// This will validate workflow rules then call execute method
|
#region Public Methods
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">type of entity</typeparam>
|
/// <summary>
|
||||||
/// <param name="input">input</param>
|
/// This will execute all the rules of the specified workflow
|
||||||
/// <param name="workflowName">workflow name</param>
|
/// </summary>
|
||||||
/// <returns>list of rule result set</returns>
|
/// <param name="workflowName">The name of the workflow with rules to execute against the inputs</param>
|
||||||
private List<RuleResultTree> ValidateWorkflowAndExecuteRule(string workflowName, RuleParameter[] ruleParams)
|
/// <param name="inputs">A variable number of inputs</param>
|
||||||
{
|
/// <returns>List of rule results</returns>
|
||||||
List<RuleResultTree> result;
|
public async ValueTask<List<RuleResultTree>> ExecuteAllRulesAsync(string workflowName, params object[] inputs)
|
||||||
|
{
|
||||||
if (RegisterRule(workflowName, ruleParams))
|
var ruleParams = new List<RuleParameter>();
|
||||||
{
|
|
||||||
result = ExecuteAllRuleByWorkflow(workflowName, ruleParams);
|
for (var i = 0; i < inputs.Length; i++)
|
||||||
}
|
{
|
||||||
else
|
var input = inputs[i];
|
||||||
{
|
ruleParams.Add(new RuleParameter($"input{i + 1}", input));
|
||||||
_logger.LogTrace($"Rule config file is not present for the {workflowName} workflow");
|
}
|
||||||
// if rules are not registered with Rules Engine
|
|
||||||
throw new ArgumentException($"Rule config file is not present for the {workflowName} workflow");
|
return await ExecuteAllRulesAsync(workflowName, ruleParams.ToArray());
|
||||||
}
|
}
|
||||||
return result;
|
|
||||||
}
|
/// <summary>
|
||||||
|
/// This will execute all the rules of the specified workflow
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// This will compile the rules and store them to dictionary
|
/// <param name="workflowName">The name of the workflow with rules to execute against the inputs</param>
|
||||||
/// </summary>
|
/// <param name="ruleParams">A variable number of rule parameters</param>
|
||||||
/// <param name="workflowName">workflow name</param>
|
/// <returns>List of rule results</returns>
|
||||||
/// <param name="ruleParams">The rule parameters.</param>
|
public async ValueTask<List<RuleResultTree>> ExecuteAllRulesAsync(string workflowName, params RuleParameter[] ruleParams)
|
||||||
/// <returns>
|
{
|
||||||
/// bool result
|
var ruleResultList = ValidateWorkflowAndExecuteRule(workflowName, ruleParams);
|
||||||
/// </returns>
|
await ExecuteActionAsync(ruleResultList);
|
||||||
private bool RegisterRule(string workflowName, params RuleParameter[] ruleParams)
|
return ruleResultList;
|
||||||
{
|
}
|
||||||
var compileRulesKey = GetCompiledRulesKey(workflowName, ruleParams);
|
|
||||||
if (_rulesCache.AreCompiledRulesUpToDate(compileRulesKey, workflowName))
|
private async ValueTask ExecuteActionAsync(IEnumerable<RuleResultTree> ruleResultList)
|
||||||
{
|
{
|
||||||
return true;
|
foreach (var ruleResult in ruleResultList)
|
||||||
}
|
{
|
||||||
|
if(ruleResult.ChildResults != null)
|
||||||
var workflow = _rulesCache.GetWorkflow(workflowName);
|
{
|
||||||
if (workflow != null)
|
await ExecuteActionAsync(ruleResult.ChildResults);
|
||||||
{
|
}
|
||||||
var dictFunc = new Dictionary<string, RuleFunc<RuleResultTree>>();
|
var actionResult = await ExecuteActionForRuleResult(ruleResult, false);
|
||||||
foreach (var rule in workflow.Rules.Where(c => c.Enabled))
|
ruleResult.ActionResult = new ActionResult {
|
||||||
{
|
Output = actionResult.Output,
|
||||||
dictFunc.Add(rule.RuleName, CompileRule(rule, ruleParams, workflow.GlobalParams?.ToArray()));
|
Exception = actionResult.Exception
|
||||||
}
|
};
|
||||||
|
}
|
||||||
_rulesCache.AddOrUpdateCompiledRule(compileRulesKey, dictFunc);
|
}
|
||||||
_logger.LogTrace($"Rules has been compiled for the {workflowName} workflow and added to dictionary");
|
|
||||||
return true;
|
public async ValueTask<ActionRuleResult> ExecuteActionWorkflowAsync(string workflowName, string ruleName, RuleParameter[] ruleParameters)
|
||||||
}
|
{
|
||||||
else
|
var compiledRule = CompileRule(workflowName, ruleName, ruleParameters);
|
||||||
{
|
var resultTree = compiledRule(ruleParameters);
|
||||||
return false;
|
return await ExecuteActionForRuleResult(resultTree, true);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private async ValueTask<ActionRuleResult> ExecuteActionForRuleResult(RuleResultTree resultTree, bool includeRuleResults = false)
|
||||||
|
{
|
||||||
private RuleFunc<RuleResultTree> CompileRule(string workflowName, string ruleName, RuleParameter[] ruleParameters)
|
var ruleActions = resultTree?.Rule?.Actions;
|
||||||
{
|
var actionInfo = resultTree?.IsSuccess == true ? ruleActions?.OnSuccess : ruleActions?.OnFailure;
|
||||||
var workflow = _rulesCache.GetWorkflow(workflowName);
|
|
||||||
if(workflow == null)
|
if (actionInfo != null)
|
||||||
{
|
{
|
||||||
throw new ArgumentException($"Workflow `{workflowName}` is not found");
|
var action = _actionFactory.Get(actionInfo.Name);
|
||||||
}
|
var ruleParameters = resultTree.Inputs.Select(kv => new RuleParameter(kv.Key, kv.Value)).ToArray();
|
||||||
var currentRule = workflow.Rules?.SingleOrDefault(c => c.RuleName == ruleName && c.Enabled);
|
return await action.ExecuteAndReturnResultAsync(new ActionContext(actionInfo.Context, resultTree), ruleParameters, includeRuleResults);
|
||||||
if (currentRule == null)
|
}
|
||||||
{
|
else
|
||||||
throw new ArgumentException($"Workflow `{workflowName}` does not contain any rule named `{ruleName}`");
|
{
|
||||||
}
|
//If there is no action,return output as null and return the result for rule
|
||||||
return CompileRule(currentRule, ruleParameters, workflow.GlobalParams?.ToArray());
|
return new ActionRuleResult {
|
||||||
}
|
Output = null,
|
||||||
|
Results = includeRuleResults ? new List<RuleResultTree>() { resultTree } : null
|
||||||
private RuleFunc<RuleResultTree> CompileRule(Rule rule, RuleParameter[] ruleParams, ScopedParam[] scopedParams)
|
};
|
||||||
{
|
}
|
||||||
return _ruleCompiler.CompileRule(rule, ruleParams, scopedParams);
|
}
|
||||||
}
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private Methods
|
||||||
/// <summary>
|
|
||||||
/// This will execute the compiled rules
|
/// <summary>
|
||||||
/// </summary>
|
/// Adds the workflow if the workflow name is not already added. Ignores the rest.
|
||||||
/// <param name="workflowName"></param>
|
/// </summary>
|
||||||
/// <param name="ruleParams"></param>
|
/// <param name="workflows">The workflow rules.</param>
|
||||||
/// <returns>list of rule result set</returns>
|
/// <exception cref="RuleValidationException"></exception>
|
||||||
private List<RuleResultTree> ExecuteAllRuleByWorkflow(string workflowName, RuleParameter[] ruleParameters)
|
public void AddWorkflow(params Workflow[] workflows)
|
||||||
{
|
{
|
||||||
_logger.LogTrace($"Compiled rules found for {workflowName} workflow and executed");
|
try
|
||||||
|
{
|
||||||
var result = new List<RuleResultTree>();
|
foreach (var workflow in workflows)
|
||||||
var compiledRulesCacheKey = GetCompiledRulesKey(workflowName, ruleParameters);
|
{
|
||||||
foreach (var compiledRule in _rulesCache.GetCompiledRules(compiledRulesCacheKey)?.Values)
|
var validator = new WorkflowsValidator();
|
||||||
{
|
validator.ValidateAndThrow(workflow);
|
||||||
var resultTree = compiledRule(ruleParameters);
|
if (!_rulesCache.ContainsWorkflows(workflow.WorkflowName))
|
||||||
result.Add(resultTree);
|
{
|
||||||
}
|
_rulesCache.AddOrUpdateWorkflows(workflow.WorkflowName, workflow);
|
||||||
|
}
|
||||||
FormatErrorMessages(result);
|
else
|
||||||
return result;
|
{
|
||||||
}
|
throw new ValidationException($"Cannot add workflow `{workflow.WorkflowName}` as it already exists. Use `AddOrUpdateWorkflow` to update existing workflow");
|
||||||
|
}
|
||||||
private string GetCompiledRulesKey(string workflowName, RuleParameter[] ruleParams)
|
}
|
||||||
{
|
}
|
||||||
var key = $"{workflowName}-" + string.Join("-", ruleParams.Select(c => c.Type.Name));
|
catch (ValidationException ex)
|
||||||
return key;
|
{
|
||||||
}
|
throw new RuleValidationException(ex.Message, ex.Errors);
|
||||||
|
}
|
||||||
private IDictionary<string, Func<ActionBase>> GetDefaultActionRegistry()
|
}
|
||||||
{
|
|
||||||
return new Dictionary<string, Func<ActionBase>>{
|
/// <summary>
|
||||||
{"OutputExpression",() => new OutputExpressionAction(_ruleExpressionParser) },
|
/// Adds new workflow rules if not previously added.
|
||||||
{"EvaluateRule", () => new EvaluateRuleAction(this,_ruleExpressionParser) }
|
/// Or updates the rules for an existing workflow.
|
||||||
};
|
/// </summary>
|
||||||
}
|
/// <param name="workflows">The workflow rules.</param>
|
||||||
|
/// <exception cref="RuleValidationException"></exception>
|
||||||
/// <summary>
|
public void AddOrUpdateWorkflow(params Workflow[] workflows)
|
||||||
/// The result
|
{
|
||||||
/// </summary>
|
try
|
||||||
/// <param name="ruleResultList">The result.</param>
|
{
|
||||||
/// <returns>Updated error message.</returns>
|
foreach (var workflow in workflows)
|
||||||
private IEnumerable<RuleResultTree> FormatErrorMessages(IEnumerable<RuleResultTree> ruleResultList)
|
{
|
||||||
{
|
var validator = new WorkflowsValidator();
|
||||||
if (_reSettings.EnableFormattedErrorMessage)
|
validator.ValidateAndThrow(workflow);
|
||||||
{
|
_rulesCache.AddOrUpdateWorkflows(workflow.WorkflowName, workflow);
|
||||||
foreach (var ruleResult in ruleResultList?.Where(r => !r.IsSuccess))
|
}
|
||||||
{
|
}
|
||||||
var errorMessage = ruleResult?.Rule?.ErrorMessage;
|
catch (ValidationException ex)
|
||||||
if (string.IsNullOrWhiteSpace(ruleResult.ExceptionMessage) && errorMessage != null)
|
{
|
||||||
{
|
throw new RuleValidationException(ex.Message, ex.Errors);
|
||||||
var errorParameters = Regex.Matches(errorMessage, ParamParseRegex);
|
}
|
||||||
|
}
|
||||||
var inputs = ruleResult.Inputs;
|
|
||||||
foreach (var param in errorParameters)
|
public List<string> GetAllRegisteredWorkflowNames()
|
||||||
{
|
{
|
||||||
var paramVal = param?.ToString();
|
return _rulesCache.GetAllWorkflowNames();
|
||||||
var property = paramVal?.Substring(2, paramVal.Length - 3);
|
}
|
||||||
if (property?.Split('.')?.Count() > 1)
|
|
||||||
{
|
/// <summary>
|
||||||
var typeName = property?.Split('.')?[0];
|
/// Checks is workflow exist.
|
||||||
var propertyName = property?.Split('.')?[1];
|
/// </summary>
|
||||||
errorMessage = UpdateErrorMessage(errorMessage, inputs, property, typeName, propertyName);
|
/// <param name="workflowName">The workflow name.</param>
|
||||||
}
|
/// <returns> <c>true</c> if contains the specified workflow name; otherwise, <c>false</c>.</returns>
|
||||||
else
|
public bool ContainsWorkflow(string workflowName)
|
||||||
{
|
{
|
||||||
var arrParams = inputs?.Select(c => new { Name = c.Key, c.Value });
|
return _rulesCache.ContainsWorkflows(workflowName);
|
||||||
var model = arrParams?.Where(a => string.Equals(a.Name, property))?.FirstOrDefault();
|
}
|
||||||
var value = model?.Value != null ? JsonConvert.SerializeObject(model?.Value) : null;
|
|
||||||
errorMessage = errorMessage?.Replace($"$({property})", value ?? $"$({property})");
|
/// <summary>
|
||||||
}
|
/// Clears the workflow.
|
||||||
}
|
/// </summary>
|
||||||
ruleResult.ExceptionMessage = errorMessage;
|
public void ClearWorkflows()
|
||||||
}
|
{
|
||||||
|
_rulesCache.Clear();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return ruleResultList;
|
/// <summary>
|
||||||
}
|
/// Removes the workflows.
|
||||||
|
/// </summary>
|
||||||
/// <summary>
|
/// <param name="workflowNames">The workflow names.</param>
|
||||||
/// Updates the error message.
|
public void RemoveWorkflow(params string[] workflowNames)
|
||||||
/// </summary>
|
{
|
||||||
/// <param name="errorMessage">The error message.</param>
|
foreach (var workflowName in workflowNames)
|
||||||
/// <param name="evaluatedParams">The evaluated parameters.</param>
|
{
|
||||||
/// <param name="property">The property.</param>
|
_rulesCache.Remove(workflowName);
|
||||||
/// <param name="typeName">Name of the type.</param>
|
}
|
||||||
/// <param name="propertyName">Name of the property.</param>
|
}
|
||||||
/// <returns>Updated error message.</returns>
|
|
||||||
private static string UpdateErrorMessage(string errorMessage, IDictionary<string, object> inputs, string property, string typeName, string propertyName)
|
/// <summary>
|
||||||
{
|
/// This will validate workflow rules then call execute method
|
||||||
var arrParams = inputs?.Select(c => new { Name = c.Key, c.Value });
|
/// </summary>
|
||||||
var model = arrParams?.Where(a => string.Equals(a.Name, typeName))?.FirstOrDefault();
|
/// <typeparam name="T">type of entity</typeparam>
|
||||||
if (model != null)
|
/// <param name="input">input</param>
|
||||||
{
|
/// <param name="workflowName">workflow name</param>
|
||||||
var modelJson = JsonConvert.SerializeObject(model?.Value);
|
/// <returns>list of rule result set</returns>
|
||||||
var jObj = JObject.Parse(modelJson);
|
private List<RuleResultTree> ValidateWorkflowAndExecuteRule(string workflowName, RuleParameter[] ruleParams)
|
||||||
JToken jToken = null;
|
{
|
||||||
var val = jObj?.TryGetValue(propertyName, StringComparison.OrdinalIgnoreCase, out jToken);
|
List<RuleResultTree> result;
|
||||||
errorMessage = errorMessage.Replace($"$({property})", jToken != null ? jToken?.ToString() : $"({property})");
|
|
||||||
}
|
if (RegisterRule(workflowName, ruleParams))
|
||||||
|
{
|
||||||
return errorMessage;
|
result = ExecuteAllRuleByWorkflow(workflowName, ruleParams);
|
||||||
}
|
}
|
||||||
#endregion
|
else
|
||||||
}
|
{
|
||||||
}
|
// if rules are not registered with Rules Engine
|
||||||
|
throw new ArgumentException($"Rule config file is not present for the {workflowName} workflow");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This will compile the rules and store them to dictionary
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="workflowName">workflow name</param>
|
||||||
|
/// <param name="ruleParams">The rule parameters.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// bool result
|
||||||
|
/// </returns>
|
||||||
|
private bool RegisterRule(string workflowName, params RuleParameter[] ruleParams)
|
||||||
|
{
|
||||||
|
var compileRulesKey = GetCompiledRulesKey(workflowName, ruleParams);
|
||||||
|
if (_rulesCache.AreCompiledRulesUpToDate(compileRulesKey, workflowName))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var workflow = _rulesCache.GetWorkflow(workflowName);
|
||||||
|
if (workflow != null)
|
||||||
|
{
|
||||||
|
var dictFunc = new Dictionary<string, RuleFunc<RuleResultTree>>();
|
||||||
|
foreach (var rule in workflow.Rules.Where(c => c.Enabled))
|
||||||
|
{
|
||||||
|
dictFunc.Add(rule.RuleName, CompileRule(rule, ruleParams, workflow.GlobalParams?.ToArray()));
|
||||||
|
}
|
||||||
|
|
||||||
|
_rulesCache.AddOrUpdateCompiledRule(compileRulesKey, dictFunc);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private RuleFunc<RuleResultTree> CompileRule(string workflowName, string ruleName, RuleParameter[] ruleParameters)
|
||||||
|
{
|
||||||
|
var workflow = _rulesCache.GetWorkflow(workflowName);
|
||||||
|
if(workflow == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Workflow `{workflowName}` is not found");
|
||||||
|
}
|
||||||
|
var currentRule = workflow.Rules?.SingleOrDefault(c => c.RuleName == ruleName && c.Enabled);
|
||||||
|
if (currentRule == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Workflow `{workflowName}` does not contain any rule named `{ruleName}`");
|
||||||
|
}
|
||||||
|
return CompileRule(currentRule, ruleParameters, workflow.GlobalParams?.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
private RuleFunc<RuleResultTree> CompileRule(Rule rule, RuleParameter[] ruleParams, ScopedParam[] scopedParams)
|
||||||
|
{
|
||||||
|
return _ruleCompiler.CompileRule(rule, ruleParams, scopedParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This will execute the compiled rules
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="workflowName"></param>
|
||||||
|
/// <param name="ruleParams"></param>
|
||||||
|
/// <returns>list of rule result set</returns>
|
||||||
|
private List<RuleResultTree> ExecuteAllRuleByWorkflow(string workflowName, RuleParameter[] ruleParameters)
|
||||||
|
{
|
||||||
|
var result = new List<RuleResultTree>();
|
||||||
|
var compiledRulesCacheKey = GetCompiledRulesKey(workflowName, ruleParameters);
|
||||||
|
foreach (var compiledRule in _rulesCache.GetCompiledRules(compiledRulesCacheKey)?.Values)
|
||||||
|
{
|
||||||
|
var resultTree = compiledRule(ruleParameters);
|
||||||
|
result.Add(resultTree);
|
||||||
|
}
|
||||||
|
|
||||||
|
FormatErrorMessages(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetCompiledRulesKey(string workflowName, RuleParameter[] ruleParams)
|
||||||
|
{
|
||||||
|
var key = $"{workflowName}-" + string.Join("-", ruleParams.Select(c => c.Type.Name));
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IDictionary<string, Func<ActionBase>> GetDefaultActionRegistry()
|
||||||
|
{
|
||||||
|
return new Dictionary<string, Func<ActionBase>>{
|
||||||
|
{"OutputExpression",() => new OutputExpressionAction(_ruleExpressionParser) },
|
||||||
|
{"EvaluateRule", () => new EvaluateRuleAction(this,_ruleExpressionParser) }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The result
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ruleResultList">The result.</param>
|
||||||
|
/// <returns>Updated error message.</returns>
|
||||||
|
private IEnumerable<RuleResultTree> FormatErrorMessages(IEnumerable<RuleResultTree> ruleResultList)
|
||||||
|
{
|
||||||
|
if (_reSettings.EnableFormattedErrorMessage)
|
||||||
|
{
|
||||||
|
foreach (var ruleResult in ruleResultList?.Where(r => !r.IsSuccess))
|
||||||
|
{
|
||||||
|
var errorMessage = ruleResult?.Rule?.ErrorMessage;
|
||||||
|
if (string.IsNullOrWhiteSpace(ruleResult.ExceptionMessage) && errorMessage != null)
|
||||||
|
{
|
||||||
|
var errorParameters = Regex.Matches(errorMessage, ParamParseRegex);
|
||||||
|
|
||||||
|
var inputs = ruleResult.Inputs;
|
||||||
|
foreach (var param in errorParameters)
|
||||||
|
{
|
||||||
|
var paramVal = param?.ToString();
|
||||||
|
var property = paramVal?.Substring(2, paramVal.Length - 3);
|
||||||
|
if (property?.Split('.')?.Count() > 1)
|
||||||
|
{
|
||||||
|
var typeName = property?.Split('.')?[0];
|
||||||
|
var propertyName = property?.Split('.')?[1];
|
||||||
|
errorMessage = UpdateErrorMessage(errorMessage, inputs, property, typeName, propertyName);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var arrParams = inputs?.Select(c => new { Name = c.Key, c.Value });
|
||||||
|
var model = arrParams?.Where(a => string.Equals(a.Name, property))?.FirstOrDefault();
|
||||||
|
var value = model?.Value != null ? JsonConvert.SerializeObject(model?.Value) : null;
|
||||||
|
errorMessage = errorMessage?.Replace($"$({property})", value ?? $"$({property})");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ruleResult.ExceptionMessage = errorMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ruleResultList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the error message.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="errorMessage">The error message.</param>
|
||||||
|
/// <param name="evaluatedParams">The evaluated parameters.</param>
|
||||||
|
/// <param name="property">The property.</param>
|
||||||
|
/// <param name="typeName">Name of the type.</param>
|
||||||
|
/// <param name="propertyName">Name of the property.</param>
|
||||||
|
/// <returns>Updated error message.</returns>
|
||||||
|
private static string UpdateErrorMessage(string errorMessage, IDictionary<string, object> inputs, string property, string typeName, string propertyName)
|
||||||
|
{
|
||||||
|
var arrParams = inputs?.Select(c => new { Name = c.Key, c.Value });
|
||||||
|
var model = arrParams?.Where(a => string.Equals(a.Name, typeName))?.FirstOrDefault();
|
||||||
|
if (model != null)
|
||||||
|
{
|
||||||
|
var modelJson = JsonConvert.SerializeObject(model?.Value);
|
||||||
|
var jObj = JObject.Parse(modelJson);
|
||||||
|
JToken jToken = null;
|
||||||
|
var val = jObj?.TryGetValue(propertyName, StringComparison.OrdinalIgnoreCase, out jToken);
|
||||||
|
errorMessage = errorMessage.Replace($"$({property})", jToken != null ? jToken?.ToString() : $"({property})");
|
||||||
|
}
|
||||||
|
|
||||||
|
return errorMessage;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -31,15 +31,13 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="FastExpressionCompiler" Version="3.2.1" />
|
<PackageReference Include="FastExpressionCompiler" Version="3.2.2" />
|
||||||
<PackageReference Include="FluentValidation" Version="10.3.0" />
|
<PackageReference Include="FluentValidation" Version="10.4.0" />
|
||||||
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
|
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.1.6" />
|
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||||
<PackageReference Include="System.Linq" Version="4.3.0" />
|
<PackageReference Include="System.Linq" Version="4.3.0" />
|
||||||
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.2.14" />
|
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.2.18" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.6" />
|
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
||||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
|
||||||
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
|
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ namespace RulesEngine.UnitTest.ActionTests
|
||||||
public async Task CustomActionOnRuleMustHaveContextValues()
|
public async Task CustomActionOnRuleMustHaveContextValues()
|
||||||
{
|
{
|
||||||
var workflow = GetWorkflow();
|
var workflow = GetWorkflow();
|
||||||
var re = new RulesEngine(workflow, null, reSettings: new ReSettings {
|
var re = new RulesEngine(workflow, reSettings: new ReSettings {
|
||||||
CustomActions = new Dictionary<string, System.Func<Actions.ActionBase>> {
|
CustomActions = new Dictionary<string, System.Func<Actions.ActionBase>> {
|
||||||
|
|
||||||
{ "ReturnContext", () => new ReturnContextAction() }
|
{ "ReturnContext", () => new ReturnContextAction() }
|
||||||
|
@ -39,7 +39,7 @@ namespace RulesEngine.UnitTest.ActionTests
|
||||||
var workflowViaTextJson = System.Text.Json.JsonSerializer.Deserialize<Workflow[]>(workflowStr,serializationOptions);
|
var workflowViaTextJson = System.Text.Json.JsonSerializer.Deserialize<Workflow[]>(workflowStr,serializationOptions);
|
||||||
|
|
||||||
|
|
||||||
var re = new RulesEngine(workflow, null, reSettings: new ReSettings {
|
var re = new RulesEngine(workflow, reSettings: new ReSettings {
|
||||||
CustomActions = new Dictionary<string, System.Func<Actions.ActionBase>> {
|
CustomActions = new Dictionary<string, System.Func<Actions.ActionBase>> {
|
||||||
|
|
||||||
{ "ReturnContext", () => new ReturnContextAction() }
|
{ "ReturnContext", () => new ReturnContextAction() }
|
||||||
|
|
|
@ -23,6 +23,16 @@ namespace RulesEngine.UnitTest
|
||||||
Assert.Equal(2 * 2, result.Output);
|
Assert.Equal(2 * 2, result.Output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task WhenExpressionIsSuccess_ComplexOutputExpressionAction_ReturnsExpressionEvaluation()
|
||||||
|
{
|
||||||
|
var engine = new RulesEngine(GetWorkflowWithActions());
|
||||||
|
var result = await engine.ExecuteActionWorkflowAsync("ActionWorkflow", "ComplexOutputRuleTest", new RuleParameter[0]);
|
||||||
|
Assert.NotNull(result);
|
||||||
|
dynamic output = result.Output;
|
||||||
|
Assert.Equal(2, output.test);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task WhenExpressionIsSuccess_EvaluateRuleAction_ReturnsExpressionEvaluation()
|
public async Task WhenExpressionIsSuccess_EvaluateRuleAction_ReturnsExpressionEvaluation()
|
||||||
{
|
{
|
||||||
|
@ -108,6 +118,19 @@ namespace RulesEngine.UnitTest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
new Rule{
|
||||||
|
RuleName = "ComplexOutputRuleTest",
|
||||||
|
RuleExpressionType = RuleExpressionType.LambdaExpression,
|
||||||
|
Expression = "1 == 1",
|
||||||
|
Actions = new RuleActions{
|
||||||
|
OnSuccess = new ActionInfo{
|
||||||
|
Name = "OutputExpression",
|
||||||
|
Context = new Dictionary<string, object>{
|
||||||
|
{"expression", "new (2 as test)"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
new Rule{
|
new Rule{
|
||||||
RuleName = "EvaluateRuleTest",
|
RuleName = "EvaluateRuleTest",
|
||||||
RuleExpressionType = RuleExpressionType.LambdaExpression,
|
RuleExpressionType = RuleExpressionType.LambdaExpression,
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,7 +1,6 @@
|
||||||
// Copyright (c) Microsoft Corporation.
|
// Copyright (c) Microsoft Corporation.
|
||||||
// Licensed under the MIT License.
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
|
||||||
using RulesEngine.ExpressionBuilders;
|
using RulesEngine.ExpressionBuilders;
|
||||||
using RulesEngine.Models;
|
using RulesEngine.Models;
|
||||||
using System;
|
using System;
|
||||||
|
@ -17,10 +16,10 @@ namespace RulesEngine.UnitTest
|
||||||
[Fact]
|
[Fact]
|
||||||
public void RuleCompiler_NullCheck()
|
public void RuleCompiler_NullCheck()
|
||||||
{
|
{
|
||||||
Assert.Throws<ArgumentNullException>(() => new RuleCompiler(null, null,null));
|
Assert.Throws<ArgumentNullException>(() => new RuleCompiler(null, null));
|
||||||
var reSettings = new ReSettings();
|
var reSettings = new ReSettings();
|
||||||
var parser = new RuleExpressionParser(reSettings);
|
var parser = new RuleExpressionParser(reSettings);
|
||||||
Assert.Throws<ArgumentNullException>(() => new RuleCompiler(new RuleExpressionBuilderFactory(reSettings, parser), null,null));
|
Assert.Throws<ArgumentNullException>(() => new RuleCompiler(null, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
@ -28,11 +27,9 @@ namespace RulesEngine.UnitTest
|
||||||
{
|
{
|
||||||
var reSettings = new ReSettings();
|
var reSettings = new ReSettings();
|
||||||
var parser = new RuleExpressionParser(reSettings);
|
var parser = new RuleExpressionParser(reSettings);
|
||||||
var compiler = new RuleCompiler(new RuleExpressionBuilderFactory(reSettings, parser),null, new NullLogger<RuleCompiler>());
|
var compiler = new RuleCompiler(new RuleExpressionBuilderFactory(reSettings, parser),null);
|
||||||
Assert.Throws<ArgumentNullException>(() => compiler.CompileRule(null, null,null));
|
Assert.Throws<ArgumentNullException>(() => compiler.CompileRule(null, null,null));
|
||||||
Assert.Throws<ArgumentNullException>(() => compiler.CompileRule(null, new RuleParameter[] { null },null));
|
Assert.Throws<ArgumentNullException>(() => compiler.CompileRule(null, new RuleParameter[] { null },null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<SignAssembly>True</SignAssembly>
|
<SignAssembly>True</SignAssembly>
|
||||||
<AssemblyOriginatorKeyFile>..\..\signing\RulesEngine-publicKey.snk</AssemblyOriginatorKeyFile>
|
<AssemblyOriginatorKeyFile>..\..\signing\RulesEngine-publicKey.snk</AssemblyOriginatorKeyFile>
|
||||||
<DelaySign>True</DelaySign>
|
<DelaySign>True</DelaySign>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AutoFixture" Version="4.17.0" />
|
<PackageReference Include="AutoFixture" Version="4.17.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
|
||||||
<PackageReference Include="Moq" Version="4.16.1" />
|
<PackageReference Include="Moq" Version="4.17.2" />
|
||||||
<PackageReference Include="System.Text.Json" Version="5.0.2" />
|
<PackageReference Include="System.Text.Json" Version="6.0.2" />
|
||||||
<PackageReference Include="xunit" Version="2.4.1" />
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="coverlet.collector" Version="3.1.0">
|
<PackageReference Include="coverlet.collector" Version="3.1.2">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
@ -48,6 +48,9 @@
|
||||||
<None Update="TestData\rules8.json">
|
<None Update="TestData\rules8.json">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
|
<None Update="TestData\rules10.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
<None Update="TestData\rules9.json">
|
<None Update="TestData\rules9.json">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
|
|
|
@ -26,7 +26,7 @@ namespace RulesEngine.UnitTest
|
||||||
{
|
{
|
||||||
var workflow = GetWorkflowList();
|
var workflow = GetWorkflowList();
|
||||||
|
|
||||||
var engine = new RulesEngine(null, null);
|
var engine = new RulesEngine();
|
||||||
engine.AddWorkflow(workflow);
|
engine.AddWorkflow(workflow);
|
||||||
|
|
||||||
var input1 = new {
|
var input1 = new {
|
||||||
|
@ -47,7 +47,7 @@ namespace RulesEngine.UnitTest
|
||||||
{
|
{
|
||||||
var workflow = GetWorkflowList();
|
var workflow = GetWorkflowList();
|
||||||
|
|
||||||
var engine = new RulesEngine(null, null);
|
var engine = new RulesEngine();
|
||||||
engine.AddWorkflow(workflow);
|
engine.AddWorkflow(workflow);
|
||||||
|
|
||||||
var input1 = new {
|
var input1 = new {
|
||||||
|
@ -77,7 +77,7 @@ namespace RulesEngine.UnitTest
|
||||||
{
|
{
|
||||||
var workflow = GetWorkflowList();
|
var workflow = GetWorkflowList();
|
||||||
|
|
||||||
var engine = new RulesEngine(new string[] { }, null, new ReSettings {
|
var engine = new RulesEngine(new string[] { }, new ReSettings {
|
||||||
EnableScopedParams = false
|
EnableScopedParams = false
|
||||||
});
|
});
|
||||||
engine.AddWorkflow(workflow);
|
engine.AddWorkflow(workflow);
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"WorkflowName": "inputWorkflow",
|
||||||
|
"Rules": [
|
||||||
|
{
|
||||||
|
"RuleName": "GiveDiscount10",
|
||||||
|
"SuccessEvent": "10",
|
||||||
|
"RuleExpressionType": "LambdaExpression",
|
||||||
|
"Expression": "input1.Data.GetProperty(\"category\").GetString() == \"abc\""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -2,12 +2,20 @@
|
||||||
"WorkflowName": "inputWorkflow",
|
"WorkflowName": "inputWorkflow",
|
||||||
"Rules": [
|
"Rules": [
|
||||||
{
|
{
|
||||||
"RuleName": "GiveDiscount10",
|
"RuleName": "upperCaseAccess",
|
||||||
"SuccessEvent": "10",
|
"SuccessEvent": "10",
|
||||||
"ErrorMessage": "One or more adjust rules failed.",
|
"ErrorMessage": "One or more adjust rules failed.",
|
||||||
"ErrorType": "Error",
|
"ErrorType": "Error",
|
||||||
"RuleExpressionType": "LambdaExpression",
|
"RuleExpressionType": "LambdaExpression",
|
||||||
"Expression": "utils.CheckExists(String(input1.Property1)) == true"
|
"Expression": "utils.CheckExists(String(input1.Property1)) == true"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"RuleName": "lowerCaseAccess",
|
||||||
|
"SuccessEvent": "10",
|
||||||
|
"ErrorMessage": "One or more adjust rules failed.",
|
||||||
|
"ErrorType": "Error",
|
||||||
|
"RuleExpressionType": "LambdaExpression",
|
||||||
|
"Expression": "utils.CheckExists(String(input1.property1)) == true"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
Loading…
Reference in New Issue