Skip to content

Commit

Permalink
Extend property covariance into the internal model. (#1713)
Browse files Browse the repository at this point in the history
* Remove IValidationRuleConfigurable and finish implementing variance in the internal api
* Update changelog
  • Loading branch information
JeremySkinner committed Jun 17, 2021
1 parent 5426d94 commit 2e897b9
Show file tree
Hide file tree
Showing 9 changed files with 26 additions and 89 deletions.
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.3.0 -
Update Russian translations (#1761)
Add Thai translations (#1768)
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; }
}

}

0 comments on commit 2e897b9

Please sign in to comment.