request/src/request.validation/ValidationOptions.cs

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;
}
}
}