Skip to main content

hypen_engine/dispatch/
action.rs

1use serde::{Deserialize, Serialize};
2
3use crate::error::EngineError;
4
5/// An action dispatched from the UI (e.g., @actions.signIn)
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct Action {
8    /// Action name (e.g., "signIn")
9    pub name: String,
10
11    /// Optional payload data
12    pub payload: Option<serde_json::Value>,
13
14    /// Optional sender/source identifier
15    pub sender: Option<String>,
16}
17
18impl Action {
19    pub fn new(name: impl Into<String>) -> Self {
20        Self {
21            name: name.into(),
22            payload: None,
23            sender: None,
24        }
25    }
26
27    pub fn with_payload(mut self, payload: serde_json::Value) -> Self {
28        self.payload = Some(payload);
29        self
30    }
31
32    pub fn with_sender(mut self, sender: impl Into<String>) -> Self {
33        self.sender = Some(sender.into());
34        self
35    }
36}
37
38/// Callback type for action handlers
39pub type ActionHandler = Box<dyn Fn(&Action) + Send + Sync>;
40
41/// Dispatches actions to registered handlers
42pub struct ActionDispatcher {
43    /// Map of action name -> handler
44    handlers: indexmap::IndexMap<String, ActionHandler>,
45}
46
47impl ActionDispatcher {
48    pub fn new() -> Self {
49        Self {
50            handlers: indexmap::IndexMap::new(),
51        }
52    }
53
54    /// Register a handler for an action
55    pub fn on<F>(&mut self, action_name: impl Into<String>, handler: F)
56    where
57        F: Fn(&Action) + Send + Sync + 'static,
58    {
59        self.handlers.insert(action_name.into(), Box::new(handler));
60    }
61
62    /// Dispatch an action to its handler
63    pub fn dispatch(&self, action: &Action) -> Result<(), EngineError> {
64        if let Some(handler) = self.handlers.get(&action.name) {
65            handler(action);
66            Ok(())
67        } else {
68            Err(EngineError::ActionNotFound(action.name.clone()))
69        }
70    }
71
72    /// Check if a handler is registered for an action
73    pub fn has_handler(&self, action_name: &str) -> bool {
74        self.handlers.contains_key(action_name)
75    }
76
77    /// Remove a handler
78    pub fn remove(&mut self, action_name: &str) {
79        self.handlers.shift_remove(action_name);
80    }
81
82    /// Clear all handlers
83    pub fn clear(&mut self) {
84        self.handlers.clear();
85    }
86}
87
88impl Default for ActionDispatcher {
89    fn default() -> Self {
90        Self::new()
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97    use std::sync::{Arc, Mutex};
98
99    #[test]
100    fn test_action_dispatch() {
101        let mut dispatcher = ActionDispatcher::new();
102        let called = Arc::new(Mutex::new(false));
103        let called_clone = called.clone();
104
105        dispatcher.on("test", move |_action| {
106            *called_clone.lock().unwrap() = true;
107        });
108
109        let action = Action::new("test");
110        dispatcher.dispatch(&action).unwrap();
111
112        assert!(*called.lock().unwrap());
113    }
114
115    #[test]
116    fn test_missing_handler() {
117        let dispatcher = ActionDispatcher::new();
118        let action = Action::new("unknown");
119        assert!(dispatcher.dispatch(&action).is_err());
120    }
121}