< Summary

Information
Class: NexusLabs.Needlr.Generators.NeedlrSourceGenBootstrap
Assembly: NexusLabs.Needlr.Generators.Attributes
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Generators.Attributes/NeedlrSourceGenBootstrap.cs
Line coverage
65%
Covered lines: 81
Uncovered lines: 43
Coverable lines: 124
Total lines: 355
Line coverage: 65.3%
Branch coverage
40%
Covered branches: 16
Total branches: 40
Branch coverage: 40%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_InjectableTypeProvider()100%11100%
get_PluginTypeProvider()100%11100%
get_DecoratorApplier()100%11100%
get_OptionsRegistrar()100%11100%
.cctor()100%11100%
Register(...)100%210%
Register(...)100%210%
Register(...)50%44100%
RegisterExtension(...)0%620%
TryGetProviders(...)66.66%7668.75%
TryGetDecoratorApplier(...)50%8661.53%
TryGetOptionsRegistrar(...)0%4260%
TryGetExtensionRegistrar(...)0%2040%
BeginTestScope(...)50%44100%
.ctor(...)100%11100%
Dispose()100%11100%
Combine(...)50%8886.95%

File(s)

/home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Generators.Attributes/NeedlrSourceGenBootstrap.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Linq;
 4using System.Threading;
 5
 6namespace NexusLabs.Needlr.Generators;
 7
 8/// <summary>
 9/// Runtime bootstrap registry for source-generated Needlr components.
 10/// </summary>
 11/// <remarks>
 12/// The source generator emits a module initializer in the host assembly that calls
 13/// one of the Register overloads with the generated TypeRegistry providers.
 14/// Needlr runtime can then discover generated registries without any runtime reflection.
 15/// </remarks>
 16public static class NeedlrSourceGenBootstrap
 17{
 18    private sealed class Registration
 19    {
 3620        public Registration(
 3621            Func<IReadOnlyList<InjectableTypeInfo>> injectableTypeProvider,
 3622            Func<IReadOnlyList<PluginTypeInfo>> pluginTypeProvider,
 3623            Action<object>? decoratorApplier = null,
 3624            Action<object, object>? optionsRegistrar = null)
 25        {
 3626            InjectableTypeProvider = injectableTypeProvider;
 3627            PluginTypeProvider = pluginTypeProvider;
 3628            DecoratorApplier = decoratorApplier;
 3629            OptionsRegistrar = optionsRegistrar;
 3630        }
 31
 18932        public Func<IReadOnlyList<InjectableTypeInfo>> InjectableTypeProvider { get; }
 18933        public Func<IReadOnlyList<PluginTypeInfo>> PluginTypeProvider { get; }
 26634        public Action<object>? DecoratorApplier { get; }
 935        public Action<object, object>? OptionsRegistrar { get; }
 36    }
 37
 1738    private static readonly object _gate = new object();
 1739    private static readonly List<Registration> _registrations = new List<Registration>();
 1740    private static readonly List<Action<object, object>> _extensionRegistrars = new List<Action<object, object>>();
 41
 1742    private static readonly AsyncLocal<Registration?> _asyncLocalOverride = new AsyncLocal<Registration?>();
 43
 44    private static Registration? _cachedCombined;
 45
 46    /// <summary>
 47    /// Registers the generated type and plugin providers for this application.
 48    /// </summary>
 49    public static void Register(
 50        Func<IReadOnlyList<InjectableTypeInfo>> injectableTypeProvider,
 51        Func<IReadOnlyList<PluginTypeInfo>> pluginTypeProvider)
 52    {
 053        Register(injectableTypeProvider, pluginTypeProvider, (Action<object>?)null);
 054    }
 55
 56    /// <summary>
 57    /// Registers the generated type, plugin, and decorator providers for this application.
 58    /// </summary>
 59    /// <param name="injectableTypeProvider">Provider for injectable types.</param>
 60    /// <param name="pluginTypeProvider">Provider for plugin types.</param>
 61    /// <param name="decoratorApplier">
 62    /// Action that applies decorators to the service collection.
 63    /// The parameter is an IServiceCollection, but typed as object to avoid dependency on Microsoft.Extensions.Dependen
 64    /// </param>
 65    public static void Register(
 66        Func<IReadOnlyList<InjectableTypeInfo>> injectableTypeProvider,
 67        Func<IReadOnlyList<PluginTypeInfo>> pluginTypeProvider,
 68        Action<object>? decoratorApplier)
 69    {
 070        Register(injectableTypeProvider, pluginTypeProvider, decoratorApplier, null);
 071    }
 72
 73    /// <summary>
 74    /// Registers the generated type, plugin, decorator, and options providers for this application.
 75    /// </summary>
 76    /// <param name="injectableTypeProvider">Provider for injectable types.</param>
 77    /// <param name="pluginTypeProvider">Provider for plugin types.</param>
 78    /// <param name="decoratorApplier">
 79    /// Action that applies decorators to the service collection.
 80    /// The parameter is an IServiceCollection, but typed as object to avoid dependency on Microsoft.Extensions.Dependen
 81    /// </param>
 82    /// <param name="optionsRegistrar">
 83    /// Action that registers options with the service collection and configuration.
 84    /// Parameters are (IServiceCollection, IConfiguration), typed as object to avoid dependencies.
 85    /// </param>
 86    public static void Register(
 87        Func<IReadOnlyList<InjectableTypeInfo>> injectableTypeProvider,
 88        Func<IReadOnlyList<PluginTypeInfo>> pluginTypeProvider,
 89        Action<object>? decoratorApplier,
 90        Action<object, object>? optionsRegistrar)
 91    {
 2192        if (injectableTypeProvider is null) throw new ArgumentNullException(nameof(injectableTypeProvider));
 2193        if (pluginTypeProvider is null) throw new ArgumentNullException(nameof(pluginTypeProvider));
 94
 2195        lock (_gate)
 96        {
 2197            _registrations.Add(new Registration(injectableTypeProvider, pluginTypeProvider, decoratorApplier, optionsReg
 2198            _cachedCombined = null;
 2199        }
 21100    }
 101
 102    /// <summary>
 103    /// Registers an extension that provides additional service registrations.
 104    /// Extensions are invoked after the main options registrar during BuildServiceProvider.
 105    /// </summary>
 106    /// <param name="extensionRegistrar">
 107    /// Action that registers extension services with the service collection and configuration.
 108    /// Parameters are (IServiceCollection, IConfiguration), typed as object to avoid dependencies.
 109    /// </param>
 110    /// <remarks>
 111    /// Use this method from extension package module initializers to register additional services.
 112    /// For example, FluentValidation can register its validators without modifying core Needlr.
 113    /// </remarks>
 114    public static void RegisterExtension(Action<object, object> extensionRegistrar)
 115    {
 0116        if (extensionRegistrar is null) throw new ArgumentNullException(nameof(extensionRegistrar));
 117
 0118        lock (_gate)
 119        {
 0120            _extensionRegistrars.Add(extensionRegistrar);
 0121            _cachedCombined = null;
 0122        }
 0123    }
 124
 125    /// <summary>
 126    /// Gets the registered providers (if any).
 127    /// </summary>
 128    public static bool TryGetProviders(
 129        out Func<IReadOnlyList<InjectableTypeInfo>> injectableTypeProvider,
 130        out Func<IReadOnlyList<PluginTypeInfo>> pluginTypeProvider)
 131    {
 186132        var local = _asyncLocalOverride.Value;
 186133        if (local is not null)
 134        {
 9135            injectableTypeProvider = local.InjectableTypeProvider;
 9136            pluginTypeProvider = local.PluginTypeProvider;
 9137            return true;
 138        }
 139
 177140        lock (_gate)
 141        {
 177142            if (_registrations.Count == 0)
 143            {
 4144                injectableTypeProvider = null!;
 4145                pluginTypeProvider = null!;
 4146                return false;
 147            }
 148
 173149            if (_cachedCombined is null)
 150            {
 5151                _cachedCombined = Combine(_registrations);
 152            }
 153
 173154            injectableTypeProvider = _cachedCombined.InjectableTypeProvider;
 173155            pluginTypeProvider = _cachedCombined.PluginTypeProvider;
 173156            return true;
 157        }
 177158    }
 159
 160    /// <summary>
 161    /// Gets the decorator applier (if any).
 162    /// </summary>
 163    /// <param name="decoratorApplier">
 164    /// Action that applies decorators to the service collection.
 165    /// The parameter is an IServiceCollection, but typed as object to avoid dependency on Microsoft.Extensions.Dependen
 166    /// </param>
 167    /// <returns>True if a decorator applier is registered.</returns>
 168    public static bool TryGetDecoratorApplier(out Action<object>? decoratorApplier)
 169    {
 258170        var local = _asyncLocalOverride.Value;
 258171        if (local is not null)
 172        {
 0173            decoratorApplier = local.DecoratorApplier;
 0174            return decoratorApplier is not null;
 175        }
 176
 258177        lock (_gate)
 178        {
 258179            if (_registrations.Count == 0)
 180            {
 6181                decoratorApplier = null;
 6182                return false;
 183            }
 184
 252185            if (_cachedCombined is null)
 186            {
 0187                _cachedCombined = Combine(_registrations);
 188            }
 189
 252190            decoratorApplier = _cachedCombined.DecoratorApplier;
 252191            return decoratorApplier is not null;
 192        }
 258193    }
 194
 195    /// <summary>
 196    /// Gets the options registrar (if any).
 197    /// </summary>
 198    /// <param name="optionsRegistrar">
 199    /// Action that registers options with the service collection and configuration.
 200    /// Parameters are (IServiceCollection, IConfiguration), typed as object to avoid dependencies.
 201    /// </param>
 202    /// <returns>True if an options registrar is registered.</returns>
 203    public static bool TryGetOptionsRegistrar(out Action<object, object>? optionsRegistrar)
 204    {
 0205        var local = _asyncLocalOverride.Value;
 0206        if (local is not null)
 207        {
 0208            optionsRegistrar = local.OptionsRegistrar;
 0209            return optionsRegistrar is not null;
 210        }
 211
 0212        lock (_gate)
 213        {
 0214            if (_registrations.Count == 0)
 215            {
 0216                optionsRegistrar = null;
 0217                return false;
 218            }
 219
 0220            if (_cachedCombined is null)
 221            {
 0222                _cachedCombined = Combine(_registrations);
 223            }
 224
 0225            optionsRegistrar = _cachedCombined.OptionsRegistrar;
 0226            return optionsRegistrar is not null;
 227        }
 0228    }
 229
 230    /// <summary>
 231    /// Gets the combined extension registrar (if any extensions are registered).
 232    /// </summary>
 233    /// <param name="extensionRegistrar">
 234    /// Combined action that invokes all registered extensions.
 235    /// Parameters are (IServiceCollection, IConfiguration), typed as object to avoid dependencies.
 236    /// </param>
 237    /// <returns>True if any extension registrars are registered.</returns>
 238    public static bool TryGetExtensionRegistrar(out Action<object, object>? extensionRegistrar)
 239    {
 0240        lock (_gate)
 241        {
 0242            if (_extensionRegistrars.Count == 0)
 243            {
 0244                extensionRegistrar = null;
 0245                return false;
 246            }
 247
 0248            var registrars = _extensionRegistrars.ToArray();
 0249            extensionRegistrar = (services, config) =>
 0250            {
 0251                foreach (var registrar in registrars)
 0252                {
 0253                    registrar(services, config);
 0254                }
 0255            };
 0256            return true;
 257        }
 0258    }
 259
 260    internal static IDisposable BeginTestScope(
 261        Func<IReadOnlyList<InjectableTypeInfo>> injectableTypeProvider,
 262        Func<IReadOnlyList<PluginTypeInfo>> pluginTypeProvider)
 263    {
 10264        if (injectableTypeProvider is null) throw new ArgumentNullException(nameof(injectableTypeProvider));
 10265        if (pluginTypeProvider is null) throw new ArgumentNullException(nameof(pluginTypeProvider));
 266
 10267        var prior = _asyncLocalOverride.Value;
 10268        _asyncLocalOverride.Value = new Registration(injectableTypeProvider, pluginTypeProvider);
 10269        return new Scope(prior);
 270    }
 271
 272    private sealed class Scope : IDisposable
 273    {
 274        private readonly Registration? _prior;
 275
 10276        public Scope(Registration? prior)
 277        {
 10278            _prior = prior;
 10279        }
 280
 281        public void Dispose()
 282        {
 10283            _asyncLocalOverride.Value = _prior;
 10284        }
 285    }
 286
 287    private static Registration Combine(IReadOnlyList<Registration> registrations)
 288    {
 289        // Snapshot the current registrations to avoid capturing a mutable List.
 12290        var injectableProviders = registrations.Select(r => r.InjectableTypeProvider).ToArray();
 12291        var pluginProviders = registrations.Select(r => r.PluginTypeProvider).ToArray();
 19292        var decoratorAppliers = registrations.Where(r => r.DecoratorApplier is not null).Select(r => r.DecoratorApplier!
 14293        var optionsRegistrars = registrations.Where(r => r.OptionsRegistrar is not null).Select(r => r.OptionsRegistrar!
 294
 295        IReadOnlyList<InjectableTypeInfo> GetInjectableTypes()
 296        {
 297            var result = new List<InjectableTypeInfo>();
 298            var seen = new HashSet<Type>();
 299
 300            foreach (var provider in injectableProviders)
 301            {
 302                foreach (var info in provider())
 303                {
 304                    if (seen.Add(info.Type))
 305                    {
 306                        result.Add(info);
 307                    }
 308                }
 309            }
 310
 311            return result;
 312        }
 313
 314        IReadOnlyList<PluginTypeInfo> GetPluginTypes()
 315        {
 316            var result = new List<PluginTypeInfo>();
 317            var seen = new HashSet<Type>();
 318
 319            foreach (var provider in pluginProviders)
 320            {
 321                foreach (var info in provider())
 322                {
 323                    if (seen.Add(info.PluginType))
 324                    {
 325                        result.Add(info);
 326                    }
 327                }
 328            }
 329
 330            return result;
 331        }
 332
 5333        Action<object>? combinedDecoratorApplier = decoratorAppliers.Length > 0
 5334            ? services =>
 5335            {
 1022336                foreach (var applier in decoratorAppliers)
 5337                {
 259338                    applier(services);
 5339                }
 252340            }
 5341            : null;
 342
 5343        Action<object, object>? combinedOptionsRegistrar = optionsRegistrars.Length > 0
 5344            ? (services, config) =>
 5345            {
 0346                foreach (var registrar in optionsRegistrars)
 5347                {
 0348                    registrar(services, config);
 5349                }
 0350            }
 5351            : null;
 352
 5353        return new Registration(GetInjectableTypes, GetPluginTypes, combinedDecoratorApplier, combinedOptionsRegistrar);
 354    }
 355}

Methods/Properties

.ctor(System.Func`1<System.Collections.Generic.IReadOnlyList`1<NexusLabs.Needlr.Generators.InjectableTypeInfo>>,System.Func`1<System.Collections.Generic.IReadOnlyList`1<NexusLabs.Needlr.Generators.PluginTypeInfo>>,System.Action`1<System.Object>,System.Action`2<System.Object,System.Object>)
get_InjectableTypeProvider()
get_PluginTypeProvider()
get_DecoratorApplier()
get_OptionsRegistrar()
.cctor()
Register(System.Func`1<System.Collections.Generic.IReadOnlyList`1<NexusLabs.Needlr.Generators.InjectableTypeInfo>>,System.Func`1<System.Collections.Generic.IReadOnlyList`1<NexusLabs.Needlr.Generators.PluginTypeInfo>>)
Register(System.Func`1<System.Collections.Generic.IReadOnlyList`1<NexusLabs.Needlr.Generators.InjectableTypeInfo>>,System.Func`1<System.Collections.Generic.IReadOnlyList`1<NexusLabs.Needlr.Generators.PluginTypeInfo>>,System.Action`1<System.Object>)
Register(System.Func`1<System.Collections.Generic.IReadOnlyList`1<NexusLabs.Needlr.Generators.InjectableTypeInfo>>,System.Func`1<System.Collections.Generic.IReadOnlyList`1<NexusLabs.Needlr.Generators.PluginTypeInfo>>,System.Action`1<System.Object>,System.Action`2<System.Object,System.Object>)
RegisterExtension(System.Action`2<System.Object,System.Object>)
TryGetProviders(System.Func`1<System.Collections.Generic.IReadOnlyList`1<NexusLabs.Needlr.Generators.InjectableTypeInfo>>&,System.Func`1<System.Collections.Generic.IReadOnlyList`1<NexusLabs.Needlr.Generators.PluginTypeInfo>>&)
TryGetDecoratorApplier(System.Action`1<System.Object>&)
TryGetOptionsRegistrar(System.Action`2<System.Object,System.Object>&)
TryGetExtensionRegistrar(System.Action`2<System.Object,System.Object>&)
BeginTestScope(System.Func`1<System.Collections.Generic.IReadOnlyList`1<NexusLabs.Needlr.Generators.InjectableTypeInfo>>,System.Func`1<System.Collections.Generic.IReadOnlyList`1<NexusLabs.Needlr.Generators.PluginTypeInfo>>)
.ctor(NexusLabs.Needlr.Generators.NeedlrSourceGenBootstrap/Registration)
Dispose()
Combine(System.Collections.Generic.IReadOnlyList`1<NexusLabs.Needlr.Generators.NeedlrSourceGenBootstrap/Registration>)