Skip to content

Commit

Permalink
Resolve issue with ruleset propogation for multiple levels of child r…
Browse files Browse the repository at this point in the history
…ules (fixes #2097)
  • Loading branch information
JeremySkinner committed Apr 7, 2023
1 parent 56313e7 commit 3e3e29f
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 2 deletions.
42 changes: 42 additions & 0 deletions src/FluentValidation.Tests/ChildRulesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,36 @@ public class ChildRulesTests {
result.Errors[0].PropertyName.ShouldEqual("Surname");
}

[Fact]
public void Multiple_levels_of_nested_child_rules_in_ruleset() {
var validator = new InlineValidator<RulesetChildValidatorRulesValidator.Baz>();
validator.RuleSet("Set1", () => {
validator.RuleForEach(baz => baz.Bars)
.ChildRules(barRule => barRule.RuleForEach(bar => bar.Foos)
.ChildRules(fooRule => fooRule.RuleForEach(foo => foo.Names)
.ChildRules(name => name.RuleFor(n => n)
.NotEmpty()
.WithMessage("Name is required"))));
});

var foos = new List<RulesetChildValidatorRulesValidator.Foo> {
new() { Names = { "Bob" }},
new() { Names = { string.Empty }},
};

var bars = new List<RulesetChildValidatorRulesValidator.Bar> {
new(),
new() { Foos = foos }
};

var baz = new RulesetChildValidatorRulesValidator.Baz {
Bars = bars
};

var result = validator.Validate(baz, options => options.IncludeRuleSets("Set1"));
result.IsValid.ShouldBeFalse();
}

private class RulesetChildRulesValidator : AbstractValidator<Person> {
public RulesetChildRulesValidator() {
RuleSet("testing", () => {
Expand Down Expand Up @@ -119,6 +149,18 @@ private class RulesetOrderValidator : AbstractValidator<Order> {
});
}
}

public class Foo {
public List<string> Names { get; set; } = new();
}

public class Bar {
public List<Foo> Foos { get; set; } = new();
}

public class Baz {
public List<Bar> Bars { get; set; } = new();
}
}

}
20 changes: 18 additions & 2 deletions src/FluentValidation/DefaultValidatorExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1203,9 +1203,25 @@ public static partial class DefaultValidatorExtensions {
/// <exception cref="ArgumentNullException"></exception>
public static IRuleBuilderOptions<T, TProperty> ChildRules<T, TProperty>(this IRuleBuilder<T, TProperty> ruleBuilder, Action<InlineValidator<TProperty>> action) {
if (action == null) throw new ArgumentNullException(nameof(action));
var validator = new InlineValidator<TProperty>();
var ruleSets = DefaultValidatorOptions.Configurable(ruleBuilder).RuleSets;
var validator = new ChildRulesContainer<TProperty>();
var parentValidator = ((RuleBuilder<T, TProperty>) ruleBuilder).ParentValidator;

string[] ruleSets;

if (parentValidator is ChildRulesContainer<T> container && container.RuleSetsToApplyToChildRules != null) {
ruleSets = container.RuleSetsToApplyToChildRules;
}
else {
ruleSets = DefaultValidatorOptions.Configurable(ruleBuilder).RuleSets;
}

// Store the correct rulesets on the child validator in case
// we have nested calls to ChildRules, which can then pick this up from
// the parent validator.
validator.RuleSetsToApplyToChildRules = ruleSets;

action(validator);

foreach(var rule in validator.Rules) {
if (rule.RuleSets == null) {
rule.RuleSets = ruleSets;
Expand Down
34 changes: 34 additions & 0 deletions src/FluentValidation/Internal/ChildRulesContainer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#region License
// Copyright (c) .NET Foundation and contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// The latest version of this file can be found at https://github.com/FluentValidation/FluentValidation
#endregion

namespace FluentValidation.Internal;

/// <summary>
/// AbstractValidator implementation for containing child rules.
/// </summary>
/// <typeparam name="T"></typeparam>
internal class ChildRulesContainer<T> : InlineValidator<T> {

/// <summary>
/// Used to keep track of rulesets from parent that need to be applied
/// to child rules in the case of multiple nested child rules.
/// </summary>
/// <see cref="DefaultValidatorExtensions.ChildRules{T,TProperty}"/>
internal string[] RuleSetsToApplyToChildRules;

}

0 comments on commit 3e3e29f

Please sign in to comment.