From 9ecaf58e19ed4ef62ef453f396b03c8a3df73eed Mon Sep 17 00:00:00 2001 From: pmosk Date: Wed, 2 Jul 2025 18:21:52 +0300 Subject: [PATCH] Use HandlerConsoleRunner --- .../ConsoleDependencyExtensions.cs | 130 +++--------------- src/Handler.Console/Handler.Console.csproj | 4 +- .../Internal/AdapterHandler.cs | 47 +++++++ .../Internal/HandlerConsoleRunner.cs | 117 ++++++++++++++++ .../Internal/IHandlerConsoleRunner.cs | 8 ++ 5 files changed, 196 insertions(+), 110 deletions(-) create mode 100644 src/Handler.Console/Internal/AdapterHandler.cs create mode 100644 src/Handler.Console/Internal/HandlerConsoleRunner.cs create mode 100644 src/Handler.Console/Internal/IHandlerConsoleRunner.cs diff --git a/src/Handler.Console/ConsoleDependencyExtensions.cs b/src/Handler.Console/ConsoleDependencyExtensions.cs index 8a30644..1ddad4d 100644 --- a/src/Handler.Console/ConsoleDependencyExtensions.cs +++ b/src/Handler.Console/ConsoleDependencyExtensions.cs @@ -1,131 +1,45 @@ using System; using System.Diagnostics.CodeAnalysis; -using System.Threading; -using System.Threading.Tasks; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; using PrimeFuncPack; namespace GarageGroup.Infra; public static class ConsoleDependencyExtensions { - public static Task RunConsoleAsync( - this Dependency dependency, - Action? configureLogger = null, - Action? configureServices = null, - [AllowNull] string inputSection = null, - [AllowNull] string[] args = null) - where THandler : IHandler + public static HandlerConsoleRunner UseConsoleRunner( + this Dependency> dependency, [AllowNull] string[] args = null) { ArgumentNullException.ThrowIfNull(dependency); - ArgumentNullException.ThrowIfNull(configureServices); - - return dependency.InnerRunConsoleAsync(configureLogger, configureServices, inputSection, args); + return new(dependency.Resolve, args); } - public static Task RunConsoleAsync( - this Dependency> dependency, - Action? configureLogger = null, - Action? configureServices = null, - [AllowNull] string inputSection = null, - [AllowNull] string[] args = null) + public static HandlerConsoleRunner UseConsoleRunner( + this Dependency dependency, [AllowNull] string[] args = null) + where THandler : IHandler { ArgumentNullException.ThrowIfNull(dependency); - return dependency.InnerRunConsoleAsync, TIn>(configureLogger, configureServices, inputSection, args); - } - - private static async Task InnerRunConsoleAsync( - this Dependency dependency, - Action? configureLogger, - Action? configureServices, - [AllowNull] string inputSection, - [AllowNull] string[] args) - where THandler : IHandler - { - var configuration = BuildConfiguration(args ?? []); - - using var serviceProvider = configuration.CreateServiceProvider(configureLogger, configureServices); - using var cancellationTokenSource = configuration.GetCancellationTokenSource(); - - var logger = serviceProvider.GetRequiredService().CreateLogger("HandlerConsoleRunner"); - - var input = configuration.ReadInput(inputSection ?? string.Empty); - var result = await dependency.Resolve(serviceProvider).InnerInvokeAsync(input, cancellationTokenSource.Token); - - return result.Fold(Unit.From, logger.LogFailure); - } - - private static async Task>> InnerInvokeAsync( - this THandler handler, TIn? input, CancellationToken cancellationToken) - where THandler : IHandler - { - try - { - return await handler.HandleAsync(input, cancellationToken); - } - finally - { - if (handler is IDisposable disposable) - { - disposable.Dispose(); - } - } - } + return new(InnerResolve, args); - private static TIn? ReadInput(this IConfiguration configuration, string sectionName) - { - if (typeof(TIn) == typeof(Unit)) - { - return default; - } - - return configuration.GetRequiredSection(sectionName).Get(); - } - - private static Unit LogFailure(this ILogger logger, Failure failure) - { - if (failure.FailureCode is not HandlerFailureCode.Persistent) - { - throw new InvalidOperationException($"An unexpected error has occured: {failure.FailureMessage}", failure.SourceException); - } - - logger.LogError(failure.SourceException, "An unexpected failure has occured: {failureMessage}", failure.FailureMessage); - return default; + IHandler InnerResolve(IServiceProvider serviceProvider) + => + dependency.Resolve(serviceProvider); } - private static CancellationTokenSource GetCancellationTokenSource(this IConfiguration configuration) - { - var timeout = configuration.GetValue("MaxTimeout"); - return timeout is null ? new() : new(timeout.Value); - } - - private static ServiceProvider CreateServiceProvider( - this IConfiguration configuration, Action? configureLogger, Action? configureServices) + public static HandlerConsoleRunner UseConsoleRunner( + this Dependency> dependency, + string inputSection, + [AllowNull] string[] args = null) { - var services = new ServiceCollection() - .AddLogging(InnerConfigureLogger) - .AddSingleton(configuration) - .AddSocketsHttpHandlerProviderAsSingleton() - .AddTokenCredentialStandardAsSingleton(); - - configureServices?.Invoke(services); - - return services.BuildServiceProvider(); + ArgumentNullException.ThrowIfNull(dependency); + return new(InnerResolve, args); - void InnerConfigureLogger(ILoggingBuilder builder) - { - builder = builder.AddConsole(); - configureLogger?.Invoke(builder); - } + IHandler InnerResolve(IServiceProvider serviceProvider) + => + new AdapterHandler( + innerHandler: dependency.Resolve(serviceProvider), + configuration: serviceProvider.GetRequiredService(), + sectionName: inputSection); } - - private static IConfiguration BuildConfiguration(string[] args) - => - new ConfigurationBuilder() - .AddJsonFile("appsettings.json", true, true) - .AddEnvironmentVariables() - .AddCommandLine(args) - .Build(); } \ No newline at end of file diff --git a/src/Handler.Console/Handler.Console.csproj b/src/Handler.Console/Handler.Console.csproj index 92e9ee6..9526d9d 100644 --- a/src/Handler.Console/Handler.Console.csproj +++ b/src/Handler.Console/Handler.Console.csproj @@ -9,13 +9,13 @@ $(NoWarn);IDE0130;CA1859 GarageGroup.Infra GarageGroup.Infra.Handler.Console - 0.12.0 + 0.13.0 - + \ No newline at end of file diff --git a/src/Handler.Console/Internal/AdapterHandler.cs b/src/Handler.Console/Internal/AdapterHandler.cs new file mode 100644 index 0000000..a13ecce --- /dev/null +++ b/src/Handler.Console/Internal/AdapterHandler.cs @@ -0,0 +1,47 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Configuration; + +namespace GarageGroup.Infra; + +internal sealed class AdapterHandler : IHandler +{ + private readonly IHandler innerHandler; + + private readonly IConfiguration configuration; + + private readonly string sectionName; + + internal AdapterHandler(IHandler innerHandler, IConfiguration configuration, [AllowNull] string sectionName) + { + this.innerHandler = innerHandler; + this.configuration = configuration; + this.sectionName = sectionName ?? string.Empty; + } + + public ValueTask>> HandleAsync( + Unit _, CancellationToken cancellationToken) + { + return ReadInput().ForwardValueAsync(InnerHandleAsync); + + ValueTask>> InnerHandleAsync(TIn? @in) + => + innerHandler.HandleAsync(@in, cancellationToken); + } + + private Result> ReadInput() + { + try + { + return configuration.GetRequiredSection(sectionName).Get(); + } + catch (Exception ex) + { + return ex.ToFailure( + HandlerFailureCode.Persistent, + $"Input of type '{typeof(TIn).FullName}' can't be readed from section '{sectionName}'"); + } + } +} \ No newline at end of file diff --git a/src/Handler.Console/Internal/HandlerConsoleRunner.cs b/src/Handler.Console/Internal/HandlerConsoleRunner.cs new file mode 100644 index 0000000..ae6bdc5 --- /dev/null +++ b/src/Handler.Console/Internal/HandlerConsoleRunner.cs @@ -0,0 +1,117 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace GarageGroup.Infra; + +public sealed class HandlerConsoleRunner : IHandlerConsoleRunner +{ + private readonly Func> handlerResolver; + + private readonly IConfiguration configuration; + + private readonly Action? configureServices; + + private readonly Action? configureLogger; + + internal HandlerConsoleRunner(Func> handlerResolver, [AllowNull] string[] args) + { + this.handlerResolver = handlerResolver; + configuration = BuildConfiguration(args ?? []); + } + + private HandlerConsoleRunner( + Func> handlerResolver, + IConfiguration configuration, + Action? configureServices, + Action? configureLogger) + { + this.handlerResolver = handlerResolver; + this.configuration = configuration; + this.configureServices = configureServices; + this.configureLogger = configureLogger; + } + + public IHandlerConsoleRunner Configure( + Action configureServices, + Action? configureLogger = null) + => + new HandlerConsoleRunner(handlerResolver, configuration, configureServices, configureLogger); + + public async Task RunAsync() + { + using var serviceProvider = CreateServiceProvider(); + using var cancellationTokenSource = GetCancellationTokenSource(); + + var handler = handlerResolver.Invoke(serviceProvider); + var result = await InnerInvokeAsync(handler, cancellationTokenSource.Token); + + _ = result.Fold(Unit.From, InnerLogFailre); + + Unit InnerLogFailre(Failure failure) + { + if (failure.FailureCode is not HandlerFailureCode.Persistent) + { + throw new InvalidOperationException($"An unexpected persistent error occured: {failure.FailureMessage}", failure.SourceException); + } + + var logger = serviceProvider.GetRequiredService().CreateLogger(); + logger.LogError(failure.SourceException, "An unexpected transient failure occured: {failureMessage}", failure.FailureMessage); + + return default; + } + } + + private static async Task>> InnerInvokeAsync( + IHandler handler, CancellationToken cancellationToken) + { + try + { + return await handler.HandleAsync(default, cancellationToken); + } + finally + { + if (handler is IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + private ServiceProvider CreateServiceProvider() + { + var services = new ServiceCollection() + .AddLogging(InnerConfigureLogger) + .AddSingleton(configuration) + .AddSocketsHttpHandlerProviderAsSingleton() + .AddTokenCredentialStandardAsSingleton(); + + configureServices?.Invoke(services); + + return services.BuildServiceProvider(); + + void InnerConfigureLogger(ILoggingBuilder builder) + { + builder = builder.AddConsole(); + configureLogger?.Invoke(builder); + } + } + + private CancellationTokenSource GetCancellationTokenSource() + { + var timeout = configuration.GetValue("MaxTimeout"); + return timeout is null ? new() : new(timeout.Value); + } + + private static IConfiguration BuildConfiguration(string[] args) + => + new ConfigurationBuilder() + .AddJsonFile("appsettings.json", true, true) + .AddEnvironmentVariables() + .AddCommandLine(args) + .Build(); +} \ No newline at end of file diff --git a/src/Handler.Console/Internal/IHandlerConsoleRunner.cs b/src/Handler.Console/Internal/IHandlerConsoleRunner.cs new file mode 100644 index 0000000..e2e3310 --- /dev/null +++ b/src/Handler.Console/Internal/IHandlerConsoleRunner.cs @@ -0,0 +1,8 @@ +using System.Threading.Tasks; + +namespace GarageGroup.Infra; + +public interface IHandlerConsoleRunner +{ + Task RunAsync(); +} \ No newline at end of file