studiole-command 0.4.1

A simple command execution framework with event subscription and progress tracking.
Documentation
use crate::prelude::*;

#[macro_export]
macro_rules! define_commands_server {
    ($($kind:ident($req:ty, $handler:ty)),* $(,)?) => {
        #[derive(Clone)]
        pub enum CommandHandler {
            $(
                $kind(Arc<$handler>),
            )*
        }

        impl IHandler for CommandHandler {}

        pub enum Command {
            $(
                $kind($req, Arc<$handler>),
            )*
        }

        #[async_trait]
        impl ICommand<CommandHandler, CommandSuccess, CommandFailure> for Command {
            fn new<T: Executable + Send + Sync + 'static>(
                request: T,
                handler: CommandHandler,
            ) -> Self {
                let request_any: Box<dyn Any> = Box::new(request);
                match handler {
                    $(
                    CommandHandler::$kind(handler) => {
                        let request = request_any
                            .downcast::<$req>()
                            .expect("Request type should match handler");
                        Self::$kind(*request, handler)
                    },
                    )*
                }
            }

            async fn execute(self) -> Result<CommandSuccess, CommandFailure> {
                match self {
                    $(
                        Self::$kind(request, handler) => {
                            match handler.execute(&request).await {
                                Ok(result) => Ok(CommandSuccess::$kind(result)),
                                Err(e) => Err(CommandFailure::$kind(e)),
                            }
                        },
                    )*
                }
            }
        }

        impl Display for Command {
            fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
                let name = match &self {
                    $(
                        Self::$kind(request, _) => request.to_string(),
                    )*
                };
                f.write_str(&name)
            }
        }

        $(
            impl From<Arc<$handler>> for CommandHandler {
                fn from(handler: Arc<$handler>) -> Self {
                    Self::$kind(handler)
                }
            }
        )*

        impl RegisterHandlers for CommandInfo {
            async fn register_handlers(
                services: &ServiceProvider,
            ) -> Result<CommandRegistry<Self>, Report<ResolveError>> {
                let mut registry = CommandRegistry::new();
                $(
                    let handler = services.get_async::<$handler>().await?;
                    registry.register::<$req, $handler>(handler);
                )*
                Ok(registry)
            }
        }

        pub trait WithCommands: Sized {
            fn with_commands(self) -> Self;
        }

        impl WithCommands for ServiceBuilder {
            fn with_commands(self) -> Self {
                self
                    $(
                        .with_type_async::<$handler>()
                    )*
                    .with_type_async::<CommandRegistry<CommandInfo>>()
                    .with_type_async::<CommandRunner<CommandInfo>>()
                    .with_type::<CommandMediator<CommandInfo>>()
                    .with_type::<WorkerPool<CommandInfo>>()
                    .with_type::<CommandEvents<CommandInfo>>()
                    .with_type::<ProgressWriterFactory>()
                    .with_type::<CliProgress<CommandInfo>>()
            }
        }
    };
}

/// Marker trait for command handler enums generated by [`define_commands_server`].
pub trait IHandler: Clone + Send + Sync {}

/// A command that pairs a request with its handler for execution.
#[async_trait]
pub trait ICommand<H: IHandler, S: ISuccess, F: IFailure>: Display + Send + Sync {
    /// Create a command from a request and its matched handler.
    fn new<T: Executable + Send + Sync + 'static>(request: T, handler: H) -> Self;
    /// Execute the command and return the result.
    async fn execute(self) -> Result<S, F>;
}