Skip to main content

studiole_command/services/
command_registry.rs

1use crate::prelude::*;
2
3/// Map request types to their handlers.
4pub struct CommandRegistry<T: ICommandInfo> {
5    handlers: HashMap<TypeId, T::Handler>,
6}
7
8impl<T: ICommandInfo> CommandRegistry<T> {
9    /// Create an empty [`CommandRegistry`].
10    #[must_use]
11    pub fn new() -> Self {
12        Self {
13            handlers: HashMap::default(),
14        }
15    }
16
17    /// Register a handler for a request type.
18    #[allow(clippy::as_conversions)]
19    pub fn register<
20        R: Executable + Send + Sync + 'static,
21        H: Execute<R, R::Response, R::ExecutionError>,
22    >(
23        &mut self,
24        handler: Arc<H>,
25    ) where
26        Arc<H>: Into<T::Handler>,
27    {
28        let request_type = TypeId::of::<R>();
29        self.handlers.insert(request_type, handler.into());
30    }
31
32    /// Resolve a request into a command by matching it to a registered handler.
33    #[allow(clippy::as_conversions)]
34    pub fn resolve<R: Executable + Send + Sync + 'static>(
35        &self,
36        request: R,
37    ) -> Result<T::Command, Report<QueueError>> {
38        let request_type = TypeId::of::<R>();
39        let handler = self
40            .handlers
41            .get(&request_type)
42            .ok_or_else(|| {
43                Report::new(QueueError::NoMatch)
44                    .attach_with("request_type", || String::from(type_name::<R>()))
45                    .attach("request", request.to_string())
46            })?
47            .clone();
48        let command = T::Command::new(request, handler);
49        Ok(command)
50    }
51}
52
53/// Describe how to populate a [`CommandRegistry`] for a command system.
54///
55/// Implemented by the `CommandInfo` type generated by [`define_commands_server`].
56/// A blanket impl bridges this to [`FromServicesAsync`] so the registry can be
57/// resolved from the DI container without violating orphan rules.
58pub trait RegisterHandlers: ICommandInfo {
59    /// Build a [`CommandRegistry`] by resolving handlers from the [`ServiceProvider`].
60    fn register_handlers(
61        services: &ServiceProvider,
62    ) -> impl Future<Output = Result<CommandRegistry<Self>, Report<ResolveError>>> + Send
63    where
64        Self: Sized;
65}
66
67impl<T: RegisterHandlers + 'static> FromServicesAsync for CommandRegistry<T> {
68    type Error = ResolveError;
69
70    async fn from_services_async(services: &ServiceProvider) -> Result<Self, Report<Self::Error>> {
71        T::register_handlers(services).await
72    }
73}
74
75/// Errors returned by [`CommandRegistry::resolve`].
76#[derive(Debug, Error)]
77pub enum QueueError {
78    #[error("Unable to match request to command")]
79    NoMatch,
80    #[error("Unable to match request to command")]
81    IncorrectCommandType,
82}