145 lines
3.6 KiB
C#
145 lines
3.6 KiB
C#
|
|
// 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;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|