Repository navigation
Expand file tree
/
Copy pathValidatingInlineValidator.cs
More file actions
131 lines (106 loc) · 6.99 KB
/
Copy pathValidatingInlineValidator.cs
File metadata and controls
131 lines (106 loc) · 6.99 KB
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
namespace CoreEx.Validation;
/// <summary>
/// Provides an implementation of the <see cref="InlineValidator{TValue}"/> that can be used directly for inline-style validation that also supports <see cref="IValidatorEx{TValue}"/>.
/// </summary>
/// <typeparam name="TValue">The value <see cref="Type"/>.</typeparam>
/// <param name="configure">The action to configure the <see cref="ValidatingInlineValidator{T}"/>.</param>
internal sealed class ValidatingInlineValidator<TValue>(Action<ValidatingInlineValidator<TValue>.Validator>? configure) : InlineValidator<TValue>(configure), IValidatorEx<TValue>
{
/// <summary>
/// Overrides the property and JSON names.
/// </summary>
/// <param name="name">The property name.</param>
/// <param name="jsonName">The JSON name where different from the property name.</param>
/// <returns>The <see cref="ValidatingInlineValidator{TValue}"/> to support fluent-style method-chaining.</returns>
/// <remarks>This will apply to all instances in which the <see cref="ValidatingInlineValidator{TValue}"/> is used; therefore, caution is required when using. This is intended for advanced usage only.</remarks>
public ValidatingInlineValidator<TValue> WithName(string name, string? jsonName = null)
{
OverrideName = name.ThrowIfNullOrEmpty();
OverrideJsonName = jsonName;
return this;
}
/// <summary>
/// Overrides the property text.
/// </summary>
/// <param name="text">The property text.</param>
/// <returns>The <see cref="ValidatingInlineValidator{TValue}"/> to support fluent-style method-chaining.</returns>
/// <remarks>This will apply to all instances in which the <see cref="ValidatingInlineValidator{TValue}"/> is used; therefore, caution is required when using. This is intended for advanced usage only.</remarks>
public ValidatingInlineValidator<TValue> WithText(LText text)
{
OverrideText = text;
return this;
}
/// <inheritdoc/>
Task<IValidationContext<TValue>> IValidatorEx<TValue>.ValidateAsync(TValue value, ValidationArgs? args, CancellationToken cancellationToken) => ValidateInternalAsync(value, args, cancellationToken);
/// <inheritdoc/>
async Task IValidatorEx<TValue>.ValidateAndThrowAsync(TValue value, ValidationArgs? args, CancellationToken cancellationToken)
=> (await ValidateInternalAsync(value, args, cancellationToken).ConfigureAwait(false)).ThrowOnError();
/// <inheritdoc/>
async Task<IValidationResult<TValue>> IValidator<TValue>.ValidateAsync(TValue value, CancellationToken cancellationToken)
=> await ValidateInternalAsync(value, null, cancellationToken).ConfigureAwait(false);
/// <summary>
/// Performs the <see cref="IValidator{T}"/> validation from above methods.
/// </summary>
private async Task<IValidationContext<TValue>> ValidateInternalAsync(TValue value, ValidationArgs? args, CancellationToken cancellationToken)
{
// Validate the value.
args ??= new ValidationArgs();
var r = await new ValueValidator<TValue>(value, Validation.ValueName, null, Validation.ValueText, c => c.Common(this), null, null).ValidateAsync(new ValidationValue<TValue>(value), args, cancellationToken).ConfigureAwait(false);
// Transform the context to expose TValue.
var vc = new ValueValidationContext(r);
return vc;
}
/// <summary>
/// Validates the <paramref name="value"/> treating <typeparamref name="TValue"/> as the root entity at the path already established in <paramref name="args"/>, without adding a '<c>.value</c>' property path segment.
/// </summary>
/// <remarks>Used by <see cref="Rules.DictionaryRule{TEntity, TProperty, TKey, TValue}"/> and <see cref="Rules.CollectionRule{TEntity, TProperty, TItem}"/>
/// when the validator was configured via an inline <see cref="Action{T}"/> to avoid an errant '<c>.value</c>' segment in error property paths.</remarks>
internal async Task<IValidationContext<TValue>> ValidateEntityAsync(TValue value, ValidationArgs args, CancellationToken cancellationToken)
{
// Run the inline rules treating TValue as the root entity at the path already in args (no ".value" wrapper).
// Nulling the entity names and using the full args path as the property name means CreateFullyQualifiedPropertyName
// returns the name directly (null entity prefix), so FullyQualifiedPropertyName == args entity path.
var entityArgs = args with { FullyQualifiedEntityName = null, FullyQualifiedJsonEntityName = null };
var r = await new ValueValidator<TValue>(value, args.FullyQualifiedEntityName ?? Validation.ValueName, args.FullyQualifiedJsonEntityName, Validation.ValueText, c => c.Common(this), null, null)
.ValidateAsync(new ValidationValue<TValue>(value), entityArgs, cancellationToken).ConfigureAwait(false);
return new ValueValidationContext(r);
}
/// <inheritdoc/>
Task IValidatorEx<TValue>.ValidateAsync(IValidationContext<TValue> context, CancellationToken cancellationToken)
=> throw new NotSupportedException($"{nameof(ValidateAsync)} is not supported by the {nameof(ValidatingInlineValidator<>)} class.");
/// <summary>
/// Custom <see cref="IValidationContext{TEntity}"/> that transforms the underlying <see cref="ValidationValue{TValue}.Value"/> and related-context.
/// </summary>
internal readonly struct ValueValidationContext(IValidationContext<ValidationValue<TValue>> parent) : IValidationContext<TValue>
{
private readonly IValidationContext<ValidationValue<TValue>> _parent = parent;
/// <inheritdoc/>
public Type EntityType => _parent.EntityType;
/// <inheritdoc/>
public string? FullyQualifiedEntityName => _parent.FullyQualifiedEntityName;
/// <inheritdoc/>
public string? FullyQualifiedJsonEntityName => _parent.FullyQualifiedJsonEntityName;
/// <inheritdoc/>
public bool UseJsonNames => _parent.UseJsonNames;
/// <inheritdoc/>
public JsonSerializerOptions? JsonSerializerOptions => _parent.JsonSerializerOptions;
/// <inheritdoc/>
public IServiceProvider? ServiceProvider => _parent.ServiceProvider;
/// <inheritdoc/>
public IDictionary<string, object?> Parameters => _parent.Parameters;
/// <inheritdoc/>
public TValue? Value => _parent.Value is null ? default : _parent.Value.Value;
/// <inheritdoc/>
public bool HasErrors => _parent.HasErrors;
/// <inheritdoc/>
public MessageItemCollection? Messages => _parent.Messages;
/// <inheritdoc/>
public bool HasError(string fullyQualifiedPropertyName) => _parent.HasError(fullyQualifiedPropertyName);
/// <inheritdoc/>
public IValidationResult ThrowOnError() => _parent.ThrowOnError();
/// <inheritdoc/>
public Exception? ToException() => _parent.ToException();
/// <inheritdoc/>
public Result ToResult() => HasErrors ? Result.ValidationError(Messages!) : Result.Success;
}
}