Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 22 additions & 108 deletions src/Handler.Console/ConsoleDependencyExtensions.cs
Original file line number Diff line number Diff line change
@@ -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<Unit> RunConsoleAsync<THandler, TIn>(
this Dependency<THandler> dependency,
Action<ILoggingBuilder>? configureLogger = null,
Action<IServiceCollection>? configureServices = null,
[AllowNull] string inputSection = null,
[AllowNull] string[] args = null)
where THandler : IHandler<TIn, Unit>
public static HandlerConsoleRunner UseConsoleRunner<THandler>(
this Dependency<IHandler<Unit, Unit>> dependency, [AllowNull] string[] args = null)
{
ArgumentNullException.ThrowIfNull(dependency);
ArgumentNullException.ThrowIfNull(configureServices);

return dependency.InnerRunConsoleAsync<THandler, TIn>(configureLogger, configureServices, inputSection, args);
return new(dependency.Resolve, args);
}

public static Task<Unit> RunConsoleAsync<TIn>(
this Dependency<IHandler<TIn, Unit>> dependency,
Action<ILoggingBuilder>? configureLogger = null,
Action<IServiceCollection>? configureServices = null,
[AllowNull] string inputSection = null,
[AllowNull] string[] args = null)
public static HandlerConsoleRunner UseConsoleRunner<THandler>(
this Dependency<THandler> dependency, [AllowNull] string[] args = null)
where THandler : IHandler<Unit, Unit>
{
ArgumentNullException.ThrowIfNull(dependency);
return dependency.InnerRunConsoleAsync<IHandler<TIn, Unit>, TIn>(configureLogger, configureServices, inputSection, args);
}

private static async Task<Unit> InnerRunConsoleAsync<THandler, TIn>(
this Dependency<THandler> dependency,
Action<ILoggingBuilder>? configureLogger,
Action<IServiceCollection>? configureServices,
[AllowNull] string inputSection,
[AllowNull] string[] args)
where THandler : IHandler<TIn, Unit>
{
var configuration = BuildConfiguration(args ?? []);

using var serviceProvider = configuration.CreateServiceProvider(configureLogger, configureServices);
using var cancellationTokenSource = configuration.GetCancellationTokenSource();

var logger = serviceProvider.GetRequiredService<ILoggerFactory>().CreateLogger("HandlerConsoleRunner");

var input = configuration.ReadInput<TIn>(inputSection ?? string.Empty);
var result = await dependency.Resolve(serviceProvider).InnerInvokeAsync(input, cancellationTokenSource.Token);

return result.Fold(Unit.From, logger.LogFailure);
}

private static async Task<Result<Unit, Failure<HandlerFailureCode>>> InnerInvokeAsync<THandler, TIn>(
this THandler handler, TIn? input, CancellationToken cancellationToken)
where THandler : IHandler<TIn, Unit>
{
try
{
return await handler.HandleAsync(input, cancellationToken);
}
finally
{
if (handler is IDisposable disposable)
{
disposable.Dispose();
}
}
}
return new(InnerResolve, args);

private static TIn? ReadInput<TIn>(this IConfiguration configuration, string sectionName)
{
if (typeof(TIn) == typeof(Unit))
{
return default;
}

return configuration.GetRequiredSection(sectionName).Get<TIn>();
}

private static Unit LogFailure(this ILogger logger, Failure<HandlerFailureCode> 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<Unit, Unit> InnerResolve(IServiceProvider serviceProvider)
=>
dependency.Resolve(serviceProvider);
}

private static CancellationTokenSource GetCancellationTokenSource(this IConfiguration configuration)
{
var timeout = configuration.GetValue<TimeSpan?>("MaxTimeout");
return timeout is null ? new() : new(timeout.Value);
}

private static ServiceProvider CreateServiceProvider(
this IConfiguration configuration, Action<ILoggingBuilder>? configureLogger, Action<IServiceCollection>? configureServices)
public static HandlerConsoleRunner UseConsoleRunner<TIn>(
this Dependency<IHandler<TIn, Unit>> 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<Unit, Unit> InnerResolve(IServiceProvider serviceProvider)
=>
new AdapterHandler<TIn>(
innerHandler: dependency.Resolve(serviceProvider),
configuration: serviceProvider.GetRequiredService<IConfiguration>(),
sectionName: inputSection);
}

private static IConfiguration BuildConfiguration(string[] args)
=>
new ConfigurationBuilder()
.AddJsonFile("appsettings.json", true, true)
.AddEnvironmentVariables()
.AddCommandLine(args)
.Build();
}
4 changes: 2 additions & 2 deletions src/Handler.Console/Handler.Console.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
<NoWarn>$(NoWarn);IDE0130;CA1859</NoWarn>
<RootNamespace>GarageGroup.Infra</RootNamespace>
<AssemblyName>GarageGroup.Infra.Handler.Console</AssemblyName>
<Version>0.12.0</Version>
<Version>0.13.0</Version>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="GarageGroup.Infra.Azure.TokenCredential" Version="0.3.0" />
<PackageReference Include="GarageGroup.Infra.Handler.Core" Version="0.6.1" />
<PackageReference Include="GarageGroup.Infra.Http.SocketsHandlerProvider" Version="3.0.0" />
<PackageReference Include="GarageGroup.Infra.Http.SocketsHandlerProvider" Version="3.1.0" />
</ItemGroup>

</Project>
47 changes: 47 additions & 0 deletions src/Handler.Console/Internal/AdapterHandler.cs
Original file line number Diff line number Diff line change
@@ -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<TIn> : IHandler<Unit, Unit>
{
private readonly IHandler<TIn, Unit> innerHandler;

private readonly IConfiguration configuration;

private readonly string sectionName;

internal AdapterHandler(IHandler<TIn, Unit> innerHandler, IConfiguration configuration, [AllowNull] string sectionName)
{
this.innerHandler = innerHandler;
this.configuration = configuration;
this.sectionName = sectionName ?? string.Empty;
}

public ValueTask<Result<Unit, Failure<HandlerFailureCode>>> HandleAsync(
Unit _, CancellationToken cancellationToken)
{
return ReadInput().ForwardValueAsync(InnerHandleAsync);

ValueTask<Result<Unit, Failure<HandlerFailureCode>>> InnerHandleAsync(TIn? @in)
=>
innerHandler.HandleAsync(@in, cancellationToken);
}

private Result<TIn?, Failure<HandlerFailureCode>> ReadInput()
{
try
{
return configuration.GetRequiredSection(sectionName).Get<TIn>();
}
catch (Exception ex)
{
return ex.ToFailure(
HandlerFailureCode.Persistent,
$"Input of type '{typeof(TIn).FullName}' can't be readed from section '{sectionName}'");
}
}
}
117 changes: 117 additions & 0 deletions src/Handler.Console/Internal/HandlerConsoleRunner.cs
Original file line number Diff line number Diff line change
@@ -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<IServiceProvider, IHandler<Unit, Unit>> handlerResolver;

private readonly IConfiguration configuration;

private readonly Action<IServiceCollection>? configureServices;

private readonly Action<ILoggingBuilder>? configureLogger;

internal HandlerConsoleRunner(Func<IServiceProvider, IHandler<Unit, Unit>> handlerResolver, [AllowNull] string[] args)
{
this.handlerResolver = handlerResolver;
configuration = BuildConfiguration(args ?? []);
}

private HandlerConsoleRunner(
Func<IServiceProvider, IHandler<Unit, Unit>> handlerResolver,
IConfiguration configuration,
Action<IServiceCollection>? configureServices,
Action<ILoggingBuilder>? configureLogger)
{
this.handlerResolver = handlerResolver;
this.configuration = configuration;
this.configureServices = configureServices;
this.configureLogger = configureLogger;
}

public IHandlerConsoleRunner Configure(
Action<IServiceCollection> configureServices,
Action<ILoggingBuilder>? 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<HandlerFailureCode> failure)
{
if (failure.FailureCode is not HandlerFailureCode.Persistent)
{
throw new InvalidOperationException($"An unexpected persistent error occured: {failure.FailureMessage}", failure.SourceException);
}

var logger = serviceProvider.GetRequiredService<ILoggerFactory>().CreateLogger<HandlerConsoleRunner>();
logger.LogError(failure.SourceException, "An unexpected transient failure occured: {failureMessage}", failure.FailureMessage);

return default;
}
}

private static async Task<Result<Unit, Failure<HandlerFailureCode>>> InnerInvokeAsync(
IHandler<Unit, Unit> 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<TimeSpan?>("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();
}
8 changes: 8 additions & 0 deletions src/Handler.Console/Internal/IHandlerConsoleRunner.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System.Threading.Tasks;

namespace GarageGroup.Infra;

public interface IHandlerConsoleRunner
{
Task RunAsync();
}