use crate::error::{Result, SaorsaAgentError};
use std::collections::HashMap;
use std::sync::Arc;
pub type CommandHandler = Arc<dyn Fn(&[&str]) -> Result<String> + Send + Sync>;
pub struct CommandDefinition {
pub name: String,
pub description: String,
pub usage: String,
pub handler: CommandHandler,
}
impl CommandDefinition {
pub fn new(name: String, description: String, usage: String, handler: CommandHandler) -> Self {
Self {
name,
description,
usage,
handler,
}
}
}
pub struct CommandRegistry {
commands: HashMap<String, CommandDefinition>,
}
impl CommandRegistry {
pub fn new() -> Self {
Self {
commands: HashMap::new(),
}
}
pub fn register_command(&mut self, def: CommandDefinition) -> Result<()> {
if self.commands.contains_key(&def.name) {
return Err(SaorsaAgentError::Extension(format!(
"command '{}' is already registered",
def.name
)));
}
self.commands.insert(def.name.clone(), def);
Ok(())
}
pub fn unregister_command(&mut self, name: &str) -> Result<()> {
self.commands
.remove(name)
.ok_or_else(|| SaorsaAgentError::Extension(format!("command '{}' not found", name)))?;
Ok(())
}
pub fn get_command(&self, name: &str) -> Option<&CommandDefinition> {
self.commands.get(name)
}
pub fn list_commands(&self) -> Vec<&CommandDefinition> {
self.commands.values().collect()
}
pub fn execute_command(&self, name: &str, args: &[&str]) -> Result<String> {
let def = self
.commands
.get(name)
.ok_or_else(|| SaorsaAgentError::Extension(format!("command '{}' not found", name)))?;
(def.handler)(args)
}
}
impl Default for CommandRegistry {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn echo_handler(args: &[&str]) -> Result<String> {
Ok(format!("echo: {}", args.join(" ")))
}
#[test]
fn register_command() {
let mut registry = CommandRegistry::new();
let def = CommandDefinition::new(
"echo".to_string(),
"Echo arguments".to_string(),
"echo <text>".to_string(),
Arc::new(echo_handler),
);
let result = registry.register_command(def);
assert!(result.is_ok());
assert!(registry.get_command("echo").is_some());
}
#[test]
fn duplicate_command_fails() {
let mut registry = CommandRegistry::new();
let def1 = CommandDefinition::new(
"echo".to_string(),
"Echo 1".to_string(),
"echo".to_string(),
Arc::new(echo_handler),
);
let def2 = CommandDefinition::new(
"echo".to_string(),
"Echo 2".to_string(),
"echo".to_string(),
Arc::new(echo_handler),
);
assert!(registry.register_command(def1).is_ok());
let result = registry.register_command(def2);
assert!(result.is_err());
match result {
Err(SaorsaAgentError::Extension(msg)) => {
assert!(msg.contains("already registered"));
}
_ => unreachable!(),
}
}
#[test]
fn unregister_command() {
let mut registry = CommandRegistry::new();
let def = CommandDefinition::new(
"echo".to_string(),
"Echo".to_string(),
"echo".to_string(),
Arc::new(echo_handler),
);
assert!(registry.register_command(def).is_ok());
assert!(registry.unregister_command("echo").is_ok());
assert!(registry.get_command("echo").is_none());
}
#[test]
fn unregister_nonexistent_fails() {
let mut registry = CommandRegistry::new();
let result = registry.unregister_command("nonexistent");
assert!(result.is_err());
match result {
Err(SaorsaAgentError::Extension(msg)) => {
assert!(msg.contains("not found"));
}
_ => unreachable!(),
}
}
#[test]
fn list_commands() {
let mut registry = CommandRegistry::new();
let def1 = CommandDefinition::new(
"echo".to_string(),
"Echo".to_string(),
"echo".to_string(),
Arc::new(echo_handler),
);
let def2 = CommandDefinition::new(
"test".to_string(),
"Test".to_string(),
"test".to_string(),
Arc::new(echo_handler),
);
assert!(registry.register_command(def1).is_ok());
assert!(registry.register_command(def2).is_ok());
let list = registry.list_commands();
assert_eq!(list.len(), 2);
}
#[test]
fn execute_command() {
let mut registry = CommandRegistry::new();
let def = CommandDefinition::new(
"echo".to_string(),
"Echo".to_string(),
"echo".to_string(),
Arc::new(echo_handler),
);
assert!(registry.register_command(def).is_ok());
let result = registry.execute_command("echo", &["hello", "world"]);
assert!(result.is_ok());
let output = result.ok().unwrap_or_default();
assert_eq!(output, "echo: hello world");
}
#[test]
fn execute_nonexistent_fails() {
let registry = CommandRegistry::new();
let result = registry.execute_command("nonexistent", &[]);
assert!(result.is_err());
match result {
Err(SaorsaAgentError::Extension(msg)) => {
assert!(msg.contains("not found"));
}
_ => unreachable!(),
}
}
}