// 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 _search = []; private readonly Lazy _validatorsTypeIndex; public ValidationOptions() { _validatorsTypeIndex = new Lazy(() => new ValidatorTypeIndex(_search.Distinct())); } public void Inspect(IEnumerable assembly) { if (_validatorsTypeIndex.IsValueCreated) { throw new InvalidOperationException("The type index has already been created. Cannot inspect new assemblies."); } _search.AddRange(assembly); } public IEnumerable> GetValidators(IServiceProvider services) { return _validatorsTypeIndex.Value.Resolve>(services); } private abstract class TypeIndex { private readonly ConcurrentDictionary> _cache = new(); protected readonly Dictionary> _closedTypeInfo = []; protected readonly List _openTypeInfo = []; protected TypeIndex(IEnumerable collection, Func 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 Resolve(IServiceProvider services) { return (IEnumerable)_cache.GetOrAdd(typeof(T), CreateResolverFactory)(services); } protected abstract IReadOnlyList IsAssignableTo(Type type); private Func> CreateResolverFactory(Type @interface) { var list = IsAssignableTo(@interface); return ResolverFactory; IEnumerable 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 collection) : TypeIndex(collection, IsValidatorType) { protected override IReadOnlyList IsAssignableTo(Type @interface) { var result = new List(); 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 : IValidator // 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 : IValidator> // 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; } } }