From cbfaed6ee68f48b5582ef0e2142bc88aa59bef17 Mon Sep 17 00:00:00 2001 From: upstream Date: Wed, 2 Sep 2026 10:59:11 +0200 Subject: [PATCH] Upstream FluentValidation/FluentValidation state of the touched files before 00e7987e97 --- .../ExclusiveBetweenValidatorTests.cs | 194 ++++++++++++++++++ .../InclusiveBetweenValidatorTests.cs | 183 +++++++++++++++++ .../Validators/RangeValidator.cs | 84 ++++++++ 3 files changed, 461 insertions(+) create mode 100644 src/FluentValidation.Tests/ExclusiveBetweenValidatorTests.cs create mode 100644 src/FluentValidation.Tests/InclusiveBetweenValidatorTests.cs create mode 100644 src/FluentValidation/Validators/RangeValidator.cs diff --git a/src/FluentValidation.Tests/ExclusiveBetweenValidatorTests.cs b/src/FluentValidation.Tests/ExclusiveBetweenValidatorTests.cs new file mode 100644 index 0000000..a4ebfa4 --- /dev/null +++ b/src/FluentValidation.Tests/ExclusiveBetweenValidatorTests.cs @@ -0,0 +1,194 @@ +#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.Tests; + +using System; +using System.Linq; +using Xunit; +using Validators; + +public class ExclusiveBetweenValidatorTests { + DateTime _fromDate; + DateTime _toDate; + + public ExclusiveBetweenValidatorTests() { + CultureScope.SetDefaultCulture(); + _fromDate = new DateTime(2009, 1, 1); + _toDate = new DateTime(2009, 12, 31); + } + + [Fact] + public void When_the_value_is_between_the_range_specified_then_the_validator_should_pass() { + var validator = new TestValidator { v => v.RuleFor(x => x.Id).ExclusiveBetween(1, 10) }; + var result = validator.Validate(new Person { Id = 5 }); + result.IsValid.ShouldBeTrue(); + } + + [Fact] + public void When_the_value_is_smaller_than_the_range_then_the_validator_should_fail() { + var validator = new TestValidator { v => v.RuleFor(x => x.Id).ExclusiveBetween(1, 10) }; + var result = validator.Validate(new Person { Id = 0 }); + result.IsValid.ShouldBeFalse(); + } + + [Fact] + public void When_the_text_is_larger_than_the_range_then_the_validator_should_fail() { + var validator = new TestValidator { v => v.RuleFor(x => x.Id).ExclusiveBetween(1, 10) }; + var result = validator.Validate(new Person { Id = 11 }); + result.IsValid.ShouldBeFalse(); + } + + [Fact] + public void When_the_value_is_exactly_the_size_of_the_upper_bound_then_the_validator_should_fail() { + var validator = new TestValidator { v => v.RuleFor(x => x.Id).ExclusiveBetween(1, 10) }; + var result = validator.Validate(new Person { Id = 10 }); + result.IsValid.ShouldBeFalse(); + } + + [Fact] + public void When_the_value_is_exactly_the_size_of_the_lower_bound_then_the_validator_should_fail() { + var validator = new TestValidator { v => v.RuleFor(x => x.Id).ExclusiveBetween(1, 10) }; + var result = validator.Validate(new Person { Id = 1 }); + result.IsValid.ShouldBeFalse(); + } + + [Fact] + public void When_the_to_is_smaller_than_the_from_then_the_validator_should_throw() { + Assert.Throws(() => new TestValidator{v => v.RuleFor(x => x.Id).ExclusiveBetween(10, 1)}); + } + + [Fact] + public void When_the_validator_fails_the_error_message_should_be_set() { + var validator = new TestValidator { v => v.RuleFor(x => x.Id).ExclusiveBetween(1, 10) }; + var result = validator.Validate(new Person { Id = 0 }); + result.Errors.Single().ErrorMessage.ShouldEqual("'Id' must be between 1 and 10 (exclusive). You entered 0."); + } + + [Fact] + public void To_and_from_properties_should_be_set() { + var propertyValidator = RangeValidatorFactory.CreateExclusiveBetween(1, 10); + propertyValidator.From.ShouldEqual(1); + propertyValidator.To.ShouldEqual(10); + } + + [Fact] + public void When_the_value_is_between_the_range_specified_then_the_validator_should_pass_for_strings() { + var validator = new TestValidator { v => v.RuleFor(x => x.Surname).ExclusiveBetween("aa", "zz") }; + var result = validator.Validate(new Person { Surname = "bbb" }); + result.IsValid.ShouldBeTrue(); + } + + [Fact] + public void When_the_value_is_smaller_than_the_range_then_the_validator_should_fail_for_strings() { + var validator = new TestValidator { v => v.RuleFor(x => x.Surname).ExclusiveBetween("bbb", "zz") }; + var result = validator.Validate(new Person { Surname = "aaa" }); + result.IsValid.ShouldBeFalse(); + } + + [Fact] + public void When_the_text_is_larger_than_the_range_then_the_validator_should_fail_for_strings() { + var validator = new TestValidator(v => v.RuleFor(x => x.Surname).ExclusiveBetween("aaa", "bbb")); + var result = validator.Validate(new Person {Surname = "zzz"}); + result.IsValid.ShouldBeFalse(); + } + + [Fact] + public void When_the_value_is_exactly_the_size_of_the_upper_bound_then_the_validator_should_fail_for_strings() { + var validator = new TestValidator(v => v.RuleFor(x => x.Surname).ExclusiveBetween("aa", "zz")); + var result = validator.Validate(new Person { Surname = "aa" }); + result.IsValid.ShouldBeFalse(); + } + + [Fact] + public void When_the_value_is_exactly_the_size_of_the_lower_bound_then_the_validator_should_fail_for_strings() { + var validator = new TestValidator(v => v.RuleFor(x => x.Surname).ExclusiveBetween("aa", "zz")); + var result = validator.Validate(new Person { Surname = "zz" }); + result.IsValid.ShouldBeFalse(); + } + + [Fact] + public void When_the_to_is_smaller_than_the_from_then_the_validator_should_throw_for_strings() { + Assert.Throws(() => RangeValidatorFactory.CreateExclusiveBetween("ccc", "aaa")); + } + + [Fact] + public void When_the_validator_fails_the_error_message_should_be_set_for_strings() { + var validator = new TestValidator(v => v.RuleFor(x => x.Surname).ExclusiveBetween("bbb", "zzz")); + var result = validator.Validate(new Person { Surname = "aaa" }); + result.Errors.Single().ErrorMessage.ShouldEqual("'Surname' must be between bbb and zzz (exclusive). You entered aaa."); + } + + [Fact] + public void To_and_from_properties_should_be_set_for_strings() { + var validator = RangeValidatorFactory.CreateExclusiveBetween("a", "c"); + validator.From.ShouldEqual("a"); + validator.To.ShouldEqual("c"); + } + + [Fact] + public void To_and_from_properties_should_be_set_for_dates() { + var validator = RangeValidatorFactory.CreateExclusiveBetween(_fromDate, _toDate); + validator.From.ShouldEqual(_fromDate); + validator.To.ShouldEqual(_toDate); + } + + [Fact] + public void Validates_with_nullable_when_property_is_null() { + var validator = new TestValidator(v => v.RuleFor(x => x.NullableInt).ExclusiveBetween(1, 5)); + var result = validator.Validate(new Person()); + result.IsValid.ShouldBeTrue(); + } + + [Fact] + public void Validates_with_nullable_when_property_not_null() { + var validator = new TestValidator(v => v.RuleFor(x => x.NullableInt).ExclusiveBetween(1, 5)); + var result = validator.Validate(new Person { NullableInt = 10 }); + result.IsValid.ShouldBeFalse(); + } + + [Fact] + public void When_the_value_is_between_the_range_specified_by_icomparer_then_the_validator_should_pass() { + var validator = new TestValidator(v => v.RuleFor(x => x.Address).ExclusiveBetween( + new Address() { Line1 = "3 Main St." }, + new Address() { Line1 = "10 Main St." }, + new StreetNumberComparer())); + var result = validator.Validate(new Person { Address = new Address() { Line1 = "5 Main St." } }); + result.IsValid.ShouldBeTrue(); + } + + [Fact] + public void When_the_value_is_smaller_than_the_range_by_icomparer_then_the_validator_should_fail() { + var validator = new TestValidator(v => v.RuleFor(x => x.Address).ExclusiveBetween( + new Address() { Line1 = "3 Main St." }, + new Address() { Line1 = "10 Main St." }, + new StreetNumberComparer())); + var result = validator.Validate(new Person { Address = new Address() { Line1 = "1 Main St." } }); + result.IsValid.ShouldBeFalse(); + } + + [Fact] + public void When_the_value_is_larger_than_the_range_by_icomparer_then_the_validator_should_fail() { + var validator = new TestValidator(v => v.RuleFor(x => x.Address).ExclusiveBetween( + new Address() { Line1 = "3 Main St." }, + new Address() { Line1 = "10 Main St." }, + new StreetNumberComparer())); + var result = validator.Validate(new Person { Address = new Address() { Line1 = "11 Main St." } }); + result.IsValid.ShouldBeFalse(); + } +} diff --git a/src/FluentValidation.Tests/InclusiveBetweenValidatorTests.cs b/src/FluentValidation.Tests/InclusiveBetweenValidatorTests.cs new file mode 100644 index 0000000..8341b18 --- /dev/null +++ b/src/FluentValidation.Tests/InclusiveBetweenValidatorTests.cs @@ -0,0 +1,183 @@ +#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.Tests; + +using System; +using System.Linq; +using Xunit; +using Validators; + +public class InclusiveBetweenValidatorTests { + public InclusiveBetweenValidatorTests() { + CultureScope.SetDefaultCulture(); + } + + [Fact] + public void When_the_value_is_between_the_range_specified_then_the_validator_should_pass() { + var validator = new TestValidator(v => v.RuleFor(x => x.Id).InclusiveBetween(1, 10)); + var result = validator.Validate(new Person { Id = 5 }); + result.IsValid.ShouldBeTrue(); + } + + [Fact] + public void When_the_value_is_smaller_than_the_range_then_the_validator_should_fail() { + var validator = new TestValidator(v => v.RuleFor(x => x.Id).InclusiveBetween(1, 10)); + var result = validator.Validate(new Person { Id = 0 }); + result.IsValid.ShouldBeFalse(); + } + + [Fact] + public void When_the_text_is_larger_than_the_range_then_the_validator_should_fail() { + var validator = new TestValidator(v => v.RuleFor(x => x.Id).InclusiveBetween(1, 10)); + var result = validator.Validate(new Person() { Id = 11 }); + result.IsValid.ShouldBeFalse(); + } + + [Fact] + public void When_the_value_is_exactly_the_size_of_the_upper_bound_then_the_validator_should_pass() { + var validator = new TestValidator(v => v.RuleFor(x => x.Id).InclusiveBetween(1, 10)); + var result = validator.Validate(new Person { Id = 10 }); + result.IsValid.ShouldBeTrue(); + } + + [Fact] + public void When_the_value_is_exactly_the_size_of_the_lower_bound_then_the_validator_should_pass() { + var validator = new TestValidator(v => v.RuleFor(x => x.Id).InclusiveBetween(1, 10)); + var result = validator.Validate(new Person { Id = 1 }); + result.IsValid.ShouldBeTrue(); + } + + [Fact] + public void When_the_to_is_smaller_than_the_from_then_the_validator_should_throw() { + Assert.Throws(() => new TestValidator(v => v.RuleFor(x => x.Id).InclusiveBetween(10, 1))); + } + + [Fact] + public void When_the_validator_fails_the_error_message_should_be_set() { + var validator = new TestValidator(v => v.RuleFor(x => x.Id).InclusiveBetween(1, 10)); + var result = validator.Validate(new Person { Id = 0 }); + result.Errors.Single().ErrorMessage.ShouldEqual("'Id' must be between 1 and 10. You entered 0."); + } + + [Fact] + public void To_and_from_properties_should_be_set() { + var validator = RangeValidatorFactory.CreateInclusiveBetween(1, 10); + validator.From.ShouldEqual(1); + validator.To.ShouldEqual(10); + } + + [Fact] + public void When_the_value_is_between_the_range_specified_then_the_validator_should_pass_for_strings() { + var validator = new TestValidator(v => v.RuleFor(x => x.Surname).InclusiveBetween("aa", "zz")); + var result = validator.Validate(new Person { Surname = "bbb" }); + result.IsValid.ShouldBeTrue(); + } + + [Fact] + public void When_the_value_is_smaller_than_the_range_then_the_validator_should_fail_for_strings() { + var validator = new TestValidator(v => v.RuleFor(x => x.Surname).InclusiveBetween("bbb", "zz")); + var result = validator.Validate(new Person { Surname = "aaa" }); + result.IsValid.ShouldBeFalse(); + } + + [Fact] + public void When_the_text_is_larger_than_the_range_then_the_validator_should_fail_for_strings() { + var validator = new TestValidator(v => v.RuleFor(x => x.Surname).InclusiveBetween("aaa", "bbb")); + var result = validator.Validate(new Person { Surname = "zzz" }); + result.IsValid.ShouldBeFalse(); + } + + [Fact] + public void When_the_value_is_exactly_the_size_of_the_upper_bound_then_the_validator_should_pass_for_strings() { + var validator = new TestValidator(v => v.RuleFor(x => x.Surname).InclusiveBetween("aa", "zz")); + var result = validator.Validate(new Person { Surname = "aa" }); + result.IsValid.ShouldBeTrue(); + } + + [Fact] + public void When_the_value_is_exactly_the_size_of_the_lower_bound_then_the_validator_should_pass_for_strings() { + var validator = new TestValidator(v => v.RuleFor(x => x.Surname).InclusiveBetween("aa", "zz")); + var result = validator.Validate(new Person { Surname = "zz" }); + result.IsValid.ShouldBeTrue(); + } + + [Fact] + public void When_the_to_is_smaller_than_the_from_then_the_validator_should_throw_for_strings() { + Assert.Throws(() => RangeValidatorFactory.CreateInclusiveBetween("ccc", "aaa")); + } + + [Fact] + public void When_the_validator_fails_the_error_message_should_be_set_for_strings() { + var validator = new TestValidator(v => v.RuleFor(x => x.Surname).InclusiveBetween("bbb", "zzz")); + var result = validator.Validate(new Person { Surname = "aaa" }); + result.Errors.Single().ErrorMessage.ShouldEqual("'Surname' must be between bbb and zzz. You entered aaa."); + } + + [Fact] + public void To_and_from_properties_should_be_set_for_strings() { + var validator = RangeValidatorFactory.CreateInclusiveBetween("a", "c"); + validator.From.ShouldEqual("a"); + validator.To.ShouldEqual("c"); + } + + [Fact] + public void Validates_with_nullable_when_property_is_null() { + var validator = new TestValidator(v => v.RuleFor(x => x.NullableInt).InclusiveBetween(1, 5)); + var result = validator.Validate(new Person()); + result.IsValid.ShouldBeTrue(); + } + + [Fact] + public void Validates_with_nullable_when_property_not_null() { + var validator = new TestValidator(v => v.RuleFor(x => x.NullableInt).InclusiveBetween(1, 5)); + var result = validator.Validate(new Person { NullableInt = 10 }); + result.IsValid.ShouldBeFalse(); + } + + [Fact] + public void When_the_value_is_between_the_range_specified_by_icomparer_then_the_validator_should_pass() { + var validator = new TestValidator(v => v.RuleFor(x => x.Address).InclusiveBetween( + new Address() { Line1 = "3 Main St." }, + new Address() { Line1 = "10 Main St." }, + new StreetNumberComparer + ())); + var result = validator.Validate(new Person { Address = new Address() { Line1 = "5 Main St." } }); + result.IsValid.ShouldBeTrue(); + } + + [Fact] + public void When_the_value_is_smaller_than_the_range_by_icomparer_then_the_validator_should_fail() { + var validator = new TestValidator(v => v.RuleFor(x => x.Address).InclusiveBetween( + new Address() { Line1 = "3 Main St." }, + new Address() { Line1 = "10 Main St." }, + new StreetNumberComparer())); + var result = validator.Validate(new Person { Address = new Address() { Line1 = "1 Main St." } }); + result.IsValid.ShouldBeFalse(); + } + + [Fact] + public void When_the_value_is_larger_than_the_range_by_icomparer_then_the_validator_should_fail() { + var validator = new TestValidator(v => v.RuleFor(x => x.Address).InclusiveBetween( + new Address() { Line1 = "3 Main St." }, + new Address() { Line1 = "10 Main St." }, + new StreetNumberComparer())); + var result = validator.Validate(new Person { Address = new Address() { Line1 = "11 Main St." } }); + result.IsValid.ShouldBeFalse(); + } +} diff --git a/src/FluentValidation/Validators/RangeValidator.cs b/src/FluentValidation/Validators/RangeValidator.cs new file mode 100644 index 0000000..eafe717 --- /dev/null +++ b/src/FluentValidation/Validators/RangeValidator.cs @@ -0,0 +1,84 @@ +#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.Validators; + +using System; +using System.Collections.Generic; + +/// +/// Base class for range validation. +/// +public abstract class RangeValidator : PropertyValidator, IBetweenValidator { + + readonly IComparer _explicitComparer; + + public RangeValidator(TProperty from, TProperty to, IComparer comparer) { + To = to; + From = from; + + _explicitComparer = comparer; + + if (comparer.Compare(to, from) == -1) { + throw new ArgumentOutOfRangeException(nameof(to), "To should be larger than from."); + } + } + + public TProperty From { get; } + public TProperty To { get; } + + object IBetweenValidator.From => From; + object IBetweenValidator.To => To; + + protected abstract bool HasError(TProperty value); + + public override bool IsValid(ValidationContext context, TProperty value) { + // If the value is null then we abort and assume success. + // This should not be a failure condition - only a NotNull/NotEmpty should cause a null to fail. + if (value == null) return true; + + if (HasError(value)) { + + context.MessageFormatter + .AppendArgument("From", From) + .AppendArgument("To", To); + + return false; + } + return true; + } + + protected int Compare(TProperty a, TProperty b) { + return _explicitComparer.Compare(a, b); + } + + protected override string GetDefaultMessageTemplate(string errorCode) { + return Localized(errorCode, Name); + } +} + +public static class RangeValidatorFactory { + public static ExclusiveBetweenValidator CreateExclusiveBetween(TProperty from, TProperty to) + where TProperty : IComparable, IComparable => + new ExclusiveBetweenValidator(from, to, ComparableComparer.Instance); + + public static InclusiveBetweenValidator CreateInclusiveBetween(TProperty from, TProperty to) + where TProperty : IComparable, IComparable { + return new InclusiveBetweenValidator(from, to, ComparableComparer.Instance); + } +}