< Summary

Information
Class: NexusLabs.Needlr.Hosting.NeedlrBootstrapper
Assembly: NexusLabs.Needlr.Hosting
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Hosting/NeedlrBootstrapper.cs
Line coverage
96%
Covered lines: 27
Uncovered lines: 1
Coverable lines: 28
Total lines: 120
Line coverage: 96.4%
Branch coverage
70%
Covered branches: 7
Total branches: 10
Branch coverage: 70%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_Factory()100%11100%
get_Cleanup()100%11100%
get_ConfigureBootstrapConfigurationBuilder()100%11100%
RunAsync()66.66%121295.83%

File(s)

/home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Hosting/NeedlrBootstrapper.cs

#LineLine coverage
 1using Microsoft.Extensions.Configuration;
 2using Microsoft.Extensions.Logging;
 3
 4namespace NexusLabs.Needlr.Hosting;
 5
 6/// <summary>
 7/// Wraps an application entry point with bootstrap lifecycle management: a pre-DI logger,
 8/// a pre-DI <see cref="IConfiguration"/>, top-level exception handling, and guaranteed cleanup.
 9/// </summary>
 10/// <remarks>
 11/// <para>
 12/// By default a console logger and an <strong>empty</strong> <see cref="IConfiguration"/> are
 13/// created automatically. Override with
 14/// <see cref="NeedlrBootstrapperExtensions.UsingLoggerFactory"/> to supply your own factory
 15/// (e.g. a Serilog two-stage init factory), and
 16/// <see cref="NeedlrBootstrapperExtensions.ConfigureBootstrapConfiguration"/> to add
 17/// configuration sources needed during the bootstrap phase.
 18/// </para>
 19/// <para>
 20/// The bootstrap configuration is <strong>not</strong> the same <see cref="IConfiguration"/>
 21/// that the application's DI container will provide. They are independent instances.
 22/// See <see cref="NeedlrBootstrapContext.BootstrapConfiguration"/> for details.
 23/// </para>
 24/// <para>
 25/// Unhandled exceptions from the callback are caught, logged at <c>Critical</c>, and then
 26/// swallowed so the process exits cleanly after cleanup.
 27/// </para>
 28/// </remarks>
 29/// <example>
 30/// <code>
 31/// await new NeedlrBootstrapper()
 32///     .ConfigureBootstrapConfiguration(builder => builder
 33///         .AddJsonFile("appsettings.json", optional: true)
 34///         .AddEnvironmentVariables())
 35///     .RunAsync(async (ctx, ct) =>
 36///     {
 37///         var host = new Syringe()
 38///             .UsingSourceGen()
 39///             .ForHost()
 40///             .UsingOptions(() => CreateHostOptions.Default.UsingCurrentProcessArgs())
 41///             .BuildHost();
 42///
 43///         await host.RunAsync(ct);
 44///     });
 45/// </code>
 46/// </example>
 47[DoNotAutoRegister]
 48public sealed record NeedlrBootstrapper
 49{
 8250    internal ILoggerFactory? Factory { get; init; }
 6051    internal Func<Task>? Cleanup { get; init; }
 3552    internal Action<IConfigurationBuilder>? ConfigureBootstrapConfigurationBuilder { get; init; }
 53
 54    /// <summary>
 55    /// Runs the application entry point with full bootstrap lifecycle management.
 56    /// </summary>
 57    /// <param name="runAsync">
 58    /// The application callback. Receives a <see cref="NeedlrBootstrapContext"/> containing
 59    /// the bootstrap logger and bootstrap configuration, and the <see cref="CancellationToken"/>
 60    /// passed to this method.
 61    /// </param>
 62    /// <param name="cancellationToken">
 63    /// Optional cancellation token forwarded to the callback.
 64    /// </param>
 65    /// <returns>A <see cref="Task"/> that completes when the application exits.</returns>
 66    /// <example>
 67    /// <code>
 68    /// await new NeedlrBootstrapper().RunAsync(async (ctx, ct) =>
 69    /// {
 70    ///     ctx.Logger.LogInformation("Application starting...");
 71    ///     var path = ctx.BootstrapConfiguration["SomeSetting"];
 72    ///     await RunMyAppAsync(ct);
 73    /// });
 74    /// </code>
 75    /// </example>
 76    public async Task RunAsync(
 77        Func<NeedlrBootstrapContext, CancellationToken, Task> runAsync,
 78        CancellationToken cancellationToken = default)
 79    {
 2880        ArgumentNullException.ThrowIfNull(runAsync);
 81
 2782        var ownsFactory = Factory is null;
 2783        var loggerFactory = Factory ?? LoggerFactory.Create(b => b.AddConsole());
 2784        var logger = loggerFactory.CreateLogger("Startup");
 85
 2786        var configBuilder = new ConfigurationBuilder();
 2787        ConfigureBootstrapConfigurationBuilder?.Invoke(configBuilder);
 2788        var bootstrapConfiguration = configBuilder.Build();
 89
 90        try
 91        {
 2792            await runAsync(
 2793                new NeedlrBootstrapContext
 2794                {
 2795                    Logger = logger,
 2796                    BootstrapConfiguration = bootstrapConfiguration,
 2797                },
 2798                cancellationToken)
 2799                .ConfigureAwait(false);
 21100        }
 6101        catch (Exception ex)
 102        {
 6103            logger.LogCritical(ex, "Application terminated unexpectedly.");
 6104        }
 105        finally
 106        {
 27107            if (Cleanup is not null)
 108            {
 16109                await Cleanup().ConfigureAwait(false);
 110            }
 111
 27112            if (ownsFactory)
 113            {
 0114                loggerFactory.Dispose();
 115            }
 116
 27117            (bootstrapConfiguration as IDisposable)?.Dispose();
 118        }
 27119    }
 120}