Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions Ifp.Validation/AsyncValidationRule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
using System.Threading.Tasks;

namespace Ifp.Validation
{

/// <summary>
/// The base class for implementing the validation logic. <see cref="ValidateObjectAsync(T)"/> must be implemented with the validation logic.
/// </summary>
/// <typeparam name="T">The type of the object to validate.</typeparam>
public abstract class AsyncValidationRule<T> : IAsyncValidationRule<T>
{
/// <summary>
/// Checks the <paramref name="objectToValidate"/> and returns a <see cref="ValidationOutcome"/>.
/// </summary>
/// <param name="objectToValidate">The object to validate.</param>
/// <returns>A <see cref="ValidationOutcome"/> that represents the result of the validation.</returns>
/// <remarks>
/// You can use the <see cref="ValidationSummaryBuilderExtensions.ToFailure(string, FailureSeverity)"/> extension method to construct a <see cref="ValidationOutcome"/>.
/// </remarks>
/// <example>
/// <para>Indicate success by returning <see cref="ValidationOutcome.Success"/>.</para>
/// <code language="cs">
/// public override ValidationOutcome ValidateObject(Animal objectToValidate)
/// {
/// return ValidationOutcome.Success;
/// }
/// </code>
///
/// <para>Return a <see cref="ValidationOutcome"/> by using the <see cref="ValidationOutcome.Failure(FailureSeverity, string)"/> method.</para>
/// <code language="cs">
/// public override ValidationOutcome ValidateObject(Animal objectToValidate)
/// {
/// return ValidationOutcome.Failure(FailureSeverity.Error, "This is an error message.");
/// }
/// </code>
///
/// <para>Return a <see cref="ValidationOutcome"/> using the <see cref="string"/> extension method
/// <see cref="ValidationSummaryBuilderExtensions.ToFailure(string, FailureSeverity)"/>.</para>
/// <code language="cs">
/// public override ValidationOutcome ValidateObject(Animal objectToValidate)
/// {
/// return "This is an error message.".ToFailure(FailureSeverity.Error);
/// }
/// </code>
/// </example>
public abstract Task<ValidationOutcome> ValidateObjectAsync(T objectToValidate);

/// <summary>
/// Returns always false. Override this property and return true if
/// the <see cref="AsyncRuleBasedValidator{T}"/> should not proceed validation tests in case this validation rule returns a <see cref="ValidationOutcome"/> with <see cref="ValidationSeverity.IsAnError"/>.
/// This is useful to prevent further processing of rules in cases where all the following rules will also fail. A typical use case is a check for <c>objectToValidate == null</c>.
/// </summary>
public virtual bool CausesValidationProcessToStop => false;
}

/// <summary>
/// The <see cref="AsyncValidationFunction{T}"/> delegate defines a function that can validate another object.
/// The signature is the same as in <see cref="IAsyncValidationRule{T}.ValidateObjectAsync(T)"/>.
/// </summary>
/// <typeparam name="T">The type of the object to validate.</typeparam>
/// <param name="objectToValidate">The object to validate.</param>
/// <returns>A <see cref="ValidationOutcome"/> that represents the result of the validation.</returns>
public delegate Task<ValidationOutcome> AsyncValidationFunction<T>(T objectToValidate);

/// <summary>
/// A class that takes a <see cref="AsyncValidationFunction{T}"/> delegate to perform the validation. This allows to define a validation rule without the need to implement a class that derives from <see cref="AsyncValidationRule{T}"/>.
/// </summary>
/// <typeparam name="T">The type of the object to validate.</typeparam>
public class AsyncValidationRuleDelegate<T> : IAsyncValidationRule<T>
{

/// <summary>
/// Constructs a new <see cref="AsyncValidationRuleDelegate{T}"/> object and takes a <see cref="AsyncValidationFunction{T}"/> delegate that does the validation.
/// </summary>
/// <param name="validationFunction">The <see cref="ValidationFunction{T}"/> delegate, that implements the validation logic.</param>
/// <remarks>
/// The <see cref="IAsyncValidationRule{T}.CausesValidationProcessToStop"/> property is <c>false</c>.
/// </remarks>
public AsyncValidationRuleDelegate(AsyncValidationFunction<T> validationFunction)
: this(validationFunction, false)
{
}

/// <summary>
/// Constructs a new <see cref="AsyncValidationRuleDelegate{T}"/> object and takes a <see cref="AsyncValidationFunction{T}"/> delegate that does the validation.
/// </summary>
/// <param name="validationFunction">The <see cref="ValidationFunction{T}"/> delegate, that implements the validation logic.</param>
/// <param name="causesValidationProcessToStop">Pass <c>true</c> to prevent the <see cref="AsyncRuleBasedValidator{T}"/> to proceed with more <see cref="IAsyncValidationRule{T}"/>.</param>
public AsyncValidationRuleDelegate(AsyncValidationFunction<T> validationFunction, bool causesValidationProcessToStop)
{
ValidationFunction = validationFunction;
CausesValidationProcessToStop = causesValidationProcessToStop;
}

/// <summary>
/// The <see cref="AsyncValidationFunction{T}"/> passes to the constructor.
/// </summary>
protected AsyncValidationFunction<T> ValidationFunction { get; }

/// <summary>
/// Implements <see cref="IAsyncValidationRule{T}.ValidateObjectAsync(T)"/> by delegating to the validation function passed in the constructor.
/// </summary>
/// <param name="objectToValidate">The object to validate.</param>
/// <returns>The <see cref="ValidationOutcome"/>.</returns>
public async Task<ValidationOutcome> ValidateObjectAsync(T objectToValidate)
=> await ValidationFunction(objectToValidate);

/// <summary>
/// Use the constructor <see cref="AsyncValidationRuleDelegate(AsyncValidationFunction{T}, bool)"/> to configure this property.
/// Return true if the <see cref="AsyncRuleBasedValidator{T}"/> should not proceed validation tests in case this validation rule returns a <see cref="ValidationOutcome"/> with <see cref="ValidationSeverity.IsAnError"/>.
/// This is useful to prevent further processing of rules in cases where all the following rules will also fail. A typical use case is a check for <c>objectToValidate == null</c>.
/// </summary>
public bool CausesValidationProcessToStop { get; }
}
}
174 changes: 174 additions & 0 deletions Ifp.Validation/AsyncValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Ifp.Validation
{

/// <summary>
/// Base class for validators. Those validators usually don't perform validations on there own
/// but delegate the validation to one or more <see cref="IAsyncValidationRule{T}"/> objects. These <see cref="IAsyncValidationRule{T}"/> objects perform a single
/// isolated validation and the <see cref="AsyncValidator{T}"/> collects the single <see cref="ValidationOutcome"/> and wrap them in a <see cref="ValidationSummary"/>.
/// </summary>
/// <typeparam name="T">The type of the object to validate.</typeparam>
public abstract class AsyncValidator<T> : IAsyncValidator<T>
{
/// <summary>
/// Validate an object and return the <see cref="ValidationOutcome"/> objects wrapped in a <see cref="ValidationOutcome"/>.
/// </summary>
/// <param name="objectToValidate">The object to validate.</param>
/// <returns>The <see cref="ValidationSummary"/> that wraps a several <see cref="ValidationOutcome"/>.
/// The <see cref="ValidationSummary"/> can be used as a model that can be presented to the user.
/// </returns>
public abstract Task<ValidationSummary> ValidateAsync(T objectToValidate);
}

/// <summary>
/// A validator that takes a set of objects that implement the <see cref="IAsyncValidationRule{T}"/> interface.
/// Use the <see cref="AsyncValidationRule{T}"/> class or the <see cref="AsyncValidationRuleDelegate{T}"/> class as base for the implementation of rules.
/// </summary>
/// <typeparam name="T">The type of the object to validate</typeparam>
public class AsyncRuleBasedValidator<T> : AsyncValidator<T>
{
/// <summary>
/// Constructs a validator that applies the given validation rules to an object in the given order.
/// </summary>
/// <param name="rules">The <see cref="IAsyncValidationRule{T}"/>s to apply to an object.</param>
public AsyncRuleBasedValidator(IEnumerable<IAsyncValidationRule<T>> rules) : this(rules.ToArray()) { }

/// <summary>
/// Constructs a validator that applies the given validation rules to an object in the given order.
/// </summary>
/// <param name="rules">The <see cref="IAsyncValidationRule{T}"/>s to apply to an object.</param>
public AsyncRuleBasedValidator(params IAsyncValidationRule<T>[] rules)
{
Rules = rules;
}

/// <summary>
/// The rules passed in the constructor.
/// </summary>
protected IAsyncValidationRule<T>[] Rules { get; }

/// <summary>
/// Processes the <see cref="Rules"/> one after the other and stops if a rule returns a
/// <see cref="ValidationOutcome"/> with <see cref="ValidationSeverity.IsAnError"/> and
/// <see cref="IAsyncValidationRule{T}.CausesValidationProcessToStop"/> both set to <c>true</c>.
/// </summary>
/// <param name="objectToValidate">The object to validate.</param>
/// <returns>A <see cref="IEnumerable{T}"/> of <see cref="ValidationOutcome"/>.</returns>
protected virtual async Task<IEnumerable<ValidationOutcome>> ProcessValidationsAsync(T objectToValidate)
{
var results = new List<ValidationOutcome>(Rules.Length);
foreach (var rule in Rules)
{
var ruleResult = await rule.ValidateObjectAsync(objectToValidate);
results.Add(ruleResult);
if (ruleResult.Severity.IsAnError && rule.CausesValidationProcessToStop)
{
break;
}
}

return results;
}

/// <summary>
/// Validates an object by applying the rules given in the constructor <see cref="AsyncRuleBasedValidator{T}"/> to the object in the order specified in the constructor./>
/// </summary>
/// <param name="objectToValidate">The object to validate.</param>
/// <returns>A <see cref="ValidationSummary"/> that contains the <see cref="ValidationOutcome"/> of every applied <see cref="IAsyncValidationRule{T}"/>.</returns>
public override async Task<ValidationSummary> ValidateAsync(T objectToValidate)
=> new ValidationSummary(await ProcessValidationsAsync(objectToValidate));
}

/// <summary>
/// An <see cref="IAsyncValidator{T}"/> which combines the <see cref="ValidationSummary"/> of other <see cref="IAsyncValidator{T}"/>s.
/// </summary>
/// <typeparam name="T">The type of the object to validate</typeparam>
public class AsyncValidatorCombiner<T> : AsyncValidator<T>, IAsyncValidator<T>
{
/// <summary>
/// Constructs an <see cref="IAsyncValidator{T}"/> that combines the <see cref="ValidationSummary"/> of the <paramref name="validators"/> in the given order.
/// </summary>
/// <param name="validators">The <see cref="IAsyncValidator{T}"/>s to combine.</param>
public AsyncValidatorCombiner(IEnumerable<IAsyncValidator<T>> validators) : this(validators.ToArray())
{

}

/// <summary>
/// Constructs an <see cref="IAsyncValidator{T}"/> that combines the <see cref="ValidationSummary"/> of the <paramref name="validators"/> in the given order.
/// </summary>
/// <param name="validators">The <see cref="IValidator{T}"/>s to combine.</param>
public AsyncValidatorCombiner(params IAsyncValidator<T>[] validators)
{
Validators = validators;
}

/// <summary>
/// The collection of validators that get combined.
/// </summary>
protected IEnumerable<IAsyncValidator<T>> Validators { get; }

/// <summary>
/// Validates <paramref name="objectToValidate"/> by calling <see cref="IAsyncValidator{T}.ValidateAsync(T)"/> of all <see cref="AsyncValidatorCombiner{T}.Validators"/> and combining
/// their <see cref="ValidationSummary"/>.
/// </summary>
/// <param name="objectToValidate">The object to validate.</param>
/// <returns></returns>
public override async Task<ValidationSummary> ValidateAsync(T objectToValidate)
{
var resultList = new List<ValidationSummary>(Validators.Count());
foreach (var validator in Validators)
{
var summary = await validator.ValidateAsync(objectToValidate);
resultList.Add(summary);
}

return new ValidationSummary(resultList);
}
}

/// <summary>
/// A validator, that delegates validation to another validator. This can be useful if the objectToValidate needs to be transformed before validation.
/// </summary>
/// <typeparam name="T">The type to which the validation is delegated.</typeparam>
/// <typeparam name="U">The type that this validator can validate.</typeparam>
public class AsyncDelegateValidator<T, U> : IAsyncValidator<U>
{
/// <summary>
/// Creates a <see cref="AsyncDelegateValidator{T, U}"/> that takes an <see cref="IAsyncValidator{T}"/> and a <paramref name="selector"/> from <typeparamref name="U"/>
/// to <typeparamref name="T"/>. The <see cref="AsyncDelegateValidator{T, U}"/> implements <see cref="IAsyncValidator{U}"/>.
/// </summary>
/// <param name="validator">An existing <see cref="IAsyncValidator{T}"/> for <typeparamref name="T"/>.</param>
/// <param name="selector">An <paramref name="selector"/> from <typeparamref name="U"/> to <typeparamref name="T"/>.</param>
public AsyncDelegateValidator(IAsyncValidator<T> validator, Func<U, T> selector)
{
Validator = validator;
Selector = selector;
}

/// <summary>
/// The validator to which the validation is delegated
/// </summary>
protected IAsyncValidator<T> Validator { get; }

/// <summary>
/// The <see cref="Selector"/> function that transforms objectToValidate from <typeparamref name="U"/> to <typeparamref name="T"/>.
/// </summary>
protected Func<U, T> Selector { get; }

/// <summary>
/// Validates an <paramref name="objectToValidate"/> of type <typeparamref name="U"/> by transforming it to <typeparamref name="T"/> and delegating
/// validation to <see cref="Validator"/>.
/// </summary>
/// <param name="objectToValidate">The object to validate.</param>
/// <returns>A <see cref="ValidationSummary"/> representing the result of a validation.</returns>
public async Task<ValidationSummary> ValidateAsync(U objectToValidate)
{
return await Validator.ValidateAsync(Selector(objectToValidate));
}
}
}
30 changes: 30 additions & 0 deletions Ifp.Validation/IAsyncValidationRule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Threading.Tasks;

namespace Ifp.Validation
{
/// <summary>
/// Classes that validate other objects should implement this interface.
/// Classes that implement this interface can be combined to rules sets by the <see cref="AsyncRuleBasedValidator{T}"/>.
/// </summary>
/// <typeparam name="T">The type of the object to validate.</typeparam>
/// <remarks>
/// You should not implement this interface directly. Use <see cref="AsyncValidationRule{T}"/> as base class instead.
/// </remarks>
/// <seealso cref="AsyncValidationRule{T}"/>
/// <seealso cref="AsyncValidationRuleDelegate{T}"/>
public interface IAsyncValidationRule<in T>
{
/// <summary>
/// Checks the <paramref name="objectToValidate"/> and returns a <see cref="ValidationOutcome"/>.
/// </summary>
/// <param name="objectToValidate">The object to validate.</param>
/// <returns>A <see cref="ValidationOutcome"/> that represents the result of the validation.</returns>
Task<ValidationOutcome> ValidateObjectAsync(T objectToValidate);

/// <summary>
/// Returns true if the <see cref="RuleBasedValidator{T}"/> should not proceed validation tests in case the validation returns a <see cref="ValidationOutcome"/> with <see cref="ValidationSeverity.IsAnError"/>.
/// This is useful to prevent further processing of rules in cases where all the following rules will also fail. A typical use case is a check for <c>objectToValidate == null</c>.
/// </summary>
bool CausesValidationProcessToStop { get; }
}
}
21 changes: 21 additions & 0 deletions Ifp.Validation/IAsyncValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System.Threading.Tasks;

namespace Ifp.Validation
{
/// <summary>
/// A validator takes an object of type <typeparamref name="T"/> and performs a validation. The result of the validation is represented by a collection
/// of <see cref="ValidationOutcome"/> wrapped in a <see cref="ValidationSummary"/>.
/// </summary>
/// <typeparam name="T">The type of the object to validate.</typeparam>
public interface IAsyncValidator<in T>
{
/// <summary>
/// Validate an object and return the <see cref="ValidationOutcome"/> objects wrapped in a <see cref="ValidationOutcome"/>.
/// </summary>
/// <param name="objectToValidate">The object to validate.</param>
/// <returns>The <see cref="ValidationSummary"/> that wraps a several <see cref="ValidationOutcome"/>.
/// The <see cref="ValidationSummary"/> can be used as a model that can be presented to the user.
/// </returns>
Task<ValidationSummary> ValidateAsync(T objectToValidate);
}
}
28 changes: 28 additions & 0 deletions Ifp.Validation/IValidationRule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
namespace Ifp.Validation
{
/// <summary>
/// Classes that validate other objects should implement this interface.
/// Classes that implement this interface can be combined to rules sets by the <see cref="RuleBasedValidator{T}"/>.
/// </summary>
/// <typeparam name="T">The type of the object to validate.</typeparam>
/// <remarks>
/// You should not implement this interface directly. Use <see cref="ValidationRule{T}"/> as base class instead.
/// </remarks>
/// <seealso cref="ValidationRule{T}"/>
/// <seealso cref="ValidationRuleDelegate{T}"/>
public interface IValidationRule<in T>
{
/// <summary>
/// Checks the <paramref name="objectToValidate"/> and returns a <see cref="ValidationOutcome"/>.
/// </summary>
/// <param name="objectToValidate">The object to validate.</param>
/// <returns>A <see cref="ValidationOutcome"/> that represents the result of the validation.</returns>
ValidationOutcome ValidateObject(T objectToValidate);

/// <summary>
/// Returns true if the <see cref="RuleBasedValidator{T}"/> should not proceed validation tests in case the validation returns a <see cref="ValidationOutcome"/> with <see cref="ValidationSeverity.IsAnError"/>.
/// This is useful to prevent further processing of rules in cases where all the following rules will also fail. A typical use case is a check for <c>objectToValidate == null</c>.
/// </summary>
bool CausesValidationProcessToStop { get; }
}
}
Loading