fix(validation): make Severity meaningful in Validation.IsValid
Validation.IsValid previously counted every problem regardless of severity, leaving Severity inert. Now IsValid only fails on Error problems, so Warning and Info no longer invalidate the result, and IsValidFor(Severity) lets callers set the failing-severity threshold.
This commit is contained in:
parent
552f88df18
commit
f7263c33a8
4 changed files with 75 additions and 3 deletions
|
|
@ -53,6 +53,7 @@ To have a consistent experience across all packages, some public interfaces have
|
|||
- **request.result:** Fold `IsSuccess` into `Result<T>.GetHashCode` to avoid collisions between a failure and a success value whose hash is `0`
|
||||
- **request.validation:** Guard null comparison bounds in `GreaterThan`/`LessThan`/`Between` etc. so a `null` bound no longer throws (`NullReferenceException`)
|
||||
- **request.validation:** Return no problems when validating a `null` instance of a reference type instead of dereferencing it (`NullReferenceException`)
|
||||
- **request.validation:** Make `Severity` meaningful — `Validation.IsValid` now only fails on `Error` problems; `Warning`/`Info` no longer invalidate. Added `Validation.IsValidFor(Severity)` to set the failing-severity threshold (see PLAN.md Inconsistencies#B)
|
||||
|
||||
### Removed
|
||||
|
||||
|
|
|
|||
49
src/request.validation.tests/ValidationSeverityTests.cs
Normal file
49
src/request.validation.tests/ValidationSeverityTests.cs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
// Copyright (c) The Geekeey Authors
|
||||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
namespace Geekeey.Request.Validation.Tests;
|
||||
|
||||
internal sealed class ValidationSeverityTests
|
||||
{
|
||||
[Test]
|
||||
public async Task I_can_have_warning_problems_not_invalidate_by_default()
|
||||
{
|
||||
var validator = new PropertyValidator<Person, string?>(person
|
||||
=> person.Name, rule => rule.Must(value => !string.IsNullOrWhiteSpace(value), "Name is required.")
|
||||
.WithSeverity(Severity.Warning));
|
||||
|
||||
var result = validator.Validate(new Person { Name = "" });
|
||||
|
||||
await Assert.That(result.Problems).Count().IsEqualTo(1);
|
||||
await Assert.That(result.IsValid).IsTrue();
|
||||
await Assert.That(result.IsValidFor(Severity.Warning)).IsFalse();
|
||||
await Assert.That(result.IsValidFor(Severity.Info)).IsFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_have_info_problems_only_invalidate_at_info_threshold()
|
||||
{
|
||||
var validator = new PropertyValidator<Person, string?>(person
|
||||
=> person.Name, rule => rule.Must(value => !string.IsNullOrWhiteSpace(value), "Name is required.")
|
||||
.WithSeverity(Severity.Info));
|
||||
|
||||
var result = validator.Validate(new Person { Name = "" });
|
||||
|
||||
await Assert.That(result.Problems).Count().IsEqualTo(1);
|
||||
await Assert.That(result.IsValid).IsTrue();
|
||||
await Assert.That(result.IsValidFor(Severity.Warning)).IsTrue();
|
||||
await Assert.That(result.IsValidFor(Severity.Info)).IsFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_have_error_problems_invalidate_by_default()
|
||||
{
|
||||
var validator = new PropertyValidator<Person, string?>(person
|
||||
=> person.Name, rule => rule.Must(value => !string.IsNullOrWhiteSpace(value), "Name is required."));
|
||||
|
||||
var result = validator.Validate(new Person { Name = "" });
|
||||
|
||||
await Assert.That(result.IsValid).IsFalse();
|
||||
await Assert.That(result.IsValidFor(Severity.Error)).IsFalse();
|
||||
}
|
||||
}
|
||||
|
|
@ -14,7 +14,8 @@ internal sealed class ValidatorTests
|
|||
|
||||
var result = validator.Validate(new Person { Name = "" });
|
||||
|
||||
await Assert.That(result.IsValid).IsFalse();
|
||||
await Assert.That(result.IsValid).IsTrue();
|
||||
await Assert.That(result.IsValidFor(Severity.Warning)).IsFalse();
|
||||
await Assert.That(result.Problems).Count().IsEqualTo(1);
|
||||
|
||||
var problem = result.Problems.Single();
|
||||
|
|
@ -39,7 +40,8 @@ internal sealed class ValidatorTests
|
|||
|
||||
using (Assert.Multiple())
|
||||
{
|
||||
await Assert.That(invalid.IsValid).IsFalse();
|
||||
await Assert.That(invalid.IsValid).IsTrue();
|
||||
await Assert.That(invalid.IsValidFor(Severity.Warning)).IsFalse();
|
||||
await Assert.That(valid.IsValid).IsTrue();
|
||||
await Assert.That(valid.Problems).IsEmpty();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,27 @@ public sealed class Validation
|
|||
/// <summary>
|
||||
/// Whether the validation was successful.
|
||||
/// </summary>
|
||||
public bool IsValid => Problems.Count is 0;
|
||||
/// <remarks>
|
||||
/// A validation is considered successful unless it contains at least one problem with
|
||||
/// <see cref="Severity.Error"/>. Problems of lower severity (<see cref="Severity.Warning"/> or
|
||||
/// <see cref="Severity.Info"/>) do not invalidate the result. Use <see cref="IsValidFor"/>
|
||||
/// to control which severities are treated as failures.
|
||||
/// </remarks>
|
||||
public bool IsValid => IsValidFor(Severity.Error);
|
||||
|
||||
/// <summary>
|
||||
/// Whether the validation was successful, treating problems at or above the given severity as failures.
|
||||
/// </summary>
|
||||
/// <param name="minimum">The minimum severity that should be considered a failure.</param>
|
||||
/// <remarks>
|
||||
/// A problem invalidates the validation when its <see cref="Severity"/> is at least as severe as
|
||||
/// <paramref name="minimum"/>. For example, <c>IsValidFor(Severity.Warning)</c> fails on errors and
|
||||
/// warnings but not on informational problems.
|
||||
/// </remarks>
|
||||
public bool IsValidFor(Severity minimum)
|
||||
{
|
||||
return !Problems.Any(problem => problem.Severity <= minimum);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The problems that were found during validation.
|
||||
|
|
|
|||
Loading…
Reference in a new issue