diff --git a/CHANGELOG.md b/CHANGELOG.md index 65a9a08..491f224 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,7 @@ To have a consistent experience across all packages, some public interfaces have - **request.result:** Correct the namespace in the `Prelude` doc comment (`Geekeey.Extensions.Result` → `Geekeey.Request.Result`) - **request.result:** Fold `IsSuccess` into `Result.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`) ### Removed diff --git a/src/request.validation.tests/ValidatorTests.cs b/src/request.validation.tests/ValidatorTests.cs index 14239f7..6613720 100644 --- a/src/request.validation.tests/ValidatorTests.cs +++ b/src/request.validation.tests/ValidatorTests.cs @@ -45,6 +45,17 @@ internal sealed class ValidatorTests } } + [Test] + public async Task I_can_validate_with_a_null_instance_without_throwing() + { + var validator = new PropertyValidator(person + => person!.Name, rule => rule.Must(value => value is not null, "Name is required.")); + + var result = validator.Validate((Person?)null); + + await Assert.That(result.IsValid).IsTrue(); + } + [Test] public async Task I_can_compose_nested_validators_and_aggregate_property_paths() { diff --git a/src/request.validation/Rule.cs b/src/request.validation/Rule.cs index 0c65cc0..a6a397c 100644 --- a/src/request.validation/Rule.cs +++ b/src/request.validation/Rule.cs @@ -46,7 +46,7 @@ internal abstract record Rule : Rule if (context.Instance is null && default(T) is null) { - return Validate((T)context.Instance!, context); + return []; } var actualType = context.Instance?.GetType().FullName ?? "null";