From 3775c942d359e361be9b0d354089d9967a2cd2e6 Mon Sep 17 00:00:00 2001 From: Jeremy Skinner <90130+JeremySkinner@users.noreply.github.com> Date: Fri, 24 Nov 2023 17:41:01 +0000 Subject: [PATCH] Add support for dependent rules for custom rules #2170 --- Changelog.txt | 1 + .../RuleDependencyTests.cs | 38 ++++++++++++++++--- src/FluentValidation/Internal/RuleBuilder.cs | 12 +++++- src/FluentValidation/Syntax.cs | 4 ++ 4 files changed, 48 insertions(+), 7 deletions(-) diff --git a/Changelog.txt b/Changelog.txt index 2b210bb9a..634b2e88b 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -3,6 +3,7 @@ Fix memory leak in NotEmptyValidator/EmptyValidator (#2174) Add more descriptive error messages if a rule throws a NullReferenceException (#2152) Add support for caching root parameter expressions (eg RuleFor(x => x)) (#2168) Add builds for .net 8 +Add support for dependent rules for custom rules (#2170) 11.8.1 - 22 Nov 2023 Fix unintentional behavioural changes in introduced in the previous release as part of #2158 diff --git a/src/FluentValidation.Tests/RuleDependencyTests.cs b/src/FluentValidation.Tests/RuleDependencyTests.cs index fc216051b..9b0acf463 100644 --- a/src/FluentValidation.Tests/RuleDependencyTests.cs +++ b/src/FluentValidation.Tests/RuleDependencyTests.cs @@ -38,8 +38,7 @@ public class RuleDependencyTests { } [Fact] - public void Does_not_invoke_dependent_rule_if_parent_rule_does_not_pass() - { + public void Does_not_invoke_dependent_rule_if_parent_rule_does_not_pass() { var validator = new TestValidator(); validator.RuleFor(x => x.Surname).NotNull() .DependentRules(() => @@ -253,8 +252,7 @@ public void Nested_dependent_rules_inside_ruleset_no_result_when_second_level_fa [Fact] - public void Nested_dependent_rules_inside_ruleset_inside_method() - { + public void Nested_dependent_rules_inside_ruleset_inside_method() { var validator = new TestValidator(); validator.RuleSet("MyRuleSet", () => { @@ -277,8 +275,36 @@ public void Nested_dependent_rules_inside_ruleset_inside_method() results.Errors.Single().PropertyName.ShouldEqual("Address"); } - private void BaseValidation(InlineValidator validator) - { + [Fact] + public void Invokes_dependent_rule_if_parent_custom_rule_passes() { + var validator = new TestValidator(); + validator.RuleFor(x => x.Surname).Custom((name, context) => { }) + .DependentRules(() => { + validator.RuleFor(x => x.Forename).NotNull(); + }); + + var results = validator.Validate(new Person {Surname = "foo"}); + results.Errors.Count.ShouldEqual(1); + results.Errors.Single().PropertyName.ShouldEqual("Forename"); + } + + [Fact] + public void Does_not_invoke_dependent_rule_if_parent_custom_rule_does_not_pass() { + var validator = new TestValidator(); + validator.RuleFor(x => x.Surname).Custom((name, context) => { + context.AddFailure("Failed"); + }) + .DependentRules(() => + { + validator.RuleFor(x => x.Forename).NotNull(); + }); + + var results = validator.Validate(new Person { Surname = null }); + results.Errors.Count.ShouldEqual(1); + results.Errors.Single().PropertyName.ShouldEqual("Surname"); + } + + private void BaseValidation(InlineValidator validator) { validator.RuleFor(x => x.Address).NotNull(); } diff --git a/src/FluentValidation/Internal/RuleBuilder.cs b/src/FluentValidation/Internal/RuleBuilder.cs index 7b9c79acc..31fb1eac4 100644 --- a/src/FluentValidation/Internal/RuleBuilder.cs +++ b/src/FluentValidation/Internal/RuleBuilder.cs @@ -94,6 +94,16 @@ internal class RuleBuilder : IRuleBuilderOptions, IR } IRuleBuilderOptions IRuleBuilderOptions.DependentRules(Action action) { + DependentRulesInternal(action); + return this; + } + + IRuleBuilderOptionsConditions IRuleBuilderOptionsConditions.DependentRules(Action action) { + DependentRulesInternal(action); + return this; + } + + private void DependentRulesInternal(Action action) { var dependencyContainer = new List>(); // Capture any rules added to the parent validator inside this delegate. using (ParentValidator.Rules.Capture(dependencyContainer.Add)) { @@ -109,10 +119,10 @@ internal class RuleBuilder : IRuleBuilderOptions, IR } Rule.AddDependentRules(dependencyContainer); - return this; } public void AddComponent(RuleComponent component) { Rule.Components.Add(component); } + } diff --git a/src/FluentValidation/Syntax.cs b/src/FluentValidation/Syntax.cs index db6b4ac87..ed0460622 100644 --- a/src/FluentValidation/Syntax.cs +++ b/src/FluentValidation/Syntax.cs @@ -92,6 +92,10 @@ public interface IRuleBuilderOptions : IRuleBuilder /// public interface IRuleBuilderOptionsConditions : IRuleBuilder { + /// + /// Creates a scope for declaring dependent rules. + /// + IRuleBuilderOptionsConditions DependentRules(Action action); } ///