//
// 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;
}
}