feat: add support for validator resolution from dependency injection
This commit is contained in:
parent
21ca8a3757
commit
22142882b5
19 changed files with 806 additions and 1 deletions
145
src/request.validation/ValidationOptions.cs
Normal file
145
src/request.validation/ValidationOptions.cs
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
// Copyright (c) The Geekeey Authors
|
||||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Geekeey.Request.Validation;
|
||||
|
||||
internal sealed class ValidationOptions
|
||||
{
|
||||
private readonly List<Type> _search = [];
|
||||
private readonly Lazy<TypeIndex> _validatorsTypeIndex;
|
||||
|
||||
public ValidationOptions()
|
||||
{
|
||||
_validatorsTypeIndex = new Lazy<TypeIndex>(() => new ValidatorTypeIndex(_search.Distinct()));
|
||||
}
|
||||
|
||||
public void Inspect(IEnumerable<Type> assembly)
|
||||
{
|
||||
if (_validatorsTypeIndex.IsValueCreated)
|
||||
{
|
||||
throw new InvalidOperationException("The type index has already been created. Cannot inspect new assemblies.");
|
||||
}
|
||||
|
||||
_search.AddRange(assembly);
|
||||
}
|
||||
|
||||
public IEnumerable<IValidator<T>> GetValidators<T>(IServiceProvider services)
|
||||
{
|
||||
return _validatorsTypeIndex.Value.Resolve<IValidator<T>>(services);
|
||||
}
|
||||
|
||||
private abstract class TypeIndex
|
||||
{
|
||||
private readonly ConcurrentDictionary<Type, Func<IServiceProvider, IEnumerable>> _cache = new();
|
||||
|
||||
protected readonly Dictionary<Type, List<Type>> _closedTypeInfo = [];
|
||||
protected readonly List<Type> _openTypeInfo = [];
|
||||
|
||||
protected TypeIndex(IEnumerable<Type> collection, Func<Type, bool> predicate)
|
||||
{
|
||||
foreach (var type in collection)
|
||||
{
|
||||
if (type.IsGenericTypeDefinition)
|
||||
{
|
||||
if (type.GetInterfaces().Any(predicate))
|
||||
{
|
||||
_openTypeInfo.Add(type);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var @interface in type.GetInterfaces().Where(predicate))
|
||||
{
|
||||
(_closedTypeInfo.TryGetValue(@interface, out var list) ? list : _closedTypeInfo[@interface] = []).Add(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<T> Resolve<T>(IServiceProvider services)
|
||||
{
|
||||
return (IEnumerable<T>)_cache.GetOrAdd(typeof(T), CreateResolverFactory<T>)(services);
|
||||
}
|
||||
|
||||
protected abstract IReadOnlyList<Type> IsAssignableTo(Type type);
|
||||
|
||||
private Func<IServiceProvider, IEnumerable<T>> CreateResolverFactory<T>(Type @interface)
|
||||
{
|
||||
var list = IsAssignableTo(@interface);
|
||||
return ResolverFactory;
|
||||
|
||||
IEnumerable<T> ResolverFactory(IServiceProvider services)
|
||||
{
|
||||
foreach (var type in list)
|
||||
{
|
||||
yield return (T)ActivatorUtilities.GetServiceOrCreateInstance(services, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool IsValidatorType(Type type)
|
||||
{
|
||||
return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IValidator<>);
|
||||
}
|
||||
|
||||
private sealed class ValidatorTypeIndex(IEnumerable<Type> collection)
|
||||
: TypeIndex(collection, IsValidatorType)
|
||||
{
|
||||
protected override IReadOnlyList<Type> IsAssignableTo(Type @interface)
|
||||
{
|
||||
var result = new List<Type>();
|
||||
|
||||
foreach (var kvp in _closedTypeInfo)
|
||||
{
|
||||
if (@interface.IsAssignableFrom(kvp.Key))
|
||||
{
|
||||
result.AddRange(kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
var validatedType = @interface.GetGenericArguments()[0];
|
||||
|
||||
foreach (var type in _openTypeInfo)
|
||||
{
|
||||
try
|
||||
{
|
||||
// open type case one: Validator<T> : IValidator<T>
|
||||
// We try to close it with the validated type.
|
||||
var impl = type.MakeGenericType(validatedType);
|
||||
if (impl.IsAssignableTo(@interface))
|
||||
{
|
||||
result.Add(impl);
|
||||
}
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// open type case two: Validator<T> : IValidator<Wrapper<T>>
|
||||
// If the validated type is generic, we try to close the validator with the validated type's generic arguments.
|
||||
if (validatedType.IsGenericType)
|
||||
{
|
||||
var impl = type.MakeGenericType(validatedType.GetGenericArguments());
|
||||
if (impl.IsAssignableTo(@interface))
|
||||
{
|
||||
result.Add(impl);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue