saorsa_agent/extension/
command_registry.rs1use crate::error::{Result, SaorsaAgentError};
4use std::collections::HashMap;
5use std::sync::Arc;
6
7pub type CommandHandler = Arc<dyn Fn(&[&str]) -> Result<String> + Send + Sync>;
9
10pub struct CommandDefinition {
12 pub name: String,
14 pub description: String,
16 pub usage: String,
18 pub handler: CommandHandler,
20}
21
22impl CommandDefinition {
23 pub fn new(name: String, description: String, usage: String, handler: CommandHandler) -> Self {
25 Self {
26 name,
27 description,
28 usage,
29 handler,
30 }
31 }
32}
33
34pub struct CommandRegistry {
36 commands: HashMap<String, CommandDefinition>,
37}
38
39impl CommandRegistry {
40 pub fn new() -> Self {
42 Self {
43 commands: HashMap::new(),
44 }
45 }
46
47 pub fn register_command(&mut self, def: CommandDefinition) -> Result<()> {
51 if self.commands.contains_key(&def.name) {
52 return Err(SaorsaAgentError::Extension(format!(
53 "command '{}' is already registered",
54 def.name
55 )));
56 }
57 self.commands.insert(def.name.clone(), def);
58 Ok(())
59 }
60
61 pub fn unregister_command(&mut self, name: &str) -> Result<()> {
65 self.commands
66 .remove(name)
67 .ok_or_else(|| SaorsaAgentError::Extension(format!("command '{}' not found", name)))?;
68 Ok(())
69 }
70
71 pub fn get_command(&self, name: &str) -> Option<&CommandDefinition> {
73 self.commands.get(name)
74 }
75
76 pub fn list_commands(&self) -> Vec<&CommandDefinition> {
78 self.commands.values().collect()
79 }
80
81 pub fn execute_command(&self, name: &str, args: &[&str]) -> Result<String> {
85 let def = self
86 .commands
87 .get(name)
88 .ok_or_else(|| SaorsaAgentError::Extension(format!("command '{}' not found", name)))?;
89 (def.handler)(args)
90 }
91}
92
93impl Default for CommandRegistry {
94 fn default() -> Self {
95 Self::new()
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102
103 fn echo_handler(args: &[&str]) -> Result<String> {
104 Ok(format!("echo: {}", args.join(" ")))
105 }
106
107 #[test]
108 fn register_command() {
109 let mut registry = CommandRegistry::new();
110 let def = CommandDefinition::new(
111 "echo".to_string(),
112 "Echo arguments".to_string(),
113 "echo <text>".to_string(),
114 Arc::new(echo_handler),
115 );
116 let result = registry.register_command(def);
117 assert!(result.is_ok());
118 assert!(registry.get_command("echo").is_some());
119 }
120
121 #[test]
122 fn duplicate_command_fails() {
123 let mut registry = CommandRegistry::new();
124 let def1 = CommandDefinition::new(
125 "echo".to_string(),
126 "Echo 1".to_string(),
127 "echo".to_string(),
128 Arc::new(echo_handler),
129 );
130 let def2 = CommandDefinition::new(
131 "echo".to_string(),
132 "Echo 2".to_string(),
133 "echo".to_string(),
134 Arc::new(echo_handler),
135 );
136 assert!(registry.register_command(def1).is_ok());
137 let result = registry.register_command(def2);
138 assert!(result.is_err());
139 match result {
140 Err(SaorsaAgentError::Extension(msg)) => {
141 assert!(msg.contains("already registered"));
142 }
143 _ => unreachable!(),
144 }
145 }
146
147 #[test]
148 fn unregister_command() {
149 let mut registry = CommandRegistry::new();
150 let def = CommandDefinition::new(
151 "echo".to_string(),
152 "Echo".to_string(),
153 "echo".to_string(),
154 Arc::new(echo_handler),
155 );
156 assert!(registry.register_command(def).is_ok());
157 assert!(registry.unregister_command("echo").is_ok());
158 assert!(registry.get_command("echo").is_none());
159 }
160
161 #[test]
162 fn unregister_nonexistent_fails() {
163 let mut registry = CommandRegistry::new();
164 let result = registry.unregister_command("nonexistent");
165 assert!(result.is_err());
166 match result {
167 Err(SaorsaAgentError::Extension(msg)) => {
168 assert!(msg.contains("not found"));
169 }
170 _ => unreachable!(),
171 }
172 }
173
174 #[test]
175 fn list_commands() {
176 let mut registry = CommandRegistry::new();
177 let def1 = CommandDefinition::new(
178 "echo".to_string(),
179 "Echo".to_string(),
180 "echo".to_string(),
181 Arc::new(echo_handler),
182 );
183 let def2 = CommandDefinition::new(
184 "test".to_string(),
185 "Test".to_string(),
186 "test".to_string(),
187 Arc::new(echo_handler),
188 );
189 assert!(registry.register_command(def1).is_ok());
190 assert!(registry.register_command(def2).is_ok());
191 let list = registry.list_commands();
192 assert_eq!(list.len(), 2);
193 }
194
195 #[test]
196 fn execute_command() {
197 let mut registry = CommandRegistry::new();
198 let def = CommandDefinition::new(
199 "echo".to_string(),
200 "Echo".to_string(),
201 "echo".to_string(),
202 Arc::new(echo_handler),
203 );
204 assert!(registry.register_command(def).is_ok());
205 let result = registry.execute_command("echo", &["hello", "world"]);
206 assert!(result.is_ok());
207 let output = result.ok().unwrap_or_default();
208 assert_eq!(output, "echo: hello world");
209 }
210
211 #[test]
212 fn execute_nonexistent_fails() {
213 let registry = CommandRegistry::new();
214 let result = registry.execute_command("nonexistent", &[]);
215 assert!(result.is_err());
216 match result {
217 Err(SaorsaAgentError::Extension(msg)) => {
218 assert!(msg.contains("not found"));
219 }
220 _ => unreachable!(),
221 }
222 }
223}