haystack_server/
actions.rs1use std::collections::HashMap;
7
8use haystack_core::data::{HDict, HGrid};
9
10pub trait ActionHandler: Send + Sync {
16 fn name(&self) -> &str;
18
19 fn invoke(&self, entity: &HDict, action: &str, args: &HDict) -> Result<HGrid, String>;
27}
28
29pub struct ActionRegistry {
33 handlers: HashMap<String, Box<dyn ActionHandler>>,
34}
35
36impl ActionRegistry {
37 pub fn new() -> Self {
39 Self {
40 handlers: HashMap::new(),
41 }
42 }
43
44 pub fn register(&mut self, handler: Box<dyn ActionHandler>) {
48 let name = handler.name().to_string();
49 self.handlers.insert(name, handler);
50 }
51
52 pub fn invoke(&self, entity: &HDict, action: &str, args: &HDict) -> Result<HGrid, String> {
57 match self.handlers.get(action) {
58 Some(handler) => handler.invoke(entity, action, args),
59 None => Err(format!("unknown action: {action}")),
60 }
61 }
62
63 pub fn list_actions(&self) -> Vec<String> {
65 self.handlers.keys().cloned().collect()
66 }
67}
68
69impl Default for ActionRegistry {
70 fn default() -> Self {
71 Self::new()
72 }
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78 use haystack_core::data::HCol;
79 use haystack_core::kinds::{HRef, Kind};
80
81 struct EchoAction;
83
84 impl ActionHandler for EchoAction {
85 fn name(&self) -> &str {
86 "echo"
87 }
88
89 fn invoke(&self, _entity: &HDict, action: &str, _args: &HDict) -> Result<HGrid, String> {
90 let mut row = HDict::new();
91 row.set("action", Kind::Str(action.to_string()));
92 row.set("result", Kind::Str("ok".to_string()));
93 let cols = vec![HCol::new("action"), HCol::new("result")];
94 Ok(HGrid::from_parts(HDict::new(), cols, vec![row]))
95 }
96 }
97
98 struct RebootAction;
100
101 impl ActionHandler for RebootAction {
102 fn name(&self) -> &str {
103 "reboot"
104 }
105
106 fn invoke(&self, _entity: &HDict, _action: &str, _args: &HDict) -> Result<HGrid, String> {
107 Ok(HGrid::new())
108 }
109 }
110
111 #[test]
112 fn invoke_known_action() {
113 let mut registry = ActionRegistry::new();
114 registry.register(Box::new(EchoAction));
115
116 let mut entity = HDict::new();
117 entity.set("id", Kind::Ref(HRef::from_val("equip-1")));
118 entity.set("equip", Kind::Marker);
119
120 let result = registry.invoke(&entity, "echo", &HDict::new());
121 assert!(result.is_ok());
122
123 let grid = result.unwrap();
124 assert_eq!(grid.len(), 1);
125 let row = grid.row(0).unwrap();
126 assert_eq!(row.get("action"), Some(&Kind::Str("echo".to_string())));
127 assert_eq!(row.get("result"), Some(&Kind::Str("ok".to_string())));
128 }
129
130 #[test]
131 fn invoke_unknown_action_returns_error() {
132 let registry = ActionRegistry::new();
133
134 let entity = HDict::new();
135 let result = registry.invoke(&entity, "nonexistent", &HDict::new());
136
137 assert!(result.is_err());
138 let err = result.unwrap_err();
139 assert!(err.contains("unknown action"));
140 assert!(err.contains("nonexistent"));
141 }
142
143 #[test]
144 fn list_actions_returns_registered_names() {
145 let mut registry = ActionRegistry::new();
146 registry.register(Box::new(EchoAction));
147 registry.register(Box::new(RebootAction));
148
149 let mut names = registry.list_actions();
150 names.sort();
151 assert_eq!(names, vec!["echo".to_string(), "reboot".to_string()]);
152 }
153
154 #[test]
155 fn empty_registry_has_no_actions() {
156 let registry = ActionRegistry::new();
157 assert!(registry.list_actions().is_empty());
158 }
159
160 #[test]
161 fn register_replaces_existing_handler() {
162 let mut registry = ActionRegistry::new();
163 registry.register(Box::new(EchoAction));
164 registry.register(Box::new(EchoAction));
166
167 let names = registry.list_actions();
168 assert_eq!(names.len(), 1);
169 }
170}