// // MessageHandler.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.Diagnostics.Contracts; using System.Reflection; using System.Runtime.InteropServices.JavaScript; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using NosSmooth.Comms.Core.Errors; using NosSmooth.Comms.Data; using NosSmooth.Comms.Data.Messages; using NosSmooth.Comms.Data.Responders; using NosSmooth.Core.Contracts; using Remora.Results; namespace NosSmooth.Comms.Core; /// /// An executor of message responders. /// public class MessageHandler { private readonly IServiceProvider _services; private readonly bool _respond; /// /// Initializes a new instance of the class. /// /// The services. /// Whether to respond to the messages. public MessageHandler(IServiceProvider services, bool respond) { _services = services; _respond = respond; } /// /// Handle the given message, call responders. /// /// The connection the message comes from. /// The message to handle. /// The cancellation token used for cancelling the operation. /// A result that may or may not have succeeded. public async Task HandleMessageAsync (ConnectionHandler connection, object wrappedMessage, CancellationToken ct) { var wrappedType = wrappedMessage.GetType(); if (!wrappedType.IsGenericType) { return new GenericError($"Message type is not MessageWrapper<>, but {wrappedType.FullName}"); } if (wrappedType.GetGenericTypeDefinition() != typeof(MessageWrapper<>)) { return new GenericError($"Message type is not MessageWrapper<>, but {wrappedType.FullName}"); } var messageType = wrappedType.GetGenericArguments().First(); var handleMessageMethod = GetType().GetMethod (nameof(GenericHandleMessageAsync), BindingFlags.NonPublic | BindingFlags.Instance)!.MakeGenericMethod (new[] { messageType }); var task = (Task)handleMessageMethod.Invoke(this, new[] { connection, wrappedMessage, ct })!; return await task; } private async Task GenericHandleMessageAsync (ConnectionHandler connection, MessageWrapper wrappedMessage, CancellationToken ct) where TMessage : notnull { var data = wrappedMessage.Data; var contractor = _services.GetService(); if (contractor is not null) { var contractorResult = await contractor.Update(wrappedMessage.Data, ct); if (!contractorResult.IsSuccess) { return contractorResult; } } await using var scope = _services.CreateAsyncScope(); var injector = scope.ServiceProvider.GetRequiredService(); injector.ConnectionHandler = connection; injector.Connection = connection.Connection; var responders = scope.ServiceProvider .GetServices>() .Select(x => x.Respond(data, ct)) .ToArray(); var results = (await Task.WhenAll(responders)) .Where(x => !x.IsSuccess) .Cast() .ToList(); var result = results.Count switch { 0 => Result.FromSuccess(), 1 => (Result)results[0], _ => new AggregateError(results) }; if (_respond && wrappedMessage.Data is not ResponseResult) { var responseResult = result; if (responders.Length == 0) { responseResult = new MessageHandlerNotFoundError(); } var response = new ResponseResult(wrappedMessage.MessageId, responseResult); var sentMessageResult = await connection.SendMessageAsync(response, ct); if (!sentMessageResult.IsSuccess) { results.Add(Result.FromError(sentMessageResult)); result = results.Count switch { 0 => Result.FromSuccess(), 1 => (Result)results[0], _ => new AggregateError(results) }; } } return result; } }