Skip to content

Commit

Permalink
Merge branch 'main' into 12.x-dev
Browse files Browse the repository at this point in the history
  • Loading branch information
JeremySkinner committed Apr 23, 2024
2 parents 4054037 + a0eaeae commit 14cef8c
Show file tree
Hide file tree
Showing 9 changed files with 117 additions and 23 deletions.
3 changes: 3 additions & 0 deletions Changelog.txt
Expand Up @@ -5,6 +5,9 @@ Removes deprecated DI extensions
Removes deprecated transform methods (#2027)
Remove the ability to disable the root-model null check (#2069)

11.9.1 - 23 Apr 2024
Fix issue with CascadeMode on child validators (#2207)

11.9.0 - 21 Dec 2023
Fix memory leak in NotEmptyValidator/EmptyValidator (#2174)
Add more descriptive error messages if a rule throws a NullReferenceException (#2152)
Expand Down
4 changes: 2 additions & 2 deletions docs/requirements_rtd.txt
@@ -1,10 +1,10 @@
mock==1.0.1
alabaster>=0.7,<0.8,!=0.7.5
alabaster==0.7.13
commonmark==0.9.1
recommonmark==0.5.0
readthedocs-sphinx-ext<2.3
jinja2<3.1.0
recommonmark==0.5.0
sphinx==1.8.5
sphinx-rtd-theme==0.4.3
docutils==0.17
docutils==0.17
29 changes: 29 additions & 0 deletions src/FluentValidation.Tests/CascadingFailuresTester.cs
Expand Up @@ -21,6 +21,7 @@
namespace FluentValidation.Tests;

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Xunit;

Expand Down Expand Up @@ -447,6 +448,34 @@ public class CascadingFailuresTester : IDisposable {
results.Errors.Count.ShouldEqual(1);
}

[Fact]
public async void Cascade_set_to_stop_in_child_validator_with_RuleForEach_in_parent() {
// See https://github.com/FluentValidation/FluentValidation/issues/2207

var childValidator = new InlineValidator<Order>();
childValidator.ClassLevelCascadeMode = CascadeMode.Stop;
childValidator.RuleFor(x => x.ProductName).NotNull();
childValidator.RuleFor(x => x.Amount).GreaterThan(0);

var parentValidator = new InlineValidator<Person>();
parentValidator.RuleForEach(x => x.Orders).SetValidator(childValidator);

var testData = new List<Order> {
// Would cause both rules to fail, but only first rule will be executed because of CascadeMode.Stop
new Order { ProductName = null, Amount = 0 },

// First rule succeeds, second rule fails.
new Order { ProductName = "foo", Amount = 0 }
};

// Bug in #2207 meant that the rule for Orders[1].Amount would never execute
// as the cascade mode logic was stopping if totalFailures > 0 rather than totalFailures > (count of failures before rule executed)
var result = parentValidator.Validate(new Person {Orders = testData});
result.Errors.Count.ShouldEqual(2);
result.Errors[0].PropertyName.ShouldEqual("Orders[0].ProductName");
result.Errors[1].PropertyName.ShouldEqual("Orders[1].Amount");
}

[Fact]
public void CascadeMode_values_should_correspond_to_correct_integers() {
// 12.0 removed the "StopOnFirstFailure" option which was value 1.
Expand Down
13 changes: 7 additions & 6 deletions src/FluentValidation.Tests/ValidatorTesterTester.cs
Expand Up @@ -30,6 +30,7 @@ public class ValidatorTesterTester {
private TestValidator validator;

public ValidatorTesterTester() {
CultureScope.SetDefaultCulture();
validator = new TestValidator();
validator.RuleFor(x => x.Forename).NotNull();
validator.RuleForEach(person => person.NickNames).MinimumLength(5);
Expand Down Expand Up @@ -954,15 +955,15 @@ public class ValidatorTesterTester {
[Fact]
public void ShouldHaveValidationErrorFor_WithPropertyName_Only_throws() {
var validator = new InlineValidator<Person>();
validator.RuleFor(x => DateTime.Now)
validator.RuleFor(x => x.Age)
.Must((x, ct) => false)
.LessThan(new DateTime(1900, 1, 1));
.LessThan(50);
Assert.Throws<ValidationTestException>(() =>
validator.TestValidate(new Person())
.ShouldHaveValidationErrorFor("Now")
.WithErrorMessage("The specified condition was not met for 'Now'.")
validator.TestValidate(new Person { Age = 100 })
.ShouldHaveValidationErrorFor("Age")
.WithErrorMessage("The specified condition was not met for 'Age'.")
.Only()
).Message.ShouldEqual("Expected to have errors only matching specified conditions\n----\nUnexpected Errors:\n[0]: 'Now' must be less than '1/1/1900 12:00:00 AM'.\n");
).Message.ShouldEqual("Expected to have errors only matching specified conditions\n----\nUnexpected Errors:\n[0]: 'Age' must be less than '50'.\n");
}

[Fact]
Expand Down
10 changes: 5 additions & 5 deletions src/FluentValidation/AbstractValidator.cs
Expand Up @@ -173,13 +173,13 @@ ValueTask<ValidationResult> completedValueTask
// Performance: Use for loop rather than foreach to reduce allocations.
for (int i = 0; i < count; i++) {
cancellation.ThrowIfCancellationRequested();
var totalFailures = context.Failures.Count;

await Rules[i].ValidateAsync(context, useAsync, cancellation);

if (ClassLevelCascadeMode == CascadeMode.Stop && result.Errors.Count > 0) {
// Bail out if we're "failing-fast".
// Check for > 0 rather than == 1 because a rule chain may have overridden the Stop behaviour to Continue
// meaning that although the first rule failed, it actually generated 2 failures if there were 2 validators
// in the chain.
if (ClassLevelCascadeMode == CascadeMode.Stop && result.Errors.Count > totalFailures) {
// Bail out if we're "failing-fast". Check to see if the number of failures
// has been increased by this rule (which could've generated 1 or more failures).
break;
}
}
Expand Down
1 change: 1 addition & 0 deletions src/FluentValidation/Resources/LanguageManager.cs
Expand Up @@ -71,6 +71,7 @@ public class LanguageManager : ILanguageManager {
KoreanLanguage.Culture => KoreanLanguage.GetTranslation(key),
MacedonianLanguage.Culture => MacedonianLanguage.GetTranslation(key),
NorwegianBokmalLanguage.Culture => NorwegianBokmalLanguage.GetTranslation(key),
NorwegianNynorskLanguage.Culture => NorwegianNynorskLanguage.GetTranslation(key),
PersianLanguage.Culture => PersianLanguage.GetTranslation(key),
PolishLanguage.Culture => PolishLanguage.GetTranslation(key),
PortugueseLanguage.Culture => PortugueseLanguage.GetTranslation(key),
Expand Down
4 changes: 2 additions & 2 deletions src/FluentValidation/Resources/Languages/GermanLanguage.cs
Expand Up @@ -43,7 +43,7 @@ internal class GermanLanguage {
"EqualValidator" => "'{PropertyName}' muss gleich '{ComparisonValue}' sein.",
"ExactLengthValidator" => "'{PropertyName}' muss genau {MaxLength} lang sein. Es wurden {TotalLength} eingegeben.",
"ExclusiveBetweenValidator" => "'{PropertyName}' muss zwischen {From} und {To} sein (exklusiv). Es wurde {PropertyValue} eingegeben.",
"InclusiveBetweenValidator" => "'{PropertyName}' muss zwischen {From} and {To} sein. Es wurde {PropertyValue} eingegeben.",
"InclusiveBetweenValidator" => "'{PropertyName}' muss zwischen {From} und {To} sein. Es wurde {PropertyValue} eingegeben.",
"CreditCardValidator" => "'{PropertyName}' ist keine gültige Kreditkartennummer.",
"ScalePrecisionValidator" => "'{PropertyName}' darf insgesamt nicht mehr als {ExpectedPrecision} Ziffern enthalten, mit Berücksichtigung von {ExpectedScale} Dezimalstellen. Es wurden {Digits} Ziffern und {ActualScale} Dezimalstellen gefunden.",
"EmptyValidator" => "'{PropertyName}' sollte leer sein.",
Expand All @@ -54,7 +54,7 @@ internal class GermanLanguage {
"MinimumLength_Simple" => "Die Länge von '{PropertyName}' muss größer oder gleich {MinLength} sein.",
"MaximumLength_Simple" => "Die Länge von '{PropertyName}' muss kleiner oder gleich {MaxLength} sein.",
"ExactLength_Simple" => "'{PropertyName}' muss genau {MaxLength} lang sein.",
"InclusiveBetween_Simple" => "'{PropertyName}' muss zwischen {From} and {To} sein.",
"InclusiveBetween_Simple" => "'{PropertyName}' muss zwischen {From} und {To} sein.",
_ => null,
};
}
@@ -0,0 +1,60 @@
#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

#pragma warning disable 618

namespace FluentValidation.Resources;

internal class NorwegianNynorskLanguage {
public const string Culture = "nn";

public static string GetTranslation(string key) => key switch {
"EmailValidator" => "'{PropertyName}' er ikkje ei gyldig e-postadresse.",
"GreaterThanOrEqualValidator" => "'{PropertyName}' skal vera større enn eller lik '{ComparisonValue}'.",
"GreaterThanValidator" => "'{PropertyName}' skal vera større enn '{ComparisonValue}'.",
"LengthValidator" => "'{PropertyName}' skal vere mellom {MinLength} og {MaxLength} teikn. Du har tasta inn {TotalLength} teikn.",
"MinimumLengthValidator" => "'{PropertyName}' skal vera større enn eller lik {MinLength} teikn. Du tasta inn {TotalLength} teikn.",
"MaximumLengthValidator" => "'{PropertyName}' skal vera mindre enn eller lik {MaxLength} teikn. Du tasta inn {TotalLength} teikn.",
"LessThanOrEqualValidator" => "'{PropertyName}' skal vera mindre enn eller lik '{ComparisonValue}'.",
"LessThanValidator" => "'{PropertyName}' skal vera mindre enn '{ComparisonValue}'.",
"NotEmptyValidator" => "'{PropertyName}' kan ikkje vera tom.",
"NotEqualValidator" => "'{PropertyName}' kan ikkje vera lik '{ComparisonValue}'.",
"NotNullValidator" => "'{PropertyName}' kan ikkje vera tom.",
"PredicateValidator" => "Den angjevne føresetnaden var ikkje oppfylt for '{PropertyName}'.",
"AsyncPredicateValidator" => "Den angjevne føresetnaden var ikkje oppfylt for '{PropertyName}'.",
"RegularExpressionValidator" => "'{PropertyName}' har ikkje rett format.",
"EqualValidator" => "'{PropertyName}' skal vera lik '{ComparisonValue}'.",
"ExactLengthValidator" => "'{PropertyName}' skal vera {MaxLength} teikn langt. Du har tasta inn {TotalLength} teikn.",
"InclusiveBetweenValidator" => "'{PropertyName}' skal vera mellom {From} og {To}. Du har tasta inn {PropertyValue}.",
"ExclusiveBetweenValidator" => "'{PropertyName}' skal vera mellom {From} og {To} (unntatt). Du har tasta inn {PropertyValue}.",
"CreditCardValidator" => "'{PropertyName}' er ikkje eit gyldig kredittkortnummer.",
"ScalePrecisionValidator" => "'{PropertyName}' kan ikkje vera meir enn {ExpectedPrecision} siffer totalt, inkludert {ExpectedScale} desimalar. {Digits} siffer og {ActualScale} desimalar vart funnen.",
"EmptyValidator" => "'{PropertyName}' skal vera tomt.",
"NullValidator" => "'{PropertyName}' skal vera tomt.",
"EnumValidator" => "'{PropertyName}' har ei rekkje verdiar men inneheld ikkje '{PropertyValue}'.",
// Additional fallback messages used by clientside validation integration.
"Length_Simple" => "'{PropertyName}' skal vera mellom {MinLength} og {MaxLength} teikn.",
"MinimumLength_Simple" => "'{PropertyName}' skal vera større enn eller lik {MinLength} teikn.",
"MaximumLength_Simple" => "'{PropertyName}' skal vera mindre enn eller lik {MaxLength} teikn.",
"ExactLength_Simple" => "'{PropertyName}' skal vera {MaxLength} teikn langt.",
"InclusiveBetween_Simple" => "'{PropertyName}' skal vera mellom {From} og {To}.",
_ => null,
};
}
16 changes: 8 additions & 8 deletions src/FluentValidation/Resources/Languages/PolishLanguage.cs
Expand Up @@ -29,9 +29,9 @@ internal class PolishLanguage {
"EmailValidator" => "Pole '{PropertyName}' nie zawiera poprawnego adresu email.",
"GreaterThanOrEqualValidator" => "Wartość pola '{PropertyName}' musi być równa lub większa niż '{ComparisonValue}'.",
"GreaterThanValidator" => "Wartość pola '{PropertyName}' musi być większa niż '{ComparisonValue}'.",
"LengthValidator" => "Długość pola '{PropertyName}' musi się zawierać pomiędzy {MinLength} i {MaxLength} znaki(ów). Wprowadzono {TotalLength} znaki(ów).",
"LengthValidator" => "Długość pola '{PropertyName}' musi zawierać się pomiędzy {MinLength} i {MaxLength} znaki(ów). Wprowadzono {TotalLength} znaki(ów).",
"MinimumLengthValidator" => "Długość pola '{PropertyName}' musi być większa lub równa {MinLength} znaki(ów). Wprowadzono {TotalLength} znaki(ów).",
"MaximumLengthValidator" => "Długość pola '{PropertyName}' musi być mniejszy lub równy {MaxLength} znaki(ów). Wprowadzono {TotalLength} znaki(ów).",
"MaximumLengthValidator" => "Długość pola '{PropertyName}' musi być mniejsza lub równa {MaxLength} znaki(ów). Wprowadzono {TotalLength} znaki(ów).",
"LessThanOrEqualValidator" => "Wartość pola '{PropertyName}' musi być równa lub mniejsza niż '{ComparisonValue}'.",
"LessThanValidator" => "Wartość pola '{PropertyName}' musi być mniejsza niż '{ComparisonValue}'.",
"NotEmptyValidator" => "Pole '{PropertyName}' nie może być puste.",
Expand All @@ -42,19 +42,19 @@ internal class PolishLanguage {
"RegularExpressionValidator" => "'{PropertyName}' wprowadzono w niepoprawnym formacie.",
"EqualValidator" => "Wartość pola '{PropertyName}' musi być równa '{ComparisonValue}'.",
"ExactLengthValidator" => "Pole '{PropertyName}' musi posiadać długość {MaxLength} znaki(ów). Wprowadzono {TotalLength} znaki(ów).",
"InclusiveBetweenValidator" => "Wartość pola '{PropertyName}' musi się zawierać pomiędzy {From} i {To}. Wprowadzono {PropertyValue}.",
"ExclusiveBetweenValidator" => "Wartość pola '{PropertyName}' musi się zawierać pomiędzy {From} i {To} (wyłącznie). Wprowadzono {PropertyValue}.",
"CreditCardValidator" => "Pole '{PropertyName}' nie zawiera poprawnego numer karty kredytowej.",
"InclusiveBetweenValidator" => "Wartość pola '{PropertyName}' musi zawierać się pomiędzy {From} i {To}. Wprowadzono {PropertyValue}.",
"ExclusiveBetweenValidator" => "Wartość pola '{PropertyName}' musi zawierać się pomiędzy {From} i {To} (wyłącznie). Wprowadzono {PropertyValue}.",
"CreditCardValidator" => "Pole '{PropertyName}' nie zawiera poprawnego numeru karty kredytowej.",
"ScalePrecisionValidator" => "Wartość pola '{PropertyName}' nie może mieć więcej niż {ExpectedPrecision} cyfr z dopuszczalną dokładnością {ExpectedScale} cyfr po przecinku. Znaleziono {Digits} cyfr i {ActualScale} cyfr po przecinku.",
"EmptyValidator" => "Pole '{PropertyName}' musi być puste.",
"NullValidator" => "Pole '{PropertyName}' musi być puste.",
"EnumValidator" => "Pole '{PropertyName}' ma zakres wartości, który nie obejmuje {PropertyValue}.",
// Additional fallback messages used by clientside validation integration.
"Length_Simple" => "Długość pola '{PropertyName}' musi się zawierać pomiędzy {MinLength} i {MaxLength} znaki(ów).",
"Length_Simple" => "Długość pola '{PropertyName}' musi zawierać się pomiędzy {MinLength} i {MaxLength} znaki(ów).",
"MinimumLength_Simple" => "Długość pola '{PropertyName}' musi być większa lub równa {MinLength} znaki(ów).",
"MaximumLength_Simple" => "Długość pola '{PropertyName}' musi być mniejszy lub równy {MaxLength} znaki(ów).",
"MaximumLength_Simple" => "Długość pola '{PropertyName}' musi być mniejsza lub równa {MaxLength} znaki(ów).",
"ExactLength_Simple" => "Pole '{PropertyName}' musi posiadać długość {MaxLength} znaki(ów).",
"InclusiveBetween_Simple" => "Wartość pola '{PropertyName}' musi się zawierać pomiędzy {From} i {To}.",
"InclusiveBetween_Simple" => "Wartość pola '{PropertyName}' musi zawierać się pomiędzy {From} i {To}.",
_ => null,
};
}

0 comments on commit 14cef8c

Please sign in to comment.