Files
mixtape/Finch/Validation/FinchMergedValidator.cs
T
2026-04-07 15:24:48 +02:00

98 lines
2.3 KiB
C#

using FluentValidation;
using FluentValidation.Results;
using System.Collections.Concurrent;
namespace Finch.Validation;
public class FinchMergedValidator<T> : IFinchMergedValidator<T>
{
protected IEnumerable<IValidator<T>> Validators { get; }
ConcurrentDictionary<Type, IEnumerable<IValidator<T>>> TypeCache = new();
public FinchMergedValidator(IEnumerable<IValidator<T>> validators)
{
Validators = validators;
}
/// <inheritdoc />
public async Task<ValidationResult> ValidateAsync(T model)
{
ValidationResult result = new();
foreach (IValidator<T> validator in ResolveFor(model))
{
ValidationResult innerResult = await validator.ValidateAsync(model);
result.Errors.AddRange(innerResult.Errors);
}
return result;
}
/// <inheritdoc />
public IEnumerable<IValidator<T>> ResolveFor(T model)
{
Type type = model.GetType();
if (!TypeCache.TryGetValue(type, out IEnumerable<IValidator<T>> validators))
{
validators = Validators.Where(validator => CanHandle(validator, type)).ToList();
TypeCache.TryAdd(type, validators);
}
return validators;
}
/// <summary>
/// Checks whether a certain validator can handle the give type
/// </summary>
bool CanHandle(IValidator<T> validator, Type modelType)
{
Type validatorType = validator.GetType();
Type typeToFind = typeof(FinchValidator<,>);
Type findValidatorBase(Type type)
{
if (type.BaseType == null)
{
return null;
}
if (type.BaseType.IsGenericType && type.BaseType.GetGenericTypeDefinition() == typeToFind)
{
return type.BaseType;
}
return findValidatorBase(type.BaseType);
}
Type finchValidatorType = findValidatorBase(validatorType);
if (finchValidatorType == null)
{
return false;
}
Type implementationType = finchValidatorType.GenericTypeArguments[1];
return implementationType.IsAssignableFrom(modelType);
}
}
public interface IFinchMergedValidator<T>
{
/// <summary>
/// Get all validators which can run for the given model
/// </summary>
IEnumerable<IValidator<T>> ResolveFor(T model);
/// <summary>
/// Validates a model by using all registered FinchValidators for this entity type
/// </summary>
Task<ValidationResult> ValidateAsync(T model);
}