Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend property covariance into the internal model. #1713

Merged
merged 2 commits into from Jun 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions 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)

Expand Down
5 changes: 2 additions & 3 deletions src/FluentValidation.Tests/CustomMessageFormatTester.cs
Expand Up @@ -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;
Expand All @@ -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}");
Expand Down
3 changes: 2 additions & 1 deletion src/FluentValidation.Tests/RuleBuilderTests.cs
Expand Up @@ -58,7 +58,8 @@ public class RuleBuilderTests {
[Fact]
public void Should_set_custom_error() {
builder.SetValidator(new TestPropertyValidator<Person, string>()).WithMessage("Bar");
_rule.Current.GetErrorMessage(null, default).ShouldEqual("Bar");
var component = (RuleComponent<Person, string>) _rule.Current;
component.GetErrorMessage(null, default).ShouldEqual("Bar");
}

[Fact]
Expand Down
10 changes: 5 additions & 5 deletions src/FluentValidation/DefaultValidatorOptions.cs
Expand Up @@ -38,7 +38,7 @@ public static class DefaultValidatorOptions {
/// <param name="configurator">Action to configure the object.</param>
/// <returns></returns>
public static IRuleBuilderInitial<T, TProperty> Configure<T, TProperty>(this IRuleBuilderInitial<T, TProperty> ruleBuilder, Action<IValidationRule<T, TProperty>> configurator) {
configurator((IValidationRule<T, TProperty>) Configurable(ruleBuilder));
configurator(Configurable(ruleBuilder));
return ruleBuilder;
}

Expand All @@ -49,7 +49,7 @@ public static class DefaultValidatorOptions {
/// <param name="configurator">Action to configure the object.</param>
/// <returns></returns>
public static IRuleBuilderOptions<T, TProperty> Configure<T, TProperty>(this IRuleBuilderOptions<T, TProperty> ruleBuilder, Action<IValidationRule<T, TProperty>> configurator) {
configurator((IValidationRule<T, TProperty>) Configurable(ruleBuilder));
configurator(Configurable(ruleBuilder));
return ruleBuilder;
}

Expand All @@ -69,8 +69,8 @@ public static class DefaultValidatorOptions {
/// </summary>
/// <param name="ruleBuilder">The rule builder.</param>
/// <returns>A configurable IValidationRule instance.</returns>
public static IValidationRuleConfigurable<T, TProperty> Configurable<T, TProperty>(IRuleBuilder<T, TProperty> ruleBuilder) {
return ((IRuleBuilderInternal<T, TProperty>) ruleBuilder).GetConfigurableRule();
public static IValidationRule<T, TProperty> Configurable<T, TProperty>(IRuleBuilder<T, TProperty> ruleBuilder) {
return ((IRuleBuilderInternal<T, TProperty>) ruleBuilder).Rule;
}

/// <summary>
Expand All @@ -79,7 +79,7 @@ public static class DefaultValidatorOptions {
/// <param name="ruleBuilder">The rule builder.</param>
/// <returns>A configurable IValidationRule instance.</returns>
public static ICollectionRule<T, TCollectionElement> Configurable<T, TCollectionElement>(IRuleBuilderInitialCollection<T, TCollectionElement> ruleBuilder) {
return (ICollectionRule<T, TCollectionElement>) ((IRuleBuilderInternal<T, TCollectionElement>) ruleBuilder).GetConfigurableRule();
return (ICollectionRule<T, TCollectionElement>) ((IRuleBuilderInternal<T, TCollectionElement>) ruleBuilder).Rule;
}

/// <summary>
Expand Down
53 changes: 1 addition & 52 deletions src/FluentValidation/IValidationRule.cs
Expand Up @@ -27,9 +27,7 @@ namespace FluentValidation {
using Results;
using Validators;

//TODO: For FV 11 merge IValidationRuleConfigurable and IValidationRule<T,Tproperty>

public interface IValidationRuleConfigurable<T, out TProperty> : IValidationRule<T> {
public interface IValidationRule<T, out TProperty> : IValidationRule<T> {
/// <summary>
/// Cascade mode for this rule.
/// </summary>
Expand Down Expand Up @@ -75,55 +73,6 @@ public interface IValidationRuleConfigurable<T, out TProperty> : IValidationRule
public Func<IMessageBuilderContext<T,TProperty>, string> MessageBuilder { set; }
}

public interface IValidationRule<T, TProperty> : IValidationRule<T> {
/// <summary>
/// Cascade mode for this rule.
/// </summary>
public CascadeMode CascadeMode { get; set; }

/// <summary>
/// Function that will be invoked if any of the validators associated with this rule fail.
/// </summary>
public Action<T, IEnumerable<ValidationFailure>> OnFailure { get; set; }

/// <summary>
/// Sets the display name for the property.
/// </summary>
/// <param name="name">The property's display name</param>
void SetDisplayName(string name);

/// <summary>
/// Sets the display name for the property using a function.
/// </summary>
/// <param name="factory">The function for building the display name</param>
void SetDisplayName(Func<ValidationContext<T>, string> factory);

/// <summary>
/// Adds a validator to this rule.
/// </summary>
void AddValidator(IPropertyValidator<T, TProperty> validator);

/// <summary>
/// Adds an async validator to this rule.
/// </summary>
/// <param name="asyncValidator">The async property validator to invoke</param>
/// <param name="fallback">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.</param>
void AddAsyncValidator(IAsyncPropertyValidator<T, TProperty> asyncValidator, IPropertyValidator<T, TProperty> fallback = null);

/// <summary>
/// The current rule component.
/// </summary>
RuleComponent<T,TProperty> 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<T,TProperty> CurrentValidator { get; }

/// <summary>
/// Allows custom creation of an error message
/// </summary>
public Func<MessageBuilderContext<T,TProperty>, string> MessageBuilder { get; set; }
}

public interface IValidationRule<T> : IValidationRule {

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion src/FluentValidation/IValidationRuleInternal.cs
Expand Up @@ -30,7 +30,7 @@ internal interface IValidationRuleInternal<T> : IValidationRule<T> {
void AddDependentRules(IEnumerable<IValidationRuleInternal<T>> rules);
}

internal interface IValidationRuleInternal<T, TProperty> : IValidationRule<T, TProperty>, IValidationRuleInternal<T>, IValidationRuleConfigurable<T,TProperty> {
internal interface IValidationRuleInternal<T, TProperty> : IValidationRule<T, TProperty>, IValidationRuleInternal<T> {
new List<RuleComponent<T,TProperty>> Components { get; }
}
}
22 changes: 5 additions & 17 deletions src/FluentValidation/Internal/RuleBase.cs
Expand Up @@ -27,7 +27,7 @@ namespace FluentValidation.Internal {
using Results;
using Validators;

internal abstract class RuleBase<T, TProperty, TValue> : IValidationRule<T, TValue>, IValidationRuleConfigurable<T,TValue> {
internal abstract class RuleBase<T, TProperty, TValue> : IValidationRule<T, TValue> {
private readonly List<RuleComponent<T, TValue>> _components = new();
private Func<CascadeMode> _cascadeModeThunk;
private string _propertyDisplayName;
Expand Down Expand Up @@ -96,15 +96,10 @@ internal abstract class RuleBase<T, TProperty, TValue> : IValidationRule<T, TVal
/// </summary>
public Action<T, IEnumerable<ValidationFailure>> OnFailure { get; set; }

/// <summary>
/// The current validator being configured by this rule.
/// </summary>
public RuleComponent<T, TValue> CurrentValidator => _components.LastOrDefault();

/// <summary>
/// The current rule component.
/// </summary>
public RuleComponent<T, TValue> Current => _components.LastOrDefault();
public IRuleComponent<T, TValue> Current => _components.LastOrDefault();

/// <summary>
/// Type of the property being validated
Expand Down Expand Up @@ -155,8 +150,6 @@ internal abstract class RuleBase<T, TProperty, TValue> : IValidationRule<T, TVal
_components.Add(component);
}

IRuleComponent<T, TValue> IValidationRuleConfigurable<T, TValue>.Current => Current;

// /// <summary>
// /// Replaces a validator in this rule. Used to wrap validators.
// /// </summary>
Expand Down Expand Up @@ -197,12 +190,7 @@ internal abstract class RuleBase<T, TProperty, TValue> : IValidationRule<T, TVal
/// <summary>
/// Allows custom creation of an error message
/// </summary>
public Func<MessageBuilderContext<T, TValue>, string> MessageBuilder { get; set; }

//TODO: Make this the default version of MessageBuilder for FV 11.
Func<IMessageBuilderContext<T, TValue>, string> IValidationRuleConfigurable<T, TValue>.MessageBuilder {
set => MessageBuilder = value;
}
public Func<IMessageBuilderContext<T, TValue>, string> MessageBuilder { get; set; }

/// <summary>
/// Dependent rules
Expand Down Expand Up @@ -239,7 +227,7 @@ public string GetDisplayName(ValidationContext<T> context)
}
}
else {
CurrentValidator.ApplyCondition(predicate);
Current.ApplyCondition(predicate);
}
}

Expand All @@ -262,7 +250,7 @@ public string GetDisplayName(ValidationContext<T> context)
}
}
else {
CurrentValidator.ApplyAsyncCondition(predicate);
Current.ApplyAsyncCondition(predicate);
}
}

Expand Down
15 changes: 6 additions & 9 deletions src/FluentValidation/Internal/RuleBuilder.cs
Expand Up @@ -33,8 +33,7 @@ internal class RuleBuilder<T, TProperty> : IRuleBuilderOptions<T, TProperty>, IR
/// </summary>
public IValidationRuleInternal<T, TProperty> Rule { get; }

//TODO: Remove in FV11 once IValidationRule<T,TProperty> and IValidationRuleConfigurable<T,TProperty> have been combined.
private IValidationRuleConfigurable<T, TProperty> ConfigurableRule => Rule;
IValidationRule<T, TProperty> IRuleBuilderInternal<T,TProperty>.Rule => Rule;

/// <summary>
/// Parent validator
Expand All @@ -51,15 +50,15 @@ internal class RuleBuilder<T, TProperty> : IRuleBuilderOptions<T, TProperty>, IR

public IRuleBuilderOptions<T, TProperty> SetValidator(IPropertyValidator<T, TProperty> validator) {
if (validator == null) throw new ArgumentNullException(nameof(validator));
ConfigurableRule.AddValidator(validator);
Rule.AddValidator(validator);
return this;
}

public IRuleBuilderOptions<T, TProperty> SetAsyncValidator(IAsyncPropertyValidator<T, TProperty> validator) {
if (validator == null) throw new ArgumentNullException(nameof(validator));
// See if the async validator supports synchronous execution too.
IPropertyValidator<T, TProperty> fallback = validator as IPropertyValidator<T, TProperty>;
ConfigurableRule.AddAsyncValidator(validator, fallback);
Rule.AddAsyncValidator(validator, fallback);
return this;
}

Expand All @@ -69,7 +68,7 @@ internal class RuleBuilder<T, TProperty> : IRuleBuilderOptions<T, TProperty>, IR
RuleSets = ruleSets
};
// ChildValidatorAdaptor supports both sync and async execution.
ConfigurableRule.AddAsyncValidator(adaptor, adaptor);
Rule.AddAsyncValidator(adaptor, adaptor);
return this;
}

Expand All @@ -79,7 +78,7 @@ internal class RuleBuilder<T, TProperty> : IRuleBuilderOptions<T, TProperty>, IR
RuleSets = ruleSets
};
// ChildValidatorAdaptor supports both sync and async execution.
ConfigurableRule.AddAsyncValidator(adaptor, adaptor);
Rule.AddAsyncValidator(adaptor, adaptor);
return this;
}

Expand All @@ -89,7 +88,7 @@ internal class RuleBuilder<T, TProperty> : IRuleBuilderOptions<T, TProperty>, IR
RuleSets = ruleSets
};
// ChildValidatorAdaptor supports both sync and async execution.
ConfigurableRule.AddAsyncValidator(adaptor, adaptor);
Rule.AddAsyncValidator(adaptor, adaptor);
return this;
}

Expand All @@ -115,7 +114,5 @@ internal class RuleBuilder<T, TProperty> : IRuleBuilderOptions<T, TProperty>, IR
public void AddComponent(RuleComponent<T,TProperty> component) {
Rule.Components.Add(component);
}

IValidationRuleConfigurable<T, TProperty> IRuleBuilderInternal<T, TProperty>.GetConfigurableRule() => Rule;
}
}
2 changes: 1 addition & 1 deletion src/FluentValidation/Syntax.cs
Expand Up @@ -115,7 +115,7 @@ public interface IConditionBuilder {
}

internal interface IRuleBuilderInternal<T, out TProperty> {
IValidationRuleConfigurable<T, TProperty> GetConfigurableRule();
IValidationRule<T, TProperty> Rule { get; }
}

}