// // CommandProcessor.cs // // Copyright (c) František Boháček. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using NosSmooth.Core.Client; using NosSmooth.Core.Errors; using NosSmooth.Core.Packets; using NosSmooth.Packets; using Remora.Results; namespace NosSmooth.Core.Commands; /// /// Calls for the executing command /// by using dependency injection. /// public class CommandProcessor { private readonly IServiceProvider _provider; /// /// Initializes a new instance of the class. /// /// The dependency injection provider. public CommandProcessor(IServiceProvider provider) { _provider = provider; } /// /// Processes the given command, calling its handler or returning error. /// /// The NosTale client. /// The command to process. /// The cancellation token for cancelling the operation. /// A result that may or may not have succeeded. /// Thrown on critical error. public Task ProcessCommand(INostaleClient client, ICommand command, CancellationToken ct = default) { var processMethod = GetType().GetMethod ( nameof(DispatchCommandHandler), BindingFlags.NonPublic | BindingFlags.Instance ); if (processMethod is null) { throw new InvalidOperationException("Could not find process command generic method in command processor."); } var boundProcessMethod = processMethod.MakeGenericMethod(command.GetType()); return (Task)boundProcessMethod.Invoke(this, new object[] { client, command, ct })!; } private async Task DispatchCommandHandler ( INostaleClient client, TCommand command, CancellationToken ct = default ) where TCommand : class, ICommand { using var scope = _provider.CreateScope(); var beforeResult = await ExecuteBeforeExecutionAsync(scope.ServiceProvider, client, command, ct); if (!beforeResult.IsSuccess) { return beforeResult; } var commandHandler = scope.ServiceProvider.GetService>(); if (commandHandler is null) { var result = Result.FromError(new CommandHandlerNotFound(command.GetType())); var afterExecutionResult = await ExecuteAfterExecutionAsync ( scope.ServiceProvider, client, command, result, ct ); if (!afterExecutionResult.IsSuccess) { return new AggregateError(result, afterExecutionResult); } return result; } Result handlerResult; try { handlerResult = await commandHandler.HandleCommand(command, ct); } catch (Exception e) { handlerResult = e; } var afterResult = await ExecuteAfterExecutionAsync ( scope.ServiceProvider, client, command, handlerResult, ct ); if (!afterResult.IsSuccess && !handlerResult.IsSuccess) { return new AggregateError(handlerResult, afterResult); } if (!handlerResult.IsSuccess) { return handlerResult; } return afterResult; } private async Task ExecuteBeforeExecutionAsync ( IServiceProvider services, INostaleClient client, TCommand command, CancellationToken ct ) where TCommand : ICommand { try { var results = await Task.WhenAll ( services.GetServices() .Select(x => x.ExecuteBeforeCommandAsync(client, command, ct)) ); var errorResults = new List(); foreach (var result in results) { if (!result.IsSuccess) { errorResults.Add(result); } } return errorResults.Count switch { 1 => errorResults[0], 0 => Result.FromSuccess(), _ => new AggregateError(errorResults.Cast().ToArray()) }; } catch (Exception e) { return e; } } private async Task ExecuteAfterExecutionAsync ( IServiceProvider services, INostaleClient client, TCommand command, Result handlerResult, CancellationToken ct ) where TCommand : ICommand { try { var results = await Task.WhenAll ( services.GetServices() .Select(x => x.ExecuteAfterCommandAsync(client, command, handlerResult, ct)) ); var errorResults = new List(); foreach (var result in results) { if (!result.IsSuccess) { errorResults.Add(result); } } return errorResults.Count switch { 1 => errorResults[0], 0 => Result.FromSuccess(), _ => new AggregateError(errorResults.Cast().ToArray()) }; } catch (Exception e) { return e; } } }