using System.Reflection; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using StellaOps.Router.Common.Models; namespace StellaOps.Microservice; /// /// Discovers endpoints using runtime reflection. /// public sealed class ReflectionEndpointDiscoveryProvider : IEndpointDiscoveryProvider { private readonly StellaMicroserviceOptions _options; private readonly IEnumerable _assemblies; private readonly IServiceProviderIsService? _serviceProviderIsService; private readonly ILogger? _logger; /// /// Initializes a new instance of the class. /// /// The microservice options. /// The assemblies to scan for endpoints. public ReflectionEndpointDiscoveryProvider( StellaMicroserviceOptions options, IEnumerable? assemblies = null, IServiceProviderIsService? serviceProviderIsService = null, ILogger? logger = null) { _options = options; _assemblies = assemblies ?? AppDomain.CurrentDomain.GetAssemblies(); _serviceProviderIsService = serviceProviderIsService; _logger = logger; } /// public IReadOnlyList DiscoverEndpoints() { var endpoints = new List(); foreach (var assembly in _assemblies) { try { foreach (var type in assembly.GetTypes()) { var attribute = type.GetCustomAttribute(); if (attribute is null) continue; if (!typeof(IStellaEndpoint).IsAssignableFrom(type)) { throw new InvalidOperationException( $"Type {type.FullName} has [StellaEndpoint] but does not implement IStellaEndpoint."); } if (_serviceProviderIsService is not null && !_serviceProviderIsService.IsService(type)) { continue; } var claims = attribute.RequiredClaims .Select(c => new ClaimRequirement { Type = c }) .ToList(); var descriptor = new EndpointDescriptor { ServiceName = _options.ServiceName, Version = _options.Version, Method = attribute.Method, Path = attribute.Path, DefaultTimeout = TimeSpan.FromSeconds(attribute.TimeoutSeconds), SupportsStreaming = attribute.SupportsStreaming, RequiringClaims = claims, HandlerType = type }; endpoints.Add(descriptor); } } catch (ReflectionTypeLoadException ex) { if (_logger is not null) { var assemblyName = assembly.FullName ?? assembly.GetName().Name ?? "unknown"; if (ex.LoaderExceptions is not null && ex.LoaderExceptions.Length > 0) { foreach (var loaderException in ex.LoaderExceptions) { if (loaderException is null) { continue; } _logger.LogWarning( loaderException, "Endpoint discovery skipped assembly {Assembly} due to type load error.", assemblyName); } } else { _logger.LogWarning( ex, "Endpoint discovery skipped assembly {Assembly} due to type load error.", assemblyName); } } } catch (Exception ex) { _logger?.LogWarning( ex, "Endpoint discovery skipped assembly {Assembly} due to reflection error.", assembly.FullName ?? assembly.GetName().Name ?? "unknown"); } } return endpoints; } }