diff --git a/Changelog.txt b/Changelog.txt index b491b7f1c..4fcc8c548 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -1,5 +1,6 @@ -11.8.2 - +11.9.0 - Fix memory leak in NotEmptyValidator/EmptyValidator (#2174) +Add more descriptive error messages if a rule throws a NullReferenceException (#2152) 11.8.1 - 22 Nov 2023 Fix unintentional behavioural changes in introduced in the previous release as part of #2158 @@ -34,7 +35,7 @@ Deprecated the ability to disable the root model null check via overriding Abstr Deprecated the Transform and TransformAsync methods (#2072) 11.5.0 - 13 Feb 2023 -MemberNameValidatorSelector now supports wildcard indexes in property paths (#2056) +MemberNameValidatorSelector now supports wildcard indexes in property paths (#2056) Added overload of TestValidateAsync that accepts a context (#2052) Minor optimization to regex validator (#2035) Added Kazakh translations (#2036) diff --git a/src/FluentValidation.Tests/NullTester.cs b/src/FluentValidation.Tests/NullTester.cs index ef70e72d5..b3a284246 100644 --- a/src/FluentValidation.Tests/NullTester.cs +++ b/src/FluentValidation.Tests/NullTester.cs @@ -18,6 +18,8 @@ namespace FluentValidation.Tests; +using System; +using System.Collections.Generic; using System.Linq; using Xunit; @@ -60,4 +62,31 @@ public class NullTester { var result = validator.Validate(new Person()); result.IsValid.ShouldBeTrue(); } -} + + [Fact] + public void NullProperty_should_throw_NullReferenceException() { + var validator = new InlineValidator(); + validator.RuleFor(x => x.Orders.Count).NotEmpty(); + + var ex = Assert.Throws(() => validator.Validate(new Person { + Orders = null + })); + + ex.Message.ShouldEqual("NullReferenceException occurred when executing rule for x => x.Orders.Count. If this property can be null you should add a null check using a When condition"); + ex.InnerException.ShouldNotBeNull(); + ex.InnerException!.GetType().ShouldEqual(typeof(NullReferenceException)); + } + + [Fact] + public void ForEachNullProperty_should_throw_NullReferenceException_when_exception_occurs() { + var validator = new InlineValidator(); + validator.RuleForEach(x => x.Orders[0].Payments).NotNull(); + + var ex = Assert.Throws(() => validator.Validate(new Person { + Orders = null + })); + ex.Message.ShouldEqual("NullReferenceException occurred when executing rule for x => x.Orders.get_Item(0).Payments. If this property can be null you should add a null check using a When condition"); + ex.InnerException.ShouldNotBeNull(); + ex.InnerException!.GetType().ShouldEqual(typeof(NullReferenceException)); + } +} \ No newline at end of file diff --git a/src/FluentValidation.Tests/Person.cs b/src/FluentValidation.Tests/Person.cs index 9b02bb301..cba14c2af 100644 --- a/src/FluentValidation.Tests/Person.cs +++ b/src/FluentValidation.Tests/Person.cs @@ -46,6 +46,8 @@ public class Person { public Address Address { get; set; } public IList Orders { get; set; } + public Order LatestOrder { get; set; } + public string Email { get; set; } public decimal Discount { get; set; } diff --git a/src/FluentValidation/Internal/CollectionPropertyRule.cs b/src/FluentValidation/Internal/CollectionPropertyRule.cs index 81e6ee1b4..b18316bbb 100644 --- a/src/FluentValidation/Internal/CollectionPropertyRule.cs +++ b/src/FluentValidation/Internal/CollectionPropertyRule.cs @@ -134,7 +134,14 @@ public CollectionPropertyRule(MemberInfo member, Func> } var cascade = CascadeMode; - var collection = PropertyFunc(context.InstanceToValidate) as IEnumerable; + IEnumerable collection; + + try { + collection = PropertyFunc(context.InstanceToValidate); + } + catch (NullReferenceException nre) { + throw new NullReferenceException($"NullReferenceException occurred when executing rule for {Expression}. If this property can be null you should add a null check using a When condition", nre); + } int count = 0; int totalFailures = context.Failures.Count; diff --git a/src/FluentValidation/Internal/PropertyRule.cs b/src/FluentValidation/Internal/PropertyRule.cs index 83306219b..9ae56bf61 100644 --- a/src/FluentValidation/Internal/PropertyRule.cs +++ b/src/FluentValidation/Internal/PropertyRule.cs @@ -145,7 +145,12 @@ TProperty PropertyFunc(T instance) if (first) { first = false; - propValue = PropertyFunc(context.InstanceToValidate); + try { + propValue = PropertyFunc(context.InstanceToValidate); + } + catch (NullReferenceException nre) { + throw new NullReferenceException($"NullReferenceException occurred when executing rule for {Expression}. If this property can be null you should add a null check using a When condition", nre); + } } bool valid = await component.ValidateAsync(context, propValue, useAsync, cancellation);