< 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: 29
Uncovered lines: 1
Coverable lines: 30
Total lines: 132
Line coverage: 96.6%
Branch coverage
80%
Covered branches: 8
Total branches: 10
Branch coverage: 80%
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()75%121296.15%

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/// Unexpected exceptions from the callback are logged at <c>Critical</c> and rethrown
 26/// after cleanup so a top-level caller produces a nonzero process exit code. Cooperative
 27/// cancellation completes normally without a critical log.
 28/// </para>
 29/// </remarks>
 30/// <example>
 31/// <code>
 32/// await new NeedlrBootstrapper()
 33///     .ConfigureBootstrapConfiguration(builder => builder
 34///         .AddJsonFile("appsettings.json", optional: true)
 35///         .AddEnvironmentVariables())
 36///     .RunAsync(async (ctx, ct) =>
 37///     {
 38///         var host = new Syringe()
 39///             .UsingSourceGen()
 40///             .ForHost()
 41///             .UsingOptions(() => CreateHostOptions.Default.UsingCurrentProcessArgs())
 42///             .BuildHost();
 43///
 44///         await host.RunAsync(ct);
 45///     });
 46/// </code>
 47/// </example>
 48[DoNotAutoRegister]
 49public sealed record NeedlrBootstrapper
 50{
 9451    internal ILoggerFactory? Factory { get; init; }
 7052    internal Func<Task>? Cleanup { get; init; }
 3953    internal Action<IConfigurationBuilder>? ConfigureBootstrapConfigurationBuilder { get; init; }
 54
 55    /// <summary>
 56    /// Runs the application entry point with full bootstrap lifecycle management.
 57    /// </summary>
 58    /// <param name="runAsync">
 59    /// The application callback. Receives a <see cref="NeedlrBootstrapContext"/> containing
 60    /// the bootstrap logger and bootstrap configuration, and the <see cref="CancellationToken"/>
 61    /// passed to this method.
 62    /// </param>
 63    /// <param name="cancellationToken">
 64    /// Optional cancellation token forwarded to the callback.
 65    /// </param>
 66    /// <returns>
 67    /// A <see cref="Task"/> that completes when the application exits normally or through
 68    /// cooperative cancellation, and faults after cleanup for an unexpected exception.
 69    /// </returns>
 70    /// <example>
 71    /// <code>
 72    /// await new NeedlrBootstrapper().RunAsync(async (ctx, ct) =>
 73    /// {
 74    ///     ctx.Logger.LogInformation("Application starting...");
 75    ///     var path = ctx.BootstrapConfiguration["SomeSetting"];
 76    ///     await RunMyAppAsync(ct);
 77    /// });
 78    /// </code>
 79    /// </example>
 80    public async Task RunAsync(
 81        Func<NeedlrBootstrapContext, CancellationToken, Task> runAsync,
 82        CancellationToken cancellationToken = default)
 83    {
 3284        ArgumentNullException.ThrowIfNull(runAsync);
 85
 3186        var ownsFactory = Factory is null;
 3187        var loggerFactory = Factory ?? LoggerFactory.Create(b => b.AddConsole());
 3188        var logger = loggerFactory.CreateLogger("Startup");
 89
 3190        var configBuilder = new ConfigurationBuilder();
 3191        ConfigureBootstrapConfigurationBuilder?.Invoke(configBuilder);
 3192        var bootstrapConfiguration = configBuilder.Build();
 93
 94        try
 95        {
 3196            await runAsync(
 3197                new NeedlrBootstrapContext
 3198                {
 3199                    Logger = logger,
 31100                    BootstrapConfiguration = bootstrapConfiguration,
 31101                },
 31102                cancellationToken)
 31103                .ConfigureAwait(false);
 22104        }
 105        // Cooperative shutdown requested through this token is an intended exit, not a
 106        // failure, so it must not log critically or fault the returned task. Any other
 107        // cancellation still propagates through the general handler below.
 108        catch (OperationCanceledException)
 2109            when (cancellationToken.IsCancellationRequested)
 110        {
 2111        }
 7112        catch (Exception ex)
 113        {
 7114            NeedlrBootstrapperLog.ApplicationTerminatedUnexpectedly(logger, ex);
 7115            throw;
 116        }
 117        finally
 118        {
 31119            if (Cleanup is not null)
 120            {
 19121                await Cleanup().ConfigureAwait(false);
 122            }
 123
 31124            if (ownsFactory)
 125            {
 0126                loggerFactory.Dispose();
 127            }
 128
 31129            (bootstrapConfiguration as IDisposable)?.Dispose();
 130        }
 24131    }
 132}