The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── .config
    └── dotnet-tools.json
├── .devcontainer
    └── devcontainer.json
├── .editorconfig
├── .github
    ├── dependabot.yml
    └── workflows
    │   ├── codeql-analysis.yml
    │   └── dotnetcore-build.yml
├── .gitignore
├── .vscode
    ├── launch.json
    ├── settings.json
    └── tasks.json
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── RulesEngine.sln
├── SECURITY.md
├── assets
    └── BlockDiagram.png
├── benchmark
    └── RulesEngineBenchmark
    │   ├── Program.cs
    │   ├── RulesEngineBenchmark.csproj
    │   └── Workflows
    │       ├── Discount.json
    │       └── NestedInputDemo.json
├── demo
    ├── DemoApp.EFDataExample
    │   ├── DemoApp.EFDataExample.csproj
    │   ├── RulesEngineContext.cs
    │   └── RulesEngineDemoContext.cs
    └── DemoApp
    │   ├── BasicDemo.cs
    │   ├── DemoApp.csproj
    │   ├── EFDemo.cs
    │   ├── JSONDemo.cs
    │   ├── NestedInputDemo.cs
    │   ├── Program.cs
    │   └── Workflows
    │       ├── Discount.json
    │       └── NestedInputDemo.json
├── deployment
    └── build-signed.ps1
├── docs
    ├── Getting-Started.md
    ├── Home.md
    ├── Introduction.md
    ├── Use-Case.md
    ├── _Sidebar.md
    ├── _config.yml
    └── index.md
├── global.json
├── schema
    ├── workflow-list-schema.json
    └── workflow-schema.json
├── scripts
    ├── check-coverage.ps1
    └── generate-coverage-report.ps1
├── signing
    └── RulesEngine-publicKey.snk
├── src
    └── RulesEngine
    │   ├── Actions
    │       ├── ActionBase.cs
    │       ├── ActionContext.cs
    │       ├── ActionFactory.cs
    │       ├── EvaluateRuleAction.cs
    │       └── ExpressionOutputAction.cs
    │   ├── CustomTypeProvider.cs
    │   ├── Exceptions
    │       ├── ExpressionParserException.cs
    │       ├── RuleException.cs
    │       ├── RuleValidationException.cs
    │       └── ScopedParamException.cs
    │   ├── ExpressionBuilders
    │       ├── LambdaExpressionBuilder.cs
    │       ├── RuleExpressionBuilderBase.cs
    │       └── RuleExpressionParser.cs
    │   ├── Extensions
    │       ├── EnumerableExtensions.cs
    │       └── ListofRuleResultTreeExtension.cs
    │   ├── HelperFunctions
    │       ├── Constants.cs
    │       ├── ExpressionUtils.cs
    │       ├── Helpers.cs
    │       ├── MemCache.cs
    │       └── Utils.cs
    │   ├── Interfaces
    │       └── IRulesEngine.cs
    │   ├── Models
    │       ├── ActionInfo.cs
    │       ├── ActionResult.cs
    │       ├── ActionRuleResult.cs
    │       ├── ReSettings.cs
    │       ├── Rule.cs
    │       ├── RuleActions.cs
    │       ├── RuleDelegate.cs
    │       ├── RuleErrorType.cs
    │       ├── RuleExpressionParameter.cs
    │       ├── RuleExpressionType.cs
    │       ├── RuleParameter.cs
    │       ├── RuleResultTree.cs
    │       ├── ScopedParam.cs
    │       └── Workflow.cs
    │   ├── Properties
    │       └── AssemblyInfo.cs
    │   ├── RuleCompiler.cs
    │   ├── RuleExpressionBuilderFactory.cs
    │   ├── RulesCache.cs
    │   ├── RulesEngine.cs
    │   ├── RulesEngine.csproj
    │   └── Validators
    │       ├── RuleValidator.cs
    │       └── WorkflowRulesValidator.cs
└── test
    └── RulesEngine.UnitTest
        ├── ActionTests
            ├── ActionContextTests.cs
            ├── CustomActionTest.cs
            ├── MockClass
            │   └── ReturnContextAction.cs
            └── RulesEngineWithActionsTests.cs
        ├── BusinessRuleEngineTest.cs
        ├── CaseSensitiveTests.cs
        ├── CustomTypeProviderTests.cs
        ├── EmptyRulesTest.cs
        ├── ExpressionUtilsTest.cs
        ├── LambdaExpressionBuilderTest.cs
        ├── ListofRuleResultTreeExtensionTest.cs
        ├── NestedRulesTest.cs
        ├── ParameterNameChangeTest.cs
        ├── RuleCompilerTest.cs
        ├── RuleExpressionBuilderFactoryTest.cs
        ├── RuleExpressionParserTests
            └── RuleExpressionParserTests.cs
        ├── RuleParameterTests.cs
        ├── RuleTestClass.cs
        ├── RuleValidationTest.cs
        ├── RulesEnabledTests.cs
        ├── RulesEngine.UnitTest.csproj
        ├── ScopedParamsTest.cs
        ├── TestData
            ├── rules1.json
            ├── rules10.json
            ├── rules11.json
            ├── rules2.json
            ├── rules3.json
            ├── rules4.json
            ├── rules5.json
            ├── rules6.json
            ├── rules7.json
            ├── rules8.json
            └── rules9.json
        ├── TypedClassTests.cs
        └── UtilsTests.cs


/.config/dotnet-tools.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "version": 1,
 3 |   "isRoot": true,
 4 |   "tools": {
 5 |     "dotnet-reportgenerator-globaltool": {
 6 |       "version": "5.1.26",
 7 |       "commands": [
 8 |         "reportgenerator"
 9 |       ]
10 |     }
11 |   }
12 | }


--------------------------------------------------------------------------------
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
 1 | {
 2 | 	"name": "RulesEngine Codespace",
 3 | 	"image": "mcr.microsoft.com/vscode/devcontainers/dotnet:0-6.0",
 4 | 	"settings": {
 5 | 		"terminal.integrated.defaultProfile.linux": "bash"
 6 | 	},
 7 | 	"extensions": [
 8 | 		"eamodio.gitlens",
 9 | 		"ms-dotnettools.csharp",
10 | 		"VisualStudioExptTeam.vscodeintellicode",
11 | 		"ms-vscode.powershell",
12 | 		"cschleiden.vscode-github-actions",
13 | 		"redhat.vscode-yaml",
14 | 		"bierner.markdown-preview-github-styles",
15 | 		"coenraads.bracket-pair-colorizer",
16 | 		"vscode-icons-team.vscode-icons",
17 | 		"editorconfig.editorconfig",
18 | 		"aliasadidev.nugetpackagemanagergui",
19 | 		"formulahendry.dotnet-test-explorer"
20 | 	],
21 | 	"postCreateCommand": "dotnet restore RulesEngine.sln && dotnet build RulesEngine.sln --configuration Release --no-restore && dotnet test RulesEngine.sln --configuration Release --no-build --verbosity minimal",
22 | 	"features": {
23 | 		"powershell": "7.1"
24 | 	},
25 | }
26 | // Built with ❤ by [Pipeline Foundation](https://pipeline.foundation)
27 | 


--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
  1 | # Rules in this file were initially inferred by Visual Studio IntelliCode from the C:\Users\purunjaybhal\source\repos\RulesEngine codebase based on best match to current usage at 22-12-2020
  2 | # You can modify the rules from these initially generated values to suit your own policies
  3 | # You can learn more about editorconfig here: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference
  4 | [*.cs]
  5 | 
  6 | 
  7 | #Core editorconfig formatting - indentation
  8 | 
  9 | #use soft tabs (spaces) for indentation
 10 | indent_style = space
 11 | 
 12 | #Formatting - indentation options
 13 | 
 14 | #indent switch case contents.
 15 | csharp_indent_case_contents = true
 16 | #indent switch labels
 17 | csharp_indent_switch_labels = true
 18 | 
 19 | #Formatting - new line options
 20 | 
 21 | #place catch statements on a new line
 22 | csharp_new_line_before_catch = true
 23 | #place else statements on a new line
 24 | csharp_new_line_before_else = true
 25 | #require members of anonymous types to be on separate lines
 26 | csharp_new_line_before_members_in_anonymous_types = true
 27 | #require members of object intializers to be on separate lines
 28 | csharp_new_line_before_members_in_object_initializers = true
 29 | #require braces to be on a new line for methods, control_blocks, and types (also known as "Allman" style)
 30 | csharp_new_line_before_open_brace = methods, control_blocks, types
 31 | 
 32 | #Formatting - organize using options
 33 | 
 34 | #do not place System.* using directives before other using directives
 35 | dotnet_sort_system_directives_first = false
 36 | 
 37 | #Formatting - spacing options
 38 | 
 39 | #require NO space between a cast and the value
 40 | csharp_space_after_cast = false
 41 | #require a space before the colon for bases or interfaces in a type declaration
 42 | csharp_space_after_colon_in_inheritance_clause = true
 43 | #require a space after a keyword in a control flow statement such as a for loop
 44 | csharp_space_after_keywords_in_control_flow_statements = true
 45 | #require a space before the colon for bases or interfaces in a type declaration
 46 | csharp_space_before_colon_in_inheritance_clause = true
 47 | #remove space within empty argument list parentheses
 48 | csharp_space_between_method_call_empty_parameter_list_parentheses = false
 49 | #remove space between method call name and opening parenthesis
 50 | csharp_space_between_method_call_name_and_opening_parenthesis = false
 51 | #do not place space characters after the opening parenthesis and before the closing parenthesis of a method call
 52 | csharp_space_between_method_call_parameter_list_parentheses = false
 53 | #remove space within empty parameter list parentheses for a method declaration
 54 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
 55 | #place a space character after the opening parenthesis and before the closing parenthesis of a method declaration parameter list.
 56 | csharp_space_between_method_declaration_parameter_list_parentheses = false
 57 | 
 58 | #Formatting - wrapping options
 59 | 
 60 | #leave code block on single line
 61 | csharp_preserve_single_line_blocks = true
 62 | #leave statements and member declarations on the same line
 63 | csharp_preserve_single_line_statements = true
 64 | 
 65 | #Style - Code block preferences
 66 | 
 67 | #prefer curly braces even for one line of code
 68 | csharp_prefer_braces = true:suggestion
 69 | 
 70 | #Style - expression bodied member options
 71 | 
 72 | #prefer block bodies for constructors
 73 | csharp_style_expression_bodied_constructors = false:suggestion
 74 | #prefer block bodies for methods
 75 | csharp_style_expression_bodied_methods = false:suggestion
 76 | 
 77 | #Style - expression level options
 78 | 
 79 | #prefer out variables to be declared inline in the argument list of a method call when possible
 80 | csharp_style_inlined_variable_declaration = true:suggestion
 81 | #prefer the language keyword for member access expressions, instead of the type name, for types that have a keyword to represent them
 82 | dotnet_style_predefined_type_for_member_access = true:suggestion
 83 | 
 84 | #Style - Expression-level  preferences
 85 | 
 86 | #prefer objects to be initialized using object initializers when possible
 87 | dotnet_style_object_initializer = true:suggestion
 88 | #prefer inferred anonymous type member names
 89 | dotnet_style_prefer_inferred_anonymous_type_member_names = false:suggestion
 90 | 
 91 | #Style - implicit and explicit types
 92 | 
 93 | #prefer var over explicit type in all cases, unless overridden by another code style rule
 94 | csharp_style_var_elsewhere = true:suggestion
 95 | #prefer var is used to declare variables with built-in system types such as int
 96 | csharp_style_var_for_built_in_types = true:suggestion
 97 | #prefer var when the type is already mentioned on the right-hand side of a declaration expression
 98 | csharp_style_var_when_type_is_apparent = true:suggestion
 99 | 
100 | #Style - language keyword and framework type options
101 | 
102 | #prefer the language keyword for local variables, method parameters, and class members, instead of the type name, for types that have a keyword to represent them
103 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
104 | 
105 | #Style - Miscellaneous preferences
106 | 
107 | #prefer anonymous functions over local functions
108 | csharp_style_pattern_local_over_anonymous_function = false:suggestion
109 | 
110 | #Style - modifier options
111 | 
112 | #prefer accessibility modifiers to be declared except for public interface members. This will currently not differ from always and will act as future proofing for if C# adds default interface methods.
113 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
114 | 
115 | #Style - Modifier preferences
116 | 
117 | #when this rule is set to a list of modifiers, prefer the specified ordering.
118 | csharp_preferred_modifier_order = public,private,internal,readonly,static,async,override,sealed:suggestion
119 | 
120 | #Style - qualification options
121 | 
122 | #prefer fields not to be prefaced with this. or Me. in Visual Basic
123 | dotnet_style_qualification_for_field = false:suggestion
124 | #prefer methods not to be prefaced with this. or Me. in Visual Basic
125 | dotnet_style_qualification_for_method = false:suggestion
126 | #prefer properties not to be prefaced with this. or Me. in Visual Basic
127 | dotnet_style_qualification_for_property = false:suggestion
128 | 
129 | 
130 | #file header
131 | [*.{cs,vb}]
132 | file_header_template = Copyright (c) Microsoft Corporation.\nLicensed under the MIT License.
133 | file_header_template_style = prepend:error
134 | file_header_template_style = replace:suggestion
135 | 


--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
 1 | version: 2
 2 | updates:
 3 |   - package-ecosystem: "github-actions"
 4 |     # default location of `.github/workflows`
 5 |     directory: "/"
 6 |     open-pull-requests-limit: 3
 7 |     schedule:
 8 |       interval: "weekly"
 9 |     # assignees:
10 |     #   - assignee_one
11 |     # reviewers:
12 |     #   - reviewer_one
13 |   - package-ecosystem: "nuget"
14 |     # location of package manifests
15 |     directory: "/"
16 |     open-pull-requests-limit: 3
17 |     schedule:
18 |       interval: "weekly"
19 |     ignore:
20 |       - dependency-name: "*"
21 |         update-types: ["version-update:semver-minor"]
22 |     # assignees:
23 |     #   - assignee_one
24 |     # reviewers:
25 |     #   - reviewer_one
26 |   - package-ecosystem: dotnet-sdk
27 |     directory: /
28 |     schedule:
29 |       interval: weekly
30 |       day: wednesday
31 |     ignore:
32 |       - dependency-name: '*'
33 |         update-types:
34 |           - version-update:semver-major
35 |           - version-update:semver-minor
36 | # Built with ❤ by [Pipeline Foundation](https://pipeline.foundation)
37 | 


--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
 1 | # For most projects, this workflow file will not need changing; you simply need
 2 | # to commit it to your repository.
 3 | #
 4 | # You may wish to alter this file to override the set of languages analyzed,
 5 | # or to provide custom queries or build logic.
 6 | #
 7 | # ******** NOTE ********
 8 | # We have attempted to detect the languages in your repository. Please check
 9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: "CodeQL"
13 | 
14 | on:
15 |   push:
16 |     branches: [ main, develop ]
17 |   pull_request:
18 |     # The branches below must be a subset of the branches above
19 |     branches: [ main ]
20 |   schedule:
21 |     - cron: '22 15 * * 4'
22 | 
23 | jobs:
24 |   analyze:
25 |     name: Analyze
26 |     runs-on: ubuntu-latest
27 | 
28 |     strategy:
29 |       fail-fast: false
30 |       matrix:
31 |         language: [ 'csharp' ]
32 |         # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
33 |         # Learn more:
34 |         # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
35 | 
36 |     steps:
37 |     - name: Checkout repository
38 |       uses: actions/checkout@v4
39 | 
40 |     - name: Setup .NET Core
41 |       uses: actions/setup-dotnet@v4
42 |       with:
43 |         dotnet-version: |
44 |           6.0.x
45 |           8.0.x
46 |           9.0.x
47 | 
48 |     # Initializes the CodeQL tools for scanning.
49 |     - name: Initialize CodeQL
50 |       uses: github/codeql-action/init@v3
51 |       with:
52 |         languages: ${{ matrix.language }}
53 |         # If you wish to specify custom queries, you can do so here or in a config file.
54 |         # By default, queries listed here will override any specified in a config file.
55 |         # Prefix the list here with "+" to use these queries and those in the config file.
56 |         # queries: ./path/to/local/query, your-org/your-repo/queries@main
57 | 
58 |     # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
59 |     # If this step fails, then you should remove it and run the build manually (see below)
60 |     - name: Autobuild
61 |       uses: github/codeql-action/autobuild@v3
62 | 
63 |     # ℹ️ Command-line programs to run using the OS shell.
64 |     # 📚 https://git.io/JvXDl
65 | 
66 |     # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
67 |     #    and modify them (or add more) to build your code if your project
68 |     #    uses a compiled language
69 | 
70 |     #- run: |
71 |     #   make bootstrap
72 |     #   make release
73 | 
74 |     - name: Perform CodeQL Analysis
75 |       uses: github/codeql-action/analyze@v3
76 |     
77 | 


--------------------------------------------------------------------------------
/.github/workflows/dotnetcore-build.yml:
--------------------------------------------------------------------------------
 1 | name: build
 2 | 
 3 | on:
 4 |   push:
 5 |     branches: [ main ]
 6 |   pull_request:
 7 |     branches: [ main, develop ]
 8 | 
 9 | jobs:
10 |   build:
11 |     runs-on: ubuntu-latest
12 |     steps:
13 |     - uses: actions/checkout@v4
14 |     - name: Setup .NET Core
15 |       uses: actions/setup-dotnet@v4
16 |       with:
17 |         dotnet-version: |
18 |           6.0.x
19 |           8.0.x
20 |           9.0.x
21 |     
22 |     - name: Install dependencies
23 |       run: dotnet restore RulesEngine.sln
24 |     
25 |     - name: Build
26 |       run: dotnet build RulesEngine.sln --configuration Release --no-restore
27 |     
28 |     - name: Test
29 |       run: dotnet test RulesEngine.sln --collect:"XPlat Code Coverage" --no-build --configuration Release --verbosity m 
30 | 
31 |     - name: Generate Report
32 |       shell: pwsh
33 |       run: ./scripts/generate-coverage-report.ps1
34 | 
35 |     - name: Check Coverage
36 |       shell: pwsh
37 |       run: ./scripts/check-coverage.ps1 -reportPath coveragereport/Cobertura.xml -threshold 94
38 |     
39 |     - name: Coveralls GitHub Action
40 |       uses: coverallsapp/github-action@v2.3.6
41 |       if: ${{ github.event_name == 'push' }}
42 |       with:
43 |         github-token: ${{ secrets.GITHUB_TOKEN }}
44 |         path-to-lcov: ./coveragereport/lcov.info
45 | 


--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
  1 | ## Ignore Visual Studio temporary files, build results, and
  2 | ## files generated by popular Visual Studio add-ons.
  3 | ##
  4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
  5 | 
  6 | # User-specific files
  7 | *.suo
  8 | *.user
  9 | *.userosscache
 10 | *.sln.docstates
 11 | 
 12 | # User-specific files (MonoDevelop/Xamarin Studio)
 13 | *.userprefs
 14 | 
 15 | # Build results
 16 | [Dd]ebug/
 17 | [Dd]ebugPublic/
 18 | [Rr]elease/
 19 | [Rr]eleases/
 20 | x64/
 21 | x86/
 22 | bld/
 23 | [Bb]in/
 24 | [Oo]bj/
 25 | [Ll]og/
 26 | 
 27 | # Visual Studio 2015/2017 cache/options directory
 28 | .vs/
 29 | # Uncomment if you have tasks that create the project's static files in wwwroot
 30 | #wwwroot/
 31 | 
 32 | # Visual Studio 2017 auto generated files
 33 | Generated\ Files/
 34 | 
 35 | # MSTest test Results
 36 | [Tt]est[Rr]esult*/
 37 | [Bb]uild[Ll]og.*
 38 | 
 39 | # NUNIT
 40 | *.VisualState.xml
 41 | TestResult.xml
 42 | 
 43 | # Build Results of an ATL Project
 44 | [Dd]ebugPS/
 45 | [Rr]eleasePS/
 46 | dlldata.c
 47 | 
 48 | # Benchmark Results
 49 | BenchmarkDotNet.Artifacts/
 50 | 
 51 | # .NET Core
 52 | project.lock.json
 53 | project.fragment.lock.json
 54 | artifacts/
 55 | **/Properties/launchSettings.json
 56 | 
 57 | # StyleCop
 58 | StyleCopReport.xml
 59 | 
 60 | # Files built by Visual Studio
 61 | *_i.c
 62 | *_p.c
 63 | *_i.h
 64 | *.ilk
 65 | *.meta
 66 | *.obj
 67 | *.iobj
 68 | *.pch
 69 | *.pdb
 70 | *.ipdb
 71 | *.pgc
 72 | *.pgd
 73 | *.rsp
 74 | *.sbr
 75 | *.tlb
 76 | *.tli
 77 | *.tlh
 78 | *.tmp
 79 | *.tmp_proj
 80 | *.log
 81 | *.vspscc
 82 | *.vssscc
 83 | .builds
 84 | *.pidb
 85 | *.svclog
 86 | *.scc
 87 | 
 88 | # Chutzpah Test files
 89 | _Chutzpah*
 90 | 
 91 | # Visual C++ cache files
 92 | ipch/
 93 | *.aps
 94 | *.ncb
 95 | *.opendb
 96 | *.opensdf
 97 | *.sdf
 98 | *.cachefile
 99 | *.VC.db
100 | *.VC.VC.opendb
101 | 
102 | # Visual Studio profiler
103 | *.psess
104 | *.vsp
105 | *.vspx
106 | *.sap
107 | 
108 | # Visual Studio Trace Files
109 | *.e2e
110 | 
111 | # TFS 2012 Local Workspace
112 | $tf/
113 | 
114 | # Guidance Automation Toolkit
115 | *.gpState
116 | 
117 | # ReSharper is a .NET coding add-in
118 | _ReSharper*/
119 | *.[Rr]e[Ss]harper
120 | *.DotSettings.user
121 | 
122 | # JustCode is a .NET coding add-in
123 | .JustCode
124 | 
125 | # TeamCity is a build add-in
126 | _TeamCity*
127 | 
128 | # DotCover is a Code Coverage Tool
129 | *.dotCover
130 | 
131 | # AxoCover is a Code Coverage Tool
132 | .axoCover/*
133 | !.axoCover/settings.json
134 | 
135 | # Visual Studio code coverage results
136 | *.coverage
137 | *.coveragexml
138 | 
139 | # NCrunch
140 | _NCrunch_*
141 | .*crunch*.local.xml
142 | nCrunchTemp_*
143 | 
144 | # MightyMoose
145 | *.mm.*
146 | AutoTest.Net/
147 | 
148 | # Web workbench (sass)
149 | .sass-cache/
150 | 
151 | # Installshield output folder
152 | [Ee]xpress/
153 | 
154 | # DocProject is a documentation generator add-in
155 | DocProject/buildhelp/
156 | DocProject/Help/*.HxT
157 | DocProject/Help/*.HxC
158 | DocProject/Help/*.hhc
159 | DocProject/Help/*.hhk
160 | DocProject/Help/*.hhp
161 | DocProject/Help/Html2
162 | DocProject/Help/html
163 | 
164 | # Click-Once directory
165 | publish/
166 | 
167 | # Publish Web Output
168 | *.[Pp]ublish.xml
169 | *.azurePubxml
170 | # Note: Comment the next line if you want to checkin your web deploy settings,
171 | # but database connection strings (with potential passwords) will be unencrypted
172 | *.pubxml
173 | *.publishproj
174 | 
175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
176 | # checkin your Azure Web App publish settings, but sensitive information contained
177 | # in these scripts will be unencrypted
178 | PublishScripts/
179 | 
180 | # NuGet Packages
181 | *.nupkg
182 | # The packages folder can be ignored because of Package Restore
183 | **/[Pp]ackages/*
184 | # except build/, which is used as an MSBuild target.
185 | !**/[Pp]ackages/build/
186 | # Uncomment if necessary however generally it will be regenerated when needed
187 | #!**/[Pp]ackages/repositories.config
188 | # NuGet v3's project.json files produces more ignorable files
189 | *.nuget.props
190 | *.nuget.targets
191 | 
192 | # Microsoft Azure Build Output
193 | csx/
194 | *.build.csdef
195 | 
196 | # Microsoft Azure Emulator
197 | ecf/
198 | rcf/
199 | 
200 | # Windows Store app package directories and files
201 | AppPackages/
202 | BundleArtifacts/
203 | Package.StoreAssociation.xml
204 | _pkginfo.txt
205 | *.appx
206 | 
207 | # Visual Studio cache files
208 | # files ending in .cache can be ignored
209 | *.[Cc]ache
210 | # but keep track of directories ending in .cache
211 | !*.[Cc]ache/
212 | 
213 | # Others
214 | ClientBin/
215 | ~$*
216 | *~
217 | *.dbmdl
218 | *.dbproj.schemaview
219 | *.jfm
220 | *.pfx
221 | *.publishsettings
222 | orleans.codegen.cs
223 | 
224 | # Including strong name files can present a security risk 
225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
226 | #*.snk
227 | 
228 | # Since there are multiple workflows, uncomment next line to ignore bower_components
229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
230 | #bower_components/
231 | 
232 | # RIA/Silverlight projects
233 | Generated_Code/
234 | 
235 | # Backup & report files from converting an old project file
236 | # to a newer Visual Studio version. Backup files are not needed,
237 | # because we have git ;-)
238 | _UpgradeReport_Files/
239 | Backup*/
240 | UpgradeLog*.XML
241 | UpgradeLog*.htm
242 | ServiceFabricBackup/
243 | *.rptproj.bak
244 | 
245 | # SQL Server files
246 | *.mdf
247 | *.ldf
248 | *.ndf
249 | 
250 | # Business Intelligence projects
251 | *.rdl.data
252 | *.bim.layout
253 | *.bim_*.settings
254 | *.rptproj.rsuser
255 | 
256 | # Microsoft Fakes
257 | FakesAssemblies/
258 | 
259 | # GhostDoc plugin setting file
260 | *.GhostDoc.xml
261 | 
262 | # Node.js Tools for Visual Studio
263 | .ntvs_analysis.dat
264 | node_modules/
265 | 
266 | # Visual Studio 6 build log
267 | *.plg
268 | 
269 | # Visual Studio 6 workspace options file
270 | *.opt
271 | 
272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
273 | *.vbw
274 | 
275 | # Visual Studio LightSwitch build output
276 | **/*.HTMLClient/GeneratedArtifacts
277 | **/*.DesktopClient/GeneratedArtifacts
278 | **/*.DesktopClient/ModelManifest.xml
279 | **/*.Server/GeneratedArtifacts
280 | **/*.Server/ModelManifest.xml
281 | _Pvt_Extensions
282 | 
283 | # Paket dependency manager
284 | .paket/paket.exe
285 | paket-files/
286 | 
287 | # FAKE - F# Make
288 | .fake/
289 | 
290 | # JetBrains Rider
291 | .idea/
292 | *.sln.iml
293 | 
294 | # CodeRush
295 | .cr/
296 | 
297 | # Python Tools for Visual Studio (PTVS)
298 | __pycache__/
299 | *.pyc
300 | 
301 | # Cake - Uncomment if you are using it
302 | # tools/**
303 | # !tools/packages.config
304 | 
305 | # Tabs Studio
306 | *.tss
307 | 
308 | # Telerik's JustMock configuration file
309 | *.jmconfig
310 | 
311 | # BizTalk build output
312 | *.btp.cs
313 | *.btm.cs
314 | *.odx.cs
315 | *.xsd.cs
316 | 
317 | # OpenCover UI analysis results
318 | OpenCover/
319 | 
320 | # Azure Stream Analytics local run output 
321 | ASALocalRun/
322 | 
323 | # MSBuild Binary and Structured Log
324 | *.binlog
325 | 
326 | # NVidia Nsight GPU debugger configuration file
327 | *.nvuser
328 | 
329 | # MFractors (Xamarin productivity tool) working folder 
330 | .mfractor/
331 | /src/RulesEngine/RulesEngine.sln.licenseheader
332 | /assets/RulesEnginePackageFile.xml
333 | coveragereport/
334 | 
335 | src/**/*.snk
336 | 
337 | dist


--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
 1 | {
 2 |    // Use IntelliSense to find out which attributes exist for C# debugging
 3 |    // Use hover for the description of the existing attributes
 4 |    // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
 5 |    "version": "0.2.0",
 6 |    "configurations": [
 7 |         {
 8 |             "name": ".NET Core Launch (console)",
 9 |             "type": "coreclr",
10 |             "request": "launch",
11 |             "preLaunchTask": "build",
12 |             // If you have changed target frameworks, make sure to update the program path.
13 |             "program": "${workspaceFolder}/demo/DemoApp/bin/Debug/netcoreapp3.1/DemoApp.dll",
14 |             "args": [],
15 |             "cwd": "${workspaceFolder}/demo/DemoApp",
16 |             // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
17 |             "console": "internalConsole",
18 |             "stopAtEntry": false
19 |         },
20 |         {
21 |             "name": ".NET Core Attach",
22 |             "type": "coreclr",
23 |             "request": "attach",
24 |             "processId": "${command:pickProcess}"
25 |         }
26 |     ]
27 | }


--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 |     "dotnetCoreExplorer.searchpatterns": "test/**/bin/Debug/netcoreapp*/*.{dll,exe,json}",
3 |     "coverage-gutters.coverageBaseDir": "coveragereport",
4 |     "coverage-gutters.coverageReportFileName": "coveragereport/**/index.html"
5 | }


--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "version": "2.0.0",
 3 |     "tasks": [
 4 |         {
 5 |             "label": "build",
 6 |             "command": "dotnet",
 7 |             "type": "process",
 8 |             "args": [
 9 |                 "build",
10 |                 "${workspaceFolder}/demo/DemoApp/DemoApp.csproj",
11 |                 "/property:GenerateFullPaths=true",
12 |                 "/consoleloggerparameters:NoSummary"
13 |             ],
14 |             "problemMatcher": "$msCompile"
15 |         },
16 |         {
17 |             "label": "publish",
18 |             "command": "dotnet",
19 |             "type": "process",
20 |             "args": [
21 |                 "publish",
22 |                 "${workspaceFolder}/demo/DemoApp/DemoApp.csproj",
23 |                 "/property:GenerateFullPaths=true",
24 |                 "/consoleloggerparameters:NoSummary"
25 |             ],
26 |             "problemMatcher": "$msCompile"
27 |         },
28 |         {
29 |             "label": "watch",
30 |             "command": "dotnet",
31 |             "type": "process",
32 |             "args": [
33 |                 "watch",
34 |                 "run",
35 |                 "${workspaceFolder}/demo/DemoApp/DemoApp.csproj",
36 |                 "/property:GenerateFullPaths=true",
37 |                 "/consoleloggerparameters:NoSummary"
38 |             ],
39 |             "problemMatcher": "$msCompile"
40 |         }
41 |     ]
42 | }


--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
  1 | # CHANGELOG
  2 | 
  3 | All notable changes to this project will be documented in this file.
  4 | 
  5 | ## [5.0.3]
  6 | - Updated dependencies to latest
  7 | - Fixed RulesEngine throwing exception when type name is same as input name
  8 | - Added config to disable FastCompile for expressions
  9 | - Added RuleParameter.Create method for better handling on types when value is null
 10 | 
 11 | ## [5.0.2]
 12 | - Fixed Scoped Params returning incorrect results in some corner case scenarios
 13 | 
 14 | ## [5.0.1]
 15 | - Added option to disable automatic type registry for input parameters in reSettings
 16 | - Added option to make expression case sensitive in reSettings
 17 | 
 18 | ## [5.0.0]
 19 | - Fixed security bug related to System.Dynamic.Linq.Core
 20 | 
 21 | ### Breaking Changes
 22 | - As a part of security bug fix, method call for only registered types via reSettings will be allowed. This only impacts strongly typed inputs and nested types
 23 | 
 24 | 
 25 | ## [4.0.0]
 26 | - RulesEngine is now available in both dotnet 6 and netstandard 2.0
 27 | - Dependency on ILogger, MemoryCache have been removed 
 28 | - Obsolete Properties and Methods have been removed
 29 | - Fixed name of RuleParameter is ignored if the type is recognized (by @peeveen)
 30 | ### Breaking Changes
 31 | - ILogger has been removed from RulesEngine and all its constructors
 32 | ```diff
 33 | - RulesEngine(string[] jsonConfig, ILogger logger = null, ReSettings reSettings = null)
 34 | + RulesEngine(string[] jsonConfig, ReSettings reSettings = null)
 35 | 
 36 | - RulesEngine(Workflow[] Workflows, ILogger logger = null, ReSettings reSettings = null)
 37 | + RulesEngine(Workflow[] Workflows, ReSettings reSettings = null)
 38 | 
 39 | - RulesEngine(ILogger logger = null, ReSettings reSettings = null)
 40 | + RulesEngine(ReSettings reSettings = null)
 41 | ```
 42 | - Obsolete methods and properties have been removed, from the follow models:-
 43 | 	- RuleResultTree
 44 | 		- `ToResultTreeMessages()` has been removed from `RuleResultTree` model
 45 | 		- `GetMessages()` has been removed from `RuleResultTree` model
 46 | 		- `RuleEvaluatedParams` has been removed from `RuleResultTree` model, Please use `Inputs` instead
 47 | 
 48 | 	- Workflow
 49 | 		- `WorkflowRulesToInject` has been removed, Please use `WorkflowsToInject` instead
 50 | 		- `ErrorType` has been removed from `Rule`
 51 | 
 52 | 	- Resettings
 53 | 		- `EnableLocalParams` has been removed from `ReSettings`, Please use `EnableScopedParams` instead
 54 | 	
 55 | 
 56 | ## [3.5.0]
 57 | - `EvaluateRule` action now support custom inputs and filtered inputs
 58 | - Added `ContainsWorkflow` method in RulesEngine (by @okolobaxa)
 59 | - Fixed minor bugs (#258 & #259)
 60 | 
 61 | ## [3.4.0]
 62 | - Made RulesEngine Strong Name and Authenticode signed
 63 | - Renamed few models to streamline names (by @alexrich)
 64 | 	- `WorkflowRules` is renamed to `Workflow`
 65 | 	- `WorkflowRulesToInject` is renamed to `WorkflowsToInject`
 66 | 	- `RuleAction` is renamed to `RuleActions`
 67 | 	
 68 | 	**Note**: The old models are still supported but will be removed with version 4.0.0
 69 | 
 70 | 
 71 | ## [3.3.0]
 72 | - Added support for actions in nested rules
 73 | - Improved serialization support for System.Text.Json for workflow model
 74 |   
 75 | Breaking Change:
 76 |   - Type of Action has been changed from `Dictionary<ActionTriggerType, ActionInfo>` to `RuleActions`
 77 |     - No impact if you are serializing workflow from json
 78 |     - For workflow objects created in code, refer - [link](https://github.com/microsoft/RulesEngine/pull/182/files#diff-a5093dda2dcc1e4958ce3533edb607bb61406e1f0a9071eca4e317bdd987c0d3)
 79 | 
 80 | ## [3.2.0]
 81 | - Added AddOrUpdateWorkflow method to update workflows atomically (by @AshishPrasad)
 82 | - Updated dependencies to latest
 83 | 
 84 | Breaking Change:
 85 |   - `AddWorkflow` now throws exception if you try to add a workflow which already exists.
 86 |   Use `AddOrUpdateWorkflow` to update existing workflow
 87 | 
 88 | ## [3.1.0]
 89 | - Added globalParams feature which can be applied to all rules
 90 | - Enabled localParams support for nested Rules
 91 | - Made certain fields in Rule model optional allowing users to define workflow with minimal fields
 92 | - Added option to disable Rule in workflow json
 93 | - Added `GetAllRegisteredWorkflow` to RulesEngine to return all registered workflows
 94 | - Runtime errors for expressions will now be logged as errorMessage instead of throwing Exceptions by default
 95 | - Fixed RuleParameter passed as null
 96 | 
 97 | ## [3.0.2]
 98 | - Fixed LocalParams cache not getting cleaned up when RemoveWorkflows and ClearWorkflows are called
 99 | 
100 | ## [3.0.1]
101 | - Moved ActionResult and ActionRuleResult under RulesEngine.Models namespace
102 | 
103 | 
104 | ## [3.0.0]
105 | ### Major Enhancements
106 | - Added support for Actions. More details on [actions wiki](https://github.com/microsoft/RulesEngine/wiki/Actions)
107 | - Major performance improvement
108 | 	- 25% improvement from previous version
109 | 	- Upto 35% improvement by disabling optional features
110 | - RulesEngine now virtually supports unlimited inputs (Previous limitation was 16 inputs)
111 | - RuleExpressionParser is now available to use expression evaluation outside RulesEngine
112 | 
113 | ### Breaking Changes
114 | - `ExecuteRule` method has been renamed to `ExecuteAllRulesAsync`
115 | - `Input` field in RuleResultTree has been changed to `Inputs` which returns all the the inputs as Dictionary of name and value pair
116 | 
117 | ## [2.1.5] - 02-11-2020
118 | - Added `Properties` field to Rule to allow custom fields to Rule
119 | 
120 | ## [2.1.4] - 15-10-2020
121 | - Added exception data properties to identify RuleName.
122 | 
123 | ## [2.1.3] - 12-10-2020
124 | - Optional parameter for rethrow exception on failure of expression compilation.
125 | 
126 | ## [2.1.2] - 02-10-2020
127 | - Fixed binary expression requirement. Now any expression will work as long as it evalutes to boolean.
128 | 
129 | ## [2.1.1] - 01-09-2020
130 | - Fixed exception thrown when errormessage field is null
131 | - Added better messaging when identifier is not found in expression
132 | - Fixed other minor bugs
133 | 
134 | ## [2.1.0] - 18-05-2020
135 | - Adding local param support to make expression authroing more intuitive.
136 | 
137 | ## [2.0.0] - 18-05-2020
138 | ### Changed
139 | - Interface simplified by removing redundant parameters in the IRulesEngine.
140 | - Custom Logger replaced with Microsoft Logger.
141 | 
142 | ## [1.0.2] - 16-01-2020
143 | ### Added
144 | - Cache system added so that rules compilation is stored and thus made more efficient.
145 | 
146 | ### Fix
147 | - Concurrency issue which arose by dictionary was resolved.
148 | 
149 | ## [1.0.1] - 24-09-2019
150 | ### Added
151 | - Exceptions handling scenario in the case a rule execution throws an exception 
152 | 
153 | ## [1.0.0] - 20-08-2019
154 | 
155 | ### Added
156 | - The first version of the NuGet
157 | 


--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
 1 | # Microsoft Open Source Code of Conduct
 2 | 
 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
 4 | 
 5 | Resources:
 6 | 
 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns
10 | 


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


--------------------------------------------------------------------------------
/RulesEngine.sln:
--------------------------------------------------------------------------------
 1 | 
 2 | Microsoft Visual Studio Solution File, Format Version 12.00
 3 | # Visual Studio Version 17
 4 | VisualStudioVersion = 17.0.31717.71
 5 | MinimumVisualStudioVersion = 10.0.40219.1
 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RulesEngine", "src\RulesEngine\RulesEngine.csproj", "{CD4DFE6A-083B-478E-8377-77F474833E30}"
 7 | EndProject
 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RulesEngine.UnitTest", "test\RulesEngine.UnitTest\RulesEngine.UnitTest.csproj", "{50E0C2A5-E2C8-4B12-8C0E-B69F698A82BF}"
 9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DemoApp", "demo\DemoApp\DemoApp.csproj", "{57BB8C07-799A-4F87-A7CC-D3D3F694DD02}"
11 | 	ProjectSection(ProjectDependencies) = postProject
12 | 		{CD4DFE6A-083B-478E-8377-77F474833E30} = {CD4DFE6A-083B-478E-8377-77F474833E30}
13 | 	EndProjectSection
14 | EndProject
15 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{019DF693-8442-45B4-88C3-55CB7AFCB42E}"
16 | 	ProjectSection(SolutionItems) = preProject
17 | 		.editorconfig = .editorconfig
18 | 		CHANGELOG.md = CHANGELOG.md
19 | 		global.json = global.json
20 | 		README.md = README.md
21 | 		schema\workflow-list-schema.json = schema\workflow-list-schema.json
22 | 		schema\workflow-schema.json = schema\workflow-schema.json
23 | 	EndProjectSection
24 | EndProject
25 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RulesEngineBenchmark", "benchmark\RulesEngineBenchmark\RulesEngineBenchmark.csproj", "{C058809F-C720-4EFC-925D-A486627B238B}"
26 | 	ProjectSection(ProjectDependencies) = postProject
27 | 		{CD4DFE6A-083B-478E-8377-77F474833E30} = {CD4DFE6A-083B-478E-8377-77F474833E30}
28 | 	EndProjectSection
29 | EndProject
30 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DemoApp.EFDataExample", "demo\DemoApp.EFDataExample\DemoApp.EFDataExample.csproj", "{E376D3E6-6890-4C09-9EA0-3EFD9C1E036D}"
31 | EndProject
32 | Global
33 | 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
34 | 		Debug|Any CPU = Debug|Any CPU
35 | 		Release|Any CPU = Release|Any CPU
36 | 	EndGlobalSection
37 | 	GlobalSection(ProjectConfigurationPlatforms) = postSolution
38 | 		{CD4DFE6A-083B-478E-8377-77F474833E30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
39 | 		{CD4DFE6A-083B-478E-8377-77F474833E30}.Debug|Any CPU.Build.0 = Debug|Any CPU
40 | 		{CD4DFE6A-083B-478E-8377-77F474833E30}.Release|Any CPU.ActiveCfg = Release|Any CPU
41 | 		{CD4DFE6A-083B-478E-8377-77F474833E30}.Release|Any CPU.Build.0 = Release|Any CPU
42 | 		{50E0C2A5-E2C8-4B12-8C0E-B69F698A82BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
43 | 		{50E0C2A5-E2C8-4B12-8C0E-B69F698A82BF}.Debug|Any CPU.Build.0 = Debug|Any CPU
44 | 		{50E0C2A5-E2C8-4B12-8C0E-B69F698A82BF}.Release|Any CPU.ActiveCfg = Release|Any CPU
45 | 		{50E0C2A5-E2C8-4B12-8C0E-B69F698A82BF}.Release|Any CPU.Build.0 = Release|Any CPU
46 | 		{57BB8C07-799A-4F87-A7CC-D3D3F694DD02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
47 | 		{57BB8C07-799A-4F87-A7CC-D3D3F694DD02}.Debug|Any CPU.Build.0 = Debug|Any CPU
48 | 		{57BB8C07-799A-4F87-A7CC-D3D3F694DD02}.Release|Any CPU.ActiveCfg = Release|Any CPU
49 | 		{57BB8C07-799A-4F87-A7CC-D3D3F694DD02}.Release|Any CPU.Build.0 = Release|Any CPU
50 | 		{C058809F-C720-4EFC-925D-A486627B238B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
51 | 		{C058809F-C720-4EFC-925D-A486627B238B}.Debug|Any CPU.Build.0 = Debug|Any CPU
52 | 		{C058809F-C720-4EFC-925D-A486627B238B}.Release|Any CPU.ActiveCfg = Release|Any CPU
53 | 		{C058809F-C720-4EFC-925D-A486627B238B}.Release|Any CPU.Build.0 = Release|Any CPU
54 | 		{E376D3E6-6890-4C09-9EA0-3EFD9C1E036D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
55 | 		{E376D3E6-6890-4C09-9EA0-3EFD9C1E036D}.Debug|Any CPU.Build.0 = Debug|Any CPU
56 | 		{E376D3E6-6890-4C09-9EA0-3EFD9C1E036D}.Release|Any CPU.ActiveCfg = Release|Any CPU
57 | 		{E376D3E6-6890-4C09-9EA0-3EFD9C1E036D}.Release|Any CPU.Build.0 = Release|Any CPU
58 | 	EndGlobalSection
59 | 	GlobalSection(SolutionProperties) = preSolution
60 | 		HideSolutionNode = FALSE
61 | 	EndGlobalSection
62 | 	GlobalSection(ExtensibilityGlobals) = postSolution
63 | 		SolutionGuid = {E1F2EC8E-4005-4DFE-90ED-296D4592867A}
64 | 	EndGlobalSection
65 | EndGlobal
66 | 


--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
 1 | <!-- BEGIN MICROSOFT SECURITY.MD V0.0.5 BLOCK -->
 2 | 
 3 | ## Security
 4 | 
 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
 6 | 
 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below.
 8 | 
 9 | ## Reporting Security Issues
10 | 
11 | **Please do not report security vulnerabilities through public GitHub issues.**
12 | 
13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report).
14 | 
15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com).  If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc).
16 | 
17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). 
18 | 
19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
20 | 
21 |   * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
22 |   * Full paths of source file(s) related to the manifestation of the issue
23 |   * The location of the affected source code (tag/branch/commit or direct URL)
24 |   * Any special configuration required to reproduce the issue
25 |   * Step-by-step instructions to reproduce the issue
26 |   * Proof-of-concept or exploit code (if possible)
27 |   * Impact of the issue, including how an attacker might exploit the issue
28 | 
29 | This information will help us triage your report more quickly.
30 | 
31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs.
32 | 
33 | ## Preferred Languages
34 | 
35 | We prefer all communications to be in English.
36 | 
37 | ## Policy
38 | 
39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd).
40 | 
41 | <!-- END MICROSOFT SECURITY.MD BLOCK -->


--------------------------------------------------------------------------------
/assets/BlockDiagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/RulesEngine/89f9bcd1f94febc0ccef089a293d81576d426b7f/assets/BlockDiagram.png


--------------------------------------------------------------------------------
/benchmark/RulesEngineBenchmark/Program.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | //  Licensed under the MIT License.
 3 | 
 4 | using BenchmarkDotNet.Attributes;
 5 | using BenchmarkDotNet.Running;
 6 | using RulesEngine.Models;
 7 | using System;
 8 | using System.Collections.Generic;
 9 | using System.IO;
10 | using BenchmarkDotNet.Jobs;
11 | using System.Text.Json;
12 | 
13 | namespace RulesEngineBenchmark
14 | {
15 | 
16 |     [MemoryDiagnoser]
17 |     [SimpleJob(RuntimeMoniker.Net60)]
18 |     [SimpleJob(RuntimeMoniker.Net80)]
19 |     [SimpleJob(RuntimeMoniker.Net90)]
20 |     public class REBenchmark
21 |     {
22 |         private readonly RulesEngine.RulesEngine rulesEngine;
23 |         private readonly object ruleInput;
24 |         private readonly List<Workflow> workflow;
25 | 
26 |         private class ListItem
27 |         {
28 |             public int Id { get; set; }
29 |             public string Value { get; set; }
30 |         }
31 | 
32 | 
33 |         public REBenchmark()
34 |         {
35 |             var files = Directory.GetFiles(Directory.GetCurrentDirectory(), "NestedInputDemo.json", SearchOption.AllDirectories);
36 |             if (files == null || files.Length == 0)
37 |             {
38 |                 throw new Exception("Rules not found.");
39 |             }
40 | 
41 |             var fileData = File.ReadAllText(files[0]);
42 |             workflow = JsonSerializer.Deserialize<List<Workflow>>(fileData);
43 | 
44 |             rulesEngine = new RulesEngine.RulesEngine(workflow.ToArray(), new ReSettings {
45 |                 EnableFormattedErrorMessage = false,
46 |                 EnableScopedParams = false
47 |             });
48 | 
49 |             ruleInput = new {
50 |                 SimpleProp = "simpleProp",
51 |                 NestedProp = new {
52 |                     SimpleProp = "nestedSimpleProp",
53 |                     ListProp = new List<ListItem>
54 |                     {
55 |                         new ListItem
56 |                         {
57 |                             Id = 1,
58 |                             Value = "first"
59 |                         },
60 |                         new ListItem
61 |                         {
62 |                             Id = 2,
63 |                             Value = "second"
64 |                         }
65 |                     }
66 |                 }
67 | 
68 |             };
69 |         }
70 | 
71 |         [Params(1000, 10000)]
72 |         public int N;
73 | 
74 |         [Benchmark]
75 |         public void RuleExecutionDefault()
76 |         {
77 |             foreach (var workflow in workflow)
78 |             {
79 |                 _ = rulesEngine.ExecuteAllRulesAsync(workflow.WorkflowName, ruleInput).Result;
80 |             }
81 |         }
82 |     }
83 |     public class Program
84 |     {
85 |         public static void Main(string[] args)
86 |         {
87 |             _ = BenchmarkRunner.Run<REBenchmark>();
88 |         }
89 |     }
90 | }
91 | 


--------------------------------------------------------------------------------
/benchmark/RulesEngineBenchmark/RulesEngineBenchmark.csproj:
--------------------------------------------------------------------------------
 1 | <Project Sdk="Microsoft.NET.Sdk">
 2 | 
 3 |   <PropertyGroup>
 4 |     <OutputType>Exe</OutputType>
 5 |     <TargetFrameworks>net6.0;net8.0;net9.0</TargetFrameworks>
 6 |   </PropertyGroup>
 7 | 
 8 |   <ItemGroup>
 9 |     <PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
10 |     <!--<PackageReference Include="RulesEngine" Version="3.0.2" />-->
11 |   </ItemGroup>
12 | 
13 |   <ItemGroup>
14 |     <ProjectReference Include="..\..\src\RulesEngine\RulesEngine.csproj" />
15 |   </ItemGroup>
16 | 
17 |   <ItemGroup>
18 |     <None Update="Workflows\Discount.json">
19 |       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
20 |     </None>
21 |     <None Update="Workflows\NestedInputDemo.json">
22 |       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
23 |     </None>
24 |   </ItemGroup>
25 | 
26 | </Project>
27 | 


--------------------------------------------------------------------------------
/benchmark/RulesEngineBenchmark/Workflows/Discount.json:
--------------------------------------------------------------------------------
 1 | [
 2 |   {
 3 |     "WorkflowName": "Discount",
 4 |     "Rules": [
 5 |       {
 6 |         "RuleName": "GiveDiscount10",
 7 |         "SuccessEvent": "10",
 8 |         "ErrorMessage": "One or more adjust rules failed.",
 9 |         "ErrorType": "Error",
10 |         "RuleExpressionType": "LambdaExpression",
11 |         "Expression": "input1.country == \"india\" AND input1.loyaltyFactor <= 2 AND input1.totalPurchasesToDate >= 5000 AND input2.totalOrders > 2 AND input3.noOfVisitsPerMonth > 2"
12 |       },
13 |       {
14 |         "RuleName": "GiveDiscount20",
15 |         "SuccessEvent": "20",
16 |         "ErrorMessage": "One or more adjust rules failed.",
17 |         "ErrorType": "Error",
18 |         "RuleExpressionType": "LambdaExpression",
19 |         "Expression": "input1.country == \"india\" AND input1.loyaltyFactor == 3 AND input1.totalPurchasesToDate >= 10000 AND input2.totalOrders > 2 AND input3.noOfVisitsPerMonth > 2"
20 |       },
21 |       {
22 |         "RuleName": "GiveDiscount25",
23 |         "SuccessEvent": "25",
24 |         "ErrorMessage": "One or more adjust rules failed.",
25 |         "ErrorType": "Error",
26 |         "RuleExpressionType": "LambdaExpression",
27 |         "Expression": "input1.country != \"india\" AND input1.loyaltyFactor >= 2 AND input1.totalPurchasesToDate >= 10000 AND input2.totalOrders > 2 AND input3.noOfVisitsPerMonth > 5"
28 |       },
29 |       {
30 |         "RuleName": "GiveDiscount30",
31 |         "SuccessEvent": "30",
32 |         "ErrorMessage": "One or more adjust rules failed.",
33 |         "ErrorType": "Error",
34 |         "RuleExpressionType": "LambdaExpression",
35 |         "Expression": "input1.loyaltyFactor > 3 AND input1.totalPurchasesToDate >= 50000 AND input1.totalPurchasesToDate <= 100000 AND input2.totalOrders > 5 AND input3.noOfVisitsPerMonth > 15"
36 |       },
37 |       {
38 |         "RuleName": "GiveDiscount30NestedOrExample",
39 |         "SuccessEvent": "30",
40 |         "ErrorMessage": "One or more adjust rules failed.",
41 |         "ErrorType": "Error",
42 |         "Operator": "OrElse",
43 |         "Rules":[
44 |           {
45 |             "RuleName": "IsLoyalAndHasGoodSpend",
46 |             "ErrorMessage": "One or more adjust rules failed.",
47 |             "ErrorType": "Error",
48 |             "RuleExpressionType": "LambdaExpression",
49 |             "Expression": "input1.loyaltyFactor > 3 AND input1.totalPurchasesToDate >= 50000 AND input1.totalPurchasesToDate <= 100000"
50 |           },
51 |           {
52 |             "RuleName": "OrHasHighNumberOfTotalOrders",
53 |             "ErrorMessage": "One or more adjust rules failed.",
54 |             "ErrorType": "Error",
55 |             "RuleExpressionType": "LambdaExpression",
56 |             "Expression": "input2.totalOrders > 15"
57 |           }
58 |         ]
59 |       },
60 |       {
61 |         "RuleName": "GiveDiscount35NestedAndExample",
62 |         "SuccessEvent": "35",
63 |         "ErrorMessage": "One or more adjust rules failed.",
64 |         "ErrorType": "Error",
65 |         "Operator": "AndAlso",
66 |         "Rules": [
67 |           {
68 |             "RuleName": "IsLoyal",
69 |             "ErrorMessage": "One or more adjust rules failed.",
70 |             "ErrorType": "Error",
71 |             "RuleExpressionType": "LambdaExpression",
72 |             "Expression": "input1.loyaltyFactor > 3"
73 |           },
74 |           {
75 |             "RuleName": "AndHasTotalPurchased100000",
76 |             "ErrorMessage": "One or more adjust rules failed.",
77 |             "ErrorType": "Error",
78 |             "RuleExpressionType": "LambdaExpression",
79 |             "Expression": "input1.totalPurchasesToDate >= 100000"
80 |           },
81 |           {
82 |             "RuleName": "AndOtherConditions",
83 |             "ErrorMessage": "One or more adjust rules failed.",
84 |             "ErrorType": "Error",
85 |             "RuleExpressionType": "LambdaExpression",
86 |             "Expression": "input2.totalOrders > 15 AND input3.noOfVisitsPerMonth > 25"
87 |           }
88 |         ]
89 |       }
90 |     ]
91 |   }
92 | ]


--------------------------------------------------------------------------------
/benchmark/RulesEngineBenchmark/Workflows/NestedInputDemo.json:
--------------------------------------------------------------------------------
 1 | [
 2 |   {
 3 |     "WorkflowName": "NestedInputDemoWorkflow1",
 4 |     "Rules": [
 5 |       {
 6 |         "RuleName": "CheckNestedSimpleProp",
 7 |         "ErrorMessage": "One or more adjust rules failed.",
 8 |         "ErrorType": "Error",
 9 |         "RuleExpressionType": "LambdaExpression",
10 |         "Expression": "input1.NestedProp.SimpleProp == \"nestedSimpleProp\""
11 |       }
12 |     ]
13 |   },
14 |   {
15 |     "WorkflowName": "NestedInputDemoWorkflow2",
16 |     "Rules": [
17 |       {
18 |         "RuleName": "CheckNestedListProp",
19 |         "ErrorMessage": "One or more adjust rules failed.",
20 |         "ErrorType": "Error",
21 |         "RuleExpressionType": "LambdaExpression",
22 |         "Expression": "input1.NestedProp.ListProp[0].Id == 1 && input1.NestedProp.ListProp[1].Value == \"second\""
23 |       }
24 |     ]
25 |   },
26 | 
27 |   {
28 |     "WorkflowName": "NestedInputDemoWorkflow3",
29 |     "Rules": [
30 |       {
31 |         "RuleName": "CheckNestedListPropFunctions",
32 |         "ErrorMessage": "One or more adjust rules failed.",
33 |         "ErrorType": "Error",
34 |         "RuleExpressionType": "LambdaExpression",
35 |         "Expression": "input1.NestedProp.ListProp[1].Value.ToUpper() = \"SECOND\""
36 |       }
37 |     ]
38 |   }
39 | ]


--------------------------------------------------------------------------------
/demo/DemoApp.EFDataExample/DemoApp.EFDataExample.csproj:
--------------------------------------------------------------------------------
 1 | <Project Sdk="Microsoft.NET.Sdk">
 2 | 
 3 |   <PropertyGroup>
 4 |     <TargetFrameworks>net8.0;net9.0</TargetFrameworks>
 5 |     <RootNamespace>DemoApp.EFDataExample</RootNamespace>
 6 |     <AssemblyName>DemoApp.EFDataExample</AssemblyName>
 7 |   </PropertyGroup>
 8 | 
 9 |   <ItemGroup>
10 |     <PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.1" />
11 |     <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.1" />
12 |   </ItemGroup>
13 | 
14 |   <ItemGroup>
15 |     <ProjectReference Include="..\..\src\RulesEngine\RulesEngine.csproj" />
16 |   </ItemGroup>
17 | 
18 | </Project>
19 | 


--------------------------------------------------------------------------------
/demo/DemoApp.EFDataExample/RulesEngineContext.cs:
--------------------------------------------------------------------------------
 1 | using System;
 2 | using System.Collections.Generic;
 3 | using System.Linq;
 4 | using System.Text.Json;
 5 | using Microsoft.EntityFrameworkCore;
 6 | using Microsoft.EntityFrameworkCore.ChangeTracking;
 7 | using Microsoft.EntityFrameworkCore.Metadata.Builders;
 8 | using RulesEngine.Models;
 9 | 
10 | namespace RulesEngine.Data
11 | {
12 |     public class RulesEngineContext : DbContext
13 |     {
14 |         public DbSet<Workflow> Workflows { get; set; }
15 | 
16 |         public DbSet<Rule> Rules { get; set; }
17 | 
18 |         protected override void OnModelCreating(ModelBuilder modelBuilder)
19 |         {
20 |             base.OnModelCreating(modelBuilder);
21 | 
22 |             modelBuilder.Entity<ScopedParam>()
23 |               .HasKey(k => k.Name);
24 | 
25 |             modelBuilder.Entity<Workflow>(entity => {
26 |                 entity.HasKey(k => k.WorkflowName);
27 |                 entity.Ignore(b => b.WorkflowsToInject);
28 |             });
29 | 
30 |             modelBuilder.Entity<Rule>().HasOne<Rule>().WithMany(r => r.Rules).HasForeignKey("RuleNameFK");
31 | 
32 |             var serializationOptions = new JsonSerializerOptions(JsonSerializerDefaults.General);
33 | 
34 |             modelBuilder.Entity<Rule>(entity => {
35 |                 entity.HasKey(k => k.RuleName);
36 | 
37 |                 var valueComparer = new ValueComparer<Dictionary<string, object>>(
38 |                     (c1, c2) => c1.SequenceEqual(c2),
39 |                     c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
40 |                     c => c);
41 | 
42 |                 entity.Property(b => b.Properties)
43 |                 .HasConversion(
44 |                     v => JsonSerializer.Serialize(v, serializationOptions),
45 |                     v => JsonSerializer.Deserialize<Dictionary<string, object>>(v, serializationOptions))
46 |                     .Metadata
47 |                     .SetValueComparer(valueComparer);
48 | 
49 |                 entity.Property(p => p.Actions)
50 |                 .HasConversion(
51 |                     v => JsonSerializer.Serialize(v, serializationOptions),
52 |                    v => JsonSerializer.Deserialize<RuleActions>(v, serializationOptions));
53 | 
54 |                 entity.Ignore(b => b.WorkflowsToInject);
55 |             });
56 |         }
57 |     }
58 | 
59 | }
60 | 


--------------------------------------------------------------------------------
/demo/DemoApp.EFDataExample/RulesEngineDemoContext.cs:
--------------------------------------------------------------------------------
 1 | using System;
 2 | using System.Collections.Generic;
 3 | using System.Text.Json;
 4 | using Microsoft.EntityFrameworkCore;
 5 | using RulesEngine.Data;
 6 | using RulesEngine.Models;
 7 | 
 8 | namespace DemoApp.EFDataExample
 9 | {
10 |     public class RulesEngineDemoContext : RulesEngineContext
11 |     {
12 |         public string DbPath { get; private set; }
13 | 
14 |         public RulesEngineDemoContext()
15 |         {
16 |             var folder = Environment.SpecialFolder.LocalApplicationData;
17 |             var path = Environment.GetFolderPath(folder);
18 |             DbPath = 
quot;{path}{System.IO.Path.DirectorySeparatorChar}RulesEngineDemo.db";
19 |         }
20 |         protected override void OnConfiguring(DbContextOptionsBuilder options)
21 |           => options.UseSqlite(
quot;Data Source={DbPath}");
22 | 
23 |     }
24 | 
25 | }


--------------------------------------------------------------------------------
/demo/DemoApp/BasicDemo.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | //  Licensed under the MIT License.
 3 | 
 4 | using RulesEngine.Models;
 5 | using System;
 6 | using System.Collections.Generic;
 7 | using System.Dynamic;
 8 | using static RulesEngine.Extensions.ListofRuleResultTreeExtension;
 9 | 
10 | namespace DemoApp
11 | {
12 |     public class BasicDemo
13 |     {
14 |         public void Run()
15 |         {
16 |             Console.WriteLine(
quot;Running {nameof(BasicDemo)}....");
17 |             List<Workflow> workflows = new List<Workflow>();
18 |             Workflow workflow = new Workflow();
19 |             workflow.WorkflowName = "Test Workflow Rule 1";
20 | 
21 |             List<Rule> rules = new List<Rule>();
22 | 
23 |             Rule rule = new Rule();
24 |             rule.RuleName = "Test Rule";
25 |             rule.SuccessEvent = "Count is within tolerance.";
26 |             rule.ErrorMessage = "Over expected.";
27 |             rule.Expression = "count < 3";
28 |             rule.RuleExpressionType = RuleExpressionType.LambdaExpression;
29 | 
30 |             rules.Add(rule);
31 | 
32 |             workflow.Rules = rules;
33 | 
34 |             workflows.Add(workflow);
35 | 
36 |             var bre = new RulesEngine.RulesEngine(workflows.ToArray(), null);
37 | 
38 |             dynamic datas = new ExpandoObject();
39 |             datas.count = 1;
40 |             var inputs = new dynamic[]
41 |               {
42 |                     datas
43 |               };
44 | 
45 |             List<RuleResultTree> resultList = bre.ExecuteAllRulesAsync("Test Workflow Rule 1", inputs).Result;
46 | 
47 |             bool outcome = false;
48 | 
49 |             //Different ways to show test results:
50 |             outcome = resultList.TrueForAll(r => r.IsSuccess);
51 | 
52 |             resultList.OnSuccess((eventName) => {
53 |                 Console.WriteLine(
quot;Result '{eventName}' is as expected.");
54 |                 outcome = true;
55 |             });
56 | 
57 |             resultList.OnFail(() => {
58 |                 outcome = false;
59 |             });
60 | 
61 |             Console.WriteLine(
quot;Test outcome: {outcome}.");
62 |         }
63 |     }
64 | }
65 | 


--------------------------------------------------------------------------------
/demo/DemoApp/DemoApp.csproj:
--------------------------------------------------------------------------------
 1 | <Project Sdk="Microsoft.NET.Sdk">
 2 | 
 3 |   <PropertyGroup>
 4 |     <OutputType>Exe</OutputType>
 5 |     <TargetFrameworks>net8.0;net9.0</TargetFrameworks>
 6 |     <StartupObject>DemoApp.Program</StartupObject>
 7 |   </PropertyGroup>
 8 | 
 9 |   <ItemGroup>
10 |     <ProjectReference Include="../../src/RulesEngine/RulesEngine.csproj" />
11 |     <ProjectReference Include="..\DemoApp.EFDataExample\DemoApp.EFDataExample.csproj" />
12 |   </ItemGroup>
13 | 
14 |   <ItemGroup>
15 |     <None Update="Workflows\Discount.json">
16 |       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
17 |     </None>
18 |     <None Update="Workflows\NestedInputDemo.json">
19 |       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
20 |     </None>
21 |   </ItemGroup>
22 | 
23 | </Project>
24 | 


--------------------------------------------------------------------------------
/demo/DemoApp/EFDemo.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | //  Licensed under the MIT License.
 3 | 
 4 | using DemoApp.EFDataExample;
 5 | using RulesEngine.Models;
 6 | using System;
 7 | using System.Collections.Generic;
 8 | using System.Dynamic;
 9 | using System.IO;
10 | using System.Linq;
11 | using static RulesEngine.Extensions.ListofRuleResultTreeExtension;
12 | using Microsoft.EntityFrameworkCore;
13 | 
14 | namespace DemoApp
15 | {
16 |     using System.Text.Json;
17 | 
18 |     public class EFDemo
19 |     {
20 |         public void Run()
21 |         {
22 |             Console.WriteLine(
quot;Running {nameof(EFDemo)}....");
23 |             var basicInfo = "{\"name\": \"hello\",\"email\": \"abcy@xyz.com\",\"creditHistory\": \"good\",\"country\": \"canada\",\"loyaltyFactor\": 3,\"totalPurchasesToDate\": 10000}";
24 |             var orderInfo = "{\"totalOrders\": 5,\"recurringItems\": 2}";
25 |             var telemetryInfo = "{\"noOfVisitsPerMonth\": 10,\"percentageOfBuyingToVisit\": 15}";
26 | 
27 |            dynamic input1 = JsonSerializer.Deserialize<ExpandoObject>(basicInfo);
28 |             dynamic input2 = JsonSerializer.Deserialize<ExpandoObject>(orderInfo);
29 |             dynamic input3 = JsonSerializer.Deserialize<ExpandoObject>(telemetryInfo);
30 | 
31 |             var inputs = new dynamic[]
32 |                 {
33 |                     input1,
34 |                     input2,
35 |                     input3
36 |                 };
37 | 
38 |             var files = Directory.GetFiles(Directory.GetCurrentDirectory(), "Discount.json", SearchOption.AllDirectories);
39 |             if (files == null || files.Length == 0)
40 |                 throw new Exception("Rules not found.");
41 | 
42 |             var fileData = File.ReadAllText(files[0]);
43 |             var workflow = JsonSerializer.Deserialize<List<Workflow>>(fileData);
44 | 
45 |             RulesEngineDemoContext db = new RulesEngineDemoContext();
46 |             if (db.Database.EnsureCreated())
47 |             {
48 |                 db.Workflows.AddRange(workflow);
49 |                 db.SaveChanges();
50 |             }
51 | 
52 |             var wfr = db.Workflows.Include(i => i.Rules).ThenInclude(i => i.Rules).ToArray();
53 | 
54 |             var bre = new RulesEngine.RulesEngine(wfr, null);
55 | 
56 |             string discountOffered = "No discount offered.";
57 | 
58 |             List<RuleResultTree> resultList = bre.ExecuteAllRulesAsync("Discount", inputs).Result;
59 | 
60 |             resultList.OnSuccess((eventName) => {
61 |                 discountOffered = 
quot;Discount offered is {eventName} % over MRP.";
62 |             });
63 | 
64 |             resultList.OnFail(() => {
65 |                 discountOffered = "The user is not eligible for any discount.";
66 |             });
67 | 
68 |             Console.WriteLine(discountOffered);
69 |         }
70 |     }
71 | }
72 | 


--------------------------------------------------------------------------------
/demo/DemoApp/JSONDemo.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | //  Licensed under the MIT License.
 3 | 
 4 | using RulesEngine.Models;
 5 | using System;
 6 | using System.Collections.Generic;
 7 | using System.Dynamic;
 8 | using System.IO;
 9 | using static RulesEngine.Extensions.ListofRuleResultTreeExtension;
10 | 
11 | namespace DemoApp
12 | {
13 |     using System.Text.Json;
14 | 
15 |     public class JSONDemo
16 |     {
17 |         public void Run()
18 |         {
19 |             Console.WriteLine(
quot;Running {nameof(JSONDemo)}....");
20 |             var basicInfo = "{\"name\": \"hello\",\"email\": \"abcy@xyz.com\",\"creditHistory\": \"good\",\"country\": \"canada\",\"loyaltyFactor\": 3,\"totalPurchasesToDate\": 10000}";
21 |             var orderInfo = "{\"totalOrders\": 5,\"recurringItems\": 2}";
22 |             var telemetryInfo = "{\"noOfVisitsPerMonth\": 10,\"percentageOfBuyingToVisit\": 15}";
23 | 
24 | 
25 | 
26 |             dynamic input1 = JsonSerializer.Deserialize<ExpandoObject>(basicInfo);
27 |             dynamic input2 = JsonSerializer.Deserialize<ExpandoObject>(orderInfo);
28 |             dynamic input3 = JsonSerializer.Deserialize<ExpandoObject>(telemetryInfo);
29 | 
30 |             var inputs = new dynamic[]
31 |                 {
32 |                     input1,
33 |                     input2,
34 |                     input3
35 |                 };
36 | 
37 |             var files = Directory.GetFiles(Directory.GetCurrentDirectory(), "Discount.json", SearchOption.AllDirectories);
38 |             if (files == null || files.Length == 0)
39 |                 throw new Exception("Rules not found.");
40 | 
41 |             var fileData = File.ReadAllText(files[0]);
42 |             var workflow = JsonSerializer.Deserialize<List<Workflow>>(fileData);
43 | 
44 |             var bre = new RulesEngine.RulesEngine(workflow.ToArray(), null);
45 | 
46 |             string discountOffered = "No discount offered.";
47 | 
48 |             List<RuleResultTree> resultList = bre.ExecuteAllRulesAsync("Discount", inputs).Result;
49 | 
50 |             resultList.OnSuccess((eventName) => {
51 |                 discountOffered = 
quot;Discount offered is {eventName} % over MRP.";
52 |             });
53 | 
54 |             resultList.OnFail(() => {
55 |                 discountOffered = "The user is not eligible for any discount.";
56 |             });
57 | 
58 |             Console.WriteLine(discountOffered);
59 |         }
60 |     }
61 | }
62 | 


--------------------------------------------------------------------------------
/demo/DemoApp/NestedInputDemo.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | //  Licensed under the MIT License.
 3 | 
 4 | using RulesEngine.Extensions;
 5 | using RulesEngine.Models;
 6 | using System;
 7 | using System.Collections.Generic;
 8 | using System.IO;
 9 | 
10 | namespace DemoApp
11 | {
12 |     using System.Text.Json;
13 | 
14 |     internal class ListItem
15 |     {
16 |         public int Id { get; set; }
17 |         public string Value { get; set; }
18 |     }
19 | 
20 |     public class NestedInputDemo
21 |     {
22 |         public void Run()
23 |         {
24 |             Console.WriteLine(
quot;Running {nameof(NestedInputDemo)}....");
25 |             var nestedInput = new {
26 |                 SimpleProp = "simpleProp",
27 |                 NestedProp = new {
28 |                     SimpleProp = "nestedSimpleProp",
29 |                     ListProp = new List<ListItem>
30 |                     {
31 |                         new ListItem
32 |                         {
33 |                             Id = 1,
34 |                             Value = "first"
35 |                         },
36 |                         new ListItem
37 |                         {
38 |                             Id = 2,
39 |                             Value = "second"
40 |                         }
41 |                     }
42 |                 }
43 | 
44 |             };
45 | 
46 |             var files = Directory.GetFiles(Directory.GetCurrentDirectory(), "NestedInputDemo.json", SearchOption.AllDirectories);
47 |             if (files == null || files.Length == 0)
48 |             {
49 |                 throw new Exception("Rules not found.");
50 |             }
51 | 
52 |             var fileData = File.ReadAllText(files[0]);
53 |             var Workflows = JsonSerializer.Deserialize<List<Workflow>>(fileData);
54 | 
55 |             var bre = new RulesEngine.RulesEngine(Workflows.ToArray(), null);
56 |             foreach (var workflow in Workflows)
57 |             {
58 |                 var resultList = bre.ExecuteAllRulesAsync(workflow.WorkflowName, nestedInput).Result;
59 | 
60 |                 resultList.OnSuccess((eventName) => {
61 |                     Console.WriteLine(
quot;{workflow.WorkflowName} evaluation resulted in success - {eventName}");
62 |                 }).OnFail(() => {
63 |                     Console.WriteLine(
quot;{workflow.WorkflowName} evaluation resulted in failure");
64 |                 });
65 | 
66 |             }
67 | 
68 | 
69 |         }
70 |     }
71 | }


--------------------------------------------------------------------------------
/demo/DemoApp/Program.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | // Licensed under the MIT License.
 3 | 
 4 | namespace DemoApp
 5 | {
 6 |     public static class Program
 7 |     {
 8 |         public static void Main(string[] args)
 9 |         {
10 |             new BasicDemo().Run();
11 |             new JSONDemo().Run();
12 |             new NestedInputDemo().Run();
13 |             new EFDemo().Run();
14 |         }
15 |     }
16 | }


--------------------------------------------------------------------------------
/demo/DemoApp/Workflows/Discount.json:
--------------------------------------------------------------------------------
 1 | [
 2 |   {
 3 |     "WorkflowName": "Discount",
 4 |     "Rules": [
 5 |       {
 6 |         "RuleName": "GiveDiscount10",
 7 |         "SuccessEvent": "10",
 8 |         "ErrorMessage": "One or more adjust rules failed.",
 9 |         "ErrorType": "Error",
10 |         "RuleExpressionType": "LambdaExpression",
11 |         "Expression": "input1.country == \"india\" AND input1.loyaltyFactor <= 2 AND input1.totalPurchasesToDate >= 5000 AND input2.totalOrders > 2 AND input3.noOfVisitsPerMonth > 2"
12 |       },
13 |       {
14 |         "RuleName": "GiveDiscount20",
15 |         "SuccessEvent": "20",
16 |         "ErrorMessage": "One or more adjust rules failed.",
17 |         "ErrorType": "Error",
18 |         "RuleExpressionType": "LambdaExpression",
19 |         "Expression": "input1.country == \"india\" AND input1.loyaltyFactor == 3 AND input1.totalPurchasesToDate >= 10000 AND input2.totalOrders > 2 AND input3.noOfVisitsPerMonth > 2"
20 |       },
21 |       {
22 |         "RuleName": "GiveDiscount25",
23 |         "SuccessEvent": "25",
24 |         "ErrorMessage": "One or more adjust rules failed.",
25 |         "ErrorType": "Error",
26 |         "RuleExpressionType": "LambdaExpression",
27 |         "Expression": "input1.country != \"india\" AND input1.loyaltyFactor >= 2 AND input1.totalPurchasesToDate >= 10000 AND input2.totalOrders > 2 AND input3.noOfVisitsPerMonth > 5"
28 |       },
29 |       {
30 |         "RuleName": "GiveDiscount30",
31 |         "SuccessEvent": "30",
32 |         "ErrorMessage": "One or more adjust rules failed.",
33 |         "ErrorType": "Error",
34 |         "RuleExpressionType": "LambdaExpression",
35 |         "Expression": "input1.loyaltyFactor > 3 AND input1.totalPurchasesToDate >= 50000 AND input1.totalPurchasesToDate <= 100000 AND input2.totalOrders > 5 AND input3.noOfVisitsPerMonth > 15"
36 |       },
37 |       {
38 |         "RuleName": "GiveDiscount30NestedOrExample",
39 |         "SuccessEvent": "30",
40 |         "ErrorMessage": "One or more adjust rules failed.",
41 |         "ErrorType": "Error",
42 |         "Operator": "OrElse",
43 |         "Rules":[
44 |           {
45 |             "RuleName": "IsLoyalAndHasGoodSpend",
46 |             "ErrorMessage": "One or more adjust rules failed.",
47 |             "ErrorType": "Error",
48 |             "RuleExpressionType": "LambdaExpression",
49 |             "Expression": "input1.loyaltyFactor > 3 AND input1.totalPurchasesToDate >= 50000 AND input1.totalPurchasesToDate <= 100000"
50 |           },
51 |           {
52 |             "RuleName": "OrHasHighNumberOfTotalOrders",
53 |             "ErrorMessage": "One or more adjust rules failed.",
54 |             "ErrorType": "Error",
55 |             "RuleExpressionType": "LambdaExpression",
56 |             "Expression": "input2.totalOrders > 15"
57 |           }
58 |         ]
59 |       },
60 |       {
61 |         "RuleName": "GiveDiscount35NestedAndExample",
62 |         "SuccessEvent": "35",
63 |         "ErrorMessage": "One or more adjust rules failed.",
64 |         "ErrorType": "Error",
65 |         "Operator": "AndAlso",
66 |         "Rules": [
67 |           {
68 |             "RuleName": "IsLoyal",
69 |             "ErrorMessage": "One or more adjust rules failed.",
70 |             "ErrorType": "Error",
71 |             "RuleExpressionType": "LambdaExpression",
72 |             "Expression": "input1.loyaltyFactor > 3"
73 |           },
74 |           {
75 |             "RuleName": "AndHasTotalPurchased100000",
76 |             "ErrorMessage": "One or more adjust rules failed.",
77 |             "ErrorType": "Error",
78 |             "RuleExpressionType": "LambdaExpression",
79 |             "Expression": "input1.totalPurchasesToDate >= 100000"
80 |           },
81 |           {
82 |             "RuleName": "AndOtherConditions",
83 |             "ErrorMessage": "One or more adjust rules failed.",
84 |             "ErrorType": "Error",
85 |             "RuleExpressionType": "LambdaExpression",
86 |             "Expression": "input2.totalOrders > 15 AND input3.noOfVisitsPerMonth > 25"
87 |           }
88 |         ]
89 |       }
90 |     ]
91 |   }
92 | ]


--------------------------------------------------------------------------------
/demo/DemoApp/Workflows/NestedInputDemo.json:
--------------------------------------------------------------------------------
 1 | [
 2 |   {
 3 |     "WorkflowName": "NestedInputDemoWorkflow1",
 4 |     "Rules": [
 5 |       {
 6 |         "RuleName": "CheckNestedSimpleProp",
 7 |         "ErrorMessage": "One or more adjust rules failed.",
 8 |         "ErrorType": "Error",
 9 |         "RuleExpressionType": "LambdaExpression",
10 |         "Expression": "input1.NestedProp.SimpleProp == \"nestedSimpleProp\""
11 |       }
12 |     ]
13 |   },
14 |   {
15 |     "WorkflowName": "NestedInputDemoWorkflow2",
16 |     "Rules": [
17 |       {
18 |         "RuleName": "CheckNestedListProp",
19 |         "ErrorMessage": "One or more adjust rules failed.",
20 |         "ErrorType": "Error",
21 |         "RuleExpressionType": "LambdaExpression",
22 |         "Expression": "input1.NestedProp.ListProp[0].Id == 1 && input1.NestedProp.ListProp[1].Value == \"second\""
23 |       }
24 |     ]
25 |   },
26 | 
27 |   {
28 |     "WorkflowName": "NestedInputDemoWorkflow3",
29 |     "Rules": [
30 |       {
31 |         "RuleName": "CheckNestedListPropFunctions",
32 |         "ErrorMessage": "One or more adjust rules failed.",
33 |         "ErrorType": "Error",
34 |         "RuleExpressionType": "LambdaExpression",
35 |         "Expression": "input1.NestedProp.ListProp[1].Value.ToUpper() = \"SECOND\""
36 |       }
37 |     ]
38 |   }
39 | ]


--------------------------------------------------------------------------------
/deployment/build-signed.ps1:
--------------------------------------------------------------------------------
 1 | param(
 2 |     [Parameter(Mandatory)]
 3 |     [string] $csprojFilePath,
 4 |     [Parameter(Mandatory)]
 5 |     [string] $signingKey
 6 | )
 7 | 
 8 | # sign and build the project
 9 | $directory = Split-Path $csprojFilePath;
10 | $signKeyFile = Join-Path $directory "signKey.snk";
11 | 
12 | $bytes = [Convert]::FromBase64String($signingKey)
13 | [IO.File]::WriteAllBytes($signKeyFile, $bytes)
14 | 
15 | dotnet build $csprojFilePath -c Release -p:ContinuousIntegrationBuild=true -p:DelaySign=false -p:AssemblyOriginatorKeyFile=$signKeyFile 


--------------------------------------------------------------------------------
/docs/Home.md:
--------------------------------------------------------------------------------
 1 | ## Welcome
 2 | Welcome to the RulesEngine Wiki!
 3 | 
 4 | The pages here are primarily intended for those who wish to contribute to the Rules Engine Project by suggesting new features or building extensions or submitting pull requests. 
 5 | 
 6 | This Wiki also includes a demo along with the explanation of different features of the project so that the using of the application can be easily understood. 
 7 | 
 8 | ## About
 9 | Deep dive into the project code and the Wiki to find different features and workings of the project. 
10 | 
11 | Search for the solution or file a new issue in [GitHub](https://github.com/microsoft/RulesEngine/issues) if you find something broken in the code.
12 | 
13 | ## Contributing
14 | This project welcomes contributions and suggestions.  Most contributions require you to agree to a
15 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
16 | the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
17 | 
18 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide
19 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions
20 | provided by the bot. You will only need to do this once across all repos using our CLA.
21 | 
22 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
23 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
24 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
25 | 


--------------------------------------------------------------------------------
/docs/Introduction.md:
--------------------------------------------------------------------------------
 1 | ## What is the Rules Engine
 2 | While building any application, the crux or the core part of it is always business logic or business rules. And as with any application, there always comes a time when some or a lot of the rules or policies change in the system. But with that change, comes a lot of rework like changing design or creating a new module altogether to code in the changes in the rules, regression testing, performance testing etc. The rework along with debugging if required amounts to a lot of unnecessary work which can otherwise be utilized for other work, thus reducing the engineering cycle by drastic amounts.  
 3 | 
 4 | In this library, we have abstracted the rules so that the core logic is always maintained while the rules change can happen in an easy way without changing the code base. Also, the input to the system is dynamic in nature so the model need not be defined in the system. It can be sent as an expando object or any other typed object and the system will be able to handle it. 
 5 | 
 6 | These all features make this library highly configurable and extensible as shown in [Getting Started with Rules Engine](https://github.com/microsoft/RulesEngine/wiki/Getting-Started).
 7 | 
 8 | 
 9 | ### How it works
10 | 
11 | ![](https://github.com/microsoft/RulesEngine/blob/main/assets/BlockDiagram.png)
12 | 
13 | Here. there are multiple actors/component involved.
14 | ##### Rules Engine
15 | This component is the Rules Engine library/NuGet package being referenced by the developer.
16 | ##### Rules Store
17 | As shown in [Rules Schema](https://github.com/microsoft/RulesEngine/wiki/Getting-Started#rules-schema), we need rules in a particular format for the library to work. While the rules structure is rigid, the data itself can be stored in any component and can be accessed in any form the developer chooses. It can be stored in the form of json files in file structure or blob, or as documents in cosmos db, or any database, azure app configuration or any other place the developer thinks is going to be appropriate based on the project requirements. 
18 | ##### Input
19 | The input(s) for the system can be taken from any place as well like user input, blobs, databases, service bus or any other system. 
20 | ##### Wrapper
21 | This library sits as a black box outside the project as a referenced project or NuGet package. Then the user can create a wrapper around the library, which will get the rules from the rules store and convert it into the WorkFlowRules structure and send it to the RulesEngine along with the input(s). The RulesEngine then computes and give the information to the wrapper and the wrapper can then do whatever the logic demands with the output information.
22 | 
23 | 


--------------------------------------------------------------------------------
/docs/Use-Case.md:
--------------------------------------------------------------------------------
 1 | ## Use Case
 2 | The use case for demo purposes used here is explained as follows. The system we are designing is an e-commerce discount calculation system. 
 3 | 
 4 | ### Rules
 5 | The rules for the discount calculation are –
 6 | 
 7 | 1.	Give the user a discount of 10% over MRP if the following conditions are followed – 
 8 |     * The user’s country is India.
 9 |     * The user’s loyalty factor is less than or equal to 2.
10 |     * All the orders purchased by the user so far should amount to more than 5,000.
11 |     * User should have at least made more than two successful orders. 
12 |     * The user should have visited the site more than two times every month.
13 | 2.	Give the user a discount of 20% over MRP if the following conditions are followed – 
14 |     * The user’s country is India.
15 |     * The user’s loyalty factor is equal to 3.
16 |     * All the orders purchased by the user so far should amount to more than 10,000.
17 |     * User should have at least made more than two successful orders. 
18 |     * The user should have visited the site more than two times every month.
19 | 3.	Give the user a discount of 25% over MRP if the following conditions are followed – 
20 |     * The user’s country is not India.
21 |     * The user’s loyalty factor is greater than or equal to 2.
22 |     * All the orders purchased by the user so far should amount to more than 10,000.
23 |     * User should have at least made more than two successful orders. 
24 |     * The user should have visited the site more than five times every month.
25 | 4.	Give the user a discount of 30% over MRP if the following conditions are followed – 
26 |     * The user’s loyalty factor is greater than 3.
27 |     * All the orders purchased by the user so far should amount to more than 50,000 but less than 100,000.
28 |     * User should have at least made more than five successful orders. 
29 |     * The user should have visited the site more than fifteen times every month.
30 | 5.	Give the user a discount of 30% over MRP if the following conditions are followed – 
31 |     * The user’s loyalty factor is greater than 3.
32 |     * All the orders purchased by the user so far should amount to more than 100,000.
33 |     * User should have at least made more than fifteen successful orders. 
34 |     * The user should have visited the site more than 25 times every month.
35 | 6.	Give 0% discount in any other case.
36 | 
37 | ### Inputs
38 | Here the inputs will be of three different types as they are coming from three different data sources/APIs. 
39 | #### User Basic Info
40 | This input has information like –
41 | * Name
42 | * Country
43 | * Email
44 | * Credit history
45 | * Loyalty factor
46 | * Sum of the purchases made by the user till date.
47 | 
48 | #### Users Order Information
49 | This input is a summarization of the orders made by the user so far. This input has information like – 
50 | * Total number of orders
51 | * Recurring items in those orders if any
52 | 
53 | #### Users Telemetry Information
54 | This input is a summarization of the telemetry information collected based on the user’s visit to the site. This input has information like – 
55 | * Number of visits to the site per month
56 | * Percentage of the number of times the user purchased something to the number of times the user visited
57 | 


--------------------------------------------------------------------------------
/docs/_Sidebar.md:
--------------------------------------------------------------------------------
 1 | [Home](https://github.com/microsoft/RulesEngine/wiki)
 2 | * [Welcome](https://github.com/microsoft/RulesEngine/wiki#welcome)
 3 | * [About](https://github.com/microsoft/RulesEngine/wiki#about)
 4 | * [Contributing](https://github.com/microsoft/RulesEngine/wiki#contributing)
 5 | 
 6 | [Introduction](https://github.com/microsoft/RulesEngine/wiki/Introduction)
 7 | * [What is the Rules Engine](https://github.com/microsoft/RulesEngine/wiki/Introduction#what-is-the-rules-engine)
 8 |   * [How it works](https://github.com/microsoft/RulesEngine/wiki/Introduction#how-it-works) 
 9 |     * [Rules Engine](https://github.com/microsoft/RulesEngine/wiki/Introduction#rules-engine)
10 |     * [Rules Store](https://github.com/microsoft/RulesEngine/wiki/Introduction#rules-store)
11 |     * [Input](https://github.com/microsoft/RulesEngine/wiki/Introduction#input)
12 |     * [Wrapper](https://github.com/microsoft/RulesEngine/wiki/Introduction#wrapper)
13 | 
14 | [Getting Started](https://github.com/microsoft/RulesEngine/wiki/Getting-Started) 
15 | * [Getting Started with Rules Engine](https://github.com/microsoft/RulesEngine/wiki/Getting-Started#getting-started-with-rules-engine)
16 |   * [Publicly accessible interfaces, models, methods and schemas](https://github.com/microsoft/RulesEngine/wiki/Getting-Started#publicly-accessible-interfaces-models-methods-and-schemas) 
17 |     * [Rules](https://github.com/microsoft/RulesEngine/wiki/Getting-Started#rules)
18 |     * [Rules Schema](https://github.com/microsoft/RulesEngine/wiki/Getting-Started#rules-schema)
19 |     * [Logger](https://github.com/microsoft/RulesEngine/wiki/Getting-Started#logger)
20 |     * [ReSettings](https://github.com/microsoft/RulesEngine/wiki/Getting-Started#resettings)
21 |     * [LocalParams](https://github.com/microsoft/RulesEngine/wiki/Getting-Started#localparams)
22 |     * [RuleParameter](https://github.com/microsoft/RulesEngine/wiki/Getting-Started#ruleparameter)
23 |     * [RuleResultTree](https://github.com/microsoft/RulesEngine/wiki/Getting-Started#ruleresulttree)
24 |     * [IRulesEngine](https://github.com/microsoft/RulesEngine/wiki/Getting-Started#irulesengine)
25 |     * [Initiating the Rules Engine](https://github.com/microsoft/RulesEngine/wiki/Getting-Started#initiating-the-rules-engine)
26 |     * [Success/Failure](https://github.com/microsoft/RulesEngine/wiki/Getting-Started#successfailure)
27 |   * [How to use Rules Engine](https://github.com/microsoft/RulesEngine/wiki/Getting-Started#how-to-use-rules-engine) 
28 | 
29 | [Use Case](https://github.com/microsoft/RulesEngine/wiki/Use-Case)
30 | * [Use Case](https://github.com/microsoft/RulesEngine/wiki/Use-Case#use-case)
31 |   * [Rules](https://github.com/microsoft/RulesEngine/wiki/Use-Case#rules)
32 |   * [Inputs](https://github.com/microsoft/RulesEngine/wiki/Use-Case#inputs)
33 |     * [User Basic Info](https://github.com/microsoft/RulesEngine/wiki/Use-Case#user-basic-info)
34 |     * [Users Order Information](https://github.com/microsoft/RulesEngine/wiki/Use-Case#users-order-information)
35 |     * [Users Telemetry Information](https://github.com/microsoft/RulesEngine/wiki/Use-Case#users-telemetry-information)


--------------------------------------------------------------------------------
/docs/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-cayman


--------------------------------------------------------------------------------
/global.json:
--------------------------------------------------------------------------------
1 | {
2 |   "sdk": {
3 |     "version": "9.0.301",
4 |     "rollForward": "latestFeature",
5 |     "allowPrerelease": false
6 |   }
7 | }


--------------------------------------------------------------------------------
/schema/workflow-list-schema.json:
--------------------------------------------------------------------------------
1 | {
2 |   "$schema": "http://json-schema.org/draft-07/schema#",
3 |   "type": "array",
4 |   "items": {
5 |     "$ref": "https://raw.githubusercontent.com/microsoft/RulesEngine/main/schema/workflow-schema.json"
6 |   }
7 | }


--------------------------------------------------------------------------------
/schema/workflow-schema.json:
--------------------------------------------------------------------------------
  1 | {
  2 |   "$schema": "http://json-schema.org/draft-07/schema#",
  3 |   "definitions": {
  4 |     "ScopedParam": {
  5 |       "type": "object",
  6 |       "properties": {
  7 |         "Name": { "type": "string" },
  8 |         "Expression": { "type": "string" }
  9 |       },
 10 |       "required": [ "Name", "Expression" ]
 11 |     },
 12 |     "Rule": {
 13 |       "title": "Rule",
 14 |       "properties": {
 15 |         "RuleName": {
 16 |           "type": "string"
 17 |         },
 18 |         "LocalParams": {
 19 |           "type": "array",
 20 |           "items": { "$ref": "#/definitions/ScopedParam" }
 21 |         },
 22 |         "Operator": {
 23 |           "enum": [
 24 |             "And",
 25 |             "AndAlso",
 26 |             "Or",
 27 |             "OrElse"
 28 |           ]
 29 |         },
 30 |         "ErrorMessage": {
 31 |           "type": "string"
 32 |         },
 33 |         "SuccessEvent": {
 34 |           "type": "string"
 35 |         },
 36 |         "Rules": {
 37 |           "type": "array",
 38 |           "items": {
 39 |             "anyOf": [
 40 |               {
 41 |                 "$ref": "#/definitions/LeafRule"
 42 |               },
 43 |               {
 44 |                 "$ref": "#/definitions/Rule"
 45 |               }
 46 |             ]
 47 |           }
 48 |         },
 49 |         "Properties": {
 50 |           "type": "object"
 51 |         },
 52 |         "Actions": {
 53 |           "$ref": "#/definitions/RuleActions"
 54 |         },
 55 |         "Enabled": {
 56 |           "type": "boolean",
 57 |           "default": true
 58 |         }
 59 |       },
 60 |       "required": [
 61 |         "RuleName",
 62 |         "Operator",
 63 |         "Rules"
 64 |       ],
 65 |       "type": "object"
 66 |     },
 67 |     "LeafRule": {
 68 |       "title": "Leaf Rule",
 69 |       "type": "object",
 70 |       "required": [
 71 |         "RuleName",
 72 |         "Expression"
 73 |       ],
 74 |       "properties": {
 75 |         "RuleName": {
 76 |           "type": "string"
 77 |         },
 78 |         "LocalParams": {
 79 |           "type": "array",
 80 |           "items": { "$ref": "#/definitions/ScopedParam" }
 81 |         },
 82 |         "Expression": {
 83 |           "type": "string"
 84 |         },
 85 |         "RuleExpressionType": {
 86 |           "enum": [
 87 |             "LambdaExpression"
 88 |           ]
 89 |         },
 90 |         "ErrorMessage": {
 91 |           "type": "string"
 92 |         },
 93 |         "SuccessEvent": {
 94 |           "type": "string"
 95 |         },
 96 |         "Properties": {
 97 |           "type": "object"
 98 |         },
 99 |         "Actions": {
100 |           "$ref": "#/definitions/RuleActions"
101 |         },
102 |         "Enabled": {
103 |           "type": "boolean",
104 |           "default": true
105 |         }
106 |       }
107 |     },
108 |     "ActionInfo": {
109 |       "properties": {
110 |         "Name": {
111 |           "type": "string"
112 |         },
113 |         "Context": {
114 |           "type": "object"
115 |         }
116 |       },
117 |       "required": [
118 |         "Name"
119 |       ]
120 |     },
121 |     "RuleActions": {
122 |       "properties": {
123 |         "OnSuccess": {
124 |           "$ref": "#/definitions/ActionInfo"
125 |         },
126 |         "OnFailure": {
127 |           "$ref": "#/definitions/ActionInfo"
128 |         }
129 |       }
130 |     }
131 | 
132 |   },
133 |   "properties": {
134 |     "WorkflowName": {
135 |       "type": "string"
136 |     },
137 |     "WorkflowsToInject": {
138 |       "type": "array",
139 |       "items": { "type": "string" }
140 |     },
141 |     "GlobalParams": {
142 |       "type": "array",
143 |       "items": { "$ref": "#/definitions/ScopedParam" }
144 |     },
145 |     "Rules": {
146 |       "type": "array",
147 |       "items": {
148 |         "anyOf": [
149 |           {
150 |             "$ref": "#/definitions/LeafRule"
151 |           },
152 |           {
153 |             "$ref": "#/definitions/Rule"
154 |           }
155 |         ]
156 |       }
157 |     }
158 |   },
159 |   "required": [
160 |     "WorkflowName",
161 |     "Rules"
162 |   ],
163 |   "type": "object"
164 | }
165 | 


--------------------------------------------------------------------------------
/scripts/check-coverage.ps1:
--------------------------------------------------------------------------------
 1 | param(
 2 |     [Parameter(Mandatory=$true)][string] $reportPath,
 3 |     [Parameter(Mandatory=$true)][decimal] $threshold
 4 | )
 5 | 
 6 | 
 7 | [XML]$report = Get-Content $reportPath;
 8 | [decimal]$coverage = [decimal]$report.coverage.'line-rate' * 100;
 9 | 
10 | if ($coverage -lt $threshold) {
11 |   Write-Error "Coverage($coverage) is less than $threshold percent"
12 |   exit 1
13 | }
14 | else{
15 |     Write-Host "Coverage($coverage) is more than $threshold percent"
16 | }
17 | 


--------------------------------------------------------------------------------
/scripts/generate-coverage-report.ps1:
--------------------------------------------------------------------------------
1 | dotnet tool restore
2 | dotnet reportgenerator "-reports:**/coverage.cobertura.xml" "-targetdir:coveragereport" -reporttypes:"Html;lcov;Cobertura"


--------------------------------------------------------------------------------
/signing/RulesEngine-publicKey.snk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/RulesEngine/89f9bcd1f94febc0ccef089a293d81576d426b7f/signing/RulesEngine-publicKey.snk


--------------------------------------------------------------------------------
/src/RulesEngine/Actions/ActionBase.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | //  Licensed under the MIT License.
 3 | 
 4 | using RulesEngine.Models;
 5 | using System;
 6 | using System.Collections.Generic;
 7 | using System.Threading.Tasks;
 8 | 
 9 | namespace RulesEngine.Actions
10 | {
11 |     public abstract class ActionBase
12 |     {
13 |         internal async virtual ValueTask<ActionRuleResult> ExecuteAndReturnResultAsync(ActionContext context, RuleParameter[] ruleParameters, bool includeRuleResults = false)
14 |         {
15 |             var result = new ActionRuleResult();
16 |             try
17 |             {
18 |                 result.Output = await Run(context, ruleParameters);
19 |             }
20 |             catch (Exception ex)
21 |             {
22 |                 result.Exception = new Exception(
quot;Exception while executing {GetType().Name}: {ex.Message}", ex);
23 |             }
24 |             finally
25 |             {
26 |                 if (includeRuleResults)
27 |                 {
28 |                     result.Results = new List<RuleResultTree>()
29 |                     {
30 |                         context.GetParentRuleResult()
31 |                     };
32 |                 }
33 |             }
34 |             return result;
35 |         }
36 |         public abstract ValueTask<object> Run(ActionContext context, RuleParameter[] ruleParameters);
37 |     }
38 | }
39 | 


--------------------------------------------------------------------------------
/src/RulesEngine/Actions/ActionContext.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | //  Licensed under the MIT License.
 3 | 
 4 | using RulesEngine.Models;
 5 | using System;
 6 | using System.Collections.Generic;
 7 | 
 8 | namespace RulesEngine.Actions
 9 | {
10 |     using System.Text.Json;
11 | 
12 |     public class ActionContext
13 |     {
14 |         private readonly IDictionary<string, string> _context;
15 |         private readonly RuleResultTree _parentResult;
16 | 
17 |         public ActionContext(IDictionary<string, object> context, RuleResultTree parentResult)
18 |         {
19 |             _context = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
20 |             foreach (var kv in context)
21 |             {
22 |                 string key = kv.Key;
23 |                 string value;
24 |                 switch (kv.Value.GetType().Name)
25 |                 {
26 |                     case "String":
27 |                     case "JsonElement":
28 |                         value =  kv.Value.ToString();
29 |                         break;
30 |                     default:
31 |                         value = JsonSerializer.Serialize(kv.Value);
32 |                         break;
33 | 
34 |                 }
35 |                 _context.Add(key, value);
36 |             }
37 |             _parentResult = parentResult;
38 |         }
39 | 
40 |         public RuleResultTree GetParentRuleResult()
41 |         {
42 |             return _parentResult;
43 |         }
44 | 
45 |         public bool TryGetContext<T>(string name,out T output)
46 |         {
47 |             try
48 |             {
49 |                 //key not found return
50 |                 //Returning a KeyNotFoundException has a significant impact on performance.
51 |                 if (!_context.ContainsKey(name))
52 |                 {
53 |                     output = default(T);
54 |                     return false;
55 |                 }
56 |                 output =  GetContext<T>(name);
57 |                 return true;
58 |             }
59 |             catch(ArgumentException)
60 |             {
61 |                 output = default(T);
62 |                 return false;
63 |             }
64 |         }
65 | 
66 |         public T GetContext<T>(string name)
67 |         {
68 |             try
69 |             {
70 |                 if (typeof(T) == typeof(string))
71 |                 {
72 |                     return (T)Convert.ChangeType(_context[name], typeof(T));
73 |                 }
74 |                 return JsonSerializer.Deserialize<T>(_context[name]);
75 |             }
76 |             catch (KeyNotFoundException)
77 |             {
78 |                 throw new ArgumentException(
quot;Argument `{name}` was not found in the action context");
79 |             }
80 |             catch (JsonException)
81 |             {
82 |                 throw new ArgumentException(
quot;Failed to convert argument `{name}` to type `{typeof(T).Name}` in the action context");
83 |             }
84 |         }
85 |     }
86 | }
87 | 


--------------------------------------------------------------------------------
/src/RulesEngine/Actions/ActionFactory.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | //  Licensed under the MIT License.
 3 | 
 4 | using System;
 5 | using System.Collections.Generic;
 6 | 
 7 | namespace RulesEngine.Actions
 8 | {
 9 |     internal class ActionFactory
10 |     {
11 |         private readonly IDictionary<string, Func<ActionBase>> _actionRegistry;
12 | 
13 |         internal ActionFactory()
14 |         {
15 |             _actionRegistry = new Dictionary<string, Func<ActionBase>>(StringComparer.OrdinalIgnoreCase);
16 |         }
17 |         internal ActionFactory(IDictionary<string, Func<ActionBase>> actionRegistry) : this()
18 |         {
19 |             foreach (var kv in actionRegistry)
20 |             {
21 |                 _actionRegistry.Add(kv.Key, kv.Value);
22 |             }
23 |         }
24 | 
25 |         internal ActionBase Get(string name)
26 |         {
27 |             if (_actionRegistry.ContainsKey(name))
28 |             {
29 |                 return _actionRegistry[name]();
30 |             }
31 |             throw new KeyNotFoundException(
quot;Action with name: {name} does not exist");
32 |         }
33 |     }
34 | }
35 | 


--------------------------------------------------------------------------------
/src/RulesEngine/Actions/EvaluateRuleAction.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | //  Licensed under the MIT License.
 3 | 
 4 | using RulesEngine.ExpressionBuilders;
 5 | using RulesEngine.Models;
 6 | using System;
 7 | using System.Collections.Generic;
 8 | using System.Linq;
 9 | using System.Threading.Tasks;
10 | 
11 | namespace RulesEngine.Actions
12 | {
13 |     public class EvaluateRuleAction : ActionBase
14 |     {
15 |         private readonly RulesEngine _ruleEngine;
16 |         private readonly RuleExpressionParser _ruleExpressionParser;
17 | 
18 |         public EvaluateRuleAction(RulesEngine ruleEngine, RuleExpressionParser ruleExpressionParser)
19 |         {
20 |             _ruleEngine = ruleEngine;
21 |             _ruleExpressionParser = ruleExpressionParser;
22 |         }
23 | 
24 |         internal async override ValueTask<ActionRuleResult> ExecuteAndReturnResultAsync(ActionContext context, RuleParameter[] ruleParameters, bool includeRuleResults = false)
25 |         {
26 |             var innerResult = await base.ExecuteAndReturnResultAsync(context, ruleParameters, includeRuleResults);
27 |             var output = innerResult.Output as ActionRuleResult;
28 |             List<RuleResultTree> resultList = null;
29 |             if (includeRuleResults)
30 |             {
31 |                 resultList = new List<RuleResultTree>(output?.Results ?? new List<RuleResultTree>() { });
32 |                 resultList.AddRange(innerResult.Results);
33 |             }
34 |             return new ActionRuleResult {
35 |                 Output = output?.Output,
36 |                 Exception = innerResult.Exception,
37 |                 Results = resultList
38 |             };
39 |         }
40 | 
41 |         public async override ValueTask<object> Run(ActionContext context, RuleParameter[] ruleParameters)
42 |         {
43 |             var workflowName = context.GetContext<string>("workflowName");
44 |             var ruleName = context.GetContext<string>("ruleName");
45 |             var filteredRuleParameters = new List<RuleParameter>(ruleParameters);
46 |             if(context.TryGetContext<List<string>>("inputFilter",out var inputFilter))
47 |             {
48 |                 filteredRuleParameters = ruleParameters.Where(c => inputFilter.Contains(c.Name)).ToList();
49 |             }
50 |             if (context.TryGetContext<List<ScopedParam>>("additionalInputs", out var additionalInputs))
51 |             {
52 |                 foreach(var additionalInput in additionalInputs)
53 |                 {
54 |                     dynamic value = _ruleExpressionParser.Evaluate<object>(additionalInput.Expression, ruleParameters);
55 |                     filteredRuleParameters.Add(new RuleParameter(additionalInput.Name, value));
56 |                     
57 |                 }
58 |             }
59 |    
60 |             var ruleResult = await _ruleEngine.ExecuteActionWorkflowAsync(workflowName, ruleName, filteredRuleParameters.ToArray());
61 |             return ruleResult;
62 |         }
63 |     }
64 | }
65 | 


--------------------------------------------------------------------------------
/src/RulesEngine/Actions/ExpressionOutputAction.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | //  Licensed under the MIT License.
 3 | 
 4 | using RulesEngine.ExpressionBuilders;
 5 | using RulesEngine.Models;
 6 | using System.Threading.Tasks;
 7 | 
 8 | namespace RulesEngine.Actions
 9 | {
10 |     public class OutputExpressionAction : ActionBase
11 |     {
12 |         private readonly RuleExpressionParser _ruleExpressionParser;
13 | 
14 |         public OutputExpressionAction(RuleExpressionParser ruleExpressionParser)
15 |         {
16 |             _ruleExpressionParser = ruleExpressionParser;
17 |         }
18 | 
19 |         public override ValueTask<object> Run(ActionContext context, RuleParameter[] ruleParameters)
20 |         {
21 |             var expression = context.GetContext<string>("expression");
22 |             return new ValueTask<object>(_ruleExpressionParser.Evaluate<object>(expression, ruleParameters));
23 |         }
24 |     }
25 | }
26 | 


--------------------------------------------------------------------------------
/src/RulesEngine/CustomTypeProvider.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | // Licensed under the MIT License.
 3 | 
 4 | using RulesEngine.HelperFunctions;
 5 | using System;
 6 | using System.Collections;
 7 | using System.Collections.Generic;
 8 | using System.Linq;
 9 | using System.Linq.Dynamic.Core;
10 | using System.Linq.Dynamic.Core.CustomTypeProviders;
11 | 
12 | namespace RulesEngine
13 | {
14 |     public class CustomTypeProvider : DefaultDynamicLinqCustomTypeProvider
15 |     {
16 |         private readonly HashSet<Type> _types;
17 | 
18 |         public CustomTypeProvider(Type[] types) : base(ParsingConfig.Default)
19 |         {
20 |             _types = new HashSet<Type>(types ?? Array.Empty<Type>());
21 | 
22 |             _types.Add(typeof(ExpressionUtils));
23 | 
24 |             _types.Add(typeof(Enumerable));
25 | 
26 |             var queue = new Queue<Type>(_types);
27 |             while (queue.Count > 0)
28 |             {
29 |                 var t = queue.Dequeue();
30 | 
31 |                 var baseType = t.BaseType;
32 |                 if (baseType != null && _types.Add(baseType))
33 |                     queue.Enqueue(baseType);
34 | 
35 |                 foreach (var interfaceType in t.GetInterfaces())
36 |                 {
37 |                     if (_types.Add(interfaceType))
38 |                         queue.Enqueue(interfaceType);
39 |                 }
40 |             }
41 | 
42 |             _types.Add(typeof(IEnumerable));
43 |         }
44 | 
45 |         public override HashSet<Type> GetCustomTypes()
46 |         {
47 |             var all = new HashSet<Type>(base.GetCustomTypes());
48 |             all.UnionWith(_types);
49 |             return all;
50 |         }
51 |     }
52 | }
53 | 


--------------------------------------------------------------------------------
/src/RulesEngine/Exceptions/ExpressionParserException.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | // Licensed under the MIT License.
 3 | 
 4 | using System;
 5 | using System.Collections.Generic;
 6 | using System.Text;
 7 | 
 8 | namespace RulesEngine.Exceptions
 9 | {
10 |     public class ExpressionParserException: Exception
11 |     {
12 |         public ExpressionParserException(string message, string expression) : base(message)
13 |         {
14 |             Data.Add("Expression", expression);
15 |         }
16 |     }
17 | }
18 | 


--------------------------------------------------------------------------------
/src/RulesEngine/Exceptions/RuleException.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | // Licensed under the MIT License.
 3 | 
 4 | using System;
 5 | using System.Collections.Generic;
 6 | using System.Text;
 7 | 
 8 | namespace RulesEngine.Exceptions
 9 | {
10 |     public class RuleException : Exception
11 |     {
12 |         public RuleException(string message) : base(message)
13 |         {
14 |         }
15 | 
16 |         public RuleException(string message, Exception innerException) : base(message, innerException)
17 |         {
18 |         }
19 |     }
20 | }
21 | 


--------------------------------------------------------------------------------
/src/RulesEngine/Exceptions/RuleValidationException.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | // Licensed under the MIT License.
 3 | 
 4 | using FluentValidation;
 5 | using FluentValidation.Results;
 6 | using System.Collections.Generic;
 7 | 
 8 | namespace RulesEngine.Exceptions
 9 | {
10 |     public class RuleValidationException : ValidationException
11 |     {
12 |         public RuleValidationException(string message, IEnumerable<ValidationFailure> errors) : base(message, errors)
13 |         {
14 |         }
15 |     }
16 | }
17 | 


--------------------------------------------------------------------------------
/src/RulesEngine/Exceptions/ScopedParamException.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | // Licensed under the MIT License.
 3 | 
 4 | using System;
 5 | using System.Collections.Generic;
 6 | using System.Text;
 7 | 
 8 | namespace RulesEngine.Exceptions
 9 | {
10 |     public class ScopedParamException: Exception
11 |     {
12 |         public ScopedParamException(string message, Exception innerException, string scopedParamName): base(message,innerException)
13 |         {
14 |             Data.Add("ScopedParamName", scopedParamName);
15 |         }
16 |     }
17 | }
18 | 


--------------------------------------------------------------------------------
/src/RulesEngine/ExpressionBuilders/LambdaExpressionBuilder.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | // Licensed under the MIT License.
 3 | 
 4 | using RulesEngine.Exceptions;
 5 | using RulesEngine.HelperFunctions;
 6 | using RulesEngine.Models;
 7 | using System;
 8 | using System.Collections.Generic;
 9 | using System.Linq;
10 | using System.Linq.Dynamic.Core.Exceptions;
11 | using System.Linq.Expressions;
12 | 
13 | namespace RulesEngine.ExpressionBuilders
14 | {
15 |     internal sealed class LambdaExpressionBuilder : RuleExpressionBuilderBase
16 |     {
17 |         private readonly ReSettings _reSettings;
18 |         private readonly RuleExpressionParser _ruleExpressionParser;
19 | 
20 |         internal LambdaExpressionBuilder(ReSettings reSettings, RuleExpressionParser ruleExpressionParser)
21 |         {
22 |             _reSettings = reSettings;
23 |             _ruleExpressionParser = ruleExpressionParser;
24 |         }
25 | 
26 |         internal override RuleFunc<RuleResultTree> BuildDelegateForRule(Rule rule, RuleParameter[] ruleParams)
27 |         {
28 |             try
29 |             {
30 |                 var ruleDelegate = _ruleExpressionParser.Compile<bool>(rule.Expression, ruleParams);
31 |                 return Helpers.ToResultTree(_reSettings, rule, null, ruleDelegate);
32 |             }
33 |             catch (Exception ex)
34 |             {
35 |                 Helpers.HandleRuleException(ex,rule,_reSettings);
36 | 
37 |                 var exceptionMessage = Helpers.GetExceptionMessage(
quot;Exception while parsing expression `{rule?.Expression}` - {ex.Message}",
38 |                                                                     _reSettings);
39 | 
40 |                 bool func(object[] param) => false;
41 |                 
42 |                 return Helpers.ToResultTree(_reSettings, rule, null,func, exceptionMessage);
43 |             }
44 |         }
45 | 
46 |         internal override Expression Parse(string expression, ParameterExpression[] parameters, Type returnType)
47 |         {
48 |             try
49 |             {
50 |                 return _ruleExpressionParser.Parse(expression, parameters, returnType);
51 |             }
52 |             catch(ParseException ex)
53 |             {
54 |                 throw new ExpressionParserException(ex.Message, expression);
55 |             }
56 |             
57 |         }
58 | 
59 |         internal override Func<object[],Dictionary<string,object>> CompileScopedParams(RuleParameter[] ruleParameters, RuleExpressionParameter[] scopedParameters)
60 |         {
61 |             return _ruleExpressionParser.CompileRuleExpressionParameters(ruleParameters, scopedParameters);
62 |         }
63 |     }
64 | }
65 | 


--------------------------------------------------------------------------------
/src/RulesEngine/ExpressionBuilders/RuleExpressionBuilderBase.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | // Licensed under the MIT License.
 3 | 
 4 | using RulesEngine.Models;
 5 | using System;
 6 | using System.Collections.Generic;
 7 | using System.Linq.Expressions;
 8 | 
 9 | namespace RulesEngine.ExpressionBuilders
10 | {
11 |     /// <summary>
12 |     /// Base class for expression builders
13 |     /// </summary>
14 |     internal abstract class RuleExpressionBuilderBase
15 |     {
16 |         /// <summary>
17 |         /// Builds the expression for rule.
18 |         /// </summary>
19 |         /// <param name="rule">The rule.</param>
20 |         /// <param name="typeParamExpressions">The type parameter expressions.</param>
21 |         /// <param name="ruleInputExp">The rule input exp.</param>
22 |         /// <returns>Expression type</returns>
23 |         internal abstract RuleFunc<RuleResultTree> BuildDelegateForRule(Rule rule, RuleParameter[] ruleParams);
24 | 
25 |         internal abstract Expression Parse(string expression, ParameterExpression[] parameters, Type returnType);
26 | 
27 |         internal abstract Func<object[], Dictionary<string, object>> CompileScopedParams(RuleParameter[] ruleParameters, RuleExpressionParameter[] scopedParameters);
28 |     }
29 | }
30 | 


--------------------------------------------------------------------------------
/src/RulesEngine/Extensions/EnumerableExtensions.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | // Licensed under the MIT License.
 3 | 
 4 | using System;
 5 | using System.Collections.Generic;
 6 | using System.Linq;
 7 | using System.Text;
 8 | using System.Threading.Tasks;
 9 | 
10 | namespace RulesEngine.Extensions
11 | {
12 |     internal static class EnumerableExtensions
13 |     {
14 |         public static IEnumerable<T> Safe<T>(this IEnumerable<T> enumerable)
15 |         {
16 |             return enumerable ?? Enumerable.Empty<T>();
17 |         }
18 |     }
19 | }
20 | 


--------------------------------------------------------------------------------
/src/RulesEngine/Extensions/ListofRuleResultTreeExtension.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | // Licensed under the MIT License.
 3 | 
 4 | using RulesEngine.Models;
 5 | using System.Collections.Generic;
 6 | using System.Linq;
 7 | 
 8 | 
 9 | namespace RulesEngine.Extensions
10 | {
11 |     public static class ListofRuleResultTreeExtension
12 |     {
13 |         public delegate void OnSuccessFunc(string eventName);
14 |         public delegate void OnFailureFunc();
15 | 
16 | 
17 |         /// <summary>
18 |         /// Calls the Success Func for the first rule which succeeded among the ruleResults
19 |         /// </summary>
20 |         /// <param name="ruleResultTrees"></param>
21 |         /// <param name="onSuccessFunc"></param>
22 |         /// <returns></returns>
23 |         public static List<RuleResultTree> OnSuccess(this List<RuleResultTree> ruleResultTrees, OnSuccessFunc onSuccessFunc)
24 |         {
25 |             var successfulRuleResult = ruleResultTrees.FirstOrDefault(ruleResult => ruleResult.IsSuccess == true);
26 |             if (successfulRuleResult != null)
27 |             {
28 |                 var eventName = successfulRuleResult.Rule.SuccessEvent ?? successfulRuleResult.Rule.RuleName;
29 |                 onSuccessFunc(eventName);
30 |             }
31 | 
32 |             return ruleResultTrees;
33 |         }
34 | 
35 |         /// <summary>
36 |         /// Calls the Failure Func if all rules failed in the ruleReults
37 |         /// </summary>
38 |         /// <param name="ruleResultTrees"></param>
39 |         /// <param name="onFailureFunc"></param>
40 |         /// <returns></returns>
41 |         public static List<RuleResultTree> OnFail(this List<RuleResultTree> ruleResultTrees, OnFailureFunc onFailureFunc)
42 |         {
43 |             bool allFailure = ruleResultTrees.All(ruleResult => ruleResult.IsSuccess == false);
44 |             if (allFailure)
45 |                 onFailureFunc();
46 |             return ruleResultTrees;
47 |         }
48 |     }
49 | }
50 | 


--------------------------------------------------------------------------------
/src/RulesEngine/HelperFunctions/Constants.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | // Licensed under the MIT License.
 3 | 
 4 | namespace RulesEngine.HelperFunctions
 5 | {
 6 |     /// <summary>
 7 |     /// Constants
 8 |     /// </summary>
 9 |     public static class Constants
10 |     {
11 |         public const string WORKFLOW_NAME_NULL_ERRMSG = "Workflow name can not be null or empty";
12 |         public const string INJECT_WORKFLOW_RULES_ERRMSG = "Atleast one of Rules or WorkflowsToInject must be not empty";
13 |         public const string RULE_CATEGORY_CONFIGURED_ERRMSG = "Rule Category should be configured";
14 |         public const string RULE_NULL_ERRMSG = "Rules can not be null or zero";
15 |         public const string NESTED_RULE_NULL_ERRMSG = "Nested rules can not be null";
16 |         public const string NESTED_RULE_CONFIGURED_ERRMSG = "Nested rules can not be configured";
17 |         public const string OPERATOR_NULL_ERRMSG = "Operator can not be null";
18 |         public const string OPERATOR_INCORRECT_ERRMSG = "Operator {PropertyValue} is not allowed";
19 |         public const string RULE_NAME_NULL_ERRMSG = "Rule Name can not be null";
20 |         public const string OPERATOR_RULES_ERRMSG = "Cannot use Rules field when Operator is null";
21 |         public const string LAMBDA_EXPRESSION_EXPRESSION_NULL_ERRMSG = "Expression cannot be null or empty when RuleExpressionType is LambdaExpression";
22 |         public const string LAMBDA_EXPRESSION_OPERATOR_ERRMSG = "Cannot use Operator field when RuleExpressionType is LambdaExpression";
23 |         public const string LAMBDA_EXPRESSION_RULES_ERRMSG = "Cannot use Rules field when RuleExpressionType is LambdaExpression";
24 | 
25 |     }
26 | }
27 | 


--------------------------------------------------------------------------------
/src/RulesEngine/HelperFunctions/ExpressionUtils.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | // Licensed under the MIT License.
 3 | 
 4 | using System;
 5 | using System.Linq;
 6 | 
 7 | namespace RulesEngine.HelperFunctions
 8 | {
 9 |     public static class ExpressionUtils
10 |     {
11 |         public static bool CheckContains(string check, string valList)
12 |         {
13 |             if (string.IsNullOrEmpty(check) || string.IsNullOrEmpty(valList))
14 |                 return false;
15 | 
16 |             var list = valList.Split(',').ToList();
17 |             return list.Contains(check);
18 |         }
19 |     }
20 | }
21 | 


--------------------------------------------------------------------------------
/src/RulesEngine/HelperFunctions/Helpers.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | // Licensed under the MIT License.
 3 | 
 4 | using RulesEngine.Exceptions;
 5 | using RulesEngine.Models;
 6 | using System;
 7 | using System.Collections.Generic;
 8 | using System.Linq;
 9 | using System.Reflection;
10 | 
11 | namespace RulesEngine.HelperFunctions
12 | {
13 |     /// <summary>
14 |     /// Helpers
15 |     /// </summary>
16 |     internal static class Helpers
17 |     {
18 |         internal static RuleFunc<RuleResultTree> ToResultTree(ReSettings reSettings, Rule rule, IEnumerable<RuleResultTree> childRuleResults, Func<object[], bool> isSuccessFunc, string exceptionMessage = "")
19 |         {
20 |             return (inputs) => {
21 |                 var isSuccess = false;
22 |                 var inputsDict = new Dictionary<string, object>();
23 |                 string finalMessage = exceptionMessage;
24 |                 try
25 |                 {
26 |                     inputsDict = inputs.ToDictionary(c => c.Name, c => c.Value);
27 |                     isSuccess = isSuccessFunc(inputs.Select(c => c.Value).ToArray());
28 |                 }
29 |                 catch (Exception ex)
30 |                 {
31 |                     finalMessage = GetExceptionMessage(
quot;Error while executing rule : {rule?.RuleName} - {ex.Message}", reSettings);
32 |                     HandleRuleException(new RuleException(exceptionMessage,ex), rule, reSettings);
33 |                     isSuccess = false;
34 |                 }
35 | 
36 |                 return new RuleResultTree {
37 |                     Rule = rule,
38 |                     Inputs = inputsDict,
39 |                     IsSuccess = isSuccess,
40 |                     ChildResults = childRuleResults,
41 |                     ExceptionMessage = finalMessage
42 |                 };
43 | 
44 |             };
45 |         }
46 | 
47 |         internal static RuleFunc<RuleResultTree> ToRuleExceptionResult(ReSettings reSettings, Rule rule,Exception ex)
48 |         {
49 |             HandleRuleException(ex, rule, reSettings);
50 |             return ToResultTree(reSettings, rule, null, (args) => false, ex.Message);
51 |         }
52 | 
53 |         internal static void HandleRuleException(Exception ex, Rule rule, ReSettings reSettings)
54 |         {
55 |             ex.Data.Add(nameof(rule.RuleName), rule.RuleName);
56 |             ex.Data.Add(nameof(rule.Expression), rule.Expression);
57 | 
58 |             if (!reSettings.EnableExceptionAsErrorMessage)
59 |             {
60 |                 throw ex;
61 |             }
62 |         }
63 | 
64 |         /// <summary>
65 |         /// 
66 |         /// </summary>
67 |         /// <param name="ex"></param>
68 |         /// <param name="message"></param>
69 |         /// <param name="rule"></param>
70 |         /// <param name="reSettings"></param>
71 |         /// <returns></returns>
72 |         internal static string GetExceptionMessage(string message,ReSettings reSettings)
73 |         {
74 |             return reSettings.IgnoreException ? "" : message;
75 |         }
76 |     }
77 | }
78 | 


--------------------------------------------------------------------------------
/src/RulesEngine/HelperFunctions/MemCache.cs:
--------------------------------------------------------------------------------
  1 | // Copyright (c) Microsoft Corporation.
  2 | // Licensed under the MIT License.
  3 | 
  4 | using System;
  5 | using System.Collections;
  6 | using System.Collections.Concurrent;
  7 | using System.Collections.Generic;
  8 | 
  9 | namespace RulesEngine.HelperFunctions
 10 | {
 11 |     public class MemCacheConfig {
 12 |         public int SizeLimit { get; set; } = 1000;
 13 |     }
 14 | 
 15 | 
 16 |     internal class MemCache
 17 |     {
 18 |         private readonly MemCacheConfig _config;
 19 |         private ConcurrentDictionary<string, (object value, DateTimeOffset expiry)> _cacheDictionary;
 20 |         private ConcurrentQueue<(string key, DateTimeOffset expiry)> _cacheEvictionQueue;
 21 | 
 22 |         public MemCache(MemCacheConfig config)
 23 |         {
 24 |             if(config == null)
 25 |             {
 26 |                 config = new MemCacheConfig();
 27 |             }
 28 |             _config = config;
 29 |             _cacheDictionary = new ConcurrentDictionary<string, (object value, DateTimeOffset expiry)>();
 30 |             _cacheEvictionQueue = new ConcurrentQueue<(string key, DateTimeOffset expiry)>();
 31 |         }
 32 | 
 33 |         public bool TryGetValue<T>(string key,out T value)
 34 |         {
 35 |             value = default;
 36 |             if (_cacheDictionary.TryGetValue(key, out var cacheItem))
 37 |             {
 38 |                 if(cacheItem.expiry < DateTimeOffset.UtcNow)
 39 |                 {
 40 |                     _cacheDictionary.TryRemove(key, out _);
 41 |                     return false;
 42 |                 }
 43 |                 else
 44 |                 {
 45 |                     value = (T)cacheItem.value;
 46 |                     return true;
 47 |                 }   
 48 |             }
 49 |             return false;
 50 |            
 51 |         }
 52 | 
 53 | 
 54 |         public T Get<T>(string key)
 55 |         {
 56 |             TryGetValue<T>(key, out var value);
 57 |             return value;
 58 |         }
 59 | 
 60 | 
 61 |         /// <summary>
 62 |         /// Returns all known keys. May return keys for expired data as well
 63 |         /// </summary>
 64 |         /// <returns></returns>
 65 |         public IEnumerable<string> GetKeys()
 66 |         {
 67 |             return _cacheDictionary.Keys;
 68 |         }
 69 | 
 70 |         public T GetOrCreate<T>(string key, Func<T> createFn, DateTimeOffset? expiry = null)
 71 |         {
 72 |             if(!TryGetValue<T>(key,out var value))
 73 |             {
 74 |                 value = createFn();
 75 |                 return Set<T>(key,value,expiry);
 76 |             }
 77 |             return value;
 78 |         }
 79 | 
 80 |         public T Set<T>(string key, T value, DateTimeOffset? expiry = null)
 81 |         {
 82 |             var fixedExpiry = expiry ?? DateTimeOffset.MaxValue;
 83 | 
 84 |             while (_cacheDictionary.Count > _config.SizeLimit)
 85 |             {
 86 |                 if (_cacheEvictionQueue.IsEmpty)
 87 |                 {
 88 |                     _cacheDictionary.Clear();
 89 |                 }
 90 |                 if(_cacheEvictionQueue.TryDequeue(out var result)
 91 |                     && _cacheDictionary.TryGetValue(result.key,out var dictionaryValue)
 92 |                     &&  dictionaryValue.expiry == result.expiry)
 93 |                 {   
 94 |                     _cacheDictionary.TryRemove(result.key, out _);
 95 |                 }
 96 |                 
 97 |             }
 98 | 
 99 |             _cacheDictionary.AddOrUpdate(key, (value, fixedExpiry), (k, v) => (value, fixedExpiry));
100 |             _cacheEvictionQueue.Enqueue((key, fixedExpiry));
101 |             return value;
102 |         }
103 | 
104 |         public void Remove(string key)
105 |         {
106 |             _cacheDictionary.TryRemove(key, out _);
107 |         }
108 | 
109 |         public void Clear()
110 |         {
111 |             _cacheDictionary.Clear();
112 |             _cacheEvictionQueue =  new ConcurrentQueue<(string key, DateTimeOffset expiry)>();
113 |         }
114 |     }
115 | }
116 | 


--------------------------------------------------------------------------------
/src/RulesEngine/HelperFunctions/Utils.cs:
--------------------------------------------------------------------------------
  1 | // Copyright (c) Microsoft Corporation.
  2 | // Licensed under the MIT License.
  3 | 
  4 | using System;
  5 | using System.Collections;
  6 | using System.Collections.Generic;
  7 | using System.Dynamic;
  8 | using System.Linq;
  9 | using System.Linq.Dynamic.Core;
 10 | 
 11 | namespace RulesEngine.HelperFunctions
 12 | {
 13 |     public static class Utils
 14 |     {
 15 |         public static object GetTypedObject(dynamic input)
 16 |         {
 17 |             if (input is ExpandoObject)
 18 |             {
 19 |                 Type type = CreateAbstractClassType(input);
 20 |                 return CreateObject(type, input);
 21 |             }
 22 |             else
 23 |             {
 24 |                 return input;
 25 |             }
 26 |         }
 27 |         public static Type CreateAbstractClassType(dynamic input)
 28 |         {
 29 |             List<DynamicProperty> props = [];
 30 | 
 31 |             if (input is System.Text.Json.JsonElement jsonElement)
 32 |             {
 33 |                 if (jsonElement.ValueKind == System.Text.Json.JsonValueKind.Null)
 34 |                 {
 35 |                     return typeof(object);
 36 |                 }
 37 |             }
 38 |             else if (input == null)
 39 |             {
 40 |                 return typeof(object);
 41 |             }
 42 | 
 43 |             if (input is not ExpandoObject expandoObject)
 44 |             {
 45 |                 return input.GetType();
 46 |             }
 47 | 
 48 |             foreach (var expando in expandoObject)
 49 |             {
 50 |                 Type value;
 51 |                 if (expando.Value is IList list)
 52 |                 {
 53 |                     if (list.Count == 0)
 54 |                     {
 55 |                         value = typeof(List<object>);
 56 |                     }
 57 |                     else
 58 |                     {
 59 |                         var internalType = CreateAbstractClassType(list[0]);
 60 |                         value = new List<object>().Cast(internalType).ToList(internalType).GetType();
 61 |                     }
 62 | 
 63 |                 }
 64 |                 else
 65 |                 {
 66 |                     value = CreateAbstractClassType(expando.Value);
 67 |                 }
 68 |                 props.Add(new DynamicProperty(expando.Key, value));
 69 |             }
 70 | 
 71 |             var type = DynamicClassFactory.CreateType(props);
 72 |             return type;
 73 |         }
 74 | 
 75 |         public static object CreateObject(Type type, dynamic input)
 76 |         {
 77 |             if (input is not ExpandoObject expandoObject)
 78 |             {
 79 |                 return Convert.ChangeType(input, type);
 80 |             }
 81 |             var obj = Activator.CreateInstance(type);
 82 | 
 83 |             var typeProps = type.GetProperties().ToDictionary(c => c.Name);
 84 | 
 85 |             foreach (var expando in expandoObject)
 86 |             {
 87 |                 if (typeProps.ContainsKey(expando.Key) &&
 88 |                     expando.Value != null && (expando.Value.GetType().Name != "DBNull" || expando.Value != DBNull.Value))
 89 |                 {
 90 |                     object val;
 91 |                     var propInfo = typeProps[expando.Key];
 92 |                     if (expando.Value is ExpandoObject)
 93 |                     {
 94 |                         var propType = propInfo.PropertyType;
 95 |                         val = CreateObject(propType, expando.Value);
 96 |                     }
 97 |                     else if (expando.Value is IList temp)
 98 |                     {
 99 |                         var internalType = propInfo.PropertyType.GenericTypeArguments.FirstOrDefault() ?? typeof(object);
100 |                         var newList = new List<object>().Cast(internalType).ToList(internalType);
101 |                         foreach (var t in temp)
102 |                         {
103 |                             var child = CreateObject(internalType, t);
104 |                             newList.Add(child);
105 |                         };
106 |                         val = newList;
107 |                     }
108 |                     else
109 |                     {
110 |                         val = expando.Value;
111 |                     }
112 |                     propInfo.SetValue(obj, val, null);
113 |                 }
114 |             }
115 | 
116 |             return obj;
117 |         }
118 | 
119 |         private static IEnumerable Cast(this IEnumerable self, Type innerType)
120 |         {
121 |             var methodInfo = typeof(Enumerable).GetMethod("Cast");
122 |             var genericMethod = methodInfo.MakeGenericMethod(innerType);
123 |             return genericMethod.Invoke(null, new[] { self }) as IEnumerable;
124 |         }
125 | 
126 |         private static IList ToList(this IEnumerable self, Type innerType)
127 |         {
128 |             var methodInfo = typeof(Enumerable).GetMethod("ToList");
129 |             var genericMethod = methodInfo.MakeGenericMethod(innerType);
130 |             return genericMethod.Invoke(null, new[] { self }) as IList;
131 |         }
132 |     }
133 | 
134 | 
135 | }
136 | 


--------------------------------------------------------------------------------
/src/RulesEngine/Interfaces/IRulesEngine.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | // Licensed under the MIT License.
 3 | 
 4 | using RulesEngine.Models;
 5 | using System.Collections.Generic;
 6 | using System.Threading.Tasks;
 7 | 
 8 | namespace RulesEngine.Interfaces
 9 | {
10 |     public interface IRulesEngine
11 |     {
12 |         /// <summary>
13 |         /// This will execute all the rules of the specified workflow
14 |         /// </summary>
15 |         /// <param name="workflowName">The name of the workflow with rules to execute against the inputs</param>
16 |         /// <param name="inputs">A variable number of inputs</param>
17 |         /// <returns>List of rule results</returns>
18 |         ValueTask<List<RuleResultTree>> ExecuteAllRulesAsync(string workflowName, params object[] inputs);
19 | 
20 |         /// <summary>
21 |         /// This will execute all the rules of the specified workflow
22 |         /// </summary>
23 |         /// <param name="workflowName">The name of the workflow with rules to execute against the inputs</param>
24 |         /// <param name="ruleParams">A variable number of rule parameters</param>
25 |         /// <returns>List of rule results</returns>
26 |         ValueTask<List<RuleResultTree>> ExecuteAllRulesAsync(string workflowName, params RuleParameter[] ruleParams);
27 |         ValueTask<ActionRuleResult> ExecuteActionWorkflowAsync(string workflowName, string ruleName, RuleParameter[] ruleParameters);
28 | 
29 |         /// <summary>
30 |         /// Adds new workflows to RulesEngine
31 |         /// </summary>
32 |         /// <param name="workflow"></param>
33 |         void AddWorkflow(params Workflow[] Workflows);
34 | 
35 |         /// <summary>
36 |         /// Removes all registered workflows from RulesEngine
37 |         /// </summary>
38 |         void ClearWorkflows();
39 | 
40 |         /// <summary>
41 |         /// Removes the workflow from RulesEngine
42 |         /// </summary>
43 |         /// <param name="workflowNames"></param>
44 |         void RemoveWorkflow(params string[] workflowNames);
45 | 
46 |         /// <summary>
47 |         /// Checks is workflow exist.
48 |         /// </summary>
49 |         /// <param name="workflowName">The workflow name.</param>
50 |         /// <returns> <c>true</c> if contains the specified workflow name; otherwise, <c>false</c>.</returns>
51 |         bool ContainsWorkflow(string workflowName);
52 | 
53 |         /// <summary>
54 |         /// Returns the list of all registered workflow names
55 |         /// </summary>
56 |         /// <returns></returns>
57 |         List<string> GetAllRegisteredWorkflowNames();
58 |         void AddOrUpdateWorkflow(params Workflow[] Workflows);
59 |     }
60 | }
61 | 


--------------------------------------------------------------------------------
/src/RulesEngine/Models/ActionInfo.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | //  Licensed under the MIT License.
 3 | 
 4 | using System.Collections.Generic;
 5 | using System.Diagnostics.CodeAnalysis;
 6 | 
 7 | namespace RulesEngine.Models
 8 | {
 9 |     [ExcludeFromCodeCoverage]
10 |     public class ActionInfo
11 |     {
12 |         public string Name { get; set; }
13 |         public Dictionary<string, object> Context { get; set; }
14 |     }
15 | }
16 | 


--------------------------------------------------------------------------------
/src/RulesEngine/Models/ActionResult.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | //  Licensed under the MIT License.
 3 | 
 4 | using System;
 5 | using System.Diagnostics.CodeAnalysis;
 6 | 
 7 | namespace RulesEngine.Models
 8 | {
 9 |     [ExcludeFromCodeCoverage]
10 |     public class ActionResult
11 |     {
12 |         public object Output { get; set; }
13 |         public Exception Exception { get; set; }
14 |     }
15 | }
16 | 


--------------------------------------------------------------------------------
/src/RulesEngine/Models/ActionRuleResult.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | //  Licensed under the MIT License.
 3 | 
 4 | using System.Collections.Generic;
 5 | using System.Diagnostics.CodeAnalysis;
 6 | 
 7 | namespace RulesEngine.Models
 8 | {
 9 |     [ExcludeFromCodeCoverage]
10 |     public class ActionRuleResult : ActionResult
11 |     {
12 |         public List<RuleResultTree> Results { get; set; }
13 |     }
14 | }
15 | 


--------------------------------------------------------------------------------
/src/RulesEngine/Models/ReSettings.cs:
--------------------------------------------------------------------------------
  1 | // Copyright (c) Microsoft Corporation.
  2 | // Licensed under the MIT License.
  3 | 
  4 | using RulesEngine.Actions;
  5 | using RulesEngine.HelperFunctions;
  6 | using System;
  7 | using System.Collections.Generic;
  8 | using System.Diagnostics.CodeAnalysis;
  9 | 
 10 | namespace RulesEngine.Models
 11 | {
 12 |     [ExcludeFromCodeCoverage]
 13 |     public class ReSettings
 14 |     {
 15 |         public ReSettings() { }
 16 | 
 17 |         // create a copy of settings
 18 |         internal ReSettings(ReSettings reSettings)
 19 |         {
 20 |             CustomTypes = reSettings.CustomTypes;
 21 |             CustomActions = reSettings.CustomActions;
 22 |             EnableExceptionAsErrorMessage = reSettings.EnableExceptionAsErrorMessage;
 23 |             IgnoreException = reSettings.IgnoreException;
 24 |             EnableFormattedErrorMessage = reSettings.EnableFormattedErrorMessage;
 25 |             EnableScopedParams = reSettings.EnableScopedParams;
 26 |             NestedRuleExecutionMode = reSettings.NestedRuleExecutionMode;
 27 |             CacheConfig = reSettings.CacheConfig;
 28 |             IsExpressionCaseSensitive = reSettings.IsExpressionCaseSensitive;
 29 |             AutoRegisterInputType = reSettings.AutoRegisterInputType;
 30 |             UseFastExpressionCompiler = reSettings.UseFastExpressionCompiler;
 31 |             EnableExceptionAsErrorMessageForRuleExpressionParsing = reSettings.EnableExceptionAsErrorMessageForRuleExpressionParsing;
 32 |         }
 33 | 
 34 | 
 35 |         /// <summary>
 36 |         /// Get/Set the custom types to be used in Rule expressions
 37 |         /// </summary>
 38 |         public Type[] CustomTypes { get; set; }
 39 | 
 40 |         /// <summary>
 41 |         /// Get/Set the custom actions that can be used in the Rules
 42 |         /// </summary>
 43 |         public Dictionary<string, Func<ActionBase>> CustomActions { get; set; }
 44 | 
 45 |         /// <summary>
 46 |         /// When set to true, returns any exception occurred 
 47 |         /// while rule execution as ErrorMessage 
 48 |         /// otherwise throws an exception
 49 |         /// </summary>
 50 |         /// <remarks>This setting is only applicable if IgnoreException is set to false</remarks>
 51 |         public bool EnableExceptionAsErrorMessage { get; set; } = true;
 52 | 
 53 |         /// <summary>
 54 |         /// When set to true, it will ignore any exception thrown with rule compilation/execution
 55 |         /// </summary>
 56 |         public bool IgnoreException { get; set; } = false;
 57 | 
 58 |         /// <summary>
 59 |         /// Enables ErrorMessage Formatting 
 60 |         /// </summary>
 61 |         public bool EnableFormattedErrorMessage { get; set; } = true;
 62 | 
 63 |         /// <summary>
 64 |         /// Enables Global params and local params for rules
 65 |         /// </summary>
 66 |         public bool EnableScopedParams { get; set; } = true;
 67 | 
 68 |         /// <summary>
 69 |         /// Sets whether expression are case sensitive
 70 |         /// </summary>
 71 |         public bool IsExpressionCaseSensitive { get; set; } = false;
 72 | 
 73 |         /// <summary>
 74 |         /// Auto Registers input type in Custom Type to allow calling method on type.
 75 |         /// Default : true
 76 |         /// </summary>
 77 |         public bool AutoRegisterInputType { get; set; } = true;
 78 | 
 79 |         /// <summary>
 80 |         /// Sets the mode for Nested rule execution, Default: All
 81 |         /// </summary>
 82 |         public NestedRuleExecutionMode NestedRuleExecutionMode { get; set; } = NestedRuleExecutionMode.All;
 83 |         public MemCacheConfig CacheConfig { get; set; }
 84 |         /// <summary>
 85 |         /// Whether to use FastExpressionCompiler for rule compilation
 86 |         /// </summary>
 87 |         public bool UseFastExpressionCompiler { get; set; } = false;
 88 |         /// <summary>
 89 |         /// Sets the mode for ParsingException to cascade to child elements and result in a expression parser
 90 |         /// Default: true
 91 |         /// </summary>
 92 |         public bool EnableExceptionAsErrorMessageForRuleExpressionParsing { get; set; } = true;
 93 |     }
 94 | 
 95 |     public enum NestedRuleExecutionMode
 96 |     {
 97 |         /// <summary>
 98 |         /// Executes all nested rules
 99 |         /// </summary>
100 |         All,
101 |         /// <summary>
102 |         /// Skips nested rules whose execution does not impact parent rule's result
103 |         /// </summary>
104 |         Performance
105 |     }
106 | }
107 | 


--------------------------------------------------------------------------------
/src/RulesEngine/Models/Rule.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | // Licensed under the MIT License.
 3 | 
 4 | using System.Collections.Generic;
 5 | using System.Diagnostics.CodeAnalysis;
 6 | 
 7 | namespace RulesEngine.Models
 8 | {
 9 |     using System.Text.Json.Serialization;
10 | 
11 |     /// <summary>
12 |     /// Rule class
13 |     /// </summary>
14 |     [ExcludeFromCodeCoverage]
15 |     public class Rule
16 |     {
17 |         /// <summary>
18 |         /// Rule name for the Rule
19 |         /// </summary>
20 |         public string RuleName { get; set; }
21 |         /// <summary>	
22 |         /// Gets or sets the custom property or tags of the rule.	
23 |         /// </summary>	
24 |         /// <value>	
25 |         /// The properties of the rule.	
26 |         /// </value>	
27 |         public Dictionary<string, object> Properties { get; set; }
28 |         public string Operator { get; set; }
29 |         public string ErrorMessage { get; set; }
30 | 
31 |         /// <summary>
32 |         /// Gets or sets whether the rule is enabled.
33 |         /// </summary>
34 |         public bool Enabled { get; set; } = true;
35 | 
36 |         [JsonConverter (typeof(JsonStringEnumConverter))]
37 |         public RuleExpressionType RuleExpressionType { get; set; } = RuleExpressionType.LambdaExpression;
38 |         public IEnumerable<string> WorkflowsToInject { get; set; }
39 |         public IEnumerable<Rule> Rules { get; set; }
40 |         public IEnumerable<ScopedParam> LocalParams { get; set; }
41 |         public string Expression { get; set; }
42 |         public RuleActions Actions { get; set; }
43 |         public string SuccessEvent { get; set; }
44 | 
45 |     }
46 | }
47 | 


--------------------------------------------------------------------------------
/src/RulesEngine/Models/RuleActions.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | //  Licensed under the MIT License.
 3 | 
 4 | using System;
 5 | using System.Diagnostics.CodeAnalysis;
 6 | 
 7 | namespace RulesEngine.Models
 8 | {
 9 |     [Obsolete("RuleAction class is deprecated. Use RuleActions class instead.")]
10 |     [ExcludeFromCodeCoverage]
11 |     public class RuleAction : RuleActions
12 |     {
13 |     }
14 |   
15 |     [ExcludeFromCodeCoverage]
16 |     public class RuleActions
17 |     {
18 |         public ActionInfo OnSuccess { get; set; }
19 |         public ActionInfo OnFailure { get; set; }
20 |     }
21 | }
22 | 


--------------------------------------------------------------------------------
/src/RulesEngine/Models/RuleDelegate.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 | 
4 | namespace RulesEngine.Models
5 | {
6 |     public delegate T RuleFunc<T>(params RuleParameter[] ruleParameters);
7 | }
8 | 


--------------------------------------------------------------------------------
/src/RulesEngine/Models/RuleErrorType.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | // Licensed under the MIT License.
 3 | 
 4 | namespace RulesEngine.Models
 5 | {
 6 |     /// <summary>
 7 |     /// This is error type of rules which will use in rule config files
 8 |     /// </summary>
 9 |     public enum ErrorType
10 |     {
11 |         Warning = 0,
12 |         Error = 1,
13 |     }
14 | }
15 | 


--------------------------------------------------------------------------------
/src/RulesEngine/Models/RuleExpressionParameter.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | //  Licensed under the MIT License.
 3 | 
 4 | using System;
 5 | using System.Diagnostics.CodeAnalysis;
 6 | using System.Linq.Expressions;
 7 | 
 8 | namespace RulesEngine.Models
 9 | {
10 |     /// <summary>
11 |     /// CompiledParam class.
12 |     /// </summary>
13 |     [ExcludeFromCodeCoverage]
14 |     public class RuleExpressionParameter
15 |     {
16 |         public ParameterExpression ParameterExpression { get; set; }
17 |         
18 |         public Expression ValueExpression { get; set; }
19 | 
20 |     }
21 | }
22 | 


--------------------------------------------------------------------------------
/src/RulesEngine/Models/RuleExpressionType.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | // Licensed under the MIT License.
 3 | 
 4 | namespace RulesEngine.Models
 5 | {
 6 |     /// <summary>
 7 |     /// This is rule expression type which will use in rule config files 
 8 |     /// </summary>
 9 |     public enum RuleExpressionType
10 |     {
11 |         LambdaExpression = 0
12 |     }
13 | }
14 | 


--------------------------------------------------------------------------------
/src/RulesEngine/Models/RuleParameter.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | // Licensed under the MIT License.
 3 | 
 4 | using RulesEngine.HelperFunctions;
 5 | using System;
 6 | using System.Diagnostics.CodeAnalysis;
 7 | using System.Linq.Expressions;
 8 | 
 9 | namespace RulesEngine.Models
10 | {
11 |     [ExcludeFromCodeCoverage]
12 |     public class RuleParameter
13 |     {
14 |         public RuleParameter(string name, object value)
15 |         {
16 |             Value = Utils.GetTypedObject(value);
17 |             Init(name, Value?.GetType());
18 |         }
19 | 
20 |        
21 |         internal RuleParameter(string name, Type type,object value = null)
22 |         {
23 |             Value = Utils.GetTypedObject(value);
24 |             Init(name, type);
25 |         }
26 | 
27 |         public Type Type { get; private set; }
28 |         public string Name { get; private set; }
29 |         public object Value { get; private set; }
30 |         public ParameterExpression ParameterExpression { get; private set; }
31 | 
32 |         private void Init(string name, Type type)
33 |         {
34 |             Name = name;
35 |             Type = type ?? typeof(object);
36 |             ParameterExpression = Expression.Parameter(Type, Name);
37 |         }
38 | 
39 |         public static RuleParameter Create(string name, Type type)
40 |         {
41 |             return new RuleParameter(name, type);
42 |         }
43 |       
44 |         public static RuleParameter Create<T>(string name, T value)
45 |         {
46 |             var typedValue = Utils.GetTypedObject(value);
47 |             var type = typedValue?.GetType() ?? typeof(T);
48 |             return new RuleParameter(name,type,value);
49 |         }
50 | 
51 | 
52 |     }
53 | }
54 | 


--------------------------------------------------------------------------------
/src/RulesEngine/Models/RuleResultTree.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | // Licensed under the MIT License.
 3 | 
 4 | using RulesEngine.HelperFunctions;
 5 | using System;
 6 | using System.Collections.Generic;
 7 | using System.Diagnostics.CodeAnalysis;
 8 | 
 9 | namespace RulesEngine.Models
10 | {
11 |     /// <summary>
12 |     /// Rule result class with child result heirarchy
13 |     /// </summary>
14 |     [ExcludeFromCodeCoverage]
15 |     public class RuleResultTree
16 |     {
17 |         /// <summary>
18 |         /// Gets or sets the rule.
19 |         /// </summary>
20 |         /// <value>
21 |         /// The rule.
22 |         /// </value>
23 |         public Rule Rule { get; set; }
24 | 
25 |         /// <summary>
26 |         /// Gets or sets a value indicating whether this instance is success.
27 |         /// </summary>
28 |         /// <value>
29 |         ///   <c>true</c> if this instance is success; otherwise, <c>false</c>.
30 |         /// </value>
31 |         public bool IsSuccess { get; set; }
32 | 
33 |         /// <summary>
34 |         /// Gets or sets the child result.
35 |         /// </summary>
36 |         /// <value>
37 |         /// The child result.
38 |         /// </value>
39 |         public IEnumerable<RuleResultTree> ChildResults { get; set; }
40 | 
41 |         /// <summary>
42 |         /// Gets or sets the input object
43 |         /// </summary>
44 |         public Dictionary<string, object> Inputs { get; set; }
45 | 
46 |         public ActionResult ActionResult { get; set; }
47 | 
48 |         /// <summary>
49 |         /// Gets the exception message in case an error is thrown during rules calculation.
50 |         /// </summary>
51 |         public string ExceptionMessage { get; set; }
52 | 
53 |     }
54 | 
55 |     /// <summary>
56 |     /// This class will hold the error messages
57 |     /// </summary>
58 |     [ExcludeFromCodeCoverage]
59 |     public class RuleResultMessage
60 |     {
61 |         /// <summary>
62 |         /// Constructor will initialize the List 
63 |         /// </summary>
64 |         public RuleResultMessage()
65 |         {
66 |             ErrorMessages = new List<string>();
67 |             WarningMessages = new List<string>();
68 |         }
69 | 
70 |         /// <summary>
71 |         /// This will hold the list of error messages
72 |         /// </summary>
73 |         public List<string> ErrorMessages { get; set; }
74 | 
75 |         /// <summary>
76 |         /// This will hold the list of warning messages
77 |         /// </summary>
78 |         public List<string> WarningMessages { get; set; }
79 |     }
80 | }
81 | 


--------------------------------------------------------------------------------
/src/RulesEngine/Models/ScopedParam.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | //  Licensed under the MIT License.
 3 | 
 4 | using System.Diagnostics.CodeAnalysis;
 5 | 
 6 | namespace RulesEngine.Models
 7 | {
 8 |     /// <summary>Class LocalParam.
 9 |     /// </summary>
10 |     [ExcludeFromCodeCoverage]
11 |     public class ScopedParam
12 |     {
13 | 
14 |         /// <summary>
15 |         /// Gets or sets the name of the param.
16 |         /// </summary>
17 |         /// <value>
18 |         /// The name of the rule.
19 |         /// </value>]
20 |         public string Name { get; set; }
21 | 
22 |         /// <summary>
23 |         /// Gets or Sets the lambda expression which can be reference in Rule. 
24 |         /// </summary>
25 |         public string Expression { get; set; }
26 |     }
27 | 
28 |     [ExcludeFromCodeCoverage]
29 |     public class LocalParam : ScopedParam { }
30 | }
31 | 


--------------------------------------------------------------------------------
/src/RulesEngine/Models/Workflow.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | // Licensed under the MIT License.
 3 | 
 4 | using System;
 5 | using System.Collections.Generic;
 6 | using System.Diagnostics.CodeAnalysis;
 7 | 
 8 | namespace RulesEngine.Models
 9 | {
10 |     [Obsolete("WorkflowRules class is deprecated. Use Workflow class instead.")]
11 |     [ExcludeFromCodeCoverage]
12 |     public class WorkflowRules : Workflow {
13 |     }
14 | 
15 |     /// <summary>
16 |     /// Workflow rules class for deserialization  the json config file
17 |     /// </summary>
18 |     [ExcludeFromCodeCoverage]
19 |     public class Workflow
20 |     {
21 |         /// <summary>
22 |         /// Gets the workflow name.
23 |         /// </summary>
24 |         public string WorkflowName { get; set; }
25 | 
26 |         /// <summary>Gets or sets the workflow rules to inject.</summary>
27 |         /// <value>The workflow rules to inject.</value>
28 |         [Obsolete("WorkflowRulesToInject is deprecated. Use WorkflowsToInject instead.")]
29 |         public IEnumerable<string> WorkflowRulesToInject {
30 |           set { WorkflowsToInject = value; }
31 |         }
32 |         public IEnumerable<string> WorkflowsToInject { get; set; }
33 | 
34 |         public RuleExpressionType RuleExpressionType { get; set; } = RuleExpressionType.LambdaExpression;
35 | 
36 |         /// <summary>
37 |         /// Gets or Sets the global params which will be applicable to all rules
38 |         /// </summary>
39 |         public IEnumerable<ScopedParam> GlobalParams { get; set; }
40 | 
41 |         /// <summary>
42 |         /// list of rules.
43 |         /// </summary>
44 |         public IEnumerable<Rule> Rules { get; set; }
45 |     }
46 | }
47 | 


--------------------------------------------------------------------------------
/src/RulesEngine/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | // Licensed under the MIT License.
 3 | 
 4 | using System.Runtime.CompilerServices;
 5 | using System.Runtime.InteropServices;
 6 | 
 7 | // Setting ComVisible to false makes the types in this assembly not visible 
 8 | // to COM components.  If you need to access a type in this assembly from 
 9 | // COM, set the ComVisible attribute to true on that type.
10 | [assembly: ComVisible(false)]
11 | [assembly: InternalsVisibleTo("RulesEngine.UnitTest, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c15956b2ac0945c55b69a185f5c3e02276693b0a5e42c8a1f08cb24e03dd87d91f9fa09f79b6b7b3aac4df46f2ea4ce4bfa31920bb0aad9f02793ab29de9fbf40f5ba9e347aa8569128459f31da1f6357eabe6e1308ac7c16b87a4d61e8d1785746a57ec67956d2e2454b3c98502a5d5c4a4168133bfaa431207c108efae03aa")]
12 | 


--------------------------------------------------------------------------------
/src/RulesEngine/RuleExpressionBuilderFactory.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | // Licensed under the MIT License.
 3 | 
 4 | using RulesEngine.ExpressionBuilders;
 5 | using RulesEngine.Models;
 6 | using System;
 7 | 
 8 | namespace RulesEngine
 9 | {
10 |     internal class RuleExpressionBuilderFactory
11 |     {
12 |         private readonly ReSettings _reSettings;
13 |         private readonly LambdaExpressionBuilder _lambdaExpressionBuilder;
14 |         public RuleExpressionBuilderFactory(ReSettings reSettings, RuleExpressionParser expressionParser)
15 |         {
16 |             _reSettings = reSettings;
17 |             _lambdaExpressionBuilder = new LambdaExpressionBuilder(_reSettings, expressionParser);
18 |         }
19 |         public RuleExpressionBuilderBase RuleGetExpressionBuilder(RuleExpressionType ruleExpressionType)
20 |         {
21 |             switch (ruleExpressionType)
22 |             {
23 |                 case RuleExpressionType.LambdaExpression:
24 |                     return _lambdaExpressionBuilder;
25 |                 default:
26 |                     throw new InvalidOperationException(
quot;{nameof(ruleExpressionType)} has not been supported yet.");
27 |             }
28 |         }
29 |     }
30 | }
31 | 


--------------------------------------------------------------------------------
/src/RulesEngine/RulesCache.cs:
--------------------------------------------------------------------------------
  1 | // Copyright (c) Microsoft Corporation.
  2 | // Licensed under the MIT License.
  3 | 
  4 | using RulesEngine.HelperFunctions;
  5 | using RulesEngine.Models;
  6 | using System;
  7 | using System.Collections.Concurrent;
  8 | using System.Collections.Generic;
  9 | using System.Linq;
 10 | 
 11 | namespace RulesEngine
 12 | {
 13 |     /// <summary>Class RulesCache.</summary>
 14 |     internal class RulesCache
 15 |     {
 16 |         /// <summary>The compile rules</summary>
 17 |         private readonly MemCache _compileRules;
 18 | 
 19 |         /// <summary>The workflow rules</summary>
 20 |         private readonly ConcurrentDictionary<string, (Workflow, long)> _workflow = new ConcurrentDictionary<string, (Workflow, long)>();
 21 | 
 22 | 
 23 |         public RulesCache(ReSettings reSettings)
 24 |         {
 25 |             _compileRules = new MemCache(reSettings.CacheConfig);
 26 |         }
 27 | 
 28 | 
 29 |         /// <summary>Determines whether [contains workflow rules] [the specified workflow name].</summary>
 30 |         /// <param name="workflowName">Name of the workflow.</param>
 31 |         /// <returns>
 32 |         ///   <c>true</c> if [contains workflow rules] [the specified workflow name]; otherwise, <c>false</c>.</returns>
 33 |         public bool ContainsWorkflows(string workflowName)
 34 |         {
 35 |             return _workflow.ContainsKey(workflowName);
 36 |         }
 37 | 
 38 |         public List<string> GetAllWorkflowNames()
 39 |         {
 40 |             return _workflow.Keys.ToList();
 41 |         }
 42 | 
 43 |         /// <summary>Adds the or update workflow rules.</summary>
 44 |         /// <param name="workflowName">Name of the workflow.</param>
 45 |         /// <param name="rules">The rules.</param>
 46 |         public void AddOrUpdateWorkflows(string workflowName, Workflow rules)
 47 |         {
 48 |             long ticks = DateTime.UtcNow.Ticks;
 49 |             _workflow.AddOrUpdate(workflowName, (rules, ticks), (k, v) => (rules, ticks));
 50 |         }
 51 | 
 52 |         /// <summary>Adds the or update compiled rule.</summary>
 53 |         /// <param name="compiledRuleKey">The compiled rule key.</param>
 54 |         /// <param name="compiledRule">The compiled rule.</param>
 55 |         public void AddOrUpdateCompiledRule(string compiledRuleKey, IDictionary<string, RuleFunc<RuleResultTree>> compiledRule)
 56 |         {
 57 |             long ticks = DateTime.UtcNow.Ticks;
 58 |             _compileRules.Set(compiledRuleKey,(compiledRule, ticks));
 59 |         }
 60 | 
 61 |         /// <summary>Checks if the compiled rules are up-to-date.</summary>
 62 |         /// <param name="compiledRuleKey">The compiled rule key.</param>
 63 |         /// <param name="workflowName">The workflow name.</param>
 64 |          /// <returns>
 65 |         ///   <c>true</c> if [compiled rules] is newer than the [workflow rules]; otherwise, <c>false</c>.</returns>
 66 |         public bool AreCompiledRulesUpToDate(string compiledRuleKey, string workflowName)
 67 |         {
 68 |             if (_compileRules.TryGetValue(compiledRuleKey, out (IDictionary<string, RuleFunc<RuleResultTree>> rules, long tick) compiledRulesObj))
 69 |             {
 70 |                 if (_workflow.TryGetValue(workflowName, out (Workflow rules, long tick) WorkflowsObj))
 71 |                 {
 72 |                     return compiledRulesObj.tick >= WorkflowsObj.tick;
 73 |                 }
 74 |             }
 75 | 
 76 |             return false;
 77 |         }
 78 | 
 79 |         /// <summary>Clears this instance.</summary>
 80 |         public void Clear()
 81 |         {
 82 |             _workflow.Clear();
 83 |             _compileRules.Clear();
 84 |         }
 85 | 
 86 |         /// <summary>Gets the work flow rules.</summary>
 87 |         /// <param name="workflowName">Name of the workflow.</param>
 88 |         /// <returns>Workflows.</returns>
 89 |         /// <exception cref="Exception">Could not find injected Workflow: {wfname}</exception>
 90 |         public Workflow GetWorkflow(string workflowName)
 91 |         {
 92 |             if (_workflow.TryGetValue(workflowName, out (Workflow rules, long tick) WorkflowsObj))
 93 |             {
 94 |                 var workflow = WorkflowsObj.rules;
 95 |                 if (workflow.WorkflowsToInject?.Any() == true)
 96 |                 {
 97 |                     if (workflow.Rules == null)
 98 |                     {
 99 |                         workflow.Rules = new List<Rule>();
100 |                     }
101 |                     foreach (string wfname in workflow.WorkflowsToInject)
102 |                     {
103 |                         var injectedWorkflow = GetWorkflow(wfname);
104 |                         if (injectedWorkflow == null)
105 |                         {
106 |                             throw new Exception(
quot;Could not find injected Workflow: {wfname}");
107 |                         }
108 | 
109 |                         workflow.Rules = workflow.Rules.Concat(injectedWorkflow.Rules).ToList();
110 |                     }
111 |                 }
112 | 
113 |                 return workflow;
114 |             }
115 |             else
116 |             {
117 |                 return null;
118 |             }
119 |         }
120 | 
121 | 
122 |         /// <summary>Gets the compiled rules.</summary>
123 |         /// <param name="compiledRulesKey">The compiled rules key.</param>
124 |         /// <returns>CompiledRule.</returns>
125 |         public IDictionary<string, RuleFunc<RuleResultTree>> GetCompiledRules(string compiledRulesKey)
126 |         {
127 |             return _compileRules.Get<(IDictionary<string, RuleFunc<RuleResultTree>> rules, long tick)>(compiledRulesKey).rules;
128 |         }
129 | 
130 |         /// <summary>Removes the specified workflow name.</summary>
131 |         /// <param name="workflowName">Name of the workflow.</param>
132 |         public void Remove(string workflowName)
133 |         {
134 |             if (_workflow.TryRemove(workflowName, out var workflowObj))
135 |             {
136 |                 var compiledKeysToRemove = _compileRules.GetKeys().Where(key => key.StartsWith(workflowName));
137 |                 foreach (var key in compiledKeysToRemove)
138 |                 {
139 |                     _compileRules.Remove(key);
140 |                 }
141 |             }
142 |         }
143 |     }
144 | }
145 | 


--------------------------------------------------------------------------------
/src/RulesEngine/RulesEngine.csproj:
--------------------------------------------------------------------------------
 1 | <Project Sdk="Microsoft.NET.Sdk">
 2 | 
 3 |   <PropertyGroup>
 4 |     <TargetFrameworks>net6.0;net8.0;net9.0;netstandard2.0</TargetFrameworks>
 5 |     <LangVersion>13.0</LangVersion>
 6 |     <Version>6.0.0</Version>
 7 |     <Copyright>Copyright (c) Microsoft Corporation.</Copyright>
 8 |     <PackageLicenseFile>LICENSE</PackageLicenseFile>
 9 |     <PackageProjectUrl>https://github.com/microsoft/RulesEngine</PackageProjectUrl>
10 |     <Authors>Purunjay Bhal</Authors>
11 |     <Description>Rules Engine is a 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.</Description>
12 |     <PackageReleaseNotes>https://github.com/microsoft/RulesEngine/blob/main/CHANGELOG.md</PackageReleaseNotes>
13 |     <PackageTags>BRE, Rules Engine, Abstraction</PackageTags>
14 |     <PackageReadmeFile>README.md</PackageReadmeFile>
15 |   </PropertyGroup>
16 | 
17 |   <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
18 |     <Optimize>true</Optimize>
19 |   </PropertyGroup>
20 |   <PropertyGroup Label="SourceLink">
21 |     <PublishRepositoryUrl>true</PublishRepositoryUrl>
22 |     <IncludeSymbols>true</IncludeSymbols>
23 |     <SymbolPackageFormat>snupkg</SymbolPackageFormat>
24 |     <SignAssembly>True</SignAssembly>
25 |     <AssemblyOriginatorKeyFile>..\..\signing\RulesEngine-publicKey.snk</AssemblyOriginatorKeyFile>
26 |     <DelaySign>True</DelaySign>
27 |     <Deterministic>true</Deterministic>
28 |     <EmbedUntrackedSources>true</EmbedUntrackedSources>
29 |   </PropertyGroup>
30 | 
31 |   <ItemGroup>
32 |     <None Include="..\..\LICENSE" Pack="true" PackagePath="\" />
33 |     <None Include="..\..\README.md" Pack="true" PackagePath="\" />
34 |   </ItemGroup>
35 | 
36 |   <ItemGroup>
37 |     <PackageReference Include="FastExpressionCompiler" Version="5.0.2" />
38 |     <PackageReference Include="FluentValidation" Version="11.11.0" />
39 |     <PackageReference Include="System.Linq.Dynamic.Core" Version="1.6.2" />
40 |     <PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
41 |   </ItemGroup>
42 | 
43 |   <Choose>
44 |     <When Condition="'$(TargetFramework)' == 'net6.0' or '$(TargetFramework)' == 'netstandard2.0'">
45 |       <ItemGroup>
46 |         <PackageReference Include="System.Text.Json" Version="6.0.11" />
47 |       </ItemGroup>
48 |     </When>
49 |     <Otherwise>
50 |       <ItemGroup>
51 |         <PackageReference Include="System.Text.Json" Version="9.0.1" />
52 |       </ItemGroup>
53 |     </Otherwise>
54 |   </Choose>
55 | 
56 |   <ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
57 |     <PackageReference Include="Microsoft.CSharp" Version="4.7.0"/>
58 |   </ItemGroup>
59 | 
60 | </Project>
61 | 


--------------------------------------------------------------------------------
/src/RulesEngine/Validators/RuleValidator.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | // Licensed under the MIT License.
 3 | 
 4 | using FluentValidation;
 5 | using RulesEngine.HelperFunctions;
 6 | using RulesEngine.Models;
 7 | using System;
 8 | using System.Collections.Generic;
 9 | using System.Linq;
10 | using System.Linq.Expressions;
11 | 
12 | namespace RulesEngine.Validators
13 | {
14 |     internal class RuleValidator : AbstractValidator<Rule>
15 |     {
16 |         private readonly List<ExpressionType> _nestedOperators = new List<ExpressionType> { ExpressionType.And, ExpressionType.AndAlso, ExpressionType.Or, ExpressionType.OrElse };
17 |         public RuleValidator()
18 |         {
19 |             RuleFor(c => c.RuleName).NotEmpty().WithMessage(Constants.RULE_NAME_NULL_ERRMSG);
20 | 
21 |             //Nested expression check
22 |             When(c => c.Operator != null, () => {
23 |                 RuleFor(c => c.Operator)
24 |                    .NotNull().WithMessage(Constants.OPERATOR_NULL_ERRMSG)
25 |                    .Must(op => _nestedOperators.Any(x => x.ToString().Equals(op, StringComparison.OrdinalIgnoreCase)))
26 |                    .WithMessage(Constants.OPERATOR_INCORRECT_ERRMSG);
27 | 
28 |                 When(c => c.Rules?.Any() != true, () => {
29 |                     RuleFor(c => c.WorkflowsToInject).NotEmpty().WithMessage(Constants.INJECT_WORKFLOW_RULES_ERRMSG);
30 |                 })
31 |                 .Otherwise(() => {
32 |                     RuleFor(c => c.Rules).Must(BeValidRulesList);
33 |                 });
34 |             });
35 |             RegisterExpressionTypeRules();
36 |         }
37 | 
38 |         private void RegisterExpressionTypeRules()
39 |         {
40 |             When(c => c.Operator == null && c.RuleExpressionType == RuleExpressionType.LambdaExpression, () => {
41 |                 RuleFor(c => c.Expression).NotEmpty().WithMessage(Constants.LAMBDA_EXPRESSION_EXPRESSION_NULL_ERRMSG);
42 |                 RuleFor(c => c.Rules).Empty().WithMessage(Constants.OPERATOR_RULES_ERRMSG);
43 |             });
44 |         }
45 | 
46 |         private bool BeValidRulesList(IEnumerable<Rule> rules)
47 |         {
48 |             if (rules?.Any() != true) return false;
49 |             var validator = new RuleValidator();
50 |             var isValid = true;
51 |             foreach (var rule in rules)
52 |             {
53 |                 isValid &= validator.Validate(rule).IsValid;
54 |                 if (!isValid) break;
55 |             }
56 |             return isValid;
57 |         }
58 |     }
59 | }
60 | 


--------------------------------------------------------------------------------
/src/RulesEngine/Validators/WorkflowRulesValidator.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | // Licensed under the MIT License.
 3 | 
 4 | using FluentValidation;
 5 | using RulesEngine.HelperFunctions;
 6 | using RulesEngine.Models;
 7 | using System.Linq;
 8 | 
 9 | namespace RulesEngine.Validators
10 | {
11 |     internal class WorkflowsValidator : AbstractValidator<Workflow>
12 |     {
13 |         public WorkflowsValidator()
14 |         {
15 |             RuleFor(c => c.WorkflowName).NotEmpty().WithMessage(Constants.WORKFLOW_NAME_NULL_ERRMSG);
16 |             When(c => c.Rules?.Any() != true, () => {
17 |                 RuleFor(c => c.WorkflowsToInject).NotEmpty().WithMessage(Constants.INJECT_WORKFLOW_RULES_ERRMSG);
18 |             }).Otherwise(() => {
19 |                 var ruleValidator = new RuleValidator();
20 |                 RuleForEach(c => c.Rules).SetValidator(ruleValidator);
21 |             });
22 |         }
23 |     }
24 | }
25 | 


--------------------------------------------------------------------------------
/test/RulesEngine.UnitTest/ActionTests/ActionContextTests.cs:
--------------------------------------------------------------------------------
  1 | // Copyright (c) Microsoft Corporation.
  2 | //  Licensed under the MIT License.
  3 | 
  4 | using AutoFixture;
  5 | using RulesEngine.Actions;
  6 | using RulesEngine.Models;
  7 | using System;
  8 | using System.Collections.Generic;
  9 | using System.Diagnostics.CodeAnalysis;
 10 | using Xunit;
 11 | 
 12 | namespace RulesEngine.UnitTest
 13 | {
 14 |     [ExcludeFromCodeCoverage]
 15 |     public class ActionContextTests
 16 |     {
 17 |         [Fact]
 18 |         public void GetParentRuleResult_ReturnsParentRule()
 19 |         {
 20 |             // Arrange
 21 |             var fixture = new Fixture();
 22 |             var contextInput = fixture.Create<string>();
 23 |             var context = new Dictionary<string, object> {
 24 |                 { nameof(contextInput), contextInput }
 25 |             };
 26 |             var parentRuleResult = new RuleResultTree();
 27 | 
 28 |             var actionContext = new ActionContext(context, parentRuleResult);
 29 | 
 30 |             // Act
 31 |             var result = actionContext.GetParentRuleResult();
 32 | 
 33 |             // Assert
 34 |             Assert.NotNull(result);
 35 |             Assert.Equal(parentRuleResult, result);
 36 |         }
 37 | 
 38 |         [Fact]
 39 |         public void GetContext_ValidName_ReturnsContext()
 40 |         {
 41 |             // Arrange
 42 |             var fixture = new Fixture();
 43 |             var contextInput = fixture.Create<string>();
 44 |             var context = new Dictionary<string, object> {
 45 |                 { nameof(contextInput), contextInput }
 46 |             };
 47 |             var parentRuleResult = new RuleResultTree();
 48 | 
 49 |             var actionContext = new ActionContext(context, parentRuleResult);
 50 |             string name = nameof(contextInput);
 51 | 
 52 |             // Act
 53 |             var result = actionContext.GetContext<string>(name);
 54 | 
 55 |             // Assert
 56 |             Assert.Equal(contextInput, result);
 57 |         }
 58 | 
 59 |         [Fact]
 60 |         public void GetContext_ObjectContext_ReturnsTypedContext()
 61 |         {
 62 |             // Arrange
 63 |             var fixture = new Fixture();
 64 |             var contextInput = fixture.CreateMany<string>();
 65 |             var context = new Dictionary<string, object> {
 66 |                 { nameof(contextInput), contextInput }
 67 |             };
 68 |             var parentRuleResult = new RuleResultTree();
 69 | 
 70 | 
 71 |             var actionContext = new ActionContext(context, parentRuleResult);
 72 |             string name = nameof(contextInput);
 73 | 
 74 |             // Act
 75 |             var result = actionContext.GetContext<List<string>>(name);
 76 | 
 77 |             // Assert
 78 |             Assert.Equal(contextInput, result);
 79 |         }
 80 | 
 81 |         [Fact]
 82 |         public void GetContext_ValidNameWithStringCaseDiffernce_ReturnsContext()
 83 |         {
 84 |             // Arrange
 85 |             var fixture = new Fixture();
 86 |             var contextInput = fixture.Create<string>();
 87 |             var context = new Dictionary<string, object> {
 88 |                 { nameof(contextInput), contextInput }
 89 |             };
 90 |             var parentRuleResult = new RuleResultTree();
 91 | 
 92 |             var actionContext = new ActionContext(context, parentRuleResult);
 93 |             string name = nameof(contextInput).ToUpper();
 94 | 
 95 |             // Act
 96 |             var result = actionContext.GetContext<string>(name);
 97 | 
 98 |             // Assert
 99 |             Assert.Equal(contextInput, result);
100 |         }
101 | 
102 |         [Fact]
103 |         public void GetContext_InvalidName_ThrowsArgumentException()
104 |         {
105 |             // Arrange
106 |             var fixture = new Fixture();
107 |             var contextInput = fixture.Create<string>();
108 |             var context = new Dictionary<string, object> {
109 |                 { nameof(contextInput), contextInput }
110 |             };
111 |             var parentRuleResult = new RuleResultTree();
112 | 
113 |             var actionContext = new ActionContext(context, parentRuleResult);
114 |             string name = fixture.Create<string>();
115 | 
116 |             // Act
117 |             Assert.Throws<ArgumentException>(() => actionContext.GetContext<string>(name));
118 |         }
119 | 
120 |         [Fact]
121 |         public void GetContext_PrimitiveInputs_ReturnsResult()
122 |         {
123 |             // Arrange
124 |             var fixture = new Fixture();
125 |             var intInput = fixture.Create<int>();
126 |             var strInput = fixture.Create<string>();
127 |             var floatInput = fixture.Create<float>();
128 | 
129 |             var context = new Dictionary<string, object> {
130 |                 { nameof(intInput), intInput },
131 |                 { nameof(strInput), strInput },
132 |                 { nameof(floatInput), floatInput },
133 |             };
134 |             var parentRuleResult = new RuleResultTree();
135 | 
136 |             var actionContext = new ActionContext(context, parentRuleResult);
137 | 
138 |             // Act
139 |             var intResult = actionContext.GetContext<int>(nameof(intInput));
140 |             var strResult = actionContext.GetContext<string>(nameof(strInput));
141 |             var floatResult = actionContext.GetContext<float>(nameof(floatInput));
142 | 
143 |             // Assert
144 |             Assert.Equal(intInput, intResult);
145 |             Assert.Equal(strInput, strResult);
146 |             Assert.Equal(floatInput, floatResult);
147 |         }
148 | 
149 |         [Fact]
150 |         public void GetContext_InvalidNameListContext_ThrowsArgumentException()
151 |         {
152 |             // Arrange
153 |             var fixture = new Fixture();
154 |             var contextInput = fixture.CreateMany<string>();
155 |             var context = new Dictionary<string, object> {
156 |                 { nameof(contextInput), contextInput }
157 |             };
158 |             var parentRuleResult = new RuleResultTree();
159 | 
160 |             var actionContext = new ActionContext(context, parentRuleResult);
161 |             string name = fixture.Create<string>();
162 | 
163 |             // Act
164 |             Assert.Throws<ArgumentException>(() => actionContext.GetContext<List<string>>(name));
165 |         }
166 | 
167 |         [Fact]
168 |         public void GetContext_InvalidTypeConversion_ThrowsArgumentException()
169 |         {
170 |             // Arrange
171 |             var fixture = new Fixture();
172 |             var contextInput = fixture.CreateMany<string>();
173 |             var context = new Dictionary<string, object> {
174 |                 { nameof(contextInput), contextInput }
175 |             };
176 |             var parentRuleResult = new RuleResultTree();
177 | 
178 |             var actionContext = new ActionContext(context, parentRuleResult);
179 |             string name = nameof(contextInput);
180 | 
181 |             // Act
182 |             Assert.Throws<ArgumentException>(() => actionContext.GetContext<RuleResultTree>(name));
183 |         }
184 |     }
185 | }
186 | 


--------------------------------------------------------------------------------
/test/RulesEngine.UnitTest/ActionTests/CustomActionTest.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | // Licensed under the MIT License.
 3 | 
 4 | using Newtonsoft.Json;
 5 | using RulesEngine.Models;
 6 | using RulesEngine.UnitTest.ActionTests.MockClass;
 7 | using System.Collections.Generic;
 8 | using System.Diagnostics.CodeAnalysis;
 9 | using System.Text.Json.Serialization;
10 | using System.Threading.Tasks;
11 | using Xunit;
12 | 
13 | namespace RulesEngine.UnitTest.ActionTests
14 | {
15 |     [ExcludeFromCodeCoverage]
16 |     public class CustomActionTest
17 |     {
18 |         [Fact]
19 |         public async Task CustomActionOnRuleMustHaveContextValues()
20 |         {
21 |             var workflow = GetWorkflow();
22 |             var re = new RulesEngine(workflow, reSettings: new ReSettings {
23 |                 CustomActions = new Dictionary<string, System.Func<Actions.ActionBase>> {
24 | 
25 |                     { "ReturnContext", () => new ReturnContextAction() }
26 |                 }
27 |             });
28 | 
29 |             var result = await re.ExecuteAllRulesAsync("successReturnContextAction", true);
30 |         }
31 | 
32 | 
33 |         [Fact]
34 |         public async Task CustomAction_WithSystemTextJsobOnRuleMustHaveContextValues()
35 |         {
36 |             var workflow = GetWorkflow();
37 |             var workflowStr = JsonConvert.SerializeObject(workflow);
38 |             var serializationOptions = new System.Text.Json.JsonSerializerOptions { Converters = { new JsonStringEnumConverter() } };
39 |             var workflowViaTextJson = System.Text.Json.JsonSerializer.Deserialize<Workflow[]>(workflowStr,serializationOptions);
40 | 
41 | 
42 |             var re = new RulesEngine(workflow, reSettings: new ReSettings {
43 |                 CustomActions = new Dictionary<string, System.Func<Actions.ActionBase>> {
44 | 
45 |                     { "ReturnContext", () => new ReturnContextAction() }
46 |                 }
47 |             });
48 | 
49 | 
50 | 
51 |             var result = await re.ExecuteAllRulesAsync("successReturnContextAction", true);
52 |         }
53 | 
54 |         private Workflow[] GetWorkflow()
55 |         {
56 |             return new Workflow[] {
57 |                 new Workflow {
58 |                     WorkflowName = "successReturnContextAction",
59 |                     Rules = new Rule[] {
60 |                         new Rule {
61 |                             RuleName = "trueRule",
62 |                             Expression = "input1 == true",
63 |                             Actions = new RuleActions() {
64 |                                 OnSuccess = new ActionInfo {
65 |                                     Name = "ReturnContext",
66 |                                     Context =  new Dictionary<string, object> {
67 |                                         {"stringContext", "hello"},
68 |                                         {"intContext",1 },
69 |                                         {"objectContext", new { a = "hello", b = 123 } }
70 |                                     }
71 |                                 }
72 | 
73 |                             }
74 | 
75 |                         },
76 |                         
77 | 
78 |                     }
79 |                 }
80 | 
81 |             };
82 |         }
83 | 
84 | 
85 |     }
86 | }
87 | 


--------------------------------------------------------------------------------
/test/RulesEngine.UnitTest/ActionTests/MockClass/ReturnContextAction.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | // Licensed under the MIT License.
 3 | 
 4 | using RulesEngine.Actions;
 5 | using RulesEngine.Models;
 6 | using System;
 7 | using System.Collections.Generic;
 8 | using System.Diagnostics.CodeAnalysis;
 9 | using System.Text;
10 | using System.Threading.Tasks;
11 | 
12 | namespace RulesEngine.UnitTest.ActionTests.MockClass
13 | {
14 |     [ExcludeFromCodeCoverage]
15 |     public class ReturnContextAction : ActionBase
16 |     {
17 |         public override ValueTask<object> Run(ActionContext context, RuleParameter[] ruleParameters)
18 |         {
19 |             var stringContext = context.GetContext<string>("stringContext");
20 |             var intContext = context.GetContext<int>("intContext");
21 |             var objectContext = context.GetContext<object>("objectContext");
22 | 
23 |             return new ValueTask<object>(new {
24 |                 stringContext,
25 |                 intContext,
26 |                 objectContext
27 |             });
28 |         }
29 |     }
30 | }
31 | 


--------------------------------------------------------------------------------
/test/RulesEngine.UnitTest/CaseSensitiveTests.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | // Licensed under the MIT License.
 3 | 
 4 | using RulesEngine.Models;
 5 | using System;
 6 | using System.Collections.Generic;
 7 | using System.Diagnostics.CodeAnalysis;
 8 | using System.Linq;
 9 | using System.Text;
10 | using System.Threading.Tasks;
11 | using Xunit;
12 | 
13 | namespace RulesEngine.UnitTest
14 | {
15 |     [ExcludeFromCodeCoverage]
16 |     public class CaseSensitiveTests
17 |     {
18 |         [Theory]
19 |         [InlineData(true,true,false)]
20 |         [InlineData(false,true,true)]
21 |         public async Task CaseSensitiveTest(bool caseSensitive, bool expected1, bool expected2)
22 |         {
23 |             var reSettings = new ReSettings {
24 |                 IsExpressionCaseSensitive = caseSensitive
25 |             };
26 | 
27 | 
28 |             var worflow = new Workflow {
29 |                 WorkflowName = "CaseSensitivityTest",
30 |                 Rules = new[] {
31 |                     new Rule {
32 |                         RuleName = "check same case1",
33 |                         Expression = "input1 == \"hello\""
34 |                     },
35 |                     new Rule {
36 |                         RuleName = "check same case2",
37 |                         Expression = "INPUT1 == \"hello\""
38 |                     }
39 |                 }
40 |             };
41 | 
42 |             var re = new RulesEngine(new[] { worflow }, reSettings);
43 |             var result = await re.ExecuteAllRulesAsync("CaseSensitivityTest", "hello");
44 | 
45 |             Assert.Equal(expected1, result[0].IsSuccess);
46 |             Assert.Equal(expected2, result[1].IsSuccess);
47 |         }
48 | 
49 | 
50 | 
51 | 
52 |     }
53 | }
54 | 


--------------------------------------------------------------------------------
/test/RulesEngine.UnitTest/CustomTypeProviderTests.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | // Licensed under the MIT License.
 3 | 
 4 | using Moq;
 5 | using System;
 6 | using System.Collections.Generic;
 7 | using System.Diagnostics.CodeAnalysis;
 8 | using System.Linq;
 9 | using Xunit;
10 | 
11 | namespace RulesEngine.UnitTest
12 | {
13 |     [Trait("Category", "Unit")]
14 |     [ExcludeFromCodeCoverage]
15 |     public class CustomTypeProviderTests : IDisposable
16 |     {
17 |         public void Dispose()
18 |         {
19 |         }
20 | 
21 |         private CustomTypeProvider CreateProvider(params Type[] customTypes)
22 |         {
23 |             return new CustomTypeProvider(customTypes);
24 |         }
25 | 
26 |         [Fact]
27 |         public void GetCustomTypes_DefaultProvider_IncludesEnumerableAndObject()
28 |         {
29 |             var provider = CreateProvider();
30 |             var allTypes = provider.GetCustomTypes();
31 |             Assert.NotEmpty(allTypes);
32 |             Assert.Contains(typeof(System.Linq.Enumerable), allTypes);
33 |             Assert.Contains(typeof(object), allTypes);
34 |         }
35 | 
36 |         [Fact]
37 |         public void GetCustomTypes_WithListOfGuid_ContainsIEnumerableOfGuid()
38 |         {
39 |             var initial = new[] { typeof(List<Guid>) };
40 |             var provider = CreateProvider(initial);
41 |             var allTypes = provider.GetCustomTypes();
42 |             Assert.Contains(typeof(IEnumerable<Guid>), allTypes);
43 |             Assert.Contains(typeof(List<Guid>), allTypes);
44 |             Assert.Contains(typeof(System.Linq.Enumerable), allTypes);
45 |             Assert.Contains(typeof(object), allTypes);
46 |         }
47 | 
48 |         [Fact]
49 |         public void GetCustomTypes_ListOfListString_ContainsIEnumerableOfListString()
50 |         {
51 |             var nestedListType = typeof(List<List<string>>);
52 |             var provider = CreateProvider(nestedListType);
53 |             var allTypes = provider.GetCustomTypes();
54 |             Assert.Contains(typeof(IEnumerable<List<string>>), allTypes);
55 |             Assert.Contains(nestedListType, allTypes);
56 |             Assert.Contains(typeof(System.Linq.Enumerable), allTypes);
57 |             Assert.Contains(typeof(object), allTypes);
58 |         }
59 | 
60 |         [Fact]
61 |         public void GetCustomTypes_ArrayOfStringArrays_ContainsIEnumerableOfStringArray()
62 |         {
63 |             var arrayType = typeof(string[][]);
64 |             var provider = CreateProvider(arrayType);
65 |             var allTypes = provider.GetCustomTypes();
66 |             Assert.Contains(typeof(IEnumerable<string[]>), allTypes);
67 |             Assert.Contains(arrayType, allTypes);
68 |             Assert.Contains(typeof(System.Linq.Enumerable), allTypes);
69 |             Assert.Contains(typeof(object), allTypes);
70 |         }
71 | 
72 |         [Fact]
73 |         public void GetCustomTypes_NullableIntArray_ContainsIEnumerableOfNullableInt()
74 |         {
75 |             var nullableInt = typeof(int?);
76 |             var arrayType = typeof(int?[]);
77 |             var provider = CreateProvider(arrayType);
78 |             var allTypes = provider.GetCustomTypes();
79 |             Assert.Contains(typeof(IEnumerable<int?>), allTypes);
80 |             Assert.Contains(arrayType, allTypes);
81 |             Assert.Contains(typeof(System.Linq.Enumerable), allTypes);
82 |             Assert.Contains(typeof(object), allTypes);
83 |         }
84 | 
85 |         [Fact]
86 |         public void GetCustomTypes_MultipleTypes_NoDuplicates()
87 |         {
88 |             var repeatedType = typeof(List<string>);
89 |             var provider = CreateProvider(repeatedType, repeatedType);
90 |             var allTypes = provider.GetCustomTypes();
91 |             var matches = allTypes.Where(t => t == repeatedType).ToList();
92 |             Assert.Single(matches);
93 |             var interfaceMatches = allTypes.Where(t => t == typeof(IEnumerable<string>)).ToList();
94 |             Assert.Single(interfaceMatches);
95 |         }
96 |     }
97 | }
98 | 


--------------------------------------------------------------------------------
/test/RulesEngine.UnitTest/EmptyRulesTest.cs:
--------------------------------------------------------------------------------
  1 | // Copyright (c) Microsoft Corporation.
  2 | // Licensed under the MIT License.
  3 | 
  4 | using Newtonsoft.Json;
  5 | using RulesEngine.Models;
  6 | using System;
  7 | using System.Collections.Generic;
  8 | using System.Diagnostics.CodeAnalysis;
  9 | using System.Dynamic;
 10 | using System.Linq;
 11 | using System.Text.Json.Serialization;
 12 | using System.Threading.Tasks;
 13 | using Xunit;
 14 | 
 15 | namespace RulesEngine.UnitTest
 16 | {
 17 |     [ExcludeFromCodeCoverage]
 18 |     public class EmptyRulesTest
 19 |     {
 20 |         [Fact]
 21 |         private async Task EmptyRules_ReturnsExepectedResults()
 22 |         {
 23 |             var workflow = GetEmptyWorkflow();
 24 |             var reSettings = new ReSettings { };
 25 |             RulesEngine rulesEngine = new RulesEngine();
 26 | 
 27 |             Func<Task> action = () => {
 28 |                 new RulesEngine(workflow, reSettings: reSettings);
 29 |                 return Task.CompletedTask;
 30 |             };
 31 | 
 32 |             Exception ex = await Assert.ThrowsAsync<Exceptions.RuleValidationException>(action);
 33 | 
 34 |             Assert.Contains("Atleast one of Rules or WorkflowsToInject must be not empty", ex.Message);
 35 |         }
 36 |         [Fact]
 37 |         private async Task NestedRulesWithEmptyNestedActions_ReturnsExepectedResults()
 38 |         {
 39 |             var workflow = GetEmptyNestedWorkflows();
 40 |             var reSettings = new ReSettings { };
 41 |             RulesEngine rulesEngine = new RulesEngine();
 42 | 
 43 |             Func<Task> action = () => {
 44 |                 new RulesEngine(workflow, reSettings: reSettings);
 45 |                 return Task.CompletedTask;
 46 |             };
 47 | 
 48 |             Exception ex = await Assert.ThrowsAsync<Exceptions.RuleValidationException>(action);
 49 | 
 50 |             Assert.Contains("Atleast one of Rules or WorkflowsToInject must be not empty", ex.Message);
 51 |         }
 52 | 
 53 |         private Workflow[] GetEmptyWorkflow()
 54 |         {
 55 |             return new[] {
 56 |                 new Workflow {
 57 |                     WorkflowName = "EmptyRulesTest",
 58 |                     Rules = new Rule[] {
 59 |                     }
 60 |                 }
 61 |             };
 62 |         }
 63 | 
 64 |         private Workflow[] GetEmptyNestedWorkflows()
 65 |         {
 66 |             return new[] {
 67 |                 new Workflow {
 68 |                     WorkflowName = "EmptyNestedRulesTest",
 69 |                     Rules = new Rule[] {
 70 |                         new Rule {
 71 |                             RuleName = "AndRuleTrueFalse",
 72 |                             Operator = "And",
 73 |                             Rules = new Rule[] {
 74 |                                 new Rule{
 75 |                                     RuleName = "trueRule1",
 76 |                                     Expression = "input1.TrueValue == true",
 77 |                                 },
 78 |                                 new Rule {
 79 |                                     RuleName = "falseRule1",
 80 |                                     Expression = "input1.TrueValue == false"
 81 |                                 }
 82 | 
 83 |                             }
 84 |                         },
 85 |                         new Rule {
 86 |                             RuleName = "OrRuleTrueFalse",
 87 |                             Operator = "Or",
 88 |                             Rules = new Rule[] {
 89 |                                 new Rule{
 90 |                                     RuleName = "trueRule2",
 91 |                                     Expression = "input1.TrueValue == true",
 92 |                                 },
 93 |                                 new Rule {
 94 |                                     RuleName = "falseRule2",
 95 |                                     Expression = "input1.TrueValue == false"
 96 |                                 }
 97 | 
 98 |                             }
 99 |                         },
100 |                         new Rule {
101 |                             RuleName = "AndRuleFalseTrue",
102 |                             Operator = "And",
103 |                             Rules = new Rule[] {
104 |                                 new Rule{
105 |                                     RuleName = "trueRule3",
106 |                                     Expression = "input1.TrueValue == false",
107 |                                 },
108 |                                 new Rule {
109 |                                     RuleName = "falseRule4",
110 |                                     Expression = "input1.TrueValue == true"
111 |                                 }
112 | 
113 |                             }
114 |                         },
115 |                          new Rule {
116 |                             RuleName = "OrRuleFalseTrue",
117 |                             Operator = "Or",
118 |                             Rules = new Rule[] {
119 |                                 new Rule{
120 |                                     RuleName = "trueRule3",
121 |                                     Expression = "input1.TrueValue == false",
122 |                                 },
123 |                                 new Rule {
124 |                                     RuleName = "falseRule4",
125 |                                     Expression = "input1.TrueValue == true"
126 |                                 }
127 | 
128 |                             }
129 |                          }
130 |                     }
131 |                 },
132 |                 new Workflow {
133 |                     WorkflowName = "EmptyNestedRulesActionsTest",
134 |                     Rules = new Rule[] {
135 |                         new Rule {
136 |                             RuleName = "AndRuleTrueFalse",
137 |                             Operator = "And",
138 |                             Rules = new Rule[] {
139 | 
140 |                             },
141 |                             Actions =  new RuleActions {
142 |                                         OnFailure = new ActionInfo{
143 |                                             Name = "OutputExpression",
144 |                                             Context = new Dictionary<string, object> {
145 |                                                 { "Expression", "input1.TrueValue" }
146 |                                             }
147 |                                         }
148 |                                     }
149 |                         }
150 |                     }
151 |                 }
152 | 
153 |             };
154 |         }
155 |     }
156 | }
157 | 


--------------------------------------------------------------------------------
/test/RulesEngine.UnitTest/ExpressionUtilsTest.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | // Licensed under the MIT License.
 3 | 
 4 | using RulesEngine.HelperFunctions;
 5 | using System.Diagnostics.CodeAnalysis;
 6 | using Xunit;
 7 | 
 8 | namespace RulesEngine.UnitTest
 9 | {
10 |     [Trait("Category", "Unit")]
11 |     [ExcludeFromCodeCoverage]
12 |     public class ExpressionUtilsTest
13 |     {
14 |         [Fact]
15 |         public void CheckContainsTest()
16 |         {
17 |             var result = ExpressionUtils.CheckContains("", "");
18 |             Assert.False(result);
19 | 
20 |             result = ExpressionUtils.CheckContains(null, "");
21 |             Assert.False(result);
22 | 
23 |             result = ExpressionUtils.CheckContains("4", "1,2,3,4,5");
24 |             Assert.True(result);
25 | 
26 |             result = ExpressionUtils.CheckContains("6", "1,2,3,4,5");
27 |             Assert.False(result);
28 |         }
29 |     }
30 | }
31 | 


--------------------------------------------------------------------------------
/test/RulesEngine.UnitTest/LambdaExpressionBuilderTest.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | // Licensed under the MIT License.
 3 | 
 4 | using RulesEngine.ExpressionBuilders;
 5 | using RulesEngine.Models;
 6 | using System.Collections.Generic;
 7 | using System.Diagnostics.CodeAnalysis;
 8 | using System.Linq;
 9 | using Xunit;
10 | 
11 | namespace RulesEngine.UnitTest
12 | {
13 |     [Trait("Category", "Unit")]
14 |     [ExcludeFromCodeCoverage]
15 |     public class LambdaExpressionBuilderTest
16 |     {
17 |         [Fact]
18 |         public void BuildExpressionForRuleTest()
19 |         {
20 |             var reSettings = new ReSettings();
21 |             var objBuilderFactory = new RuleExpressionBuilderFactory(reSettings, new RuleExpressionParser(reSettings));
22 |             var builder = objBuilderFactory.RuleGetExpressionBuilder(RuleExpressionType.LambdaExpression);
23 | 
24 |             var ruleParameters = new RuleParameter[] {
25 |                 new RuleParameter("RequestType","Sales"),
26 |                 new RuleParameter("RequestStatus", "Active"),
27 |                 new RuleParameter("RegistrationStatus", "InProcess")
28 |             };
29 | 
30 | 
31 |             var mainRule = new Rule {
32 |                 RuleName = "rule1",
33 |                 Operator = "And",
34 |                 Rules = new List<Rule>()
35 |             };
36 | 
37 |             var dummyRule = new Rule {
38 |                 RuleName = "testRule1",
39 |                 RuleExpressionType = RuleExpressionType.LambdaExpression,
40 |                 Expression = "RequestType == \"vod\""
41 |             };
42 | 
43 |             mainRule.Rules = mainRule.Rules.Append(dummyRule);
44 |             var func = builder.BuildDelegateForRule(dummyRule, ruleParameters);
45 | 
46 |             Assert.NotNull(func);
47 |             Assert.Equal(typeof(RuleResultTree), func.Method.ReturnType);
48 |         }
49 |     }
50 | }
51 | 


--------------------------------------------------------------------------------
/test/RulesEngine.UnitTest/ListofRuleResultTreeExtensionTest.cs:
--------------------------------------------------------------------------------
  1 | // Copyright (c) Microsoft Corporation.
  2 | //  Licensed under the MIT License.
  3 | 
  4 | using RulesEngine.Extensions;
  5 | using RulesEngine.Models;
  6 | using System.Collections.Generic;
  7 | using System.Diagnostics.CodeAnalysis;
  8 | using Xunit;
  9 | 
 10 | namespace RulesEngine.UnitTest
 11 | {
 12 |     [Trait("Category", "Unit")]
 13 |     [ExcludeFromCodeCoverage]
 14 |     public class ListofRuleResultTreeExtensionTest
 15 |     {
 16 |         [Fact]
 17 |         public void OnSuccessWithSuccessTest()
 18 |         {
 19 |             var rulesResultTree = new List<RuleResultTree>()
 20 |             {
 21 |                 new RuleResultTree()
 22 |                 {
 23 |                     ChildResults = null,
 24 |                     ExceptionMessage = string.Empty,
 25 |                     Inputs = new Dictionary<string, object>(),
 26 |                     IsSuccess = true,
 27 |                     Rule = new Rule()
 28 |                     {
 29 |                         RuleName = "Test Rule 1"
 30 |                     }
 31 |                 },
 32 |                 new RuleResultTree()
 33 |                 {
 34 |                     ChildResults = null,
 35 |                     ExceptionMessage = string.Empty,
 36 |                     Inputs = new Dictionary<string, object>(),
 37 |                     IsSuccess = false,
 38 |                     Rule = new Rule()
 39 |                     {
 40 |                         RuleName = "Test Rule 2"
 41 |                     }
 42 |                 },
 43 | 
 44 |             };
 45 | 
 46 |             var successEventName = string.Empty;
 47 | 
 48 |             rulesResultTree.OnSuccess((eventName) => {
 49 |                 successEventName = eventName;
 50 |             });
 51 | 
 52 |             Assert.Equal("Test Rule 1", successEventName);
 53 |         }
 54 | 
 55 |         [Fact]
 56 |         public void OnSuccessWithSuccessWithEventTest()
 57 |         {
 58 |             var rulesResultTree = new List<RuleResultTree>()
 59 |             {
 60 |                 new RuleResultTree()
 61 |                 {
 62 |                     ChildResults = null,
 63 |                     ExceptionMessage = string.Empty,
 64 |                     Inputs = new Dictionary<string, object>(),
 65 |                     IsSuccess = true,
 66 |                     Rule = new Rule()
 67 |                     {
 68 |                         RuleName = "Test Rule 1",
 69 |                         SuccessEvent = "Event 1"
 70 |                     }
 71 |                 },
 72 |                 new RuleResultTree()
 73 |                 {
 74 |                     ChildResults = null,
 75 |                     ExceptionMessage = string.Empty,
 76 |                     Inputs = new Dictionary<string, object>(),
 77 |                     IsSuccess = false,
 78 |                     Rule = new Rule()
 79 |                     {
 80 |                         RuleName = "Test Rule 2"
 81 |                     }
 82 |                 },
 83 | 
 84 |             };
 85 | 
 86 |             var successEventName = string.Empty;
 87 | 
 88 |             rulesResultTree.OnSuccess((eventName) => {
 89 |                 successEventName = eventName;
 90 |             });
 91 | 
 92 |             Assert.Equal("Event 1", successEventName);
 93 |         }
 94 | 
 95 |         [Fact]
 96 |         public void OnSuccessWithouSuccessTest()
 97 |         {
 98 |             var rulesResultTree = new List<RuleResultTree>()
 99 |             {
100 |                 new RuleResultTree()
101 |                 {
102 |                     ChildResults = null,
103 |                     ExceptionMessage = string.Empty,
104 |                     Inputs = new Dictionary<string, object>(),
105 |                     IsSuccess = false,
106 |                     Rule = new Rule()
107 |                     {
108 |                         RuleName = "Test Rule 1"
109 |                     }
110 |                 },
111 |                 new RuleResultTree()
112 |                 {
113 |                     ChildResults = null,
114 |                     ExceptionMessage = string.Empty,
115 |                     Inputs = new Dictionary<string, object>(),
116 |                     IsSuccess = false,
117 |                     Rule = new Rule()
118 |                     {
119 |                         RuleName = "Test Rule 2"
120 |                     }
121 |                 },
122 | 
123 |             };
124 | 
125 |             var successEventName = string.Empty;
126 | 
127 |             rulesResultTree.OnSuccess((eventName) => {
128 |                 successEventName = eventName;
129 |             });
130 | 
131 |             Assert.Equal(successEventName, string.Empty);
132 |         }
133 | 
134 | 
135 |         [Fact]
136 |         public void OnFailWithSuccessTest()
137 |         {
138 |             var rulesResultTree = new List<RuleResultTree>()
139 |             {
140 |                 new RuleResultTree()
141 |                 {
142 |                     ChildResults = null,
143 |                     ExceptionMessage = string.Empty,
144 |                     Inputs = new Dictionary<string, object>(),
145 |                     IsSuccess = true,
146 |                     Rule = new Rule()
147 |                     {
148 |                         RuleName = "Test Rule 1"
149 |                     }
150 |                 },
151 |                 new RuleResultTree()
152 |                 {
153 |                     ChildResults = null,
154 |                     ExceptionMessage = string.Empty,
155 |                     Inputs = new Dictionary<string, object>(),
156 |                     IsSuccess = false,
157 |                     Rule = new Rule()
158 |                     {
159 |                         RuleName = "Test Rule 2"
160 |                     }
161 |                 },
162 | 
163 |             };
164 | 
165 |             var successEventName = true;
166 | 
167 |             rulesResultTree.OnFail(() => {
168 |                 successEventName = false;
169 |             });
170 | 
171 |             Assert.True(successEventName);
172 |         }
173 | 
174 |         [Fact]
175 |         public void OnFailWithoutSuccessTest()
176 |         {
177 |             var rulesResultTree = new List<RuleResultTree>()
178 |             {
179 |                 new RuleResultTree()
180 |                 {
181 |                     ChildResults = null,
182 |                     ExceptionMessage = string.Empty,
183 |                     Inputs = new Dictionary<string, object>(),
184 |                     IsSuccess = false,
185 |                     Rule = new Rule()
186 |                     {
187 |                         RuleName = "Test Rule 1"
188 |                     }
189 |                 },
190 |                 new RuleResultTree()
191 |                 {
192 |                     ChildResults = null,
193 |                     ExceptionMessage = string.Empty,
194 |                     Inputs = new Dictionary<string, object>(),
195 |                     IsSuccess = false,
196 |                     Rule = new Rule()
197 |                     {
198 |                         RuleName = "Test Rule 2"
199 |                     }
200 |                 },
201 | 
202 |             };
203 | 
204 |             var successEventName = true;
205 | 
206 |             rulesResultTree.OnFail(() => {
207 |                 successEventName = false;
208 |             });
209 | 
210 |             Assert.False(successEventName);
211 |         }
212 |     }
213 | }
214 | 


--------------------------------------------------------------------------------
/test/RulesEngine.UnitTest/ParameterNameChangeTest.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | // Licensed under the MIT License.
 3 | 
 4 | using RulesEngine.Models;
 5 | using System;
 6 | using System.Diagnostics.CodeAnalysis;
 7 | using System.Dynamic;
 8 | using System.Linq;
 9 | using System.Threading.Tasks;
10 | using Xunit;
11 | 
12 | namespace RulesEngine.UnitTest
13 | {
14 |     [ExcludeFromCodeCoverage]
15 |     public class ParameterNameChangeTest
16 |     {
17 |         [Fact]
18 |         public async Task RunTwiceTest_ReturnsExpectedResults()
19 |         {
20 |             var workflow = new Workflow {
21 |                 WorkflowName = "ParameterNameChangeWorkflow",
22 |                 Rules = new Rule[] {
23 |                     new Rule {
24 |                         RuleName = "ParameterNameChangeRule",
25 |                         RuleExpressionType = RuleExpressionType.LambdaExpression,
26 |                         Expression = "test.blah == 1"
27 |                     }
28 |                 }
29 |             };
30 |             var engine = new RulesEngine();
31 |             engine.AddOrUpdateWorkflow(workflow);
32 | 
33 |             dynamic dynamicBlah = new ExpandoObject();
34 |             dynamicBlah.blah = (Int64)1;
35 |             var input_pass = new RuleParameter("test", dynamicBlah);
36 |             var input_fail = new RuleParameter("SOME_OTHER_NAME", dynamicBlah);
37 |             // RuleParameter name matches expression, so should pass.
38 |             var pass_results = await engine.ExecuteAllRulesAsync("ParameterNameChangeWorkflow", input_pass);
39 |             // RuleParameter name DOES NOT MATCH expression, so should fail.
40 |             var fail_results = await engine.ExecuteAllRulesAsync("ParameterNameChangeWorkflow", input_fail);
41 |             Assert.True(pass_results.First().IsSuccess);
42 |             Assert.False(fail_results.First().IsSuccess);
43 |         }
44 |     }
45 | }
46 | 


--------------------------------------------------------------------------------
/test/RulesEngine.UnitTest/RuleCompilerTest.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | // Licensed under the MIT License.
 3 | 
 4 | using RulesEngine.ExpressionBuilders;
 5 | using RulesEngine.Models;
 6 | using System;
 7 | using System.Diagnostics.CodeAnalysis;
 8 | using Xunit;
 9 | 
10 | namespace RulesEngine.UnitTest
11 | {
12 |     [Trait("Category", "Unit")]
13 |     [ExcludeFromCodeCoverage]
14 |     public class RuleCompilerTest
15 |     {
16 |         [Fact]
17 |         public void RuleCompiler_NullCheck()
18 |         {
19 |             Assert.Throws<ArgumentNullException>(() => new RuleCompiler(null, null));
20 |             var reSettings = new ReSettings();
21 |             var parser = new RuleExpressionParser(reSettings);
22 |             Assert.Throws<ArgumentNullException>(() => new RuleCompiler(null, null));
23 |         }
24 | 
25 |         [Fact]
26 |         public void RuleCompiler_CompileRule_ThrowsException()
27 |         {
28 |             var reSettings = new ReSettings();
29 |             var parser = new RuleExpressionParser(reSettings);
30 |             var compiler = new RuleCompiler(new RuleExpressionBuilderFactory(reSettings, parser),null);
31 |             Assert.Throws<ArgumentNullException>(() => compiler.CompileRule(null, RuleExpressionType.LambdaExpression,null,null));
32 |             Assert.Throws<ArgumentNullException>(() => compiler.CompileRule(null, RuleExpressionType.LambdaExpression, new RuleParameter[] { null },null));
33 |         }
34 |     }
35 | }
36 | 


--------------------------------------------------------------------------------
/test/RulesEngine.UnitTest/RuleExpressionBuilderFactoryTest.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | // Licensed under the MIT License.
 3 | 
 4 | using RulesEngine.ExpressionBuilders;
 5 | using RulesEngine.Models;
 6 | using System;
 7 | using System.Diagnostics.CodeAnalysis;
 8 | using Xunit;
 9 | 
10 | namespace RulesEngine.UnitTest
11 | {
12 |     [Trait("Category", "Unit")]
13 |     [ExcludeFromCodeCoverage]
14 |     public class RuleExpressionBuilderFactoryTest
15 |     {
16 |         [Theory]
17 |         [InlineData(RuleExpressionType.LambdaExpression, typeof(LambdaExpressionBuilder))]
18 |         public void RuleGetExpressionBuilderTest(RuleExpressionType expressionType, Type expectedExpressionBuilderType)
19 |         {
20 |             var reSettings = new ReSettings();
21 |             var parser = new RuleExpressionParser(reSettings);
22 |             var objBuilderFactory = new RuleExpressionBuilderFactory(reSettings, parser);
23 |             var builder = objBuilderFactory.RuleGetExpressionBuilder(expressionType);
24 | 
25 |             var builderType = builder.GetType();
26 |             Assert.Equal(expectedExpressionBuilderType.ToString(), builderType.ToString());
27 |         }
28 |     }
29 | }
30 | 


--------------------------------------------------------------------------------
/test/RulesEngine.UnitTest/RuleExpressionParserTests/RuleExpressionParserTests.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | // Licensed under the MIT License.
 3 | 
 4 | using Newtonsoft.Json.Linq;
 5 | using RulesEngine.ExpressionBuilders;
 6 | using RulesEngine.Models;
 7 | using System.Diagnostics.CodeAnalysis;
 8 | using Xunit;
 9 | 
10 | namespace RulesEngine.UnitTest.RuleExpressionParserTests
11 | {
12 |     [ExcludeFromCodeCoverage]
13 |     public class RuleExpressionParserTests
14 |     {
15 |         public RuleExpressionParserTests() { 
16 |            
17 |         
18 |         }
19 | 
20 | 
21 |         [Fact]
22 |         public void TestExpressionWithJObject()
23 |         {
24 |             var settings = new ReSettings {
25 |                 CustomTypes = new[]
26 |                 {
27 |                     typeof(JObject),
28 |                     typeof(JToken),
29 |                     typeof(JArray)
30 |                 }
31 |             };
32 |             var parser = new RuleExpressionParser(settings);
33 | 
34 |             var json = @"{
35 |                ""list"": [
36 |                     { ""item1"": ""hello"", ""item3"": 1 },
37 |                     { ""item2"": ""world"" }
38 |                ]
39 |             }";
40 |             var input = JObject.Parse(json);
41 | 
42 |             var result1 = parser.Evaluate<object>(
43 |                 "Convert.ToInt32(input[\"list\"][0][\"item3\"]) == 1",
44 |                 new[] { new RuleParameter("input", input) }
45 |             );
46 |             Assert.True((bool)result1);
47 | 
48 |             var result2 = parser.Evaluate<object>(
49 |                 "Convert.ToString(input[\"list\"][1][\"item2\"]) == \"world\"",
50 |                 new[] { new RuleParameter("input", input) }
51 |             );
52 |             Assert.True((bool)result2);
53 | 
54 |             var result3 = parser.Evaluate<object>(
55 |                 "string.Concat(" +
56 |                   "Convert.ToString(input[\"list\"][0][\"item1\"]), " +
57 |                   "Convert.ToString(input[\"list\"][1][\"item2\"]))",
58 |                 new[] { new RuleParameter("input", input) }
59 |             );
60 |             Assert.Equal("helloworld", result3);
61 |         }
62 | 
63 |         [Theory]
64 |         [InlineData(false)]
65 |         public void TestExpressionWithDifferentCompilerSettings(bool fastExpressionEnabled){
66 |             var ruleParser = new RuleExpressionParser(new Models.ReSettings() { UseFastExpressionCompiler = fastExpressionEnabled });
67 | 
68 |             decimal? d1 = null;
69 |             var result = ruleParser.Evaluate<bool>("d1 < 20", new[] { Models.RuleParameter.Create("d1", d1) });
70 |             Assert.False(result);
71 |         }
72 |     }
73 | 
74 |     
75 | }
76 | 


--------------------------------------------------------------------------------
/test/RulesEngine.UnitTest/RuleParameterTests.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | // Licensed under the MIT License.
 3 | 
 4 | using AutoFixture;
 5 | using RulesEngine.Models;
 6 | using System;
 7 | using Xunit;
 8 | 
 9 | namespace RulesEngine.UnitTest;
10 | public class RuleParameterTests
11 | {
12 |     [Fact]
13 |     public void Create_SetsPropertiesCorrectly()
14 |     {
15 |         var fixture = new Fixture();
16 |         var name = fixture.Create<string>();
17 |         var type = fixture.Create<Type>();
18 | 
19 |         var result = RuleParameter.Create(name, type);
20 | 
21 |         Assert.Equal(name, result.Name);
22 |         Assert.Equal(type, result.Type);
23 |     }
24 | }
25 | 


--------------------------------------------------------------------------------
/test/RulesEngine.UnitTest/RuleTestClass.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | //  Licensed under the MIT License.
 3 | 
 4 | using Newtonsoft.Json;
 5 | using System.Diagnostics.CodeAnalysis;
 6 | 
 7 | namespace RulesEngine.UnitTest
 8 | {
 9 |     [ExcludeFromCodeCoverage]
10 |     public class RuleTestClass
11 |     {
12 |         [JsonProperty("country")]
13 |         public string Country { get; set; }
14 | 
15 |         [JsonProperty("loyaltyFactor")]
16 |         public int loyaltyFactor { get; set; }
17 |         public int TotalPurchasesToDate { get; set; }
18 |     }
19 | }
20 | 


--------------------------------------------------------------------------------
/test/RulesEngine.UnitTest/RuleValidationTest.cs:
--------------------------------------------------------------------------------
 1 | // Copyright (c) Microsoft Corporation.
 2 | // Licensed under the MIT License.
 3 | 
 4 | using Newtonsoft.Json;
 5 | using RulesEngine.HelperFunctions;
 6 | using RulesEngine.Models;
 7 | using System;
 8 | using System.Collections.Generic;
 9 | using System.Diagnostics.CodeAnalysis;
10 | using System.Dynamic;
11 | using System.Linq;
12 | using System.Text.Json.Serialization;
13 | using System.Threading.Tasks;
14 | using Xunit;
15 | 
16 | namespace RulesEngine.UnitTest
17 | {
18 |     [ExcludeFromCodeCoverage]
19 |     public class RuleValidationTest
20 |     {
21 |         [Fact]
22 |         public async Task NullExpressionithLambdaExpression_ReturnsExepectedResults()
23 |         {
24 |             var workflow = GetNullExpressionithLambdaExpressionWorkflow();
25 |             var reSettings = new ReSettings { };
26 |             RulesEngine rulesEngine = new RulesEngine();
27 | 
28 |             Func<Task> action = () => {
29 |                 new RulesEngine(workflow, reSettings: reSettings);
30 |                 return Task.CompletedTask;
31 |             };
32 | 
33 |             Exception ex = await Assert.ThrowsAsync<Exceptions.RuleValidationException>(action);
34 | 
35 |             Assert.Contains(Constants.LAMBDA_EXPRESSION_EXPRESSION_NULL_ERRMSG, ex.Message);
36 | 
37 |         }
38 | 
39 |         [Fact]
40 |         public async Task NestedRulesWithMissingOperator_ReturnsExepectedResults()
41 |         {
42 |             var workflow = GetEmptyOperatorWorkflow();
43 |             var reSettings = new ReSettings { };
44 |             RulesEngine rulesEngine = new RulesEngine();
45 | 
46 |             Func<Task> action = () => {
47 |                 new RulesEngine(workflow, reSettings: reSettings);
48 |                 return Task.CompletedTask;
49 |             };
50 | 
51 |             Exception ex = await Assert.ThrowsAsync<Exceptions.RuleValidationException>(action);
52 | 
53 |             Assert.Contains(Constants.OPERATOR_RULES_ERRMSG, ex.Message);
54 | 
55 |         }
56 | 
57 |         private Workflow[] GetNullExpressionithLambdaExpressionWorkflow()
58 |         {
59 |             return new[] {
60 |                 new Workflow {
61 |                     WorkflowName = "NestedRulesTest",
62 |                     Rules = new Rule[] {
63 |                         new Rule {
64 |                             RuleName = "TestRule",
65 |                             RuleExpressionType = RuleExpressionType.LambdaExpression,
66 |                         }
67 |                     }
68 |                 }
69 |             };
70 |         }
71 | 
72 |         private Workflow[] GetEmptyOperatorWorkflow()
73 |         {
74 |             return new[] {
75 |                 new Workflow {
76 |                     WorkflowName = "NestedRulesTest",
77 |                     Rules = new Rule[] {
78 |                         new Rule {
79 |                             RuleName = "AndRuleTrueFalse",
80 |                             Expression = "true == true",
81 |                             Rules = new Rule[] {
82 |                                 new Rule{
83 |                                     RuleName = "trueRule1",
84 |                                     Expression = "input1.TrueValue == true",
85 |                                 },
86 |                                 new Rule {
87 |                                     RuleName = "falseRule1",
88 |                                     Expression = "input1.TrueValue == false"
89 |                                 }
90 | 
91 |                             }
92 |                         }
93 |                     }
94 |                 }
95 |             };
96 |         }
97 |     }
98 | }
99 | 


--------------------------------------------------------------------------------
/test/RulesEngine.UnitTest/RulesEngine.UnitTest.csproj:
--------------------------------------------------------------------------------
 1 | <Project Sdk="Microsoft.NET.Sdk">
 2 |   <PropertyGroup>
 3 |     <TargetFrameworks>net6.0;net8.0;net9.0</TargetFrameworks>
 4 |     <SignAssembly>True</SignAssembly>
 5 |     <AssemblyOriginatorKeyFile>..\..\signing\RulesEngine-publicKey.snk</AssemblyOriginatorKeyFile>
 6 |     <DelaySign>True</DelaySign>
 7 |   </PropertyGroup>
 8 |   <ItemGroup>
 9 |     <PackageReference Include="AutoFixture" Version="5.0.0-preview0012" />
10 |     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
11 |     <PackageReference Include="Moq" Version="4.20.72" />
12 |     <PackageReference Include="System.Text.Json" Version="9.0.2" />
13 |     <PackageReference Include="xunit" Version="2.9.3" />
14 |     <PackageReference Include="xunit.runner.visualstudio" Version="3.0.2">
15 |       <PrivateAssets>all</PrivateAssets>
16 |       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
17 |     </PackageReference>
18 |     <PackageReference Include="coverlet.collector" Version="6.0.4">
19 |       <PrivateAssets>all</PrivateAssets>
20 |       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
21 |     </PackageReference>
22 |   </ItemGroup>
23 |   <ItemGroup>
24 |     <ProjectReference Include="..\..\src\RulesEngine\RulesEngine.csproj" />
25 |   </ItemGroup>
26 |   <ItemGroup>
27 |     <None Update="TestData\rules1.json">
28 |       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
29 |     </None>
30 |     <None Update="TestData\rules11.json">
31 |       <CopyToOutputDirectory>Always</CopyToOutputDirectory>
32 |     </None>
33 |     <None Update="TestData\rules4.json">
34 |       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
35 |     </None>
36 |     <None Update="TestData\rules3.json">
37 |       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
38 |     </None>
39 |     <None Update="TestData\rules2.json">
40 |       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
41 |     </None>
42 |     <None Update="TestData\rules5.json">
43 |       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
44 |     </None>
45 |     <None Update="TestData\rules6.json">
46 |       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
47 |     </None>
48 |     <None Update="TestData\rules7.json">
49 |       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
50 |     </None>
51 |     <None Update="TestData\rules8.json">
52 |       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
53 |     </None>
54 |     <None Update="TestData\rules10.json">
55 |       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
56 |     </None>
57 |     <None Update="TestData\rules9.json">
58 |       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
59 |     </None>
60 |   </ItemGroup>
61 | </Project>


--------------------------------------------------------------------------------
/test/RulesEngine.UnitTest/TestData/rules1.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "WorkflowName": "inputWorkflow",
 3 |   "Rules": [
 4 |     {
 5 |       "RuleName": "GiveDiscount10",
 6 |       "SuccessEvent": "10",
 7 |       "ErrorMessage": "One or more adjust rules failed.",
 8 |       "ErrorType": "Error",
 9 |       "RuleExpressionType": "LambdaExpression",
10 |       "Expression": "input1.country == \"canada\" AND input1.loyaltyFactor <= 4"
11 |     }
12 |   ]
13 | }


--------------------------------------------------------------------------------
/test/RulesEngine.UnitTest/TestData/rules10.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "WorkflowName": "inputWorkflow",
 3 |     "Rules": [
 4 |       {
 5 |         "RuleName": "GiveDiscount10",
 6 |         "SuccessEvent": "10",
 7 |         "RuleExpressionType": "LambdaExpression",
 8 |         "Expression": "input1.Data.GetProperty(\"category\").GetString() == \"abc\""
 9 |       }
10 |     ]
11 |   } 


--------------------------------------------------------------------------------
/test/RulesEngine.UnitTest/TestData/rules11.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "WorkflowName": "MyWorkflow",
 3 |   "WorkflowsToInject": null,
 4 |   "RuleExpressionType": 0,
 5 |   "GlobalParams": [
 6 |     {
 7 |       "Name": "threshold",
 8 |       "Expression": "double.Parse(\u00220.25\u0022)"
 9 |     }
10 |   ],
11 |   "Rules": [
12 |     {
13 |       "RuleName": "Activation",
14 |       "Properties": null,
15 |       "Operator": null,
16 |       "ErrorMessage": null,
17 |       "Enabled": true,
18 |       "RuleExpressionType": 0,
19 |       "WorkflowsToInject": null,
20 |       "Rules": null,
21 |       "LocalParams": [
22 |         {
23 |           "Name": "ruleCount",
24 |           "Expression": "int.Parse(\u002215\u0022)"
25 |         }
26 |       ],
27 |       "Expression": "input1.Count \u003E= ruleCount \u0026\u0026 input1.Where(x =\u003E x.Value \u003E= threshold).Count() \u003E= ruleCount",
28 |       "Actions": null,
29 |       "SuccessEvent": null
30 |     },
31 |     {
32 |       "RuleName": "Deactivation",
33 |       "Properties": null,
34 |       "Operator": null,
35 |       "ErrorMessage": null,
36 |       "Enabled": true,
37 |       "RuleExpressionType": 0,
38 |       "WorkflowsToInject": null,
39 |       "Rules": null,
40 |       "LocalParams": [
41 |         {
42 |           "Name": "ruleCount",
43 |           "Expression": "int.Parse(\u002230\u0022)"
44 |         }
45 |       ],
46 |       "Expression": "input1.Count \u003E= ruleCount \u0026\u0026 input1.OrderByDescending(o =\u003E o.ChangeDateTime).Take(ruleCount).All(a =\u003E a.Value \u003C threshold)",
47 |       "Actions": null,
48 |       "SuccessEvent": null
49 |     }
50 |   ]
51 | }


--------------------------------------------------------------------------------
/test/RulesEngine.UnitTest/TestData/rules2.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "WorkflowName": "inputWorkflow",
 3 |   "Rules": [
 4 |     {
 5 |       "RuleName": "Rule1",
 6 |       "Operator": "Or",
 7 |       "ErrorMessage": "One or more adjust rules failed.",
 8 |       "ErrorType": "Error",
 9 |       "Rules": [
10 |         {
11 |           "RuleName": "GiveDiscount10",
12 |           "SuccessEvent": "10",
13 |           "ErrorMessage": "One or more adjust rules failed.",
14 |           "ErrorType": "Error",
15 |           "RuleExpressionType": "LambdaExpression",
16 |           "Expression": "input1.country == \"india\" AND input1.loyaltyFactor <= 2 AND input1.totalPurchasesToDate >= 5000 AND input2.totalOrders > 2 AND input3.noOfVisitsPerMonth > 2"
17 |         },
18 |         {
19 |           "RuleName": "GiveDiscount20",
20 |           "SuccessEvent": "20",
21 |           "ErrorMessage": "One or more adjust rules failed.",
22 |           "ErrorType": "Error",
23 |           "RuleExpressionType": "LambdaExpression",
24 |           "Expression": "input1.country == \"india\" AND input1.loyaltyFactor == 3 AND input1.totalPurchasesToDate >= 10000 AND input2.totalOrders > 2 AND input3.noOfVisitsPerMonth > 2"
25 |         },
26 |         {
27 |           "RuleName": "GiveDiscount25",
28 |           "SuccessEvent": "25",
29 |           "ErrorMessage": "One or more adjust rules failed.",
30 |           "ErrorType": "Error",
31 |           "RuleExpressionType": "LambdaExpression",
32 |           "Expression": "input1.country != \"india\" AND input1.loyaltyFactor >= 2 AND input1.totalPurchasesToDate >= 10000 AND input2.totalOrders > 2 AND input3.noOfVisitsPerMonth > 5"
33 |         }
34 |       ]
35 |     }
36 |   ]
37 | }
38 | 


--------------------------------------------------------------------------------
/test/RulesEngine.UnitTest/TestData/rules3.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "WorkflowName": "inputWorkflow",
 3 |   "Rules": [
 4 |     {
 5 |       "RuleName": "GiveDiscount10",
 6 |       "SuccessEvent": "10",
 7 |       "ErrorMessage": "One or more adjust rules failed.",
 8 |       "ErrorType": "Error",
 9 |       "RuleExpressionType": "LambdaExpression",
10 |       "Expression": "input1.couy == \"india\" AND input1.loyaltyFactor <= 2 AND input1.totalPurchasesToDate >= 5000 AND input2.totalOrders > 2 AND input2.noOfVisitsPerMonth > 2"
11 |     }
12 |   ]
13 | }


--------------------------------------------------------------------------------
/test/RulesEngine.UnitTest/TestData/rules4.json:
--------------------------------------------------------------------------------
 1 | [
 2 |   {
 3 |     "WorkflowName": "inputWorkflow",
 4 |     "Rules": [
 5 |       {
 6 |         "RuleName": "GiveDiscount10",
 7 |         "SuccessEvent": "10",
 8 |         "ErrorMessage": "One or more adjust rules failed, with loyaltyFactor : $(model1.loyaltyFactor), country : $(model1.country), totalPurchasesToDate : $(model1.totalPurchasesToDate), model2 : $(model2)",
 9 |         "ErrorType": "Error",
10 |         "localParams": [
11 |           {
12 |             "Name": "model1",
13 |             "Expression": "input1.FirstOrDefault(country.Equals(\"india\", StringComparison.OrdinalIgnoreCase))"
14 |           },
15 |           {
16 |             "Name": "model2",
17 |             "Expression": "model1.country == \"india\""
18 |           }
19 |         ],
20 |         "RuleExpressionType": "LambdaExpression",
21 |         "Expression": "model1.country == \"india\" AND model1.loyaltyFactor <= 2 AND model1.totalPurchasesToDate >= 5000 AND model2"
22 |       },
23 |       {
24 |         "RuleName": "GiveDiscount100",
25 |         "SuccessEvent": "10",
26 |         "ErrorType": "Error",
27 |         "localParams": [
28 |           {
29 |             "Name": "model1",
30 |             "Expression": "input1.FirstOrDefault(country.Equals(\"india\", StringComparison.OrdinalIgnoreCase))"
31 |           },
32 |           {
33 |             "Name": "model2",
34 |             "Expression": "model1.country == \"india\""
35 |           }
36 |         ],
37 |         "RuleExpressionType": "LambdaExpression",
38 |         "Expression": "model1.country == \"india\" AND model1.loyaltyFactor < 0 AND model1.totalPurchasesToDate >= 5000 AND model2"
39 |       },
40 |       {
41 |         "RuleName": "GiveDiscount25",
42 |         "SuccessEvent": "25",
43 |         "ErrorMessage": "One or more adjust rules failed, country : $(input4.country), loyaltyFactor : $(input4.loyaltyFactor), totalPurchasesToDate : $(input4.totalPurchasesToDate), totalOrders : $(input5.totalOrders), noOfVisitsPerMonth : $(input30.noOfVisitsPerMonth), $(model2)",
44 |         "ErrorType": "Error",
45 |         "localParams": [
46 |           {
47 |             "Name": "model1",
48 |             "Expression": "input1.FirstOrDefault(country.Equals(\"india\", StringComparison.OrdinalIgnoreCase))"
49 |           },
50 |           {
51 |             "Name": "model2",
52 |             "Expression": "model1.country == \"india\""
53 |           }
54 |         ],
55 |         "RuleExpressionType": "LambdaExpression",
56 |         "Expression": "input4.country == \"india\" AND input4.loyaltyFactor >= 2 AND input4.totalPurchasesToDate <= 10 AND input5.totalOrders > 2 AND input3.noOfVisitsPerMonth > 5"
57 |       },
58 |       {
59 |         "RuleName": "GiveDiscount30",
60 |         "SuccessEvent": "30",
61 |         "ErrorMessage": "One or more adjust rules failed.",
62 |         "ErrorType": "Error",
63 |         "RuleExpressionType": "LambdaExpression",
64 |         "Expression": "input4.loyaltyFactor > 30 AND input4.totalPurchasesToDate >= 50000 AND input4.totalPurchasesToDate <= 100000 AND input5.totalOrders > 5 AND input3.noOfVisitsPerMonth > 15"
65 |       },
66 |       {
67 |         "RuleName": "GiveDiscount35",
68 |         "SuccessEvent": "35",
69 |         "ErrorMessage": "One or more adjust rules failed, totalPurchasesToDate : $(input4.totalPurchasesToDate), totalOrders : $(input5.totalOrders)",
70 |         "ErrorType": "Error",
71 |         "RuleExpressionType": "LambdaExpression",
72 |         "Expression": "input4.loyaltyFactor > 30 AND input4.totalPurchasesToDate >= 100000 AND input5.totalOrders > 15 AND input3.noOfVisitsPerMonth > 25"
73 |       }
74 |     ]
75 |   }
76 | ]


--------------------------------------------------------------------------------
/test/RulesEngine.UnitTest/TestData/rules5.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "WorkflowName": "inputWorkflow",
 3 |   "Rules": [
 4 |     {
 5 |       "RuleName": "upperCaseAccess",
 6 |       "SuccessEvent": "10",
 7 |       "ErrorMessage": "One or more adjust rules failed.",
 8 |       "ErrorType": "Error",
 9 |       "RuleExpressionType": "LambdaExpression",
10 |       "Expression": "utils.CheckExists(String(input1.Property1)) == true"
11 |     },
12 |     {
13 |       "RuleName": "lowerCaseAccess",
14 |       "SuccessEvent": "10",
15 |       "ErrorMessage": "One or more adjust rules failed.",
16 |       "ErrorType": "Error",
17 |       "RuleExpressionType": "LambdaExpression",
18 |       "Expression": "utils.CheckExists(String(input1.property1)) == true"
19 |     }
20 |   ]
21 | } 


--------------------------------------------------------------------------------
/test/RulesEngine.UnitTest/TestData/rules6.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "WorkflowName": "inputWorkflow",
 3 |     "Rules": [
 4 |         {
 5 |             "RuleName": "GiveDiscount10",
 6 |             "SuccessEvent": "10",
 7 |             "ErrorMessage": "One or more adjust rules failed.",
 8 |             "ErrorType": "Error",
 9 |             "RuleExpressionType": "LambdaExpression",
10 |             "Expression": "input1.Property1.Contains(\"hell\")"
11 |         },
12 |         {
13 |             "RuleName": "GiveDiscount20",
14 |             "SuccessEvent": "20",
15 |             "ErrorMessage": "One or more adjust rules failed.",
16 |             "ErrorType": "Error",
17 |             "RuleExpressionType": "LambdaExpression",
18 |             "Expression": "input1.Property1.Contains(\"hell\") && !input1.Boolean"
19 |         },
20 |         {
21 |             "RuleName": "GiveDiscount30",
22 |             "SuccessEvent": "30",
23 |             "ErrorMessage": "One or more adjust rules failed.",
24 |             "ErrorType": "Error",
25 |             "RuleExpressionType": "LambdaExpression",
26 |             "Expression": "input1.Method.Invoke()"
27 |         }
28 |     ]
29 | }


--------------------------------------------------------------------------------
/test/RulesEngine.UnitTest/TestData/rules7.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "WorkflowName": "inputWorkflow",
 3 |     "Rules": [
 4 |         {
 5 |             "RuleName": "GiveDiscount10",
 6 |             "SuccessEvent": "10",
 7 |             "ErrorMessage": "One or more adjust rules failed.",
 8 |             "ErrorType": "Error",
 9 |             "RuleExpressionType": "LambdaExpression",
10 |             "Expression": "!input1.Boolean"
11 |         },
12 |         {
13 |             "RuleName": "GiveDiscount20",
14 |             "SuccessEvent": "20",
15 |             "ErrorMessage": "One or more adjust rules failed.",
16 |             "ErrorType": "Error",
17 |             "RuleExpressionType": "LambdaExpression",
18 |             "Expression": "!input1.Boolean && true"
19 |         }
20 |     ]
21 | }


--------------------------------------------------------------------------------
/test/RulesEngine.UnitTest/TestData/rules8.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "WorkflowName": "inputWorkflow",
 3 |     "Rules": [
 4 |         {
 5 |             "RuleName": "GiveDiscount10",
 6 |             "SuccessEvent": "10",
 7 |             "ErrorMessage": "One or more adjust rules failed.",
 8 |             "ErrorType": "Error",
 9 |             "RuleExpressionType": "LambdaExpression",
10 |             "Expression": "input1.Boolean"
11 |         },
12 |         {
13 |             "RuleName": "GiveDiscount20",
14 |             "SuccessEvent": "20",
15 |             "ErrorMessage": "One or more adjust rules failed.",
16 |             "ErrorType": "Error",
17 |             "RuleExpressionType": "LambdaExpression",
18 |             "Expression": "input1.Boolean && true || (input1.Boolean)"
19 |         }
20 |     ]
21 | }


--------------------------------------------------------------------------------
/test/RulesEngine.UnitTest/TestData/rules9.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "WorkflowName": "inputWorkflow",
 3 |     "Rules": [
 4 |       {
 5 |         "RuleName": "GiveDiscount10",
 6 |         "SuccessEvent": "10",
 7 |         "ErrorMessage": "One or more adjust rules failed.",
 8 |         "ErrorType": "Error",
 9 |         "RuleExpressionType": "LambdaExpression",
10 |         "Expression": "input1.Boolean"
11 |       },
12 |       {
13 |         "RuleName": "GiveDiscount20",
14 |         "SuccessEvent": "20",
15 |         "ErrorMessage": "One or more adjust rules failed.",
16 |         "ErrorType": "Error",
17 |         "RuleExpressionType": "LambdaExpression",
18 |         "Expression": "input1.Boolean && input1.Data.NotExistingMethod()"
19 |       }
20 |     ]
21 |   } 


--------------------------------------------------------------------------------
/test/RulesEngine.UnitTest/UtilsTests.cs:
--------------------------------------------------------------------------------
  1 | // Copyright (c) Microsoft Corporation.
  2 | // Licensed under the MIT License.
  3 | 
  4 | using Newtonsoft.Json.Linq;
  5 | using RulesEngine.HelperFunctions;
  6 | using System;
  7 | using System.Collections.Generic;
  8 | using System.Diagnostics.CodeAnalysis;
  9 | using System.Dynamic;
 10 | using Xunit;
 11 | 
 12 | namespace RulesEngine.UnitTest
 13 | {
 14 |     [ExcludeFromCodeCoverage]
 15 |     public class TestClass
 16 |     {
 17 |         public string Test { get; set; }
 18 |         public List<int> TestList { get; set; }
 19 |     }
 20 | 
 21 |     [Trait("Category", "Unit")]
 22 |     [ExcludeFromCodeCoverage]
 23 |     public class UtilsTests
 24 |     {
 25 | 
 26 |         [Fact]
 27 |         public void GetTypedObject_dynamicObject()
 28 |         {
 29 |             dynamic obj = new ExpandoObject();
 30 |             obj.Test = "hello";
 31 |             obj.TestList = new List<int> { 1, 2, 3 };
 32 |             object typedobj = Utils.GetTypedObject(obj);
 33 |             Assert.IsNotType<ExpandoObject>(typedobj);
 34 |             Assert.NotNull(typedobj.GetType().GetProperty("Test"));
 35 |         }
 36 | 
 37 |         [Fact]
 38 |         public void GetTypedObject_dynamicObject_multipleObjects()
 39 |         {
 40 |             dynamic obj = new ExpandoObject();
 41 |             obj.Test = "hello";
 42 |             obj.TestList = new List<int> { 1, 2, 3 };
 43 |             dynamic obj2 = new ExpandoObject();
 44 |             obj2.Test = "world";
 45 |             obj2.TestList = new List<int> { 1, 2, 3 };
 46 |             object typedobj = Utils.GetTypedObject(obj);
 47 |             object typedobj2 = Utils.GetTypedObject(obj2);
 48 |             Assert.IsNotType<ExpandoObject>(typedobj);
 49 |             Assert.NotNull(typedobj.GetType().GetProperty("Test"));
 50 |             Assert.Equal(typedobj.GetType(), typedobj2.GetType());
 51 |         }
 52 | 
 53 | 
 54 |         [Fact]
 55 |         public void GetTypedObject_nonDynamicObject()
 56 |         {
 57 |             var obj = new {
 58 |                 Test = "hello"
 59 |             };
 60 |             var typedobj = Utils.GetTypedObject(obj);
 61 |             Assert.IsNotType<ExpandoObject>(typedobj);
 62 |             Assert.NotNull(typedobj.GetType().GetProperty("Test"));
 63 |         }
 64 | 
 65 | 
 66 |         [Fact]
 67 |         public void GetJObject_nonDynamicObject()
 68 |         {
 69 |             dynamic obj = JObject.FromObject(new {
 70 |                 Test = "hello"
 71 |             });
 72 |             dynamic typedobj = Utils.GetTypedObject(obj);
 73 |             Assert.IsNotType<ExpandoObject>(typedobj);
 74 |             Assert.IsType<JObject>(typedobj);
 75 |             Assert.NotNull(typedobj.Test);
 76 |         }
 77 | 
 78 | 
 79 |         [Fact]
 80 |         public void CreateObject_dynamicObject()
 81 |         {
 82 |             dynamic obj = new ExpandoObject();
 83 |             obj.Test = "test";
 84 |             obj.TestList = new List<int> { 1, 2, 3 };
 85 | 
 86 |             object newObj = Utils.CreateObject(typeof(TestClass), obj);
 87 |             Assert.IsNotType<ExpandoObject>(newObj);
 88 |             Assert.NotNull(newObj.GetType().GetProperty("Test"));
 89 | 
 90 |         }
 91 | 
 92 |         [Fact]
 93 |         public void CreateAbstractType_dynamicObject()
 94 |         {
 95 |             dynamic obj = new ExpandoObject();
 96 |             obj.Test = "test";
 97 |             obj.TestList = new List<int> { 1, 2, 3 };
 98 |             obj.testEmptyList = new List<object>();
 99 | 
100 |             Type type = Utils.CreateAbstractClassType(obj);
101 |             Assert.NotEqual(typeof(ExpandoObject), type);
102 |             Assert.NotNull(type.GetProperty("Test"));
103 | 
104 |         }
105 | 
106 | 
107 |     }
108 | }
109 | 


--------------------------------------------------------------------------------