diff --git a/Changelog.txt b/Changelog.txt index 3b87cf7c6..90d629e39 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -1,3 +1,6 @@ +11.0.0 - +Ensure property covariance is properly handled throughout the internal model (#1713) + 10.2.3 - 3 Jun 2021 Resolve issue with rulesets not cascading correctly to Inheritance Validators (#1754) diff --git a/src/FluentValidation.Tests/CustomMessageFormatTester.cs b/src/FluentValidation.Tests/CustomMessageFormatTester.cs index ca9118144..aeaa598e6 100644 --- a/src/FluentValidation.Tests/CustomMessageFormatTester.cs +++ b/src/FluentValidation.Tests/CustomMessageFormatTester.cs @@ -37,12 +37,12 @@ public class CustomMessageFormatTester { validator.RuleFor(x => x.Surname).NotNull().WithMessage("{PropertyName}"); string error = validator.Validate(new Person()).Errors.Single().ErrorMessage; error.ShouldEqual(expected); - } + } [Fact] public void Uses_custom_delegate_for_building_message() { validator.RuleFor(x => x.Surname).NotNull().Configure(cfg => { - cfg.MessageBuilder = context => "Test " + ((Person)context.InstanceToValidate).Id; + cfg.MessageBuilder = context => "Test " + context.InstanceToValidate.Id; }); var error = validator.Validate(new Person()).Errors.Single().ErrorMessage; @@ -64,7 +64,6 @@ public class CustomMessageFormatTester { result.Errors[1].ErrorMessage.ShouldEqual("'Surname' must not be empty."); } - [Fact] public void Uses_property_value_in_message() { validator.RuleFor(x => x.Surname).NotEqual("foo").WithMessage(person => $"was {person.Surname}"); diff --git a/src/FluentValidation.Tests/RuleBuilderTests.cs b/src/FluentValidation.Tests/RuleBuilderTests.cs index 9f1f8026f..122d6c45f 100644 --- a/src/FluentValidation.Tests/RuleBuilderTests.cs +++ b/src/FluentValidation.Tests/RuleBuilderTests.cs @@ -58,7 +58,8 @@ public class RuleBuilderTests { [Fact] public void Should_set_custom_error() { builder.SetValidator(new TestPropertyValidator()).WithMessage("Bar"); - _rule.Current.GetErrorMessage(null, default).ShouldEqual("Bar"); + var component = (RuleComponent) _rule.Current; + component.GetErrorMessage(null, default).ShouldEqual("Bar"); } [Fact] diff --git a/src/FluentValidation/DefaultValidatorOptions.cs b/src/FluentValidation/DefaultValidatorOptions.cs index 9a07cda41..c7ed56765 100644 --- a/src/FluentValidation/DefaultValidatorOptions.cs +++ b/src/FluentValidation/DefaultValidatorOptions.cs @@ -38,7 +38,7 @@ public static class DefaultValidatorOptions { /// Action to configure the object. /// public static IRuleBuilderInitial Configure(this IRuleBuilderInitial ruleBuilder, Action> configurator) { - configurator((IValidationRule) Configurable(ruleBuilder)); + configurator(Configurable(ruleBuilder)); return ruleBuilder; } @@ -49,7 +49,7 @@ public static class DefaultValidatorOptions { /// Action to configure the object. /// public static IRuleBuilderOptions Configure(this IRuleBuilderOptions ruleBuilder, Action> configurator) { - configurator((IValidationRule) Configurable(ruleBuilder)); + configurator(Configurable(ruleBuilder)); return ruleBuilder; } @@ -69,8 +69,8 @@ public static class DefaultValidatorOptions { /// /// The rule builder. /// A configurable IValidationRule instance. - public static IValidationRuleConfigurable Configurable(IRuleBuilder ruleBuilder) { - return ((IRuleBuilderInternal) ruleBuilder).GetConfigurableRule(); + public static IValidationRule Configurable(IRuleBuilder ruleBuilder) { + return ((IRuleBuilderInternal) ruleBuilder).Rule; } /// @@ -79,7 +79,7 @@ public static class DefaultValidatorOptions { /// The rule builder. /// A configurable IValidationRule instance. public static ICollectionRule Configurable(IRuleBuilderInitialCollection ruleBuilder) { - return (ICollectionRule) ((IRuleBuilderInternal) ruleBuilder).GetConfigurableRule(); + return (ICollectionRule) ((IRuleBuilderInternal) ruleBuilder).Rule; } /// diff --git a/src/FluentValidation/IValidationRule.cs b/src/FluentValidation/IValidationRule.cs index c7015aad3..979453517 100644 --- a/src/FluentValidation/IValidationRule.cs +++ b/src/FluentValidation/IValidationRule.cs @@ -27,9 +27,7 @@ namespace FluentValidation { using Results; using Validators; - //TODO: For FV 11 merge IValidationRuleConfigurable and IValidationRule - - public interface IValidationRuleConfigurable : IValidationRule { + public interface IValidationRule : IValidationRule { /// /// Cascade mode for this rule. /// @@ -75,55 +73,6 @@ public interface IValidationRuleConfigurable : IValidationRule public Func, string> MessageBuilder { set; } } - public interface IValidationRule : IValidationRule { - /// - /// Cascade mode for this rule. - /// - public CascadeMode CascadeMode { get; set; } - - /// - /// Function that will be invoked if any of the validators associated with this rule fail. - /// - public Action> OnFailure { get; set; } - - /// - /// Sets the display name for the property. - /// - /// The property's display name - void SetDisplayName(string name); - - /// - /// Sets the display name for the property using a function. - /// - /// The function for building the display name - void SetDisplayName(Func, string> factory); - - /// - /// Adds a validator to this rule. - /// - void AddValidator(IPropertyValidator validator); - - /// - /// Adds an async validator to this rule. - /// - /// The async property validator to invoke - /// A synchronous property validator to use as a fallback if executed synchronously. This parameter is optional. If omitted, the async validator will be called synchronously if needed. - void AddAsyncValidator(IAsyncPropertyValidator asyncValidator, IPropertyValidator fallback = null); - - /// - /// The current rule component. - /// - RuleComponent Current { get; } - - [Obsolete("The current validator is no longer directly exposed. Access the current component with rule.Current instead. This property will be removed in FluentValidation 11.")] - RuleComponent CurrentValidator { get; } - - /// - /// Allows custom creation of an error message - /// - public Func, string> MessageBuilder { get; set; } - } - public interface IValidationRule : IValidationRule { /// diff --git a/src/FluentValidation/IValidationRuleInternal.cs b/src/FluentValidation/IValidationRuleInternal.cs index d61f2d9cc..f76724baa 100644 --- a/src/FluentValidation/IValidationRuleInternal.cs +++ b/src/FluentValidation/IValidationRuleInternal.cs @@ -30,7 +30,7 @@ internal interface IValidationRuleInternal : IValidationRule { void AddDependentRules(IEnumerable> rules); } - internal interface IValidationRuleInternal : IValidationRule, IValidationRuleInternal, IValidationRuleConfigurable { + internal interface IValidationRuleInternal : IValidationRule, IValidationRuleInternal { new List> Components { get; } } } diff --git a/src/FluentValidation/Internal/RuleBase.cs b/src/FluentValidation/Internal/RuleBase.cs index e100f4e44..ffca950ef 100644 --- a/src/FluentValidation/Internal/RuleBase.cs +++ b/src/FluentValidation/Internal/RuleBase.cs @@ -27,7 +27,7 @@ namespace FluentValidation.Internal { using Results; using Validators; - internal abstract class RuleBase : IValidationRule, IValidationRuleConfigurable { + internal abstract class RuleBase : IValidationRule { private readonly List> _components = new(); private Func _cascadeModeThunk; private string _propertyDisplayName; @@ -96,15 +96,10 @@ internal abstract class RuleBase : IValidationRule public Action> OnFailure { get; set; } - /// - /// The current validator being configured by this rule. - /// - public RuleComponent CurrentValidator => _components.LastOrDefault(); - /// /// The current rule component. /// - public RuleComponent Current => _components.LastOrDefault(); + public IRuleComponent Current => _components.LastOrDefault(); /// /// Type of the property being validated @@ -155,8 +150,6 @@ internal abstract class RuleBase : IValidationRule IValidationRuleConfigurable.Current => Current; - // /// // /// Replaces a validator in this rule. Used to wrap validators. // /// @@ -197,12 +190,7 @@ internal abstract class RuleBase : IValidationRule /// Allows custom creation of an error message /// - public Func, string> MessageBuilder { get; set; } - - //TODO: Make this the default version of MessageBuilder for FV 11. - Func, string> IValidationRuleConfigurable.MessageBuilder { - set => MessageBuilder = value; - } + public Func, string> MessageBuilder { get; set; } /// /// Dependent rules @@ -239,7 +227,7 @@ public string GetDisplayName(ValidationContext context) } } else { - CurrentValidator.ApplyCondition(predicate); + Current.ApplyCondition(predicate); } } @@ -262,7 +250,7 @@ public string GetDisplayName(ValidationContext context) } } else { - CurrentValidator.ApplyAsyncCondition(predicate); + Current.ApplyAsyncCondition(predicate); } } diff --git a/src/FluentValidation/Internal/RuleBuilder.cs b/src/FluentValidation/Internal/RuleBuilder.cs index ad0885a7a..c0b2ab7a4 100644 --- a/src/FluentValidation/Internal/RuleBuilder.cs +++ b/src/FluentValidation/Internal/RuleBuilder.cs @@ -33,8 +33,7 @@ internal class RuleBuilder : IRuleBuilderOptions, IR /// public IValidationRuleInternal Rule { get; } - //TODO: Remove in FV11 once IValidationRule and IValidationRuleConfigurable have been combined. - private IValidationRuleConfigurable ConfigurableRule => Rule; + IValidationRule IRuleBuilderInternal.Rule => Rule; /// /// Parent validator @@ -51,7 +50,7 @@ internal class RuleBuilder : IRuleBuilderOptions, IR public IRuleBuilderOptions SetValidator(IPropertyValidator validator) { if (validator == null) throw new ArgumentNullException(nameof(validator)); - ConfigurableRule.AddValidator(validator); + Rule.AddValidator(validator); return this; } @@ -59,7 +58,7 @@ internal class RuleBuilder : IRuleBuilderOptions, IR if (validator == null) throw new ArgumentNullException(nameof(validator)); // See if the async validator supports synchronous execution too. IPropertyValidator fallback = validator as IPropertyValidator; - ConfigurableRule.AddAsyncValidator(validator, fallback); + Rule.AddAsyncValidator(validator, fallback); return this; } @@ -69,7 +68,7 @@ internal class RuleBuilder : IRuleBuilderOptions, IR RuleSets = ruleSets }; // ChildValidatorAdaptor supports both sync and async execution. - ConfigurableRule.AddAsyncValidator(adaptor, adaptor); + Rule.AddAsyncValidator(adaptor, adaptor); return this; } @@ -79,7 +78,7 @@ internal class RuleBuilder : IRuleBuilderOptions, IR RuleSets = ruleSets }; // ChildValidatorAdaptor supports both sync and async execution. - ConfigurableRule.AddAsyncValidator(adaptor, adaptor); + Rule.AddAsyncValidator(adaptor, adaptor); return this; } @@ -89,7 +88,7 @@ internal class RuleBuilder : IRuleBuilderOptions, IR RuleSets = ruleSets }; // ChildValidatorAdaptor supports both sync and async execution. - ConfigurableRule.AddAsyncValidator(adaptor, adaptor); + Rule.AddAsyncValidator(adaptor, adaptor); return this; } @@ -115,7 +114,5 @@ internal class RuleBuilder : IRuleBuilderOptions, IR public void AddComponent(RuleComponent component) { Rule.Components.Add(component); } - - IValidationRuleConfigurable IRuleBuilderInternal.GetConfigurableRule() => Rule; } } diff --git a/src/FluentValidation/Syntax.cs b/src/FluentValidation/Syntax.cs index 60c6c2113..8372c64ea 100644 --- a/src/FluentValidation/Syntax.cs +++ b/src/FluentValidation/Syntax.cs @@ -115,7 +115,7 @@ public interface IConditionBuilder { } internal interface IRuleBuilderInternal { - IValidationRuleConfigurable GetConfigurableRule(); + IValidationRule Rule { get; } } }