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

AndreuCodina / CrossValidation / 5304511320

18 Jun 2023 03:52PM UTC coverage: 83.752% (-11.7%) from 95.463%
5304511320

Pull #40

github

web-flow
Merge ef6355e4d into d173638cd
Pull Request #40: Update coverage badge in README with pull requests

331 of 394 branches covered (84.01%)

Branch coverage included in aggregate %.

1035 of 1237 relevant lines covered (83.67%)

130.23 hits per line

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

95.97
/src/CrossValidation/ValidationOperation.cs
1
using System.Net;
2
using CrossValidation.Errors;
3
using CrossValidation.Resources;
4
using CrossValidation.Validations;
5
using CrossValidation.Validators;
6

7
namespace CrossValidation;
8

9
/// <summary>
10
/// Manages a validation operation
11
///
12
/// In a validation such as:
13
/// 
14
/// Validate.That(request.Age)
15
///     .WithMessage("The age is required").NotNull() // Validation operation
16
///     .WithMessage("The age must be greater than or equal to 18").GreaterThanOrEqual(18) // Validation operation
17
///     .Transform(age => age + 1) // Validation operation
18
///     .Must(() => true) // Validation operation
19
/// </summary>
20
public interface IValidationOperation
21
{
22
    Func<object>? GetNonGenericFieldValue { get; set; }
23
    Func<IValidator<ICrossError>>? Validator { get; set; }
24
    Func<Task<IValidator<ICrossError>>>? AsyncValidator { get; set; }
25
    Action? Scope { get; set; }
26
    string? Code { get; set; }
27
    string? Message { get; set; }
28
    string? Details { get; set; }
29
    ICrossError? Error { get; set; }
30
    string? FieldDisplayName { get; set; }
31
    HttpStatusCode? HttpStatusCode { get; set; }
32
    Type? CrossErrorToException { get; set; }
33
    Func<bool>? Condition { get; set; }
34
    Func<Task<bool>>? AsyncCondition { get; set; }
35
    IValidationOperation? NextValidation { get; set; }
36
    bool HasFailed { get; set; }
37
    bool HasBeenExecuted { get; set; }
38
    bool HasPendingAsyncValidation { get; set; }
39
    bool IsScopeCreator { get; set; }
40
    List<IValidationOperation>? ScopeValidations { get; set; }
41
    int? Index { get; set; }
42
    public bool IsInsideScope { get; set; }
43
    public IValidationOperation? ScopeCreatorValidation { get; set; }
44
    public bool GeneralizeError { get; set; }
45
    public ScopeType? ScopeType { get; set; }
46
    public string? ParentPath { get; set; }
47
    public string? FieldPath { get; set; }
48
    public string? FieldName { get; set; }
49
    ValueTask TraverseAsync(ValidationContext context);
50
    void MarkAllDescendantValidationsAsNotPendingAsync();
51
    ValueTask ExecuteAsync(ValidationContext context, bool useAsync);
52
    void HandleError(ICrossError error, ValidationContext context);
53
    void TakeCustomizationsFromInstanceError(ICrossError error, ValidationContext context);
54
    void TakeCustomizationsFromError(ICrossError error, ValidationContext context);
55
    void MarkAsPendingAsyncValidation();
56
    void MarkAsFailed();
57
    
58
}
59

60
internal class ValidationOperation
61
{
62
    public Func<object>? GetNonGenericFieldValue { get; set; }
873✔
63
    public Func<IValidator<ICrossError>>? Validator { get; set; }
940✔
64
    public Func<Task<IValidator<ICrossError>>>? AsyncValidator { get; set; }
282✔
65
    public Action? Scope { get; set; }
375✔
66
    public string? Code { get; set; }
512✔
67
    public string? Message { get; set; }
372✔
68
    public string? Details { get; set; }
303✔
69
    public ICrossError? Error { get; set; }
241✔
70
    public string? FieldDisplayName { get; set; }
325✔
71
    public HttpStatusCode? HttpStatusCode { get; set; }
303✔
72
    public Type? CrossErrorToException { get; set; }
1,393✔
73
    public Func<bool>? Condition { get; set; }
512✔
74
    public Func<Task<bool>>? AsyncCondition { get; set; }
391✔
75
    public IValidationOperation? NextValidation { get; set; }
1,563✔
76
    public bool HasFailed { get; set; }
2,985✔
77
    public bool HasBeenExecuted { get; set; }
326✔
78
    public bool HasPendingAsyncValidation { get; set; }
2,262✔
79
    public bool IsScopeCreator { get; set; }
250✔
80
    public List<IValidationOperation>? ScopeValidations { get; set; }
1,291✔
81
    public int? Index { get; set; }
1,050✔
82
    public bool IsInsideScope { get; set; }
1,074✔
83
    public IValidationOperation? ScopeCreatorValidation { get; set; }
1,029✔
84
    public bool GeneralizeError { get; set; }
1,415✔
85
    public ScopeType? ScopeType { get; set; }
2,349✔
86
    public string? ParentPath { get; set; }
1,194✔
87
    public string? FieldPath { get; set; }
1,260✔
88
    public string? FieldName { get; set; }
417✔
89

90
    public async ValueTask TraverseAsync(ValidationContext context)
91
    {
92
        bool StopExecution()
93
        {
94
            var isModelValidator = ScopeType is not null && ScopeType is Validations.ScopeType.ModelValidator;
209!
95
            var stopForEach = (ScopeType is not null && ScopeType is Validations.ScopeType.ForEach && context.ValidationMode is not ValidationMode.AccumulateFirstErrorsAndAllIterationFirstErrors);
209✔
96
            var stopWhenNotNull = (ScopeType is not null && ScopeType is Validations.ScopeType.WhenNotNull);
209!
97
            return HasFailed
209✔
98
                    && (!IsScopeCreator || stopForEach || stopWhenNotNull)
209✔
99
                    && !isModelValidator;
209✔
100
        }
101
        if (StopExecution())
148✔
102
        {
103
            return;
7✔
104
        }
105
        
106
        if (!HasBeenExecuted)
141✔
107
        {
108
            await ExecuteAsync(context, useAsync: true);
109✔
109

110
            if (HasFailed)
92✔
111
            {
112
                return;
15✔
113
            }
114
        }
115
        
116
        if (IsScopeCreator && ScopeValidations is not null)
109✔
117
        {
118
            foreach (var scopeValidation in ScopeValidations)
182✔
119
            {
120
                do
121
                {
122
                    if (StopExecution())
61✔
123
                    {
124
                        break;
125
                    }
126
                    
127
                    MarkAllDescendantValidationsAsNotPendingAsync();
58✔
128
                    await scopeValidation.TraverseAsync(context);
58✔
129
                } while (scopeValidation.HasPendingAsyncValidation);
42✔
130
            }
39✔
131
        }
132

133
        if (!HasFailed && NextValidation is not null)
93✔
134
        {
135
            await NextValidation.TraverseAsync(context);
62✔
136
        }
137
    }
92✔
138

139
    public void MarkAllDescendantValidationsAsNotPendingAsync()
140
    {
141
        HasPendingAsyncValidation = false;
648✔
142
        
143
        if (ScopeValidations is not null)
648✔
144
        {
145
            foreach (var scopeValidation in ScopeValidations)
474✔
146
            {
147
                scopeValidation.MarkAllDescendantValidationsAsNotPendingAsync();
141✔
148
            }
149
        }
150
        
151
        if (NextValidation is not null)
648✔
152
        {
153
            NextValidation.MarkAllDescendantValidationsAsNotPendingAsync();
449✔
154
        }
155
    }
648✔
156

157
    public async ValueTask ExecuteAsync(ValidationContext context, bool useAsync)
158
    {
159
        if (Condition is not null)
426✔
160
        {
161
            if (!Condition())
40✔
162
            {
163
                return;
7✔
164
            }
165
        }
166
        else if (AsyncCondition is not null)
386✔
167
        {
168
            if (!useAsync)
2!
169
            {
170
                throw new InvalidOperationException("An asynchronous condition cannot be used in synchronous mode");
×
171
            }
172
            
173
            if (!await AsyncCondition())
2✔
174
            {
175
                return;
1✔
176
            }
177
        }
178
        
179
        if (Validator is not null)
418✔
180
        {
181
            var error = Validator!().GetError();
238✔
182

183
            if (error is null)
236✔
184
            {
185
                return;
108✔
186
            }
187
            
188
            HandleError(error, context);
128✔
189
            MarkAsFailed();
37✔
190
        }
191
        else if (AsyncValidator is not null)
180✔
192
        {
193
            if (!useAsync)
47!
194
            {
195
                throw new InvalidOperationException("An asynchronous validator cannot be used in synchronous mode");
×
196
            }
197
            
198
            var error = (await AsyncValidator()).GetError();
47✔
199

200
            if (error is null)
47✔
201
            {
202
                return;
31✔
203
            }
204
            
205
            HandleError(error, context);
16✔
206
            MarkAsFailed();
7✔
207
        }
208
        else if (Scope is not null)
133✔
209
        {
210
            Scope();
115✔
211
        }
212

213
        HasBeenExecuted = true;
155✔
214
    }
302✔
215

216
    public void HandleError(ICrossError error, ValidationContext context)
217
    {
218
        var errorToAdd = context.Error ?? (Error ?? error);
156✔
219
        AddError(errorToAdd, context);
156✔
220

221
        if (context is {ValidationMode: ValidationMode.StopOnFirstError})
156✔
222
        {
223
            if (context.ErrorsCollected!.Count == 1)
112✔
224
            {
225
                throw context.ErrorsCollected[0].ToException();
112✔
226
            }
227
        }
228
    }
44✔
229
    
230
    public void TakeCustomizationsFromInstanceError(ICrossError error, ValidationContext context)
231
    {
232
        if (GeneralizeError)
12✔
233
        {
234
            return;
1✔
235
        }
236

237
        var codeToAdd = Code ?? error.Code;
11✔
238
        var isInstanceCallerWithCodeAndWithoutMessage = Code is not null && Message is null;
11✔
239
        
240
        if (isInstanceCallerWithCodeAndWithoutMessage && codeToAdd is not null)
11✔
241
        {
242
            Message = CrossValidationOptions.GetMessageFromCode(codeToAdd);
3✔
243
        }
244
        else
245
        {
246
            Message ??= error.Message;
8✔
247
        }
248

249
        Code = codeToAdd;
11✔
250
    }
11✔
251
    
252
    public void TakeCustomizationsFromError(ICrossError error, ValidationContext context)
253
    {
254
        Code = context.Code ?? (error.Code ?? Code);
35✔
255
        Message = context.Message ?? (error.Message ?? Message);
35✔
256
        Details = context.Details ?? (error.Details ?? Details);
35✔
257
        HttpStatusCode = context.HttpStatusCode ?? (error.HttpStatusCode ?? HttpStatusCode);
35✔
258
        FieldDisplayName = context.FieldDisplayName ?? (error.FieldDisplayName ?? FieldDisplayName);
35✔
259
    }
35✔
260
    
261
    public void MarkAsPendingAsyncValidation()
262
    {
263
        HasPendingAsyncValidation = true;
142✔
264
        
265
        if (IsInsideScope)
142✔
266
        {
267
            ScopeCreatorValidation!.MarkAsPendingAsyncValidation();
87✔
268
        }
269
    }
142✔
270

271
    public void MarkAsFailed()
272
    {
273
        HasFailed = true;
142✔
274
        
275
        if (IsInsideScope)
142✔
276
        {
277
            ScopeCreatorValidation!.MarkAsFailed();
98✔
278
        }
279
    }
142✔
280
    
281
    private void AddError(ICrossError error, ValidationContext context)
282
    {
283
        AddCustomizationsToError(error, context);
156✔
284
        context.ErrorsCollected.Add(error);
156✔
285
    }
156✔
286

287
    private void AddCustomizationsToError(ICrossError error, ValidationContext context)
288
    {
289
        error.CrossErrorToException = CrossErrorToException;
156✔
290
        error.FieldName = FieldName;
156✔
291
        error.FieldDisplayName = GetFieldDisplayNameToFill(error, context);
156✔
292
        error.GetFieldValue = GetNonGenericFieldValue;
156✔
293
        error.Code = GetCodeToFill(error, context);
156✔
294
        error.Message = GetMessageToFill(error, context);
156✔
295
        error.Details = context.Details ?? (Details ?? error.Details);
156✔
296
        error.HttpStatusCode = context.HttpStatusCode ?? (HttpStatusCode ?? error.HttpStatusCode);
156✔
297
    }
156✔
298

299
    private string? GetCodeToFill(ICrossError error, ValidationContext context)
300
    {
301
        if (context.Code is not null)
156✔
302
        {
303
            return context.Code;
3✔
304
        }
305
        
306
        if (Code is not null)
153✔
307
        {
308
            return Code;
36✔
309
        }
310
        
311
        if (GeneralizeError)
117✔
312
        {
313
            return nameof(ErrorResource.General);
52✔
314
        }
315

316
        return error.Code;
65✔
317
    }
318

319
    private string? GetMessageToFill(ICrossError error, ValidationContext context)
320
    {
321
        if (context.Message is not null)
156✔
322
        {
323
            return context.Message;
3✔
324
        }
325
        
326
        if (Message is not null)
153✔
327
        {
328
            return Message;
39✔
329
        }
330

331
        if (Code is not null)
114✔
332
        {
333
            return CrossValidationOptions.GetMessageFromCode(Code);
15✔
334
        }
335
        
336
        if (GeneralizeError)
99✔
337
        {
338
            return CrossValidationOptions.GetMessageFromCode(nameof(ErrorResource.General));
37✔
339
        }
340

341
        if (error.Message is not null)
62!
342
        {
343
            return error.Message;
62✔
344
        }
345

346
        if (error.Code is not null)
×
347
        {
348
            return CrossValidationOptions.GetMessageFromCode(error.Code);
×
349
        }
350

351
        return null;
×
352
    }
353

354
    private string? GetFieldDisplayNameToFill(ICrossError error, ValidationContext context)
355
    {
356
        if (context.FieldDisplayName is not null)
156✔
357
        {
358
            return context.FieldDisplayName;
3✔
359
        }
360
        
361
        if (FieldDisplayName is not null)
153✔
362
        {
363
            return FieldDisplayName;
14✔
364
        }
365

366
        if (error.FieldDisplayName is not null)
139✔
367
        {
368
            return error.FieldDisplayName;
5✔
369
        }
370

371
        if (error.FieldName is not null)
134✔
372
        {
373
            return error.FieldName;
65✔
374
        }
375

376
        return null;
69✔
377
    }
378
}
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