• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

microsoft / RulesEngine / 12843565311

18 Jan 2025 11:22AM UTC coverage: 94.26% (-1.8%) from 96.012%
12843565311

push

github

web-flow
[Short-Circuiting The Evaluation While Parsing] (#638)

* short-circuiting dynamic evaluation

304 of 368 branches covered (82.61%)

19 of 20 new or added lines in 1 file covered. (95.0%)

11 existing lines in 4 files now uncovered.

624 of 662 relevant lines covered (94.26%)

103.95 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.85
/src/RulesEngine/RuleCompiler.cs
1
// Copyright (c) Microsoft Corporation.
2
// Licensed under the MIT License.
3

4
using RulesEngine.Exceptions;
5
using RulesEngine.ExpressionBuilders;
6
using RulesEngine.HelperFunctions;
7
using RulesEngine.Models;
8
using System;
9
using System.Collections.Generic;
10
using System.Linq;
11
using System.Linq.Expressions;
12

13
namespace RulesEngine
14
{
15
    /// <summary>
16
    /// Rule compilers
17
    /// </summary>
18
    internal class RuleCompiler
19
    {
20
        /// <summary>
21
        /// The nested operators
22
        /// </summary>
23
        private readonly ExpressionType[] nestedOperators = new ExpressionType[] { ExpressionType.And, ExpressionType.AndAlso, ExpressionType.Or, ExpressionType.OrElse };
94✔
24

25
        /// <summary>
26
        /// The expression builder factory
27
        /// </summary>
28
        private readonly RuleExpressionBuilderFactory _expressionBuilderFactory;
29
        private readonly ReSettings _reSettings;
30

31
        /// <summary>
32
        /// Initializes a new instance of the <see cref="RuleCompiler"/> class.
33
        /// </summary>
34
        /// <param name="expressionBuilderFactory">The expression builder factory.</param>
35
        /// <exception cref="ArgumentNullException">expressionBuilderFactory</exception>
36
        internal RuleCompiler(RuleExpressionBuilderFactory expressionBuilderFactory, ReSettings reSettings)
94✔
37
        {
38
            _expressionBuilderFactory = expressionBuilderFactory ?? throw new ArgumentNullException($"{nameof(expressionBuilderFactory)} can't be null.");
94✔
39
            _reSettings = reSettings;
92✔
40
        }
92✔
41

42
        /// <summary>
43
        /// Compiles the rule
44
        /// </summary>
45
        /// <typeparam name="T"></typeparam>
46
        /// <param name="rule"></param>
47
        /// <param name="input"></param>
48
        /// <param name="ruleParam"></param>
49
        /// <returns>Compiled func delegate</returns>
50
        internal RuleFunc<RuleResultTree> CompileRule(Rule rule, RuleExpressionType ruleExpressionType, RuleParameter[] ruleParams, Lazy<RuleExpressionParameter[]> globalParams)
51
        {
52
            if (rule == null)
130✔
53
            {
54
                var ex =  new ArgumentNullException(nameof(rule));
2✔
55
                throw ex;
2✔
56
            }
57
            try
58
            {
59
                var globalParamExp = globalParams.Value;
128✔
60
                var extendedRuleParams = ruleParams.Concat(globalParamExp.Select(c => new RuleParameter(c.ParameterExpression.Name,c.ParameterExpression.Type)))
147✔
61
                                                   .ToArray();
128✔
62
                var ruleExpression = GetDelegateForRule(rule, extendedRuleParams);
128✔
63
                
64

65
                return GetWrappedRuleFunc(rule,ruleExpression,ruleParams,globalParamExp);
128✔
66
            }
UNCOV
67
            catch (Exception ex)
×
68
            {
UNCOV
69
                var message = $"Error while compiling rule `{rule.RuleName}`: {ex.Message}";
×
UNCOV
70
                return Helpers.ToRuleExceptionResult(_reSettings, rule, new RuleException(message, ex));
×
71
            }
72
        }
128✔
73

74

75

76
        /// <summary>
77
        /// Gets the expression for rule.
78
        /// </summary>
79
        /// <param name="rule">The rule.</param>
80
        /// <param name="typeParameterExpressions">The type parameter expressions.</param>
81
        /// <param name="ruleInputExp">The rule input exp.</param>
82
        /// <returns></returns>
83
        private RuleFunc<RuleResultTree> GetDelegateForRule(Rule rule, RuleParameter[] ruleParams)
84
        {
85
            var scopedParamList = GetRuleExpressionParameters(rule.RuleExpressionType, rule?.LocalParams, ruleParams);
179!
86

87
            var extendedRuleParams = ruleParams.Concat(scopedParamList.Select(c => new RuleParameter(c.ParameterExpression.Name, c.ParameterExpression.Type)))
210✔
88
                                               .ToArray();
179✔
89

90
            RuleFunc<RuleResultTree> ruleFn;
91
            
92
            if (Enum.TryParse(rule.Operator, out ExpressionType nestedOperator) && nestedOperators.Contains(nestedOperator) &&
179✔
93
                rule.Rules != null && rule.Rules.Any())
179✔
94
            {
95
                ruleFn = BuildNestedRuleFunc(rule, nestedOperator, extendedRuleParams);
27✔
96
            }
97
            else
98
            {
99
                ruleFn = BuildRuleFunc(rule, extendedRuleParams);
152✔
100
            }
101

102
            return GetWrappedRuleFunc(rule, ruleFn, ruleParams, scopedParamList);
179✔
103
        }
104

105
        internal RuleExpressionParameter[] GetRuleExpressionParameters(RuleExpressionType ruleExpressionType,IEnumerable<ScopedParam> localParams, RuleParameter[] ruleParams)
106
        {
107
            if(!_reSettings.EnableScopedParams)
264✔
108
            {
109
                return new RuleExpressionParameter[] { };
7✔
110
            }
111
            var ruleExpParams = new List<RuleExpressionParameter>();
257✔
112

113
            if (localParams?.Any() == true)
257✔
114
            {
115

116
                var parameters = ruleParams.Select(c => c.ParameterExpression)
107✔
117
                                            .ToList();
38✔
118

119
                var expressionBuilder = GetExpressionBuilder(ruleExpressionType);
38✔
120

121
                foreach (var lp in localParams)
170✔
122
                {
123
                    try
124
                    {
125
                        var lpExpression = expressionBuilder.Parse(lp.Expression, parameters.ToArray(), null);
47✔
126
                        var ruleExpParam = new RuleExpressionParameter() {
47✔
127
                            ParameterExpression = Expression.Parameter(lpExpression.Type, lp.Name),
47✔
128
                            ValueExpression = lpExpression
47✔
129
                        };
47✔
130
                        parameters.Add(ruleExpParam.ParameterExpression);
47✔
131
                        ruleExpParams.Add(ruleExpParam);
47✔
132
                    }
47✔
133
                    catch(Exception ex)
134
                    {
UNCOV
135
                        var message = $"{ex.Message}, in ScopedParam: {lp.Name}";
×
UNCOV
136
                        throw new RuleException(message);
×
137
                    }
138
                }
139
            }
140
            return ruleExpParams.ToArray();
257✔
141

142
        }
143

144
        /// <summary>
145
        /// Builds the expression.
146
        /// </summary>
147
        /// <param name="rule">The rule.</param>
148
        /// <param name="typeParameterExpressions">The type parameter expressions.</param>
149
        /// <param name="ruleInputExp">The rule input exp.</param>
150
        /// <returns></returns>
151
        /// <exception cref="InvalidOperationException"></exception>
152
        private RuleFunc<RuleResultTree> BuildRuleFunc(Rule rule, RuleParameter[] ruleParams)
153
        {
154
            var ruleExpressionBuilder = GetExpressionBuilder(rule.RuleExpressionType);
152✔
155

156
            var ruleFunc = ruleExpressionBuilder.BuildDelegateForRule(rule, ruleParams);
152✔
157

158
            return ruleFunc;
152✔
159
        }
160

161
        /// <summary>
162
        /// Builds the nested expression.
163
        /// </summary>
164
        /// <param name="parentRule">The parent rule.</param>
165
        /// <param name="childRules">The child rules.</param>
166
        /// <param name="operation">The operation.</param>
167
        /// <param name="typeParameterExpressions">The type parameter expressions.</param>
168
        /// <param name="ruleInputExp">The rule input exp.</param>
169
        /// <returns>Expression of func delegate</returns>
170
        /// <exception cref="InvalidCastException"></exception>
171
        private RuleFunc<RuleResultTree> BuildNestedRuleFunc(Rule parentRule, ExpressionType operation, RuleParameter[] ruleParams)
172
        {
173
            var ruleFuncList = new List<RuleFunc<RuleResultTree>>();
27✔
174
            foreach (var r in parentRule.Rules.Where(c => c.Enabled))
213✔
175
            {
176
                ruleFuncList.Add(GetDelegateForRule(r, ruleParams));
51✔
177
            }
178

179
            return (paramArray) => {
27✔
180
                var (isSuccess, resultList) = ApplyOperation(paramArray, ruleFuncList, operation);
28✔
181
                bool isSuccessFn(object[] p) => isSuccess;
28✔
182
                var result = Helpers.ToResultTree(_reSettings, parentRule, resultList, isSuccessFn);
28✔
183
                return result(paramArray);
28✔
184
            };
27✔
185
        }
186

187

188
        private (bool isSuccess ,IEnumerable<RuleResultTree> result) ApplyOperation(RuleParameter[] paramArray,IEnumerable<RuleFunc<RuleResultTree>> ruleFuncList, ExpressionType operation)
189
        {
190
            if (ruleFuncList?.Any() != true)
28!
191
            {
192
                return (false,new List<RuleResultTree>());
3✔
193
            }
194

195
            var resultList = new List<RuleResultTree>();
25✔
196
            var isSuccess = false;
25✔
197

198
            if(operation == ExpressionType.And || operation == ExpressionType.AndAlso)
25✔
199
            {
200
                isSuccess = true;
12✔
201
            }
202

203
            foreach(var ruleFunc in ruleFuncList)
150✔
204
            {
205
                var ruleResult = ruleFunc(paramArray);
52✔
206
                resultList.Add(ruleResult);
52✔
207
                switch (operation)
208
                {
209
                    case ExpressionType.And:
210
                    case ExpressionType.AndAlso:
211
                        isSuccess = isSuccess && ruleResult.IsSuccess;
18✔
212
                        if(_reSettings.NestedRuleExecutionMode ==  NestedRuleExecutionMode.Performance && isSuccess == false)
18✔
213
                        {
214
                            return (isSuccess, resultList);
2✔
215
                        }
216
                        break;
217

218
                    case ExpressionType.Or:
219
                    case ExpressionType.OrElse:
220
                        isSuccess = isSuccess || ruleResult.IsSuccess;
34✔
221
                        if (_reSettings.NestedRuleExecutionMode == NestedRuleExecutionMode.Performance && isSuccess == true)
34✔
222
                        {
223
                            return (isSuccess, resultList);
2✔
224
                        }
225
                        break;
226
                }
227
                
228
            }
229
            return (isSuccess, resultList);
21✔
230
        }
4✔
231

232
        internal Func<object[],Dictionary<string,object>> CompileScopedParams(RuleExpressionType ruleExpressionType, RuleParameter[] ruleParameters,RuleExpressionParameter[] ruleExpParams)
233
        {
234
            return GetExpressionBuilder(ruleExpressionType).CompileScopedParams(ruleParameters, ruleExpParams);
41✔
235

236
        }
237

238
        private RuleFunc<RuleResultTree> GetWrappedRuleFunc(Rule rule, RuleFunc<RuleResultTree> ruleFunc,RuleParameter[] ruleParameters,RuleExpressionParameter[] ruleExpParams)
239
        {
240
            if(ruleExpParams.Length == 0)
307✔
241
            {
242
                return ruleFunc;
266✔
243
            }
244
            var paramDelegate = CompileScopedParams(rule.RuleExpressionType,ruleParameters, ruleExpParams);
41✔
245

246
            return (ruleParams) => {
41✔
247
                var inputs = ruleParams.Select(c => c.Value).ToArray();
115✔
248
                IEnumerable<RuleParameter> scopedParams;
41✔
249
                try
41✔
250
                {
41✔
251
                    var scopedParamsDict = paramDelegate(inputs);
42✔
252
                    scopedParams = scopedParamsDict.Select(c => new RuleParameter(c.Key, c.Value));
88✔
253
                }
39✔
254
                catch(Exception ex)
3✔
255
                {
41✔
256
                    var message = $"Error while executing scoped params for rule `{rule.RuleName}` - {ex}";
3✔
257
                    var resultFn = Helpers.ToRuleExceptionResult(_reSettings, rule, new RuleException(message, ex));
3✔
258
                    return resultFn(ruleParams);
3✔
259
                }
41✔
260
               
41✔
261
                var extendedInputs = ruleParams.Concat(scopedParams);
39✔
262
                var result = ruleFunc(extendedInputs.ToArray());
39✔
263
                return result;
39✔
264
            };
44✔
265
        }
266

267
        private RuleExpressionBuilderBase GetExpressionBuilder(RuleExpressionType expressionType)
268
        {
269
            return _expressionBuilderFactory.RuleGetExpressionBuilder(expressionType);
231✔
270
        }
271
    }
272
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc