use crate::config::schema::CommandDefinition;
use crate::error::{RegistryError, Result};
use crate::executor::CommandHandler;
use std::collections::HashMap;
pub struct CommandRegistry {
commands: HashMap<String, (CommandDefinition, Box<dyn CommandHandler>)>,
aliases: HashMap<String, String>,
}
impl CommandRegistry {
pub fn new() -> Self {
Self {
commands: HashMap::new(),
aliases: HashMap::new(),
}
}
pub fn register(
&mut self,
definition: CommandDefinition,
handler: Box<dyn CommandHandler>,
) -> Result<()> {
let cmd_name = &definition.name;
if self.commands.contains_key(cmd_name) {
return Err(RegistryError::DuplicateRegistration {
name: cmd_name.clone(),
suggestion: None,
}
.into());
}
if self.aliases.contains_key(cmd_name) {
let existing_cmd = self.aliases.get(cmd_name).unwrap();
return Err(RegistryError::DuplicateAlias {
alias: cmd_name.clone(),
existing_command: existing_cmd.clone(),
suggestion: None,
}
.into());
}
for alias in &definition.aliases {
if self.commands.contains_key(alias) {
return Err(RegistryError::DuplicateAlias {
alias: alias.clone(),
existing_command: alias.clone(),
suggestion: None,
}
.into());
}
if self.aliases.contains_key(alias) {
let existing_cmd = self.aliases.get(alias).unwrap();
return Err(RegistryError::DuplicateAlias {
alias: alias.clone(),
existing_command: existing_cmd.clone(),
suggestion: None,
}
.into());
}
}
for alias in &definition.aliases {
self.aliases.insert(alias.clone(), cmd_name.clone());
}
self.commands
.insert(cmd_name.clone(), (definition, handler));
Ok(())
}
pub fn resolve_name(&self, name: &str) -> Option<&str> {
if let Some((cmd_def, _)) = self.commands.get(name) {
return Some(cmd_def.name.as_str());
}
self.aliases.get(name).map(|s| s.as_str())
}
pub fn get_definition(&self, name: &str) -> Option<&CommandDefinition> {
let canonical_name = self.resolve_name(name)?;
self.commands.get(canonical_name).map(|(def, _)| def)
}
#[allow(clippy::borrowed_box)]
pub fn get_handler(&self, name: &str) -> Option<&Box<dyn CommandHandler>> {
let canonical_name = self.resolve_name(name)?;
self.commands
.get(canonical_name)
.map(|(_, handler)| handler)
}
pub fn list_commands(&self) -> Vec<&CommandDefinition> {
self.commands.values().map(|(def, _)| def).collect()
}
pub fn len(&self) -> usize {
self.commands.len()
}
pub fn is_empty(&self) -> bool {
self.commands.is_empty()
}
pub fn contains(&self, name: &str) -> bool {
self.resolve_name(name).is_some()
}
}
impl Default for CommandRegistry {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::any::Any;
#[derive(Default)]
struct TestContext;
impl crate::context::ExecutionContext for TestContext {
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
struct TestHandler;
impl CommandHandler for TestHandler {
fn execute(
&self,
_context: &mut dyn crate::context::ExecutionContext,
_args: &HashMap<String, String>,
) -> crate::error::Result<()> {
Ok(())
}
}
fn create_test_definition(name: &str, aliases: Vec<&str>) -> CommandDefinition {
CommandDefinition {
name: name.to_string(),
aliases: aliases.iter().map(|s| s.to_string()).collect(),
description: format!("{} command", name),
required: false,
arguments: vec![],
options: vec![],
implementation: format!("{}_handler", name),
}
}
#[test]
fn test_new_registry_is_empty() {
let registry = CommandRegistry::new();
assert!(registry.is_empty());
assert_eq!(registry.len(), 0);
assert_eq!(registry.list_commands().len(), 0);
}
#[test]
fn test_register_command() {
let mut registry = CommandRegistry::new();
let definition = create_test_definition("test", vec![]);
let result = registry.register(definition, Box::new(TestHandler));
assert!(result.is_ok());
assert_eq!(registry.len(), 1);
assert!(!registry.is_empty());
}
#[test]
fn test_register_command_with_aliases() {
let mut registry = CommandRegistry::new();
let definition = create_test_definition("hello", vec!["hi", "greet"]);
registry
.register(definition, Box::new(TestHandler))
.unwrap();
assert_eq!(registry.len(), 1);
assert!(registry.contains("hello"));
assert!(registry.contains("hi"));
assert!(registry.contains("greet"));
}
#[test]
fn test_register_duplicate_command_fails() {
let mut registry = CommandRegistry::new();
let def1 = create_test_definition("test", vec![]);
let def2 = create_test_definition("test", vec![]);
registry.register(def1, Box::new(TestHandler)).unwrap();
let result = registry.register(def2, Box::new(TestHandler));
assert!(result.is_err());
match result.unwrap_err() {
crate::error::DynamicCliError::Registry(RegistryError::DuplicateRegistration {
name,
..
}) => {
assert_eq!(name, "test");
}
_ => panic!("Wrong error type"),
}
}
#[test]
fn test_register_duplicate_alias_fails() {
let mut registry = CommandRegistry::new();
let def1 = create_test_definition("cmd1", vec!["c"]);
let def2 = create_test_definition("cmd2", vec!["c"]);
registry.register(def1, Box::new(TestHandler)).unwrap();
let result = registry.register(def2, Box::new(TestHandler));
assert!(result.is_err());
match result.unwrap_err() {
crate::error::DynamicCliError::Registry(RegistryError::DuplicateAlias {
alias,
existing_command,
..
}) => {
assert_eq!(alias, "c");
assert_eq!(existing_command, "cmd1");
}
_ => panic!("Wrong error type"),
}
}
#[test]
fn test_alias_conflicts_with_command_name() {
let mut registry = CommandRegistry::new();
let def1 = create_test_definition("test", vec![]);
let def2 = create_test_definition("other", vec!["test"]);
registry.register(def1, Box::new(TestHandler)).unwrap();
let result = registry.register(def2, Box::new(TestHandler));
assert!(result.is_err());
}
#[test]
fn test_command_name_conflicts_with_alias() {
let mut registry = CommandRegistry::new();
let def1 = create_test_definition("cmd1", vec!["other"]);
let def2 = create_test_definition("other", vec![]);
registry.register(def1, Box::new(TestHandler)).unwrap();
let result = registry.register(def2, Box::new(TestHandler));
assert!(result.is_err());
}
#[test]
fn test_resolve_command_name() {
let mut registry = CommandRegistry::new();
let definition = create_test_definition("test", vec![]);
registry
.register(definition, Box::new(TestHandler))
.unwrap();
assert_eq!(registry.resolve_name("test"), Some("test"));
}
#[test]
fn test_resolve_alias() {
let mut registry = CommandRegistry::new();
let definition = create_test_definition("hello", vec!["hi", "greet"]);
registry
.register(definition, Box::new(TestHandler))
.unwrap();
assert_eq!(registry.resolve_name("hi"), Some("hello"));
assert_eq!(registry.resolve_name("greet"), Some("hello"));
}
#[test]
fn test_resolve_unknown_name() {
let registry = CommandRegistry::new();
assert_eq!(registry.resolve_name("unknown"), None);
}
#[test]
fn test_get_definition_by_name() {
let mut registry = CommandRegistry::new();
let definition = create_test_definition("test", vec![]);
registry
.register(definition, Box::new(TestHandler))
.unwrap();
let retrieved = registry.get_definition("test");
assert!(retrieved.is_some());
assert_eq!(retrieved.unwrap().name, "test");
}
#[test]
fn test_get_definition_by_alias() {
let mut registry = CommandRegistry::new();
let definition = create_test_definition("hello", vec!["hi"]);
registry
.register(definition, Box::new(TestHandler))
.unwrap();
let retrieved = registry.get_definition("hi");
assert!(retrieved.is_some());
assert_eq!(retrieved.unwrap().name, "hello");
}
#[test]
fn test_get_definition_unknown() {
let registry = CommandRegistry::new();
assert!(registry.get_definition("unknown").is_none());
}
#[test]
fn test_get_handler_by_name() {
let mut registry = CommandRegistry::new();
let definition = create_test_definition("test", vec![]);
registry
.register(definition, Box::new(TestHandler))
.unwrap();
let handler = registry.get_handler("test");
assert!(handler.is_some());
}
#[test]
fn test_get_handler_by_alias() {
let mut registry = CommandRegistry::new();
let definition = create_test_definition("hello", vec!["hi"]);
registry
.register(definition, Box::new(TestHandler))
.unwrap();
let handler = registry.get_handler("hi");
assert!(handler.is_some());
}
#[test]
fn test_get_handler_unknown() {
let registry = CommandRegistry::new();
assert!(registry.get_handler("unknown").is_none());
}
#[test]
fn test_list_commands_empty() {
let registry = CommandRegistry::new();
let commands = registry.list_commands();
assert_eq!(commands.len(), 0);
}
#[test]
fn test_list_commands_multiple() {
let mut registry = CommandRegistry::new();
registry
.register(
create_test_definition("cmd1", vec![]),
Box::new(TestHandler),
)
.unwrap();
registry
.register(
create_test_definition("cmd2", vec![]),
Box::new(TestHandler),
)
.unwrap();
registry
.register(
create_test_definition("cmd3", vec![]),
Box::new(TestHandler),
)
.unwrap();
let commands = registry.list_commands();
assert_eq!(commands.len(), 3);
let names: Vec<&str> = commands.iter().map(|c| c.name.as_str()).collect();
assert!(names.contains(&"cmd1"));
assert!(names.contains(&"cmd2"));
assert!(names.contains(&"cmd3"));
}
#[test]
fn test_complete_workflow() {
let mut registry = CommandRegistry::new();
let def1 = create_test_definition("simulate", vec!["sim", "run"]);
let def2 = create_test_definition("validate", vec!["val", "check"]);
let def3 = create_test_definition("help", vec!["h", "?"]);
registry.register(def1, Box::new(TestHandler)).unwrap();
registry.register(def2, Box::new(TestHandler)).unwrap();
registry.register(def3, Box::new(TestHandler)).unwrap();
assert_eq!(registry.len(), 3);
assert_eq!(registry.resolve_name("simulate"), Some("simulate"));
assert_eq!(registry.resolve_name("sim"), Some("simulate"));
assert_eq!(registry.resolve_name("validate"), Some("validate"));
assert_eq!(registry.resolve_name("val"), Some("validate"));
assert!(registry.get_handler("simulate").is_some());
assert!(registry.get_handler("sim").is_some());
assert!(registry.get_handler("h").is_some());
let sim_def = registry.get_definition("sim");
assert!(sim_def.is_some());
assert_eq!(sim_def.unwrap().name, "simulate");
}
#[test]
fn test_default_trait() {
let registry: CommandRegistry = Default::default();
assert!(registry.is_empty());
}
#[test]
fn test_contains_method() {
let mut registry = CommandRegistry::new();
let definition = create_test_definition("test", vec!["t"]);
registry
.register(definition, Box::new(TestHandler))
.unwrap();
assert!(registry.contains("test"));
assert!(registry.contains("t"));
assert!(!registry.contains("unknown"));
}
#[test]
fn test_multiple_aliases_same_command() {
let mut registry = CommandRegistry::new();
let definition = create_test_definition("command", vec!["c", "cmd", "com"]);
registry
.register(definition, Box::new(TestHandler))
.unwrap();
assert_eq!(registry.resolve_name("c"), Some("command"));
assert_eq!(registry.resolve_name("cmd"), Some("command"));
assert_eq!(registry.resolve_name("com"), Some("command"));
let handler1 = registry.get_handler("c");
let handler2 = registry.get_handler("cmd");
assert!(handler1.is_some());
assert!(handler2.is_some());
}
#[test]
fn test_case_sensitivity() {
let mut registry = CommandRegistry::new();
let definition = create_test_definition("Test", vec![]);
registry
.register(definition, Box::new(TestHandler))
.unwrap();
assert!(registry.contains("Test"));
assert!(!registry.contains("test"));
assert!(!registry.contains("TEST"));
}
#[test]
fn test_empty_alias_list() {
let mut registry = CommandRegistry::new();
let definition = create_test_definition("test", vec![]);
let result = registry.register(definition, Box::new(TestHandler));
assert!(result.is_ok());
assert!(registry.contains("test"));
}
}