studiole-command 0.4.1

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

/// Map request types to their handlers.
pub struct CommandRegistry<T: ICommandInfo> {
    handlers: HashMap<TypeId, T::Handler>,
}

impl<T: ICommandInfo> CommandRegistry<T> {
    /// Create an empty [`CommandRegistry`].
    #[must_use]
    pub fn new() -> Self {
        Self {
            handlers: HashMap::default(),
        }
    }

    /// Register a handler for a request type.
    #[allow(clippy::as_conversions)]
    pub fn register<
        R: Executable + Send + Sync + 'static,
        H: Execute<R, R::Response, R::ExecutionError>,
    >(
        &mut self,
        handler: Arc<H>,
    ) where
        Arc<H>: Into<T::Handler>,
    {
        let request_type = TypeId::of::<R>();
        self.handlers.insert(request_type, handler.into());
    }

    /// Resolve a request into a command by matching it to a registered handler.
    #[allow(clippy::as_conversions)]
    pub fn resolve<R: Executable + Send + Sync + 'static>(
        &self,
        request: R,
    ) -> Result<T::Command, Report<QueueError>> {
        let request_type = TypeId::of::<R>();
        let handler = self
            .handlers
            .get(&request_type)
            .ok_or_else(|| {
                Report::new(QueueError::NoMatch)
                    .attach_with("request_type", || String::from(type_name::<R>()))
                    .attach("request", request.to_string())
            })?
            .clone();
        let command = T::Command::new(request, handler);
        Ok(command)
    }
}

/// Describe how to populate a [`CommandRegistry`] for a command system.
///
/// Implemented by the `CommandInfo` type generated by [`define_commands_server`].
/// A blanket impl bridges this to [`FromServicesAsync`] so the registry can be
/// resolved from the DI container without violating orphan rules.
pub trait RegisterHandlers: ICommandInfo {
    /// Build a [`CommandRegistry`] by resolving handlers from the [`ServiceProvider`].
    fn register_handlers(
        services: &ServiceProvider,
    ) -> impl Future<Output = Result<CommandRegistry<Self>, Report<ResolveError>>> + Send
    where
        Self: Sized;
}

impl<T: RegisterHandlers + 'static> FromServicesAsync for CommandRegistry<T> {
    type Error = ResolveError;

    async fn from_services_async(services: &ServiceProvider) -> Result<Self, Report<Self::Error>> {
        T::register_handlers(services).await
    }
}

/// Errors returned by [`CommandRegistry::resolve`].
#[derive(Debug, Error)]
pub enum QueueError {
    #[error("Unable to match request to command")]
    NoMatch,
    #[error("Unable to match request to command")]
    IncorrectCommandType,
}