a3s-boot 0.1.3

Adapter-first modular Rust web framework for A3S inspired by Nest.js
Documentation
use super::context::CqrsContext;
use super::handlers::{CommandHandler, EventHandler, QueryHandler};
use super::messages::{Command, CqrsEvent, Query};
use crate::{BootError, BoxFuture, Result};
use std::any::{type_name, Any};
use std::marker::PhantomData;
use std::sync::Arc;

pub(crate) trait ErasedCommandHandler: Send + Sync + 'static {
    fn execute(
        &self,
        command: Box<dyn Any + Send>,
        context: CqrsContext,
    ) -> BoxFuture<'static, Result<Box<dyn Any + Send>>>;
}

pub(crate) struct TypedCommandHandler<C>
where
    C: Command,
{
    pub(crate) inner: Arc<dyn CommandHandler<C>>,
    pub(crate) marker: PhantomData<fn(C)>,
}

impl<C> ErasedCommandHandler for TypedCommandHandler<C>
where
    C: Command,
{
    fn execute(
        &self,
        command: Box<dyn Any + Send>,
        context: CqrsContext,
    ) -> BoxFuture<'static, Result<Box<dyn Any + Send>>> {
        let handler = Arc::clone(&self.inner);
        Box::pin(async move {
            let command = *command.downcast::<C>().map_err(|_| {
                BootError::Internal(format!(
                    "command handler received the wrong command type: {}",
                    type_name::<C>()
                ))
            })?;
            let output = handler.execute(command, context).await?;
            Ok(Box::new(output) as Box<dyn Any + Send>)
        })
    }
}

pub(crate) trait ErasedQueryHandler: Send + Sync + 'static {
    fn execute(
        &self,
        query: Box<dyn Any + Send>,
        context: CqrsContext,
    ) -> BoxFuture<'static, Result<Box<dyn Any + Send>>>;
}

pub(crate) struct TypedQueryHandler<Q>
where
    Q: Query,
{
    pub(crate) inner: Arc<dyn QueryHandler<Q>>,
    pub(crate) marker: PhantomData<fn(Q)>,
}

impl<Q> ErasedQueryHandler for TypedQueryHandler<Q>
where
    Q: Query,
{
    fn execute(
        &self,
        query: Box<dyn Any + Send>,
        context: CqrsContext,
    ) -> BoxFuture<'static, Result<Box<dyn Any + Send>>> {
        let handler = Arc::clone(&self.inner);
        Box::pin(async move {
            let query = *query.downcast::<Q>().map_err(|_| {
                BootError::Internal(format!(
                    "query handler received the wrong query type: {}",
                    type_name::<Q>()
                ))
            })?;
            let output = handler.execute(query, context).await?;
            Ok(Box::new(output) as Box<dyn Any + Send>)
        })
    }
}

pub(crate) trait ErasedEventHandler: Send + Sync + 'static {
    fn handle(
        &self,
        event: &(dyn Any + Send + Sync),
        context: CqrsContext,
    ) -> BoxFuture<'static, Result<()>>;
}

pub(crate) struct TypedEventHandler<E>
where
    E: CqrsEvent,
{
    pub(crate) inner: Arc<dyn EventHandler<E>>,
    pub(crate) marker: PhantomData<fn(E)>,
}

impl<E> ErasedEventHandler for TypedEventHandler<E>
where
    E: CqrsEvent,
{
    fn handle(
        &self,
        event: &(dyn Any + Send + Sync),
        context: CqrsContext,
    ) -> BoxFuture<'static, Result<()>> {
        let handler = Arc::clone(&self.inner);
        let event = match event.downcast_ref::<E>() {
            Some(event) => event.clone(),
            None => {
                return Box::pin(async {
                    Err(BootError::Internal(format!(
                        "event handler received the wrong event type: {}",
                        type_name::<E>()
                    )))
                });
            }
        };
        Box::pin(async move { handler.handle(event, context).await })
    }
}

pub(crate) fn downcast_output<T>(
    output: Box<dyn Any + Send>,
    handler_name: &'static str,
) -> Result<T>
where
    T: Send + 'static,
{
    output.downcast::<T>().map(|output| *output).map_err(|_| {
        BootError::Internal(format!(
            "CQRS handler returned the wrong output type: {handler_name}"
        ))
    })
}