Skip to content

Commit

Permalink
Fix #1698 ArgumentOutOfRangeException when condition returns false fo…
Browse files Browse the repository at this point in the history
…r RuleForEach containing multiple components.
  • Loading branch information
JeremySkinner committed Apr 15, 2021
1 parent 7c0251a commit db0879b
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 21 deletions.
3 changes: 3 additions & 0 deletions Changelog.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
10.0.3 - 15 April 2021
Fix ArgumentOutOfRangeException when condition returns false for RuleForEach containing multiple components (#1698)

10.0.2 - 9 April 2021
Expose ErrorCode property on IRuleComponent.

Expand Down
2 changes: 1 addition & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<VersionPrefix>10.0.2</VersionPrefix>
<VersionPrefix>10.0.3</VersionPrefix>
<VersionSuffix></VersionSuffix>
<!-- Use CI build number as version suffix (if defined) -->
<!--<VersionSuffix Condition="'$(GITHUB_RUN_NUMBER)'!=''">ci-$(GITHUB_RUN_NUMBER)</VersionSuffix>-->
Expand Down
29 changes: 29 additions & 0 deletions src/FluentValidation.Tests/ForEachRuleTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,35 @@ public class AppropriatenessAnswerViewModelRequiredValidator<T,TProperty> : Prop
result.Errors[0].ErrorMessage.ShouldEqual("1 must not be empty");
}

[Fact]
public void Failing_condition_should_prevent_multiple_components_running_and_not_throw() {
// https://github.com/FluentValidation/FluentValidation/issues/1698
var validator = new InlineValidator<Person>();

validator.RuleForEach(x => x.Orders)
.NotNull()
.NotNull()
.When(x => x.Orders.Count > 0);

var result = validator.Validate(new Person());
result.IsValid.ShouldBeTrue();
}

[Fact]
public async Task Failing_condition_should_prevent_multiple_components_running_and_not_throw_async() {
// https://github.com/FluentValidation/FluentValidation/issues/1698
var validator = new InlineValidator<Person>();

validator.RuleForEach(x => x.Orders)
.MustAsync((o, ct) => Task.FromResult(o != null))
.MustAsync((o, ct) => Task.FromResult(o != null))
.When(x => x.Orders.Count > 0);

var result = await validator.ValidateAsync(new Person());
result.IsValid.ShouldBeTrue();
}


public class OrderValidator : AbstractValidator<Order> {
public OrderValidator() {
RuleFor(x => x.ProductName).NotEmpty();
Expand Down
36 changes: 16 additions & 20 deletions src/FluentValidation/Internal/CollectionPropertyRule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -328,21 +328,19 @@ public CollectionPropertyRule(MemberInfo member, Func<T, IEnumerable<TElement>>
// being retrieved (thereby possibly avoiding NullReferenceExceptions).
// Must call ToList so we don't modify the original collection mid-loop.
var validators = Components.ToList();
int validatorIndex = 0;
foreach (var validator in Components) {
if (validator.HasCondition) {
if (!validator.InvokeCondition(context)) {
validators.RemoveAt(validatorIndex);

foreach (var component in Components) {
if (component.HasCondition) {
if (!component.InvokeCondition(context)) {
validators.Remove(component);
}
}

if (validator.HasAsyncCondition) {
if (!validator.InvokeAsyncCondition(context, default).GetAwaiter().GetResult()) {
validators.RemoveAt(validatorIndex);
if (component.HasAsyncCondition) {
if (!component.InvokeAsyncCondition(context, default).GetAwaiter().GetResult()) {
validators.Remove(component);
}
}

validatorIndex++;
}

return validators;
Expand All @@ -356,21 +354,19 @@ public CollectionPropertyRule(MemberInfo member, Func<T, IEnumerable<TElement>>
// being retrieved (thereby possibly avoiding NullReferenceExceptions).
// Must call ToList so we don't modify the original collection mid-loop.
var validators = Components.ToList();
int validatorIndex = 0;
foreach (var validator in Components) {
if (validator.HasCondition) {
if (!validator.InvokeCondition(context)) {
validators.RemoveAt(validatorIndex);

foreach (var component in Components) {
if (component.HasCondition) {
if (!component.InvokeCondition(context)) {
validators.Remove(component);
}
}

if (validator.HasAsyncCondition) {
if (!await validator.InvokeAsyncCondition(context, cancellation)) {
validators.RemoveAt(validatorIndex);
if (component.HasAsyncCondition) {
if (!await component.InvokeAsyncCondition(context, cancellation)) {
validators.Remove(component);
}
}

validatorIndex++;
}

return validators;
Expand Down

0 comments on commit db0879b

Please sign in to comment.