Skip to content

Commit

Permalink
Add support for dependent rules for custom rules #2170
Browse files Browse the repository at this point in the history
  • Loading branch information
JeremySkinner committed Nov 24, 2023
1 parent e2c38e3 commit 3775c94
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 7 deletions.
1 change: 1 addition & 0 deletions Changelog.txt
Expand Up @@ -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
Expand Down
38 changes: 32 additions & 6 deletions src/FluentValidation.Tests/RuleDependencyTests.cs
Expand Up @@ -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(() =>
Expand Down Expand Up @@ -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", () =>
{
Expand All @@ -277,8 +275,36 @@ public void Nested_dependent_rules_inside_ruleset_inside_method()
results.Errors.Single().PropertyName.ShouldEqual("Address");
}

private void BaseValidation(InlineValidator<Person> 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<Person> validator) {
validator.RuleFor(x => x.Address).NotNull();
}

Expand Down
12 changes: 11 additions & 1 deletion src/FluentValidation/Internal/RuleBuilder.cs
Expand Up @@ -94,6 +94,16 @@ internal class RuleBuilder<T, TProperty> : IRuleBuilderOptions<T, TProperty>, IR
}

IRuleBuilderOptions<T, TProperty> IRuleBuilderOptions<T, TProperty>.DependentRules(Action action) {
DependentRulesInternal(action);
return this;
}

IRuleBuilderOptionsConditions<T, TProperty> IRuleBuilderOptionsConditions<T, TProperty>.DependentRules(Action action) {
DependentRulesInternal(action);
return this;
}

private void DependentRulesInternal(Action action) {
var dependencyContainer = new List<IValidationRuleInternal<T>>();
// Capture any rules added to the parent validator inside this delegate.
using (ParentValidator.Rules.Capture(dependencyContainer.Add)) {
Expand All @@ -109,10 +119,10 @@ internal class RuleBuilder<T, TProperty> : IRuleBuilderOptions<T, TProperty>, IR
}

Rule.AddDependentRules(dependencyContainer);
return this;
}

public void AddComponent(RuleComponent<T,TProperty> component) {
Rule.Components.Add(component);
}

}
4 changes: 4 additions & 0 deletions src/FluentValidation/Syntax.cs
Expand Up @@ -92,6 +92,10 @@ public interface IRuleBuilderOptions<T, out TProperty> : IRuleBuilder<T, TProper
/// <typeparam name="T"></typeparam>
/// <typeparam name="TProperty"></typeparam>
public interface IRuleBuilderOptionsConditions<T, out TProperty> : IRuleBuilder<T, TProperty> {
/// <summary>
/// Creates a scope for declaring dependent rules.
/// </summary>
IRuleBuilderOptionsConditions<T, TProperty> DependentRules(Action action);
}

/// <summary>
Expand Down

0 comments on commit 3775c94

Please sign in to comment.