Skip to main content

hypen_engine/dispatch/
action.rs

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