Files
git.stella-ops.org/src/Router/__Libraries/StellaOps.Microservice/ReflectionEndpointDiscoveryProvider.cs
2026-01-13 18:53:39 +02:00

120 lines
4.5 KiB
C#

using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using StellaOps.Router.Common.Models;
namespace StellaOps.Microservice;
/// <summary>
/// Discovers endpoints using runtime reflection.
/// </summary>
public sealed class ReflectionEndpointDiscoveryProvider : IEndpointDiscoveryProvider
{
private readonly StellaMicroserviceOptions _options;
private readonly IEnumerable<Assembly> _assemblies;
private readonly IServiceProviderIsService? _serviceProviderIsService;
private readonly ILogger? _logger;
/// <summary>
/// Initializes a new instance of the <see cref="ReflectionEndpointDiscoveryProvider"/> class.
/// </summary>
/// <param name="options">The microservice options.</param>
/// <param name="assemblies">The assemblies to scan for endpoints.</param>
public ReflectionEndpointDiscoveryProvider(
StellaMicroserviceOptions options,
IEnumerable<Assembly>? assemblies = null,
IServiceProviderIsService? serviceProviderIsService = null,
ILogger? logger = null)
{
_options = options;
_assemblies = assemblies ?? AppDomain.CurrentDomain.GetAssemblies();
_serviceProviderIsService = serviceProviderIsService;
_logger = logger;
}
/// <inheritdoc />
public IReadOnlyList<EndpointDescriptor> DiscoverEndpoints()
{
var endpoints = new List<EndpointDescriptor>();
foreach (var assembly in _assemblies)
{
try
{
foreach (var type in assembly.GetTypes())
{
var attribute = type.GetCustomAttribute<StellaEndpointAttribute>();
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;
}
}