using System.Reflection; using Microsoft.Extensions.DependencyInjection; 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; /// /// 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) { _options = options; _assemblies = assemblies ?? AppDomain.CurrentDomain.GetAssemblies(); _serviceProviderIsService = serviceProviderIsService; } /// 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) { // Skip assemblies that cannot be loaded } } return endpoints; } }