Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Changelog.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
7.6.105 -
7.6.105 - 11 August 2018
Added Albanian language translations
Added Chinese Traditional language translation
Fix #858 ValidationTestExtension.When() incorrectly checks each failure instead of all failures

7.6.104 - 6 July 2018
Added AbstractValidator.PreValidate to allow immediate cancellation of validation.
Expand Down
2 changes: 1 addition & 1 deletion build.ps1
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
param(
[string]$version = '7.6.104',
[string]$version = '7.6.105',
[string]$configuration = 'Release',
[string]$path = $PSScriptRoot
)
Expand Down
14 changes: 14 additions & 0 deletions src/FluentValidation.Tests/ValidatorTesterTester.cs
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,20 @@ public void ShouldHaveChildValidator_should_work_with_DependentRules() {
validator.ShouldHaveChildValidator(x => x.Children, typeof(InlineValidator<Person>));
}

[Fact]
public void Allows_only_one_failure_to_match() {
var validator = new InlineValidator<Person> {
v => v.RuleFor(x => x.Surname).Equal("a").WithErrorCode("nota"),
v => v.RuleFor(x => x.Surname).Equal("b").WithErrorCode("notb")
};

var person = new Person() { Surname = "c" };
var result = validator.TestValidate(person);
Comment on lines +375 to +381
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Local scope variable 'validator' shadows ValidatorTesterTester.validator.

Suggested change
var validator = new InlineValidator<Person> {
v => v.RuleFor(x => x.Surname).Equal("a").WithErrorCode("nota"),
v => v.RuleFor(x => x.Surname).Equal("b").WithErrorCode("notb")
};
var person = new Person() { Surname = "c" };
var result = validator.TestValidate(person);
var inlineValidator = new InlineValidator<Person> {
v => v.RuleFor(x => x.Surname).Equal("a").WithErrorCode("nota"),
v => v.RuleFor(x => x.Surname).Equal("b").WithErrorCode("notb")
};
var person = new Person() { Surname = "c" };
var result = inlineValidator.TestValidate(person);

Copilot uses AI. Check for mistakes.

Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent whitespace: there are multiple spaces before the statement end. This should be a single space or no space before the semicolon for consistency with the rest of the codebase.

Suggested change

Copilot uses AI. Check for mistakes.
result.ShouldHaveError().WithErrorCode("nota");
result.ShouldHaveError().WithErrorCode("notb");
}

private class AddressValidator : AbstractValidator<Address> {
}

Expand Down
1 change: 1 addition & 0 deletions src/FluentValidation/Resources/LanguageManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public LanguageManager() {
var languages = new Language[] {
new EnglishLanguage(),
new ChineseSimplifiedLanguage(),
new ChineseTraditionalLanguage(),
new CroatianLanguage(),
new CzechLanguage(),
new DanishLanguage(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#region License
// Copyright (c) Jeremy Skinner (http://www.jeremyskinner.co.uk)
//
// 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/JeremySkinner/FluentValidation
#endregion

namespace FluentValidation.Resources {
using Validators;

internal class ChineseTraditionalLanguage : Language {
public override string Name => "zh-TW";

public ChineseTraditionalLanguage() {
Translate<EmailValidator>("'{PropertyName}' 不是有效的電子郵件地址。");
Translate<GreaterThanOrEqualValidator>("'{PropertyName}' 必須大於或等於 '{ComparisonValue}'。");
Translate<GreaterThanValidator>("'{PropertyName}' 必須大於 '{ComparisonValue}'。");
Translate<LengthValidator>("'{PropertyName}' 的長度必須在 {MinLength} 到 {MaxLength} 字符,您輸入了 {TotalLength} 字符。");
Translate<MinimumLengthValidator>("'{PropertyName}' 必須大於或等於{MinLength}個字符。您輸入了{TotalLength}個字符。");
Translate<MaximumLengthValidator>("'{PropertyName}' 必須小於或等於{MaxLength}個字符。您輸入了{TotalLength}個字符。");
Translate<LessThanOrEqualValidator>("'{PropertyName}' 必須小於或等於 '{ComparisonValue}'。");
Translate<LessThanValidator>("'{PropertyName}' 必須小於 '{ComparisonValue}'。");
Translate<NotEmptyValidator>("'{PropertyName}' 不能為空。");
Translate<NotEqualValidator>("'{PropertyName}' 不能和 '{ComparisonValue}' 相等。");
Translate<NotNullValidator>("'{PropertyName}' 不能為Null。");
Translate<PredicateValidator>("'{PropertyName}' 不符合指定的條件。");
Translate<AsyncPredicateValidator>("'{PropertyName}' 不符合指定的條件。");
Translate<RegularExpressionValidator>("'{PropertyName}' 的格式不正確。");
Translate<EqualValidator>("'{PropertyName}' 應該和 '{ComparisonValue}' 相等。");
Translate<ExactLengthValidator>("'{PropertyName}' 必須是 {MaxLength} 個字符,您輸入了 {TotalLength} 字符。");
Translate<InclusiveBetweenValidator>("'{PropertyName}' 必須在 {From} (包含)和 {To} (包含)之間, 您輸入了 {Value}。");
Translate<ExclusiveBetweenValidator>("'{PropertyName}' 必須在 {From} (不包含)和 {To} (不包含)之間, 您輸入了 {Value}。");
Translate<CreditCardValidator>("'{PropertyName}' 不是有效的信用卡號碼。");
Translate<ScalePrecisionValidator>("'{PropertyName}' 總位數不能超過 {expectedPrecision} 位,其中小數部份 {expectedScale} 位。您共計輸入了 {digits} 位數字,其中小數部份{actualScale} 位。");
Translate<EmptyValidator>("'{PropertyName}' 必須為空。");
Translate<NullValidator>("'{PropertyName}' 必須為Null。");
Translate<EnumValidator>("'{PropertyName}' 的數值範圍不包含 '{PropertyValue}'。");
}
}
}
22 changes: 12 additions & 10 deletions src/FluentValidation/TestHelper/ValidatorTestExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,20 +121,22 @@ public static void ShouldNotHaveError<T, TValue>(this TestValidationResult<T, TV
}

public static IEnumerable<ValidationFailure> When(this IEnumerable<ValidationFailure> failures, Func<ValidationFailure, bool> failurePredicate, string exceptionMessage = null) {
foreach (var failure in failures) {
if (!failurePredicate(failure)) {
string message = "Expected validation error was not found";
bool anyMatched = failures.Any(failurePredicate);

if (exceptionMessage != null) {
message = exceptionMessage.Replace("{Code}", failure.ErrorCode)
.Replace("{Message}", failure.ErrorMessage)
.Replace("{State}", failure.CustomState?.ToString() ?? "");
}
if (!anyMatched) {
var failure = failures.First();
Comment on lines +124 to +127
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Handling empty failure sequences will throw an unexpected InvalidOperationException instead of the intended ValidationTestException.

When failures is empty, Any returns false, so the if (!anyMatched) branch executes, and failures.First() throws InvalidOperationException before the intended ValidationTestException. Previously this method returned quietly in the no-failures case (with earlier helpers asserting on failure presence). To preserve the expected behavior and exception type, add an explicit empty-sequence guard (e.g., use FirstOrDefault() and handle null, or materialize to a list and check Count == 0 before accessing the first element).


string message = "Expected validation error was not found";

throw new ValidationTestException(message);
if (exceptionMessage != null) {
message = exceptionMessage.Replace("{Code}", failure.ErrorCode)
.Replace("{Message}", failure.ErrorMessage)
.Replace("{State}", failure.CustomState?.ToString() ?? "");
}
}

throw new ValidationTestException(message);
}

return failures;
}

Expand Down