/
RuleBase.cs
330 lines (284 loc) · 12.2 KB
/
RuleBase.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
#region License
// Copyright (c) .NET Foundation and contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// The latest version of this file can be found at https://github.com/FluentValidation/FluentValidation
#endregion
namespace FluentValidation.Internal {
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Results;
using Validators;
internal abstract class RuleBase<T, TProperty, TValue> : IValidationRule<T, TValue> {
private readonly List<RuleComponent<T, TValue>> _components = new();
private Func<CascadeMode> _cascadeModeThunk;
private string _propertyDisplayName;
private string _propertyName;
private Func<ValidationContext<T>, bool> _condition;
private Func<ValidationContext<T>, CancellationToken, Task<bool>> _asyncCondition;
private string _displayName;
private Func<ValidationContext<T>, string> _displayNameFactory;
public List<RuleComponent<T, TValue>> Components => _components;
/// <inheritdoc />
IEnumerable<IRuleComponent> IValidationRule.Components => _components;
/// <summary>
/// Condition for all validators in this rule.
/// </summary>
internal Func<ValidationContext<T>, bool> Condition => _condition;
/// <summary>
/// Asynchronous condition for all validators in this rule.
/// </summary>
internal Func<ValidationContext<T>, CancellationToken, Task<bool>> AsyncCondition => _asyncCondition;
/// <summary>
/// Property associated with this rule.
/// </summary>
public MemberInfo Member { get; }
/// <summary>
/// Function that can be invoked to retrieve the value of the property.
/// </summary>
public Func<T, TProperty> PropertyFunc { get; }
/// <summary>
/// Expression that was used to create the rule.
/// </summary>
public LambdaExpression Expression { get; }
/// <summary>
/// Sets the display name for the property.
/// </summary>
/// <param name="name">The property's display name</param>
public void SetDisplayName(string name) {
_displayName = name;
_displayNameFactory = null;
}
/// <summary>
/// Sets the display name for the property using a function.
/// </summary>
/// <param name="factory">The function for building the display name</param>
public void SetDisplayName(Func<ValidationContext<T>, string> factory) {
if (factory == null) throw new ArgumentNullException(nameof(factory));
_displayNameFactory = factory;
_displayName = null;
}
/// <summary>
/// Rule set that this rule belongs to (if specified)
/// </summary>
public string[] RuleSets { get; set; }
/// <summary>
/// Function that will be invoked if any of the validators associated with this rule fail.
/// </summary>
[Obsolete("OnFailure callbacks are deprecated and will be removed in FluentValidation 11. Please use a custom validator instead.")]
public Action<T, IEnumerable<ValidationFailure>> OnFailure { get; set; }
/// <summary>
/// The current rule component.
/// </summary>
public IRuleComponent<T, TValue> Current => _components.LastOrDefault();
/// <summary>
/// Type of the property being validated
/// </summary>
public Type TypeToValidate { get; }
/// <inheritdoc />
public bool HasCondition => Condition != null;
/// <inheritdoc />
public bool HasAsyncCondition => AsyncCondition != null;
/// <summary>
/// Cascade mode for this rule.
/// </summary>
public CascadeMode CascadeMode {
get => _cascadeModeThunk();
set => _cascadeModeThunk = () => value;
}
/// <summary>
/// Creates a new property rule.
/// </summary>
/// <param name="member">Property</param>
/// <param name="propertyFunc">Function to get the property value</param>
/// <param name="expression">Lambda expression used to create the rule</param>
/// <param name="cascadeModeThunk">Function to get the cascade mode.</param>
/// <param name="typeToValidate">Type to validate</param>
public RuleBase(MemberInfo member, Func<T, TProperty> propertyFunc, LambdaExpression expression, Func<CascadeMode> cascadeModeThunk, Type typeToValidate) {
Member = member;
PropertyFunc = propertyFunc;
Expression = expression;
TypeToValidate = typeToValidate;
_cascadeModeThunk = cascadeModeThunk;
var containerType = typeof(T);
PropertyName = ValidatorOptions.Global.PropertyNameResolver(containerType, member, expression);
_displayNameFactory = context => ValidatorOptions.Global.DisplayNameResolver(containerType, member, expression);
}
public void AddValidator(IPropertyValidator<T, TValue> validator) {
var component = new RuleComponent<T, TValue>(validator);
_components.Add(component);
}
public void AddAsyncValidator(IAsyncPropertyValidator<T, TValue> asyncValidator, IPropertyValidator<T, TValue> fallback = null) {
var component = new RuleComponent<T, TValue>(asyncValidator, fallback);
_components.Add(component);
}
// /// <summary>
// /// Replaces a validator in this rule. Used to wrap validators.
// /// </summary>
// public void Replace(PropertyValidatorOptions<T,TValue> original, PropertyValidatorOptions<T,TValue> newValidator) {
// var index = _validators.IndexOf(original);
//
// if (index > -1) {
// _validators[index] = newValidator;
// }
// }
// /// <summary>
// /// Remove a validator in this rule.
// /// </summary>
// public void Remove(PropertyValidatorOptions<T,TValue> original) {
// _validators.Remove(original);
// }
/// <summary>
/// Clear all validators from this rule.
/// </summary>
public void ClearValidators() {
_components.Clear();
}
/// <summary>
/// Returns the property name for the property being validated.
/// Returns null if it is not a property being validated (eg a method call)
/// </summary>
public string PropertyName {
get { return _propertyName; }
set {
_propertyName = value;
_propertyDisplayName = _propertyName.SplitPascalCase();
}
}
/// <summary>
/// Allows custom creation of an error message
/// </summary>
public Func<IMessageBuilderContext<T, TValue>, string> MessageBuilder { get; set; }
/// <summary>
/// Dependent rules
/// </summary>
internal List<IValidationRuleInternal<T>> DependentRules { get; private protected set; }
IEnumerable<IValidationRule> IValidationRule.DependentRules => DependentRules;
string IValidationRule.GetDisplayName(IValidationContext context) =>
GetDisplayName(context != null ? ValidationContext<T>.GetFromNonGenericContext(context) : null);
/// <summary>
/// Display name for the property.
/// </summary>
public string GetDisplayName(ValidationContext<T> context)
=> _displayNameFactory?.Invoke(context) ?? _displayName ?? _propertyDisplayName;
/// <summary>
/// Applies a condition to the rule
/// </summary>
/// <param name="predicate"></param>
/// <param name="applyConditionTo"></param>
public void ApplyCondition(Func<ValidationContext<T>, bool> predicate, ApplyConditionTo applyConditionTo = ApplyConditionTo.AllValidators) {
// Default behaviour for When/Unless as of v1.3 is to apply the condition to all previous validators in the chain.
if (applyConditionTo == ApplyConditionTo.AllValidators) {
foreach (var validator in _components) {
validator.ApplyCondition(predicate);
}
if (DependentRules != null) {
foreach (var dependentRule in DependentRules) {
dependentRule.ApplyCondition(predicate, applyConditionTo);
}
}
}
else {
Current.ApplyCondition(predicate);
}
}
/// <summary>
/// Applies the condition to the rule asynchronously
/// </summary>
/// <param name="predicate"></param>
/// <param name="applyConditionTo"></param>
public void ApplyAsyncCondition(Func<ValidationContext<T>, CancellationToken, Task<bool>> predicate, ApplyConditionTo applyConditionTo = ApplyConditionTo.AllValidators) {
// Default behaviour for When/Unless as of v1.3 is to apply the condition to all previous validators in the chain.
if (applyConditionTo == ApplyConditionTo.AllValidators) {
foreach (var validator in _components) {
validator.ApplyAsyncCondition(predicate);
}
if (DependentRules != null) {
foreach (var dependentRule in DependentRules) {
dependentRule.ApplyAsyncCondition(predicate, applyConditionTo);
}
}
}
else {
Current.ApplyAsyncCondition(predicate);
}
}
public void ApplySharedCondition(Func<ValidationContext<T>, bool> condition) {
if (_condition == null) {
_condition = condition;
}
else {
var original = _condition;
_condition = ctx => condition(ctx) && original(ctx);
}
}
public void ApplySharedAsyncCondition(Func<ValidationContext<T>, CancellationToken, Task<bool>> condition) {
if (_asyncCondition == null) {
_asyncCondition = condition;
}
else {
var original = _asyncCondition;
_asyncCondition = async (ctx, ct) => await condition(ctx, ct) && await original(ctx, ct);
}
}
object IValidationRule<T>.GetPropertyValue(T instance) => PropertyFunc(instance);
/// <summary>
/// Prepares the <see cref="MessageFormatter"/> of <paramref name="context"/> for an upcoming <see cref="ValidationFailure"/>.
/// </summary>
/// <param name="context">The validator context</param>
/// <param name="value">Property value.</param>
protected void PrepareMessageFormatterForValidationError(ValidationContext<T> context, TValue value) {
context.MessageFormatter.AppendPropertyName(context.DisplayName);
context.MessageFormatter.AppendPropertyValue(value);
// If there's a collection index cached in the root context data then add it
// to the message formatter. This happens when a child validator is executed
// as part of a call to RuleForEach. Usually parameters are not flowed through to
// child validators, but we make an exception for collection indices.
if (context.RootContextData.TryGetValue("__FV_CollectionIndex", out var index)) {
// If our property validator has explicitly added a placeholder for the collection index
// don't overwrite it with the cached version.
if (!context.MessageFormatter.PlaceholderValues.ContainsKey("CollectionIndex")) {
context.MessageFormatter.AppendArgument("CollectionIndex", index);
}
}
}
/// <summary>
/// Creates an error validation result for this validator.
/// </summary>
/// <param name="context">The validator context</param>
/// <param name="value">The property value</param>
/// <param name="component">The current rule component.</param>
/// <returns>Returns an error validation result.</returns>
protected ValidationFailure CreateValidationError(ValidationContext<T> context, TValue value, RuleComponent<T, TValue> component) {
var error = MessageBuilder != null
? MessageBuilder(new MessageBuilderContext<T, TValue>(context, value, component))
: component.GetErrorMessage(context, value);
var failure = new ValidationFailure(context.PropertyName, error, value);
failure.FormattedMessagePlaceholderValues = new Dictionary<string, object>(context.MessageFormatter.PlaceholderValues);
failure.ErrorCode = component.ErrorCode ?? ValidatorOptions.Global.ErrorCodeResolver(component.Validator);
failure.Severity = component.SeverityProvider != null
? component.SeverityProvider(context, value)
: ValidatorOptions.Global.Severity;
if (component.CustomStateProvider != null) {
failure.CustomState = component.CustomStateProvider(context, value);
}
return failure;
}
}
}