Initial Commit

pull/2/head
Dishant Munjal 2019-08-13 15:36:57 +05:30
commit b960802bab
49 changed files with 2742 additions and 0 deletions

331
.gitignore vendored Normal file
View File

@ -0,0 +1,331 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
**/Properties/launchSettings.json
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush
.cr/
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
/src/RulesEngine/RulesEngine.sln.licenseheader

9
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,9 @@
# Microsoft Open Source Code of Conduct
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
Resources:
- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) Microsoft Corporation.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE

87
README.md Normal file
View File

@ -0,0 +1,87 @@
# Rules Engine
## Overview
Rules Engine is a NuGet Package for abstracting business logic/rules/policies out of the system. This works in a very simple way by giving you an ability to put your rules in a store outside the core logic of the system thus ensuring that any change in rules doesn't affect the core system.
## Installation
To install this NuGet Package, please install the NuGet RulesEngine from [NuGet.org](https://www.nuget.org/) using manage nuget explorer in visual studio.
## How to use it
To use the Rules Engine, please install it as a NuGet Package into the project you want to use.
You need to store the rules based on the [schema definition](https://github.com/microsoft/RulesEngine/blob/master/schema/workflowRules-schema.json) given and they can be stored in any store as deemed appropriate like Azure Blob Storage, Cosmos DB, Azure App Configuration, SQL Servers, file systems etc. The expressions are supposed to be a [lambda expressions](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/statements-expressions-operators/lambda-expressions).
An example rule could be -
```json
[
{
"WorkflowName": "Discount",
"Rules": [
{
"RuleName": "GiveDiscount10",
"SuccessEvent": "10",
"ErrorMessage": "One or more adjust rules failed.",
"ErrorType": "Error",
"RuleExpressionType": "LambdaExpression",
"Expression": "input1.country == \"india\" AND input1.loyalityFactor <= 2 AND input1.totalPurchasesToDate >= 5000"
},
{
"RuleName": "GiveDiscount20",
"SuccessEvent": "20",
"ErrorMessage": "One or more adjust rules failed.",
"ErrorType": "Error",
"RuleExpressionType": "LambdaExpression",
"Expression": "input1.country == \"india\" AND input1.loyalityFactor >= 3 AND input1.totalPurchasesToDate >= 10000"
}
]
}
]
```
You can inject the rules into the Rules Engine by initiating an instance by using the following code -
```c#
var rulesEngine = new RulesEngine(workflowRules, logger);
```
Here, *workflowRules* is a list of deserialized object based out of the schema explained above and *logger* is a custom logger instance made out of an [ILogger](https://github.com/microsoft/RulesEngine/wiki/Getting-Started#logger) instance.
Once done, the Rules Engine needs to execute the rules for a given input. It can be done by calling the method ExecuteRule as shown below -
```c#
List<RuleResultTree> response = rulesEngine.ExecuteRule(workflowName, input);
```
Here, *workflowName* is the name of the workflow, which is *Discount* in the above mentioned example. And *input* is the object which needs to be checked against the rules.
The *response* will contain a list of [*RuleResultTree*](https://github.com/microsoft/RulesEngine/wiki/Getting-Started#ruleresulttree) which gives information if a particular rule passed or failed.
_Note: A detailed example showcasing how to use Rules Engine is explained in [Getting Started page](https://github.com/microsoft/RulesEngine/wiki/Getting-Started) of [Rules Engine Wiki](https://github.com/microsoft/RulesEngine/wiki)._
_A demo app for the is available at [this location](https://github.com/microsoft/RulesEngine/tree/master/demo)._
## How it works
![](https://github.com/microsoft/RulesEngine/blob/master/assets/BlockDiagram.png)
The rules can be stored in any store and be fed to the system in a structure which follows a proper [schema](https://github.com/microsoft/RulesEngine/blob/master/schema/workflowRules-schema.json) of WorkFlow model.
The wrapper needs to be created over the Rules Engine package, which will get the rules and input message(s) from any store that your system dictates and put it into the Engine. Also, the wrapper then needs to handle the output using appropriate means.
_Note: To know in detail of the workings of Rules Engine, please visit [How it works section](https://github.com/microsoft/RulesEngine/wiki/Introduction#how-it-works) in [Rules Engine Wiki](https://github.com/microsoft/RulesEngine/wiki)._
## Contributing
This project welcomes contributions and suggestions. Most contributions require you to agree to a
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
---
_For more details please check out [Rules Engine Wiki](https://github.com/microsoft/RulesEngine/wiki)._

BIN
assets/BlockDiagram.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
<PackageReference Include="RulesEngine" Version="1.0.0-beta" />
</ItemGroup>
<ItemGroup>
<None Update="Workflows\Discount.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

63
demo/DemoApp/Program.cs Normal file
View File

@ -0,0 +1,63 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using RulesEngine.Models;
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.IO;
using static RulesEngine.Extensions.ListofRuleResultTreeExtension;
namespace DemoApp
{
class Program
{
static void Main(string[] args)
{
var basicInfo = "{\"name\": \"Dishant\",\"email\": \"dishantmunjal@live.com\",\"creditHistory\": \"good\",\"country\": \"canada\",\"loyalityFactor\": 3,\"totalPurchasesToDate\": 10000}";
var orderInfo = "{\"totalOrders\": 5,\"recurringItems\": 2}";
var telemetryInfo = "{\"noOfVisitsPerMonth\": 10,\"percentageOfBuyingToVisit\": 15}";
var converter = new ExpandoObjectConverter();
dynamic input1 = JsonConvert.DeserializeObject<ExpandoObject>(basicInfo, converter);
dynamic input2 = JsonConvert.DeserializeObject<ExpandoObject>(orderInfo, converter);
dynamic input3 = JsonConvert.DeserializeObject<ExpandoObject>(telemetryInfo, converter);
var inputs = new dynamic[]
{
input1,
input2,
input3
};
var files = Directory.GetFiles(Directory.GetCurrentDirectory(), "Discount.json", SearchOption.AllDirectories);
if (files == null || files.Length == 0)
throw new Exception("Rules not found.");
var fileData = File.ReadAllText(files[0]);
var workflowRules = JsonConvert.DeserializeObject<List<WorkflowRules>>(fileData);
var bre = new RulesEngine.RulesEngine(workflowRules.ToArray(), null);
string discountOffered = "No discount offered.";
List<RuleResultTree> resultList = bre.ExecuteRule("Discount", inputs);
resultList.OnSuccess((eventName) =>
{
discountOffered = $"Discount offered is {eventName} % over MRP.";
});
resultList.OnFail(() =>
{
discountOffered = "The user is not eligible for any discount.";
});
Console.WriteLine(discountOffered);
}
}
}

View File

@ -0,0 +1,47 @@
[
{
"WorkflowName": "Discount",
"Rules": [
{
"RuleName": "GiveDiscount10",
"SuccessEvent": "10",
"ErrorMessage": "One or more adjust rules failed.",
"ErrorType": "Error",
"RuleExpressionType": "LambdaExpression",
"Expression": "input1.country == \"india\" AND input1.loyalityFactor <= 2 AND input1.totalPurchasesToDate >= 5000 AND input2.totalOrders > 2 AND input3.noOfVisitsPerMonth > 2"
},
{
"RuleName": "GiveDiscount20",
"SuccessEvent": "20",
"ErrorMessage": "One or more adjust rules failed.",
"ErrorType": "Error",
"RuleExpressionType": "LambdaExpression",
"Expression": "input1.country == \"india\" AND input1.loyalityFactor == 3 AND input1.totalPurchasesToDate >= 10000 AND input2.totalOrders > 2 AND input3.noOfVisitsPerMonth > 2"
},
{
"RuleName": "GiveDiscount25",
"SuccessEvent": "25",
"ErrorMessage": "One or more adjust rules failed.",
"ErrorType": "Error",
"RuleExpressionType": "LambdaExpression",
"Expression": "input1.country != \"india\" AND input1.loyalityFactor >= 2 AND input1.totalPurchasesToDate >= 10000 AND input2.totalOrders > 2 AND input3.noOfVisitsPerMonth > 5"
},
{
"RuleName": "GiveDiscount30",
"SuccessEvent": "30",
"ErrorMessage": "One or more adjust rules failed.",
"ErrorType": "Error",
"RuleExpressionType": "LambdaExpression",
"Expression": "input1.loyalityFactor > 3 AND input1.totalPurchasesToDate >= 50000 AND input1.totalPurchasesToDate <= 100000 AND input2.totalOrders > 5 AND input3.noOfVisitsPerMonth > 15"
},
{
"RuleName": "GiveDiscount35",
"SuccessEvent": "35",
"ErrorMessage": "One or more adjust rules failed.",
"ErrorType": "Error",
"RuleExpressionType": "LambdaExpression",
"Expression": "input1.loyalityFactor > 3 AND input1.totalPurchasesToDate >= 100000 AND input2.totalOrders > 15 AND input3.noOfVisitsPerMonth > 25"
}
]
}
]

View File

@ -0,0 +1,87 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"Rule": {
"properties": {
"RuleName": {
"type": "string"
},
"Operator": {
"enum": [ "And", "AndAlso", "Or", "OrElse" ]
},
"ErrorMessage": {
"type": "string"
},
"ErrorType": {
"enum": [ "Warning", "Error" ]
},
"SuccessEvent": {
"type": "string"
},
"Rules": {
"type": "array",
"items": {
"anyOf": [
{ "$ref": "#/definitions/LeafRule" },
{ "$ref": "#/definitions/Rule" }
]
}
}
},
"required": [
"RuleName",
"Operator",
"Rules"
],
"type": "object"
},
"LeafRule": {
"type": "object",
"required": [
"RuleName",
"Expression",
"RuleExpressionType"
],
"properties": {
"RuleName": {
"type": "string"
},
"Expression": {
"type": "string"
},
"RuleExpressionType": {
"enum": [ "LambdaExpression" ]
},
"ErrorMessage": {
"type": "string"
},
"ErrorType": {
"enum": [ "Warning", "Error" ]
},
"SuccessEvent": {
"type": "string"
}
}
}
},
"properties": {
"WorkFlowName": {
"type": "string"
},
"Rules": {
"type": "array",
"items": {
"anyOf": [
{ "$ref": "#/definitions/LeafRule" },
{ "$ref": "#/definitions/Rule" }
]
}
}
},
"required": [
"WorkflowName",
"Rules"
],
"type": "object"
}

View File

@ -0,0 +1,40 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29123.89
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RulesEngine", "RulesEngine\RulesEngine.csproj", "{CD4DFE6A-083B-478E-8377-77F474833E30}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RulesEngine.UnitTest", "..\..\test\RulesEngine.UnitTest\RulesEngine.UnitTest.csproj", "{50E0C2A5-E2C8-4B12-8C0E-B69F698A82BF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DemoApp", "..\..\demo\DemoApp\DemoApp.csproj", "{57BB8C07-799A-4F87-A7CC-D3D3F694DD02}"
ProjectSection(ProjectDependencies) = postProject
{CD4DFE6A-083B-478E-8377-77F474833E30} = {CD4DFE6A-083B-478E-8377-77F474833E30}
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{CD4DFE6A-083B-478E-8377-77F474833E30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CD4DFE6A-083B-478E-8377-77F474833E30}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CD4DFE6A-083B-478E-8377-77F474833E30}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CD4DFE6A-083B-478E-8377-77F474833E30}.Release|Any CPU.Build.0 = Release|Any CPU
{50E0C2A5-E2C8-4B12-8C0E-B69F698A82BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{50E0C2A5-E2C8-4B12-8C0E-B69F698A82BF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{50E0C2A5-E2C8-4B12-8C0E-B69F698A82BF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{50E0C2A5-E2C8-4B12-8C0E-B69F698A82BF}.Release|Any CPU.Build.0 = Release|Any CPU
{57BB8C07-799A-4F87-A7CC-D3D3F694DD02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{57BB8C07-799A-4F87-A7CC-D3D3F694DD02}.Debug|Any CPU.Build.0 = Debug|Any CPU
{57BB8C07-799A-4F87-A7CC-D3D3F694DD02}.Release|Any CPU.ActiveCfg = Release|Any CPU
{57BB8C07-799A-4F87-A7CC-D3D3F694DD02}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E1F2EC8E-4005-4DFE-90ED-296D4592867A}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using RulesEngine.HelperFunctions;
using System;
using System.Collections.Generic;
using System.Linq.Dynamic.Core.CustomTypeProviders;
namespace RulesEngine
{
public class CustomTypeProvider : DefaultDynamicLinqCustomTypeProvider
{
private HashSet<Type> _types;
public CustomTypeProvider(Type[] types) : base()
{
_types = new HashSet<Type>(types ?? new Type[] { });
_types.Add(typeof(ExpressionUtils));
}
public override HashSet<Type> GetCustomTypes()
{
return _types;
}
}
}

View File

@ -0,0 +1,16 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using FluentValidation;
using FluentValidation.Results;
using System.Collections.Generic;
namespace RulesEngine.Exceptions
{
public class RuleValidationException : ValidationException
{
public RuleValidationException(string message, IEnumerable<ValidationFailure> errors) : base(message, errors)
{
}
}
}

View File

@ -0,0 +1,35 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using RulesEngine;
using RulesEngine.ExpressionBuilders;
using RulesEngine.HelperFunctions;
using RulesEngine.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Linq.Expressions;
namespace RulesEngine.ExpressionBuilders
{
/// <summary>
/// This class will build the list expression
/// </summary>
internal sealed class LambdaExpressionBuilder : RuleExpressionBuilderBase
{
private readonly ReSettings _reSettings;
internal LambdaExpressionBuilder(ReSettings reSettings)
{
_reSettings = reSettings;
}
internal override Expression<Func<RuleInput, RuleResultTree>> BuildExpressionForRule(Rule rule, IEnumerable<ParameterExpression> typeParamExpressions, ParameterExpression ruleInputExp)
{
var config = new ParsingConfig { CustomTypeProvider = new CustomTypeProvider(_reSettings.CustomTypes) };
var e = DynamicExpressionParser.ParseLambda(config, typeParamExpressions.ToArray(), null, rule.Expression);
var body = (BinaryExpression)e.Body;
return Helpers.ToResultTreeExpression(rule, null, body, typeParamExpressions, ruleInputExp);
}
}
}

View File

@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using RulesEngine.Models;
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
namespace RulesEngine.ExpressionBuilders
{
/// <summary>
/// Base class for expression builders
/// </summary>
internal abstract class RuleExpressionBuilderBase
{
/// <summary>
/// Builds the expression for rule.
/// </summary>
/// <param name="rule">The rule.</param>
/// <param name="typeParamExpressions">The type parameter expressions.</param>
/// <param name="ruleInputExp">The rule input exp.</param>
/// <returns>Expression type</returns>
internal abstract Expression<Func<RuleInput, RuleResultTree>> BuildExpressionForRule(Rule rule, IEnumerable<ParameterExpression> typeParamExpressions, ParameterExpression ruleInputExp);
}
}

View File

@ -0,0 +1,36 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using RulesEngine.Models;
using System.Collections.Generic;
using System.Linq;
namespace RulesEngine.Extensions
{
public static class ListofRuleResultTreeExtension
{
public delegate void OnSuccessFunc(string eventName);
public delegate void OnFailureFunc();
public static List<RuleResultTree> OnSuccess(this List<RuleResultTree> ruleResultTrees, OnSuccessFunc onSuccessFunc)
{
var successfulRuleResult = ruleResultTrees.FirstOrDefault(ruleResult => ruleResult.IsSuccess == true);
if (successfulRuleResult != null)
{
var eventName = successfulRuleResult.Rule.SuccessEvent ?? successfulRuleResult.Rule.RuleName;
onSuccessFunc(eventName);
}
return ruleResultTrees;
}
public static List<RuleResultTree> OnFail(this List<RuleResultTree> ruleResultTrees, OnFailureFunc onFailureFunc)
{
bool allFailure = ruleResultTrees.All(ruleResult => ruleResult.IsSuccess == false);
if (allFailure)
onFailureFunc();
return ruleResultTrees;
}
}
}

View File

@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace RulesEngine.HelperFunctions
{
/// <summary>
/// Constants
/// </summary>
public static class Constants
{
public const string WORKFLOW_NAME_NULL_ERRMSG = "Workflow name can not be null or empty";
public const string INJECT_WORKFLOW_RULES_ERRMSG = "Atleast one of Rules or WorkflowRulesToInject must be not empty";
public const string RULE_CATEGORY_CONFIGURED_ERRMSG = "Rule Category should be configured";
public const string RULE_NULL_ERRMSG = "Rules can not be null or zero";
public const string NESTED_RULE_NULL_ERRMSG = "Nested rules can not be null";
public const string NESTED_RULE_CONFIGURED_ERRMSG = "Nested rules can not be configured";
public const string OPERATOR_NULL_ERRMSG = "Operator can not be null";
public const string OPERATOR_INCORRECT_ERRMSG = "Operator {PropertyValue} is not allowed";
public const string RULE_NAME_NULL_ERRMSG = "Rule Name can not be null";
public const string LAMBDA_EXPRESSION_EXPRESSION_NULL_ERRMSG = "Expression cannot be null or empty when RuleExpressionType is LambdaExpression";
public const string LAMBDA_EXPRESSION_OPERATOR_ERRMSG = "Cannot use Operator field when RuleExpressionType is LambdaExpression";
public const string LAMBDA_EXPRESSION_RULES_ERRMSG = "Cannot use Rules field when RuleExpressionType is LambdaExpression";
}
}

View File

@ -0,0 +1,20 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Linq;
namespace RulesEngine.HelperFunctions
{
public static class ExpressionUtils
{
public static bool CheckContains(string check, string valList)
{
if (String.IsNullOrEmpty(check) || String.IsNullOrEmpty(valList))
return false;
var list = valList.Split(',').ToList();
return list.Contains(check);
}
}
}

View File

@ -0,0 +1,120 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using RulesEngine.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
namespace RulesEngine.HelperFunctions
{
/// <summary>
/// Helpers
/// </summary>
internal static class Helpers
{
/// <summary>
/// To the result tree expression.
/// </summary>
/// <param name="rule">The rule.</param>
/// <param name="childRuleResults">The child rule results.</param>
/// <param name="isSuccessExp">The is success exp.</param>
/// <param name="typeParamExpressions">The type parameter expressions.</param>
/// <param name="ruleInputExp">The rule input exp.</param>
/// <returns>Expression of func</returns>
internal static Expression<Func<RuleInput, RuleResultTree>> ToResultTreeExpression(Rule rule, IEnumerable<MemberInitExpression> childRuleResults, BinaryExpression isSuccessExp, IEnumerable<ParameterExpression> typeParamExpressions, ParameterExpression ruleInputExp)
{
var memberInit = ToResultTree(rule, childRuleResults, isSuccessExp, typeParamExpressions, null);
var lambda = Expression.Lambda<Func<RuleInput, RuleResultTree>>(memberInit, new[] { ruleInputExp });
return lambda;
}
/// <summary>
/// To the result tree member expression
/// </summary>
/// <param name="rule">The rule.</param>
/// <param name="childRuleResults">The child rule results.</param>
/// <param name="isSuccessExp">The is success exp.</param>
/// <param name="childRuleResultsblockexpr">The child rule results block expression.</param>
/// <returns></returns>
internal static MemberInitExpression ToResultTree(Rule rule, IEnumerable<MemberInitExpression> childRuleResults, BinaryExpression isSuccessExp, IEnumerable<ParameterExpression> typeParamExpressions, BlockExpression childRuleResultsblockexpr)
{
var createdType = typeof(RuleResultTree);
var ctor = Expression.New(createdType);
var ruleProp = createdType.GetProperty(nameof(RuleResultTree.Rule));
var isSuccessProp = createdType.GetProperty(nameof(RuleResultTree.IsSuccess));
var childResultProp = createdType.GetProperty(nameof(RuleResultTree.ChildResults));
var inputProp = createdType.GetProperty(nameof(RuleResultTree.Input));
var rulePropBinding = Expression.Bind(ruleProp, Expression.Constant(rule));
var isSuccessPropBinding = Expression.Bind(isSuccessProp, isSuccessExp);
var inputBinding = Expression.Bind(inputProp, typeParamExpressions.FirstOrDefault());
MemberInitExpression memberInit;
if (childRuleResults != null)
{
var ruleResultTreeArr = Expression.NewArrayInit(typeof(RuleResultTree), childRuleResults);
var childResultPropBinding = Expression.Bind(childResultProp, ruleResultTreeArr);
memberInit = Expression.MemberInit(ctor, new[] { rulePropBinding, isSuccessPropBinding, childResultPropBinding, inputBinding });
}
else if (childRuleResultsblockexpr != null)
{
var childResultPropBinding = Expression.Bind(childResultProp, childRuleResultsblockexpr);
memberInit = Expression.MemberInit(ctor, new[] { rulePropBinding, isSuccessPropBinding, childResultPropBinding, inputBinding });
}
else
{
memberInit = Expression.MemberInit(ctor, new[] { rulePropBinding, isSuccessPropBinding, inputBinding });
}
return memberInit;
}
/// <summary>
/// To the result tree error messages
/// </summary>
/// <param name="ruleResultTree">ruleResultTree</param>
/// <param name="ruleResultMessage">ruleResultMessage</param>
internal static void ToResultTreeMessages(RuleResultTree ruleResultTree, ref RuleResultMessage ruleResultMessage)
{
if (ruleResultTree.ChildResults != null)
{
GetChildRuleMessages(ruleResultTree.ChildResults, ref ruleResultMessage);
}
else
{
if (ruleResultTree.IsSuccess)
{
string errMsg = ruleResultTree.Rule.ErrorMessage;
errMsg = string.IsNullOrEmpty(errMsg) ? $"Error message does not configured for {ruleResultTree.Rule.RuleName}" : errMsg;
if (ruleResultTree.Rule.ErrorType == ErrorType.Error && !ruleResultMessage.ErrorMessages.Contains(errMsg))
{
ruleResultMessage.ErrorMessages.Add(errMsg);
}
else if (ruleResultTree.Rule.ErrorType == ErrorType.Warning && !ruleResultMessage.WarningMessages.Contains(errMsg))
{
ruleResultMessage.WarningMessages.Add(errMsg);
}
}
}
}
/// <summary>
/// To get the child error message recersivly
/// </summary>
/// <param name="childResultTree">childResultTree</param>
/// <param name="ruleResultMessage">ruleResultMessage</param>
private static void GetChildRuleMessages(IEnumerable<RuleResultTree> childResultTree, ref RuleResultMessage ruleResultMessage)
{
foreach (var item in childResultTree)
{
ToResultTreeMessages(item, ref ruleResultMessage);
}
}
}
}

View File

@ -0,0 +1,127 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Linq.Dynamic.Core;
namespace RulesEngine.HelperFunctions
{
public static class Utils
{
public static object GetTypedObject(dynamic input)
{
if(input is ExpandoObject)
{
Type type = CreateAbstractClassType(input);
return CreateObject(type, input);
}
else
{
return input;
}
}
public static Type CreateAbstractClassType(dynamic input)
{
List<DynamicProperty> props = new List<DynamicProperty>();
if(input == null)
{
return typeof(object);
}
if(!(input is ExpandoObject))
{
return input.GetType();
}
else
{
foreach (var expando in (IDictionary<string, object>)input)
{
Type value;
if (expando.Value is IList)
{
if (((IList)expando.Value).Count == 0)
value = typeof(List<object>);
else
{
var internalType = CreateAbstractClassType(((IList)expando.Value)[0]);
value = new List<object>().Cast(internalType).ToList(internalType).GetType();
}
}
else
{
value = CreateAbstractClassType(expando.Value);
}
props.Add(new DynamicProperty(expando.Key, value));
}
}
var type = DynamicClassFactory.CreateType(props);
return type;
}
public static object CreateObject(Type type, dynamic input)
{
if (!(input is ExpandoObject))
{
return Convert.ChangeType(input, type);
}
object obj = Activator.CreateInstance(type);
foreach (var expando in (IDictionary<string, object>)input)
{
if (type.GetProperties().Any(c => c.Name == expando.Key) &&
expando.Value != null && (expando.Value.GetType().Name != "DBNull" || expando.Value != DBNull.Value))
{
object val;
if (expando.Value is ExpandoObject)
{
var propType = type.GetProperty(expando.Key).PropertyType;
val = CreateObject(propType, expando.Value);
}
else if (expando.Value is IList)
{
var internalType = type.GetProperty(expando.Key).PropertyType.GenericTypeArguments.FirstOrDefault()??typeof(object);
var temp = (IList)expando.Value;
var newList = new List<object>();
for (int i = 0; i < temp.Count; i++)
{
var child = CreateObject(internalType, temp[i]);
newList.Add(child);
};
val = newList.Cast(internalType).ToList(internalType);
}
else
{
val = expando.Value;
}
type.GetProperty(expando.Key).SetValue(obj, val, null);
}
}
return obj;
}
private static IEnumerable Cast(this IEnumerable self, Type innerType)
{
var methodInfo = typeof(Enumerable).GetMethod("Cast");
var genericMethod = methodInfo.MakeGenericMethod(innerType);
return genericMethod.Invoke(null, new[] { self }) as IEnumerable;
}
private static IList ToList(this IEnumerable self, Type innerType)
{
var methodInfo = typeof(Enumerable).GetMethod("ToList");
var genericMethod = methodInfo.MakeGenericMethod(innerType);
return genericMethod.Invoke(null, new[] { self }) as IList;
}
}
}

View File

@ -0,0 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
namespace RulesEngine
{
public interface ILogger
{
void LogTrace(string msg);
void LogError(Exception ex);
}
}

View File

@ -0,0 +1,46 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using RulesEngine.Models;
using System.Collections.Generic;
namespace RulesEngine.Interfaces
{
public interface IRulesEngine
{
/// <summary>
/// This will execute all the rules of the specified workflow
/// </summary>
/// <param name="workflowName"></param>
/// <param name="input"></param>
/// <param name="otherInputs"></param>
/// <returns>List of Result</returns>
List<RuleResultTree> ExecuteRule(string workflowName, IEnumerable<dynamic> input, object[] otherInputs);
/// <summary>
/// This will execute all the rules of the specified workflow
/// </summary>
/// <param name="workflowName"></param>
/// <param name="inputs"></param>
/// <returns>List of Result</returns>
List<RuleResultTree> ExecuteRule(string workflowName, object[] inputs);
/// <summary>
///
/// </summary>
/// <param name="workflowName"></param>
/// <param name="input"></param>
/// <returns></returns>
List<RuleResultTree> ExecuteRule(string workflowName, object input);
/// <summary>
/// This will execute all the rules of the specified workflow
/// </summary>
/// <param name="workflowName"></param>
/// <param name="ruleParams"></param>
/// <returns>List of Result</returns>
List<RuleResultTree> ExecuteRule(string workflowName, RuleParameter[] ruleParams);
}
}

View File

@ -0,0 +1,20 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
namespace RulesEngine.Models
{
internal class CompiledRule
{
/// <summary>
/// Gets or sets the compiled rules.
/// </summary>
/// <value>
/// The compiled rules.
/// </value>
internal List<Delegate> CompiledRules { get; set; }
}
}

View File

@ -0,0 +1,14 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Diagnostics.CodeAnalysis;
namespace RulesEngine.Models
{
[ExcludeFromCodeCoverage]
public class ReSettings
{
public Type[] CustomTypes { get; set; }
}
}

View File

@ -0,0 +1,81 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System.Collections.Generic;
namespace RulesEngine.Models
{
/// <summary>
/// Rule class
/// </summary>
public class Rule
{
/// <summary>
/// Gets or sets the name of the rule.
/// </summary>
/// <value>
/// The name of the rule.
/// </value>
public string RuleName { get; set; }
/// <summary>
/// Gets or sets the operator.
/// </summary>
/// <value>
/// The operator.
/// </value>
public string Operator { get; set; }
/// <summary>
/// Gets or sets the error message.
/// </summary>
/// <value>
/// The error message.
/// </value>
public string ErrorMessage { get; set; }
/// <summary>
/// Gets or sets the type of the error.
/// </summary>
/// <value>
/// The type of the error.
/// </value>
[JsonConverter(typeof(StringEnumConverter))]
public ErrorType ErrorType { get; set; }
/// <summary>
/// Gets or sets the type of the rule expression.
/// </summary>
/// <value>
/// The type of the rule expression.
/// </value>
[JsonConverter(typeof(StringEnumConverter))]
public RuleExpressionType? RuleExpressionType { get; set; }
/// <summary>
/// Gets or sets the names of common workflows
/// </summary>
public List<string> WorkflowRulesToInject { get; set; }
/// <summary>
/// Gets or sets the rules.
/// </summary>
/// <value>
/// The rules.
/// </value>
public List<Rule> Rules { get; set; }
/// <summary>
/// Gets or Sets the lambda expression.
/// </summary>
public string Expression { get; set; }
public string SuccessEvent { get; set; }
}
}

View File

@ -0,0 +1,14 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace RulesEngine.Models
{
/// <summary>
/// This is error type of rules which will use in rule config files
/// </summary>
public enum ErrorType
{
Warning = 0,
Error = 1,
}
}

View File

@ -0,0 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace RulesEngine.Models
{
/// <summary>
/// This is rule expression type which will use in rule config files
/// </summary>
public enum RuleExpressionType
{
LambdaExpression = 0
}
}

View File

@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
namespace RulesEngine.Models
{
/// <summary>
/// Rule input
/// </summary>
[ExcludeFromCodeCoverage]
internal class RuleInput
{
/// <summary>
/// Gets the today UTC.
/// </summary>
/// <value>
/// The today UTC.
/// </value>
public DateTime TodayUtc { get; set; }
}
}

View File

@ -0,0 +1,36 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Diagnostics.CodeAnalysis;
namespace RulesEngine.Models
{
[ExcludeFromCodeCoverage]
public class RuleParameter
{
public RuleParameter(Type type)
{
Type = type;
Name = type.Name;
}
public RuleParameter(Type type,string name)
{
Type = type;
Name = name;
}
public RuleParameter(string name,object value)
{
Type = value.GetType();
Name = name;
Value = value;
}
public Type Type { get; set; }
public string Name { get; set; }
public object Value { get; set; }
}
}

View File

@ -0,0 +1,81 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using RulesEngine.HelperFunctions;
using System.Collections.Generic;
namespace RulesEngine.Models
{
/// <summary>
/// Rule result class with child result heirarchy
/// </summary>
public class RuleResultTree
{
/// <summary>
/// Gets or sets the rule.
/// </summary>
/// <value>
/// The rule.
/// </value>
public Rule Rule { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance is success.
/// </summary>
/// <value>
/// <c>true</c> if this instance is success; otherwise, <c>false</c>.
/// </value>
public bool IsSuccess { get; set; }
/// <summary>
/// Gets or sets the child result.
/// </summary>
/// <value>
/// The child result.
/// </value>
public IEnumerable<RuleResultTree> ChildResults { get; set; }
/// <summary>
/// Gets or sets the input object
/// </summary>
public object Input { get; set; }
/// <summary>
/// This method will return all the error and warning messages to caller
/// </summary>
/// <returns>RuleResultMessage</returns>
public RuleResultMessage GetMessages()
{
RuleResultMessage ruleResultMessage = new RuleResultMessage();
Helpers.ToResultTreeMessages(this, ref ruleResultMessage);
return ruleResultMessage;
}
}
/// <summary>
/// This class will hold the error messages
/// </summary>
public class RuleResultMessage
{
/// <summary>
/// Constructor will innitilaze the List
/// </summary>
public RuleResultMessage()
{
ErrorMessages = new List<string>();
WarningMessages = new List<string>();
}
/// <summary>
/// This will hold the list of error messages
/// </summary>
public List<string> ErrorMessages { get; set; }
/// <summary>
/// This will hold the list of warning messages
/// </summary>
public List<string> WarningMessages { get; set; }
}
}

View File

@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Collections.Generic;
namespace RulesEngine.Models
{
/// <summary>
/// Workflow rules class for deserialization the json config file
/// </summary>
public class WorkflowRules
{
/// <summary>
/// Gets the workflow name.
/// </summary>
public string WorkflowName { get; set; }
public List<string> WorkflowRulesToInject { get; set; }
/// <summary>
/// list of rules.
/// </summary>
public List<Rule> Rules { get; set; }
}
}

View File

@ -0,0 +1,22 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
namespace RulesEngine
{
internal class NullLogger : ILogger
{
public void LogError(Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex);
Console.WriteLine(ex);
}
public void LogTrace(string msg)
{
System.Diagnostics.Debug.WriteLine(msg);
Console.WriteLine(msg);
}
}
}

View File

@ -0,0 +1,11 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
[assembly: InternalsVisibleTo("RulesEngine.UnitTest")]

View File

@ -0,0 +1,266 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using RulesEngine.HelperFunctions;
using RulesEngine.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
namespace RulesEngine
{
/// <summary>
/// Rule compilers
/// </summary>
internal class RuleCompiler
{
/// <summary>
/// The nested operators
/// </summary>
private readonly ExpressionType[] nestedOperators = new ExpressionType[] { ExpressionType.And, ExpressionType.AndAlso, ExpressionType.Or, ExpressionType.OrElse };
/// <summary>
/// The expression builder factory
/// </summary>
private readonly RuleExpressionBuilderFactory _expressionBuilderFactory;
private readonly ILogger _logger;
/// <summary>
/// Initializes a new instance of the <see cref="RuleCompiler"/> class.
/// </summary>
/// <param name="expressionBuilderFactory">The expression builder factory.</param>
/// <exception cref="ArgumentNullException">expressionBuilderFactory</exception>
internal RuleCompiler(RuleExpressionBuilderFactory expressionBuilderFactory,ILogger logger)
{
if (expressionBuilderFactory == null)
{
throw new ArgumentNullException($"{nameof(expressionBuilderFactory)} can't be null.");
}
if (logger == null)
{
throw new ArgumentNullException($"{nameof(logger)} can't be null.");
}
_logger = logger;
_expressionBuilderFactory = expressionBuilderFactory;
}
/// <summary>
/// Compiles the rule
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="rule"></param>
/// <param name="input"></param>
/// <param name="ruleParam"></param>
/// <returns>Compiled func delegate</returns>
public Delegate CompileRule(Rule rule,params RuleParameter[] ruleParams)
{
try
{
IEnumerable<ParameterExpression> typeParameterExpressions = GetParameterExpression(ruleParams).ToList(); // calling ToList to avoid multiple calls this the method for nested rule scenario.
ParameterExpression ruleInputExp = Expression.Parameter(typeof(RuleInput), nameof(RuleInput));
Expression<Func<RuleInput, RuleResultTree>> ruleExpression = GetExpressionForRule(rule, typeParameterExpressions, ruleInputExp);
var lambdaParameterExps = new List<ParameterExpression>(typeParameterExpressions) { ruleInputExp };
var expression = Expression.Lambda(ruleExpression.Body, lambdaParameterExps);
return expression.Compile();
}
catch (Exception ex)
{
_logger.LogError(ex);
throw;
}
}
// <summary>
/// Gets the parameter expression.
/// </summary>
/// <param name="ruleParams">The types.</param>
/// <returns></returns>
/// <exception cref="ArgumentException">
/// types
/// or
/// type
/// </exception>
private IEnumerable<ParameterExpression> GetParameterExpression(params RuleParameter[] ruleParams)
{
if (ruleParams == null || !ruleParams.Any())
{
throw new ArgumentException($"{nameof(ruleParams)} can't be null/empty.");
}
foreach (var ruleParam in ruleParams)
{
if (ruleParam == null)
{
throw new ArgumentException($"{nameof(ruleParam)} can't be null.");
}
yield return Expression.Parameter(ruleParam.Type, ruleParam.Name);
}
}
/// <summary>
/// Gets the expression for rule.
/// </summary>
/// <param name="rule">The rule.</param>
/// <param name="typeParameterExpressions">The type parameter expressions.</param>
/// <param name="ruleInputExp">The rule input exp.</param>
/// <returns></returns>
private Expression<Func<RuleInput, RuleResultTree>> GetExpressionForRule(Rule rule, IEnumerable<ParameterExpression> typeParameterExpressions, ParameterExpression ruleInputExp)
{
ExpressionType nestedOperator;
if (Enum.TryParse(rule.Operator, out nestedOperator) && nestedOperators.Contains(nestedOperator) &&
rule.Rules != null && rule.Rules.Any())
{
return BuildNestedExpression(rule, nestedOperator, typeParameterExpressions, ruleInputExp);
}
else
{
return BuildExpression(rule, typeParameterExpressions, ruleInputExp);
}
}
/// <summary>
/// Builds the expression.
/// </summary>
/// <param name="rule">The rule.</param>
/// <param name="typeParameterExpressions">The type parameter expressions.</param>
/// <param name="ruleInputExp">The rule input exp.</param>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
private Expression<Func<RuleInput, RuleResultTree>> BuildExpression(Rule rule, IEnumerable<ParameterExpression> typeParameterExpressions, ParameterExpression ruleInputExp)
{
if (!rule.RuleExpressionType.HasValue)
{
throw new InvalidOperationException($"RuleExpressionType can not be null for leaf level expressions.");
}
var ruleExpressionBuilder = _expressionBuilderFactory.RuleGetExpressionBuilder(rule.RuleExpressionType.Value);
var expression = ruleExpressionBuilder.BuildExpressionForRule(rule, typeParameterExpressions, ruleInputExp);
return expression;
}
/// <summary>
/// Builds the nested expression.
/// </summary>
/// <param name="parentRule">The parent rule.</param>
/// <param name="childRules">The child rules.</param>
/// <param name="operation">The operation.</param>
/// <param name="typeParameterExpressions">The type parameter expressions.</param>
/// <param name="ruleInputExp">The rule input exp.</param>
/// <returns>Expression of func delegate</returns>
/// <exception cref="InvalidCastException"></exception>
private Expression<Func<RuleInput, RuleResultTree>> BuildNestedExpression(Rule parentRule, ExpressionType operation, IEnumerable<ParameterExpression> typeParameterExpressions, ParameterExpression ruleInputExp)
{
List<Expression<Func<RuleInput, RuleResultTree>>> expressions = new List<Expression<Func<RuleInput, RuleResultTree>>>();
foreach (var r in parentRule.Rules)
{
expressions.Add(GetExpressionForRule(r, typeParameterExpressions, ruleInputExp));
}
List<MemberInitExpression> childRuleResultTree = new List<MemberInitExpression>();
foreach (var exp in expressions)
{
var resultMemberInitExpression = exp.Body as MemberInitExpression;
if (resultMemberInitExpression == null)// assert is a MemberInitExpression
{
throw new InvalidCastException($"expression.Body '{exp.Body}' is not of MemberInitExpression type.");
}
childRuleResultTree.Add(resultMemberInitExpression);
}
Expression<Func<RuleInput, RuleResultTree>> nestedExpression = Helpers.ToResultTreeExpression(parentRule, childRuleResultTree, BinaryExpression(expressions, operation), typeParameterExpressions, ruleInputExp);
return nestedExpression;
}
/// <summary>
/// Binaries the expression.
/// </summary>
/// <param name="expressions">The expressions.</param>
/// <param name="operationType">Type of the operation.</param>
/// <returns>Binary Expression</returns>
private BinaryExpression BinaryExpression(IList<Expression<Func<RuleInput, RuleResultTree>>> expressions, ExpressionType operationType)
{
if (expressions.Count == 1)
{
return ResolveIsSuccessBinding(expressions.First());
}
BinaryExpression nestedBinaryExp = Expression.MakeBinary(operationType, ResolveIsSuccessBinding(expressions[0]), ResolveIsSuccessBinding(expressions[1]));
for (int i = 2; expressions.Count > i; i++)
{
nestedBinaryExp = Expression.MakeBinary(operationType, nestedBinaryExp, ResolveIsSuccessBinding(expressions[i]));
}
return nestedBinaryExp;
}
/// <summary>
/// Resolves the is success binding.
/// </summary>
/// <param name="expression">The expression.</param>
/// <returns>Binary expression of IsSuccess prop</returns>
/// <exception cref="ArgumentNullException">expression</exception>
/// <exception cref="InvalidCastException"></exception>
/// <exception cref="NullReferenceException">
/// IsSuccess
/// or
/// IsSuccess
/// or
/// IsSuccess
/// </exception>
private BinaryExpression ResolveIsSuccessBinding(Expression<Func<RuleInput, RuleResultTree>> expression)
{
if (expression == null)
{
throw new ArgumentNullException($"{nameof(expression)} should not be null.");
}
var memberInitExpression = expression.Body as MemberInitExpression;
if (memberInitExpression == null)// assert it's a MemberInitExpression
{
throw new InvalidCastException($"expression.Body '{expression.Body}' is not of MemberInitExpression type.");
}
MemberAssignment isSuccessBinding = (MemberAssignment)memberInitExpression.Bindings.FirstOrDefault(f => f.Member.Name == nameof(RuleResultTree.IsSuccess));
if (isSuccessBinding == null)
{
throw new NullReferenceException($"Expected {nameof(RuleResultTree.IsSuccess)} property binding not found in {memberInitExpression}.");
}
if (isSuccessBinding.Expression == null)
{
throw new NullReferenceException($"{nameof(RuleResultTree.IsSuccess)} assignment expression can not be null.");
}
BinaryExpression isSuccessExpression = isSuccessBinding.Expression as BinaryExpression;
if (isSuccessExpression == null)
{
throw new NullReferenceException($"Expected {nameof(RuleResultTree.IsSuccess)} assignment expression to be of {typeof(BinaryExpression)} and not {isSuccessBinding.Expression.GetType()}");
}
return isSuccessExpression;
}
}
}

View File

@ -0,0 +1,28 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using RulesEngine.ExpressionBuilders;
using RulesEngine.Models;
using System;
namespace RulesEngine
{
internal class RuleExpressionBuilderFactory
{
private ReSettings _reSettings;
public RuleExpressionBuilderFactory(ReSettings reSettings)
{
_reSettings = reSettings;
}
public RuleExpressionBuilderBase RuleGetExpressionBuilder(RuleExpressionType ruleExpressionType)
{
switch (ruleExpressionType)
{
case RuleExpressionType.LambdaExpression:
return new LambdaExpressionBuilder(_reSettings);
default:
throw new InvalidOperationException($"{nameof(ruleExpressionType)} has not been supported yet.");
}
}
}
}

View File

@ -0,0 +1,272 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using RulesEngine.HelperFunctions;
using RulesEngine.Interfaces;
using RulesEngine.Models;
using RulesEngine.Validators;
using RulesEngine.Exceptions;
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using FluentValidation;
namespace RulesEngine
{
public class RulesEngine : IRulesEngine
{
#region Variables
private Dictionary<string, object> compileRulesDic;
private Dictionary<string, WorkflowRules> workflowRulesDic;
private readonly ILogger _logger;
private readonly ReSettings _reSettings;
#endregion
#region Constructor
public RulesEngine(string[] jsonConfig, ILogger logger, ReSettings reSettings = null) : this(logger, reSettings)
{
var workflowRules = jsonConfig.Select(item => JsonConvert.DeserializeObject<WorkflowRules>(item)).ToArray();
AddWorkflow(workflowRules);
}
public RulesEngine(WorkflowRules[] workflowRules, ILogger logger, ReSettings reSettings = null) : this(logger, reSettings)
{
AddWorkflow(workflowRules);
}
public RulesEngine(ILogger logger, ReSettings reSettings = null)
{
_logger = logger ?? new NullLogger();
_reSettings = reSettings ?? new ReSettings();
InitializeVariables();
}
#endregion
#region Public Methods
/// <summary>
/// This will execute all the rules of the specified workflow
/// </summary>
/// <typeparam name="T">type of input</typeparam>
/// <param name="input">input</param>
/// <param name="workflowName">Workflow Name</param>
/// <returns>List of Result</returns>
public List<RuleResultTree> ExecuteRule(string workflowName, IEnumerable<dynamic> input, object[] otherInputs)
{
_logger.LogTrace($"Called ExecuteRule for workflow {workflowName} and count of input {input.Count()}");
var result = new List<RuleResultTree>();
foreach (var item in input)
{
var ruleInputs = new List<object>();
ruleInputs.Add(item);
if (otherInputs != null)
ruleInputs.AddRange(otherInputs);
result.AddRange(ExecuteRule(workflowName, ruleInputs.ToArray()));
}
return result;
}
public List<RuleResultTree> ExecuteRule(string workflowName, object[] inputs)
{
var ruleParams = new List<RuleParameter>();
for (int i = 0; i < inputs.Length; i++)
{
var input = inputs[i];
var obj = Utils.GetTypedObject(input);
ruleParams.Add(new RuleParameter($"input{i + 1}", obj));
}
return ExecuteRule(workflowName, ruleParams.ToArray());
}
public List<RuleResultTree> ExecuteRule(string workflowName, object input)
{
var inputs = new[] { input };
return ExecuteRule(workflowName, inputs);
}
public List<RuleResultTree> ExecuteRule(string workflowName, RuleParameter[] ruleParams)
{
return ValidateWorkflowAndExecuteRule(workflowName, ruleParams);
}
#endregion
#region Private Methods
/// <summary>
/// This is for Initializing the variables
/// </summary>
private void InitializeVariables()
{
if (compileRulesDic == null)
compileRulesDic = new Dictionary<string, object>();
if (workflowRulesDic == null)
workflowRulesDic = new Dictionary<string, WorkflowRules>();
}
public void AddWorkflow(params WorkflowRules[] workflowRules)
{
try
{
foreach (var workflowRule in workflowRules)
{
var validator = new WorkflowRulesValidator();
validator.ValidateAndThrow(workflowRule);
if (!workflowRulesDic.ContainsKey(workflowRule.WorkflowName))
{
workflowRulesDic[workflowRule.WorkflowName] = workflowRule;
}
else
{
throw new ArgumentException($"Workflow with name: {workflowRule} already exists");
}
}
}
catch (ValidationException ex)
{
throw new RuleValidationException(ex.Message, ex.Errors);
}
}
public void ClearWorkflows()
{
workflowRulesDic.Clear();
compileRulesDic.Clear();
}
public void RemoveWorkflow(params string[] workflowNames)
{
foreach (var workflowName in workflowNames)
{
workflowRulesDic.Remove(workflowName);
var compiledKeysToRemove = compileRulesDic.Keys.Where(key => key.StartsWith(workflowName));
foreach(var key in compiledKeysToRemove)
{
compileRulesDic.Remove(key);
}
}
}
/// <summary>
/// This will validate workflow rules then call execute method
/// </summary>
/// <typeparam name="T">type of entity</typeparam>
/// <param name="input">input</param>
/// <param name="workflowName">workflow name</param>
/// <returns>list of rule result set</returns>
private List<RuleResultTree> ValidateWorkflowAndExecuteRule(string workflowName, RuleParameter[] ruleParams)
{
List<RuleResultTree> result;
if (RegisterRule(workflowName, ruleParams))
{
result = ExecuteRuleByWorkflow(workflowName, ruleParams);
}
else
{
_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 result;
}
/// <summary>
/// This will compile the rules and store them to dictionary
/// </summary>
/// <typeparam name="T">type of entity</typeparam>
/// <param name="workflowName">workflow name</param>
/// <returns>bool result</returns>
private bool RegisterRule(string workflowName, params RuleParameter[] ruleParams)
{
string compileRulesKey = GetCompileRulesKey(workflowName, ruleParams);
if (compileRulesDic.ContainsKey(compileRulesKey))
return true;
var workflowRules = GetWorkFlowRules(workflowName);
if (workflowRules != null)
{
var lstFunc = new List<Delegate>();
foreach (var rule in workflowRulesDic[workflowName].Rules)
{
RuleCompiler ruleCompiler = new RuleCompiler(new RuleExpressionBuilderFactory(_reSettings), _logger);
lstFunc.Add(ruleCompiler.CompileRule(rule, ruleParams));
}
compileRulesDic.Add(compileRulesKey, new CompiledRule() { CompiledRules = lstFunc });
_logger.LogTrace($"Rules has been compiled for the {workflowName} workflow and added to dictionary");
return true;
}
else
{
return false;
}
}
private WorkflowRules GetWorkFlowRules(string workflowName)
{
workflowRulesDic.TryGetValue(workflowName, out var workflowRules);
if (workflowRules == null) return null;
else
{
if (workflowRules.WorkflowRulesToInject?.Any() == true)
{
if (workflowRules.Rules == null)
{
workflowRules.Rules = new List<Rule>();
}
foreach (string wfname in workflowRules.WorkflowRulesToInject)
{
var injectedWorkflow = GetWorkFlowRules(wfname);
if (injectedWorkflow == null)
{
throw new Exception($"Could not find injected Workflow: {wfname}");
}
workflowRules.Rules.AddRange(injectedWorkflow.Rules);
}
}
return workflowRules;
}
}
private static string GetCompileRulesKey(string workflowName, RuleParameter[] ruleParams)
{
return $"{workflowName}-" + String.Join("-", ruleParams.Select(c => c.Type.Name));
}
/// <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> ExecuteRuleByWorkflow(string workflowName, RuleParameter[] ruleParams)
{
_logger.LogTrace($"Compiled rules found for {workflowName} workflow and executed");
List<RuleResultTree> result = new List<RuleResultTree>();
var compileRulesKey = GetCompileRulesKey(workflowName, ruleParams);
var inputs = ruleParams.Select(c => c.Value);
foreach (var compiledRule in (compileRulesDic[compileRulesKey] as CompiledRule).CompiledRules)
{
result.Add(compiledRule.DynamicInvoke(new List<object>(inputs) { new RuleInput() }.ToArray()) as RuleResultTree);
}
return result;
}
#endregion
}
}

View File

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentValidation" Version="8.4.0" />
<PackageReference Include="Microsoft.CSharp" Version="4.5.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
<PackageReference Include="System.Linq" Version="4.3.0" />
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.0.18" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,62 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using FluentValidation;
using RulesEngine.HelperFunctions;
using RulesEngine.Models;
namespace RulesEngine.Validators
{
internal class RuleValidator : AbstractValidator<Rule>
{
private readonly List<ExpressionType> _nestedOperators = new List<ExpressionType> { ExpressionType.And, ExpressionType.AndAlso, ExpressionType.Or, ExpressionType.OrElse };
public RuleValidator()
{
RuleFor(c => c.RuleName).NotEmpty().WithMessage(Constants.RULE_NAME_NULL_ERRMSG);
//Nested expression check
When(c => c.RuleExpressionType == null,() =>
{
RuleFor(c => c.Operator)
.NotNull().WithMessage(Constants.OPERATOR_NULL_ERRMSG)
.Must(op => _nestedOperators.Any(x => x.ToString().Equals(op, StringComparison.OrdinalIgnoreCase)))
.WithMessage(Constants.OPERATOR_INCORRECT_ERRMSG);
When(c => c.Rules?.Any() != true, () =>
{
RuleFor(c => c.WorkflowRulesToInject).NotEmpty().WithMessage(Constants.INJECT_WORKFLOW_RULES_ERRMSG);
})
.Otherwise(() => {
RuleFor(c => c.Rules).Must(BeValidRulesList);
});
});
RegisterExpressionTypeRules();
}
private void RegisterExpressionTypeRules()
{
When(c => c.RuleExpressionType == RuleExpressionType.LambdaExpression, () =>
{
RuleFor(c => c.Expression).NotEmpty().WithMessage(Constants.LAMBDA_EXPRESSION_EXPRESSION_NULL_ERRMSG);
RuleFor(c => c.Operator).Null().WithMessage(Constants.LAMBDA_EXPRESSION_OPERATOR_ERRMSG);
RuleFor(c => c.Rules).Null().WithMessage(Constants.LAMBDA_EXPRESSION_RULES_ERRMSG);
});
}
private bool BeValidRulesList(List<Rule> rules)
{
if (rules?.Any() != true) return false;
var validator = new RuleValidator();
var isValid = true;
foreach(var rule in rules){
isValid &= validator.Validate(rule).IsValid;
if (!isValid) break;
}
return isValid;
}
}
}

View File

@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Linq;
using FluentValidation;
using RulesEngine.HelperFunctions;
using RulesEngine.Models;
namespace RulesEngine.Validators
{
internal class WorkflowRulesValidator : AbstractValidator<WorkflowRules>
{
public WorkflowRulesValidator()
{
RuleFor(c => c.WorkflowName).NotEmpty().WithMessage(Constants.WORKFLOW_NAME_NULL_ERRMSG);
When(c => c.Rules?.Any() != true, () =>
{
RuleFor(c => c.WorkflowRulesToInject).NotEmpty().WithMessage(Constants.INJECT_WORKFLOW_RULES_ERRMSG);
}).Otherwise(() => {
var ruleValidator = new RuleValidator();
RuleForEach(c => c.Rules).SetValidator(ruleValidator);
});
}
}
}

View File

@ -0,0 +1,172 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using RulesEngine;
using RulesEngine.Exceptions;
using RulesEngine.HelperFunctions;
using RulesEngine.Models;
using Moq;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.IO;
using System.Linq;
using Xunit;
namespace RulesEngine.UnitTest
{
[Trait("Category", "Unit")]
public class RulesEngineTest
{
[Theory]
[InlineData("rules1.json")]
public void RulesEngine_New_ReturnsNotNull(string ruleFileName)
{
var re = GetRulesEngine(ruleFileName);
Assert.NotNull(re);
}
[Theory]
[InlineData("rules2.json")]
public void RulesEngine_InjectedRules_ReturnsListOfRuleResultTree(string ruleFileName)
{
var re = GetRulesEngine(ruleFileName);
dynamic input = GetInput();
dynamic input2 = GetInput();
input2.value1 = "val1";
var result = re.ExecuteRule("inputWorkflowReference", new List<dynamic>() { input, input2 }.AsEnumerable(), new object[] { });
Assert.NotNull(result);
Assert.IsType<List<RuleResultTree>>(result);
}
[Theory]
[InlineData("rules2.json")]
public void ExecuteRule_ReturnsListOfRuleResultTree(string ruleFileName)
{
var re = GetRulesEngine(ruleFileName);
dynamic input = GetInput();
dynamic input2 = GetInput();
input2.value1 = "val1";
var result = re.ExecuteRule("inputWorkflow", new List<dynamic>() { input, input2 }.AsEnumerable(), new object[] { });
Assert.NotNull(result);
Assert.IsType<List<RuleResultTree>>(result);
}
[Theory]
[InlineData("rules2.json")]
public void ExecuteRule_SingleObject_ReturnsListOfRuleResultTree(string ruleFileName)
{
var re = GetRulesEngine(ruleFileName);
dynamic input = GetInput();
dynamic input2 = GetInput();
input2.value1 = "val1";
var result = re.ExecuteRule("inputWorkflow",input);
Assert.NotNull(result);
Assert.IsType<List<RuleResultTree>>(result);
}
[Theory]
[InlineData("rules2.json")]
public void ExecuteRule_ReturnsListOfRuleResultTree_ResultMessage(string ruleFileName)
{
var re = GetRulesEngine(ruleFileName);
dynamic input = GetInput();
dynamic input2 = GetInput();
input2.value1 = "val1";
List<RuleResultTree> result = re.ExecuteRule("inputWorkflow", input);
Assert.NotNull(result);
Assert.IsType<List<RuleResultTree>>(result);
Assert.NotNull(result.First().GetMessages());
Assert.NotNull(result.First().GetMessages().WarningMessages);
}
[Fact]
public void RulesEngine_New_IncorrectJSON_ThrowsException()
{
Assert.Throws<RuleValidationException>(() =>
{
var workflow = new WorkflowRules();
var re = CreateRulesEngine(workflow);
});
Assert.Throws<RuleValidationException>(() =>
{
var workflow = new WorkflowRules() { WorkflowName = "test" };
var re = CreateRulesEngine(workflow);
});
}
[Theory]
[InlineData("rules1.json")]
public void ExecuteRule_InvalidWorkFlow_ThrowsException(string ruleFileName)
{
var re = GetRulesEngine(ruleFileName);
dynamic input = GetInput();
Assert.Throws<ArgumentException>(() => { re.ExecuteRule("inputWorkflow1", new List<dynamic>() { input }.AsEnumerable(), new object[] { }); });
}
[Theory]
[InlineData("rules1.json")]
[InlineData("rules2.json")]
public void ExecuteRule_InputWithVariableProps_ReturnsResult(string ruleFileName)
{
var re = GetRulesEngine(ruleFileName);
dynamic input = GetInput();
dynamic input2 = GetInput();
input2.valueX = "hello";
var result = re.ExecuteRule("inputWorkflow", new List<dynamic>() { input, input2 }.AsEnumerable(), new object[] { });
Assert.NotNull(result);
Assert.IsType<List<RuleResultTree>>(result);
}
private RulesEngine CreateRulesEngine(WorkflowRules workflow)
{
var json = JsonConvert.SerializeObject(workflow);
return new RulesEngine(new string[] { json }, null);
}
private RulesEngine GetRulesEngine(string filename)
{
var filePath = Path.Combine(Directory.GetCurrentDirectory() as string, "TestData", filename);
var data = File.ReadAllText(filePath);
var injectWorkflow = new WorkflowRules
{
WorkflowName = "inputWorkflowReference",
WorkflowRulesToInject = new List<string> { "inputWorkflow" }
};
var injectWorkflowStr = JsonConvert.SerializeObject(injectWorkflow);
var mockLogger = new Mock<ILogger>();
return new RulesEngine(new string[] { data, injectWorkflowStr}, mockLogger.Object);
}
private dynamic GetInput()
{
dynamic input = new ExpandoObject();
input.value1 = "value1";
input.value2 = "value2";
input.value3 = 1;
input.value4 = new { subValue = "subValue", nullValue = default(string) };
return input;
}
}
}

View File

@ -0,0 +1,48 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using RulesEngine;
using Moq;
using System;
using Xunit;
namespace RulesEngine.UnitTest
{
[Trait("Category", "Unit")]
public class CustomTypeProviderTests : IDisposable
{
private MockRepository mockRepository;
public CustomTypeProviderTests()
{
this.mockRepository = new MockRepository(MockBehavior.Strict);
}
public void Dispose()
{
this.mockRepository.VerifyAll();
}
private CustomTypeProvider CreateProvider()
{
return new CustomTypeProvider(null);
}
[Fact]
public void GetCustomTypes_StateUnderTest_ExpectedBehavior()
{
// Arrange
var unitUnderTest = this.CreateProvider();
// Act
var result = unitUnderTest.GetCustomTypes();
// Assert
Assert.NotEmpty(result);
}
}
}

View File

@ -0,0 +1,28 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using RulesEngine.HelperFunctions;
using Xunit;
namespace RulesEngine.UnitTest
{
[Trait("Category", "Unit")]
public class ExpressionUtilsTest
{
[Fact]
public void CheckContainsTest()
{
var result = ExpressionUtils.CheckContains("", "");
Assert.False(result);
result = ExpressionUtils.CheckContains(null, "");
Assert.False(result);
result = ExpressionUtils.CheckContains("4", "1,2,3,4,5");
Assert.True(result);
result = ExpressionUtils.CheckContains("6", "1,2,3,4,5");
Assert.False(result);
}
}
}

View File

@ -0,0 +1,46 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using RulesEngine;
using RulesEngine.Models;
using System.Collections.Generic;
using System.Linq.Expressions;
using Xunit;
namespace RulesEngine.UnitTest
{
[Trait("Category", "Unit")]
public class LambdaExpressionBuilderTest
{
[Fact]
public void BuildExpressionForRuleTest()
{
var objBuilderFactory = new RuleExpressionBuilderFactory(new ReSettings());
var builder = objBuilderFactory.RuleGetExpressionBuilder(RuleExpressionType.LambdaExpression);
var parameterExpressions = new List<ParameterExpression>();
parameterExpressions.Add(Expression.Parameter(typeof(string), "RequestType"));
parameterExpressions.Add(Expression.Parameter(typeof(string), "RequestStatus"));
parameterExpressions.Add(Expression.Parameter(typeof(string), "RegistrationStatus"));
Rule mainRule = new Rule();
mainRule.RuleName = "rule1";
mainRule.Operator = "And";
mainRule.Rules = new List<Rule>();
Rule dummyRule = new Rule();
dummyRule.RuleName = "testRule1";
dummyRule.RuleExpressionType = RuleExpressionType.LambdaExpression;
dummyRule.Expression = "RequestType == \"vod\"";
mainRule.Rules.Add(dummyRule);
ParameterExpression ruleInputExp = Expression.Parameter(typeof(RuleInput), nameof(RuleInput));
var expression = builder.BuildExpressionForRule(dummyRule, parameterExpressions, ruleInputExp);
Assert.NotNull(expression);
Assert.Equal(typeof(RuleResultTree), expression.ReturnType);
}
}
}

View File

@ -0,0 +1,30 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using RulesEngine;
using System;
using System.Collections.Generic;
using System.Text;
using Xunit;
namespace RulesEngine.UnitTest
{
[Trait("Category","Unit")]
public class NullLoggerTest
{
[Fact]
public void NullLogger_LogTrace()
{
var logger = new NullLogger();
logger.LogTrace("hello");
}
[Fact]
public void NullLogger_LogError()
{
var logger = new NullLogger();
logger.LogError(new Exception("hello"));
}
}
}

View File

@ -0,0 +1,33 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using RulesEngine;
using RulesEngine.Models;
using System;
using System.Collections.Generic;
using System.Text;
using Xunit;
namespace RulesEngine.UnitTest
{
[Trait("Category","Unit")]
public class RuleCompilerTest
{
[Fact]
public void RuleCompiler_NullCheck()
{
Assert.Throws<ArgumentNullException>(() => new RuleCompiler(null, null));
Assert.Throws<ArgumentNullException>(() => new RuleCompiler(new RuleExpressionBuilderFactory(new ReSettings()), null));
}
[Fact]
public void RuleCompiler_CompileRule_ThrowsException()
{
var compiler = new RuleCompiler(new RuleExpressionBuilderFactory(new ReSettings()), new NullLogger());
Assert.Throws<ArgumentException>(() => compiler.CompileRule(null, null));
Assert.Throws<ArgumentException>(() => compiler.CompileRule(null, new RuleParameter[] { null}));
}
}
}

View File

@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using RulesEngine;
using RulesEngine.ExpressionBuilders;
using RulesEngine.Models;
using System;
using Xunit;
namespace RulesEngine.UnitTest
{
[Trait("Category", "Unit")]
public class RuleExpressionBuilderFactoryTest
{
[Theory]
[InlineData(RuleExpressionType.LambdaExpression, typeof(LambdaExpressionBuilder))]
public void RuleGetExpressionBuilderTest(RuleExpressionType expressionType, Type expectedExpressionBuilderType)
{
var objBuilderFactory = new RuleExpressionBuilderFactory(new ReSettings());
var builder = objBuilderFactory.RuleGetExpressionBuilder(expressionType);
var builderType = builder.GetType();
Assert.Equal(expectedExpressionBuilderType.ToString(), builderType.ToString());
}
}
}

View File

@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="Moq" Version="4.12.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\RulesEngine\RulesEngine\RulesEngine.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="TestData\rules1.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="TestData\rules2.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@ -0,0 +1,20 @@
{
"WorkflowName": "inputWorkflow",
"Rules": [
{
"RuleName": "Rule1",
"Operator": "AndAlso",
"ErrorMessage": "One or more adjust rules failed.",
"ErrorType": "Error",
"Rules": [
{
"RuleName": "SubRule1",
"Expression": "input1.Request_RequestType == \"vod\" AND input1.Labor_BillingCode == \"billable\" AND ((input1.Request_RegistrationStatus == \"cancelled with t&e\" AND input1.Request_Status == \"cancelled\") OR (input1.Request_Status != \"cancelled\"))",
"ErrorMessage": "SubError message 1",
"ErrorType": "Error",
"RuleExpressionType": "LambdaExpression"
}
]
}
]
}

View File

@ -0,0 +1,34 @@
{
"WorkflowName": "inputWorkflow",
"Rules": [
{
"RuleName": "Rule1",
"Operator": "Or",
"ErrorMessage": "One or more adjust rules failed.",
"ErrorType": "Error",
"Rules": [
{
"RuleName": "SubRule1",
"Expression": "input1.Request_RequestType == \"vod\" AND input1.Labor_BillingCode == \"billable\" AND ((input1.Request_RegistrationStatus == \"cancelled with t&e\" AND input1.Request_Status == \"cancelled\") OR (input1.Request_Status != \"cancelled\"))",
"ErrorMessage": "SubError message 1",
"ErrorType": "Error",
"RuleExpressionType": "LambdaExpression"
},
{
"RuleName": "SubRule2",
"Expression": "1 == 1",
"ErrorMessage": "SubError message 2",
"ErrorType": "Error",
"RuleExpressionType": "LambdaExpression"
},
{
"RuleName": "SubRule3",
"Expression": "input1.Request_RequestType == \"vod\" AND input1.Labor_BillingCode == \"billable\" AND ((input1.Request_RegistrationStatus == \"cancelled with t&e\" AND input1.Request_Status == \"cancelled\") OR (input1.Request_Status != \"cancelled\"))",
"ErrorMessage": "SubError message 3",
"ErrorType": "Error",
"RuleExpressionType": "LambdaExpression"
}
]
}
]
}

View File

@ -0,0 +1,74 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using RulesEngine.HelperFunctions;
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Text;
using Xunit;
namespace RulesEngine.UnitTest
{
public class TestClass
{
public string test { get; set; }
public List<int> testList { get; set; }
}
[Trait("Category","Unit")]
public class UtilsTests
{
[Fact]
public void GetTypedObject_dynamicObject()
{
dynamic obj = new ExpandoObject();
obj.test = "hello";
obj.testList = new List<int> { 1, 2, 3 };
object typedobj = Utils.GetTypedObject(obj);
Assert.IsNotType<ExpandoObject>(typedobj);
Assert.NotNull(typedobj.GetType().GetProperty("test"));
}
[Fact]
public void GetTypedObject_nonDynamicObject()
{
var obj = new {
test = "hello"
};
object typedobj = Utils.GetTypedObject(obj);
Assert.IsNotType<ExpandoObject>(typedobj);
Assert.NotNull(typedobj.GetType().GetProperty("test"));
}
[Fact]
public void CreateObject_dynamicObject()
{
dynamic obj = new ExpandoObject();
obj.test = "test";
obj.testList = new List<int> { 1, 2, 3 };
object newObj = Utils.CreateObject(typeof(TestClass), obj);
Assert.IsNotType<ExpandoObject>(newObj);
Assert.NotNull(newObj.GetType().GetProperty("test"));
}
[Fact]
public void CreateAbstractType_dynamicObject()
{
dynamic obj = new ExpandoObject();
obj.test = "test";
obj.testList = new List<int> { 1, 2, 3 };
obj.testEmptyList = new List<object>();
Type type = Utils.CreateAbstractClassType( obj);
Assert.NotEqual(typeof(ExpandoObject), type);
Assert.NotNull(type.GetProperty("test"));
}
}
}