Skip to content

Commit

Permalink
Extend variance into the internal model.
Browse files Browse the repository at this point in the history
  • Loading branch information
JeremySkinner committed Apr 20, 2021
1 parent 4d77fa5 commit 9c0b8f3
Show file tree
Hide file tree
Showing 11 changed files with 112 additions and 16 deletions.
3 changes: 3 additions & 0 deletions src/FluentValidation.Tests/AbstractValidatorTester.cs
Expand Up @@ -25,6 +25,7 @@ namespace FluentValidation.Tests {
using System.Threading.Tasks;
using Xunit;
using Results;
using Validators;


public class AbstractValidatorTester {
Expand Down Expand Up @@ -357,5 +358,7 @@ private class DerivedPerson : Person { }
new ValidationResult(new List<ValidationFailure> {new ValidationFailure(nameof(Person.AnotherInt), $"{nameof(Person.AnotherInt)} Test Message")})
};



}
}
21 changes: 21 additions & 0 deletions src/FluentValidation.Tests/ForEachRuleTests.cs
Expand Up @@ -664,6 +664,27 @@ public class AppropriatenessAnswerViewModelRequiredValidator<T,TProperty> : Prop
}


[Fact]
public void Shouldnt_throw_exception_when_configuring_rule_after_ForEach() {
var validator = new InlineValidator<Person>();

validator.RuleFor(x => x.Orders)
.ForEach(o => {
o.Must(v => true);
})
.Must((val) => true)
.WithMessage("what");

// The RuleBuilder is RuleBuilder<Person, IList<Order>>
// after the ForEach, it's returned as an IRuleBuilderOptions<Person, IEnumerable<Order>>

var result = validator.Validate(new Person() {
Orders = new List<Order>() { new Order()}
});

result.IsValid.ShouldBeTrue();
}

public class OrderValidator : AbstractValidator<Order> {
public OrderValidator() {
RuleFor(x => x.ProductName).NotEmpty();
Expand Down
2 changes: 1 addition & 1 deletion src/FluentValidation.Tests/RuleBuilderTests.cs
Expand Up @@ -61,7 +61,7 @@ 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");
((RuleComponent<Person,string>)_rule.Current).GetErrorMessage(null, default).ShouldEqual("Bar");
}

[Fact]
Expand Down
4 changes: 2 additions & 2 deletions src/FluentValidation/DefaultValidatorOptions.cs
Expand Up @@ -70,7 +70,7 @@ public static class DefaultValidatorOptions {
/// <param name="ruleBuilder">The rule builder.</param>
/// <returns>A configurable IValidationRule instance.</returns>
public static IValidationRule<T, TProperty> Configurable<T, TProperty>(IRuleBuilder<T, TProperty> ruleBuilder) {
return ((RuleBuilder<T, TProperty>) ruleBuilder).Rule;
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>) ((RuleBuilder<T, TCollectionElement>) ruleBuilder).Rule;
return (ICollectionRule<T, TCollectionElement>) ((IRuleBuilderInternal<T, TCollectionElement>) ruleBuilder).Rule;
}

/// <summary>
Expand Down
10 changes: 5 additions & 5 deletions src/FluentValidation/IValidationRule.cs
Expand Up @@ -28,8 +28,7 @@ namespace FluentValidation {
using Results;
using Validators;


public interface IValidationRule<T, TProperty> : IValidationRule<T> {
public interface IValidationRule<T, out TProperty> : IValidationRule<T> {
/// <summary>
/// Cascade mode for this rule.
/// </summary>
Expand Down Expand Up @@ -67,15 +66,16 @@ public interface IValidationRule<T, TProperty> : IValidationRule<T> {
/// <summary>
/// The current rule component.
/// </summary>
RuleComponent<T,TProperty> Current { get; }
IRuleComponent<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; }
IRuleComponent<T,TProperty> CurrentValidator { get; }


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

public interface IValidationRule<T> : IValidationRule {
Expand Down
52 changes: 52 additions & 0 deletions src/FluentValidation/Internal/IRuleComponent.cs
Expand Up @@ -19,8 +19,60 @@
#endregion

namespace FluentValidation.Internal {
using System;
using System.Threading;
using System.Threading.Tasks;
using Validators;

/// <summary>
/// An individual component within a rule with a validator attached.
/// </summary>
public interface IRuleComponent<T, out TProperty> : IRuleComponent {
/// <summary>
/// The error code associated with this rule component.
/// </summary>
new string ErrorCode { get; set; }

/// <summary>
/// Function used to retrieve custom state for the validator
/// </summary>
Func<ValidationContext<T>, TProperty, object> CustomStateProvider { set; }

/// <summary>
/// Function used to retrieve the severity for the validator
/// </summary>
Func<ValidationContext<T>, TProperty, Severity> SeverityProvider { set; }

/// <summary>
/// Adds a condition for this validator. If there's already a condition, they're combined together with an AND.
/// </summary>
/// <param name="condition"></param>
void ApplyCondition(Func<ValidationContext<T>, bool> condition);

/// <summary>
/// Adds a condition for this validator. If there's already a condition, they're combined together with an AND.
/// </summary>
/// <param name="condition"></param>
void ApplyAsyncCondition(Func<ValidationContext<T>, CancellationToken, Task<bool>> condition);

/// <summary>
/// Sets the overridden error message template for this validator.
/// </summary>
/// <param name="errorFactory">A function for retrieving the error message template.</param>
void SetErrorMessage(Func<ValidationContext<T>, TProperty, string> errorFactory);

/// <summary>
/// Sets the overridden error message template for this validator.
/// </summary>
/// <param name="errorMessage">The error message to set</param>
void SetErrorMessage(string errorMessage);

/// <summary>
/// Sets the on failure callback.
/// </summary>
Action<T, ValidationContext<T>, TProperty, string> OnFailure { set; }
}

/// <summary>
/// An individual component within a rule with a validator attached.
/// </summary>
Expand Down
17 changes: 16 additions & 1 deletion src/FluentValidation/Internal/MessageBuilderContext.cs
Expand Up @@ -3,7 +3,19 @@
using Resources;
using Validators;

public class MessageBuilderContext<T,TProperty> {
public interface IMessageBuilderContext<T> {
IRuleComponent Component { get; }
IPropertyValidator PropertyValidator { get; }
ValidationContext<T> ParentContext { get; }
string PropertyName { get; }
string DisplayName { get; }
MessageFormatter MessageFormatter { get; }
T InstanceToValidate { get; }
object PropertyValue { get; }
string GetDefaultMessage();
}

public class MessageBuilderContext<T,TProperty> : IMessageBuilderContext<T> {
private ValidationContext<T> _innerContext;
private TProperty _value;

Expand All @@ -14,6 +26,7 @@ public class MessageBuilderContext<T,TProperty> {
}

public RuleComponent<T,TProperty> Component { get; }
IRuleComponent IMessageBuilderContext<T>.Component => Component;

public IPropertyValidator PropertyValidator
=> Component.Validator;
Expand All @@ -29,6 +42,8 @@ public IPropertyValidator PropertyValidator
public MessageFormatter MessageFormatter => _innerContext.MessageFormatter;

public T InstanceToValidate => _innerContext.InstanceToValidate;
object IMessageBuilderContext<T>.PropertyValue => PropertyValue;

public TProperty PropertyValue => _value;

public string GetDefaultMessage() {
Expand Down
6 changes: 3 additions & 3 deletions src/FluentValidation/Internal/RuleBase.cs
Expand Up @@ -99,12 +99,12 @@ internal abstract class RuleBase<T, TProperty, TValue> : IValidationRule<T, TVal
/// <summary>
/// The current validator being configured by this rule.
/// </summary>
public RuleComponent<T,TValue> CurrentValidator => _components.LastOrDefault();
public IRuleComponent<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 @@ -195,7 +195,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; }
public Func<IMessageBuilderContext<T>, string> MessageBuilder { get; set; }

/// <summary>
/// Dependent rules
Expand Down
4 changes: 3 additions & 1 deletion src/FluentValidation/Internal/RuleBuilder.cs
Expand Up @@ -26,12 +26,13 @@ namespace FluentValidation.Internal {
/// </summary>
/// <typeparam name="T">Type of object being validated</typeparam>
/// <typeparam name="TProperty">Type of property being validated</typeparam>
internal class RuleBuilder<T, TProperty> : IRuleBuilderOptions<T, TProperty>, IRuleBuilderInitial<T, TProperty>, IRuleBuilderInitialCollection<T,TProperty>, IRuleBuilderOptionsConditions<T, TProperty> {
internal class RuleBuilder<T, TProperty> : IRuleBuilderOptions<T, TProperty>, IRuleBuilderInitial<T, TProperty>, IRuleBuilderInitialCollection<T,TProperty>, IRuleBuilderOptionsConditions<T, TProperty>, IRuleBuilderInternal<T,TProperty> {

/// <summary>
/// The rule being created by this RuleBuilder.
/// </summary>
public IValidationRuleInternal<T, TProperty> Rule { get; }
IValidationRule<T, TProperty> IRuleBuilderInternal<T, TProperty>.Rule => Rule;

/// <summary>
/// Parent validator
Expand Down Expand Up @@ -112,5 +113,6 @@ internal class RuleBuilder<T, TProperty> : IRuleBuilderOptions<T, TProperty>, IR
public void AddComponent(RuleComponent<T,TProperty> component) {
Rule.Components.Add(component);
}

}
}
5 changes: 2 additions & 3 deletions src/FluentValidation/Internal/RuleComponent.cs
Expand Up @@ -22,15 +22,14 @@ namespace FluentValidation.Internal {
using System;
using System.Threading;
using System.Threading.Tasks;
using Internal;
using Validators;

/// <summary>
/// An individual component within a rule.
/// In a rule definition such as RuleFor(x => x.Name).NotNull().NotEqual("Foo")
/// the NotNull and the NotEqual are both rule components.
/// </summary>
public class RuleComponent<T,TProperty> : IRuleComponent {
public class RuleComponent<T,TProperty> : IRuleComponent<T, TProperty> {
private string _errorMessage;
private Func<ValidationContext<T>, TProperty, string> _errorMessageFactory;
private Func<ValidationContext<T>, bool> _condition;
Expand Down Expand Up @@ -216,7 +215,7 @@ internal virtual Task<bool> ValidateAsync(ValidationContext<T> context, TPropert
_errorMessageFactory = null;
}

internal Action<T, ValidationContext<T>, TProperty, string> OnFailure { get; set; }
public Action<T, ValidationContext<T>, TProperty, string> OnFailure { get; set; }
}

}
4 changes: 4 additions & 0 deletions src/FluentValidation/Syntax.cs
Expand Up @@ -114,4 +114,8 @@ public interface IConditionBuilder {
void Otherwise(Action action);
}

internal interface IRuleBuilderInternal<T, out TProperty> {
IValidationRule<T, TProperty> Rule { get; }
}

}

0 comments on commit 9c0b8f3

Please sign in to comment.