Skip to main content

dynamic_cli/registry/
command_registry.rs

1//! Command registry implementation
2//!
3//! This module provides the central registry for storing and retrieving
4//! command definitions and their associated handlers.
5//!
6//! # Architecture
7//!
8//! The registry maintains two main data structures:
9//! - A map of command names to their definitions and handlers
10//! - A map of aliases to canonical command names
11//!
12//! This design allows O(1) lookup by both command name and alias.
13//!
14//! # Example
15//!
16//! ```
17//! use dynamic_cli::registry::CommandRegistry;
18//! use dynamic_cli::config::schema::CommandDefinition;
19//! use dynamic_cli::executor::CommandHandler;
20//! use std::collections::HashMap;
21//!
22//! // Create a registry
23//! let mut registry = CommandRegistry::new();
24//!
25//! // Define a command
26//! let definition = CommandDefinition {
27//!     name: "hello".to_string(),
28//!     aliases: vec!["hi".to_string(), "greet".to_string()],
29//!     description: "Say hello".to_string(),
30//!     required: false,
31//!     arguments: vec![],
32//!     options: vec![],
33//!     implementation: "hello_handler".to_string(),
34//! };
35//!
36//! // Create a handler
37//! struct HelloCommand;
38//! impl CommandHandler for HelloCommand {
39//!     fn execute(
40//!         &self,
41//!         _ctx: &mut dyn dynamic_cli::context::ExecutionContext,
42//!         _args: &HashMap<String, String>,
43//!     ) -> dynamic_cli::Result<()> {
44//!         println!("Hello!");
45//!         Ok(())
46//!     }
47//! }
48//!
49//! // Register the command
50//! registry.register(definition, Box::new(HelloCommand))?;
51//!
52//! // Retrieve by name
53//! assert!(registry.get_handler("hello").is_some());
54//!
55//! // Retrieve by alias
56//! assert_eq!(registry.resolve_name("hi"), Some("hello"));
57//! # Ok::<(), dynamic_cli::error::DynamicCliError>(())
58//! ```
59
60use crate::config::schema::CommandDefinition;
61use crate::error::{RegistryError, Result};
62use crate::executor::CommandHandler;
63use std::collections::HashMap;
64
65/// Central registry for commands and their handlers
66///
67/// The registry stores all registered commands along with their definitions
68/// and handlers. It provides efficient lookup by both command name and alias.
69///
70/// # Thread Safety
71///
72/// The registry is designed to be constructed once during application startup
73/// and then shared immutably across the application. For multi-threaded access,
74/// wrap it in `Arc<CommandRegistry>`.
75///
76/// # Example
77///
78/// ```
79/// use dynamic_cli::registry::CommandRegistry;
80/// use dynamic_cli::config::schema::CommandDefinition;
81/// use dynamic_cli::executor::CommandHandler;
82/// use std::collections::HashMap;
83///
84/// let mut registry = CommandRegistry::new();
85///
86/// // Register commands during initialization
87/// # let definition = CommandDefinition {
88/// #     name: "test".to_string(),
89/// #     aliases: vec![],
90/// #     description: "Test".to_string(),
91/// #     required: false,
92/// #     arguments: vec![],
93/// #     options: vec![],
94/// #     implementation: "test_handler".to_string(),
95/// # };
96/// # struct TestCommand;
97/// # impl CommandHandler for TestCommand {
98/// #     fn execute(&self, _: &mut dyn dynamic_cli::context::ExecutionContext, _: &HashMap<String, String>) -> dynamic_cli::Result<()> { Ok(()) }
99/// # }
100/// registry.register(definition, Box::new(TestCommand))?;
101///
102/// // Use throughout the application
103/// if let Some(handler) = registry.get_handler("test") {
104///     // Execute the command
105/// }
106/// # Ok::<(), dynamic_cli::error::DynamicCliError>(())
107/// ```
108pub struct CommandRegistry {
109    /// Map of command names to their data
110    /// Key: canonical command name
111    /// Value: (CommandDefinition, Box<dyn CommandHandler>)
112    commands: HashMap<String, (CommandDefinition, Box<dyn CommandHandler>)>,
113
114    /// Map of aliases to canonical command names
115    /// Key: alias
116    /// Value: canonical command name
117    ///
118    /// This allows O(1) resolution of aliases to command names.
119    aliases: HashMap<String, String>,
120}
121
122impl CommandRegistry {
123    /// Create a new empty registry
124    ///
125    /// # Example
126    ///
127    /// ```
128    /// use dynamic_cli::registry::CommandRegistry;
129    ///
130    /// let registry = CommandRegistry::new();
131    /// assert_eq!(registry.list_commands().len(), 0);
132    /// ```
133    pub fn new() -> Self {
134        Self {
135            commands: HashMap::new(),
136            aliases: HashMap::new(),
137        }
138    }
139
140    /// Register a command with its handler
141    ///
142    /// This method registers a command definition along with its handler.
143    /// It also registers all aliases for the command.
144    ///
145    /// # Arguments
146    ///
147    /// * `definition` - The command definition from the configuration
148    /// * `handler` - The handler implementation for this command
149    ///
150    /// # Returns
151    ///
152    /// - `Ok(())` if registration succeeds
153    /// - `Err(RegistryError)` if:
154    ///   - A command with the same name is already registered
155    ///   - An alias conflicts with an existing command or alias
156    ///
157    /// # Errors
158    ///
159    /// - [`RegistryError::DuplicateRegistration`] if the command name already exists
160    /// - [`RegistryError::DuplicateAlias`] if an alias is already in use
161    ///
162    /// # Example
163    ///
164    /// ```
165    /// use dynamic_cli::registry::CommandRegistry;
166    /// use dynamic_cli::config::schema::CommandDefinition;
167    /// use dynamic_cli::executor::CommandHandler;
168    /// use std::collections::HashMap;
169    ///
170    /// let mut registry = CommandRegistry::new();
171    ///
172    /// let definition = CommandDefinition {
173    ///     name: "simulate".to_string(),
174    ///     aliases: vec!["sim".to_string(), "run".to_string()],
175    ///     description: "Run simulation".to_string(),
176    ///     required: false,
177    ///     arguments: vec![],
178    ///     options: vec![],
179    ///     implementation: "sim_handler".to_string(),
180    /// };
181    ///
182    /// struct SimCommand;
183    /// impl CommandHandler for SimCommand {
184    ///     fn execute(
185    ///         &self,
186    ///         _: &mut dyn dynamic_cli::context::ExecutionContext,
187    ///         _: &HashMap<String, String>,
188    ///     ) -> dynamic_cli::Result<()> {
189    ///         Ok(())
190    ///     }
191    /// }
192    ///
193    /// // Register the command
194    /// registry.register(definition, Box::new(SimCommand))?;
195    ///
196    /// // Can now access by name or alias
197    /// assert!(registry.get_handler("simulate").is_some());
198    /// assert_eq!(registry.resolve_name("sim"), Some("simulate"));
199    /// # Ok::<(), dynamic_cli::error::DynamicCliError>(())
200    /// ```
201    pub fn register(
202        &mut self,
203        definition: CommandDefinition,
204        handler: Box<dyn CommandHandler>,
205    ) -> Result<()> {
206        let cmd_name = &definition.name;
207
208        // Check if command name is already registered
209        if self.commands.contains_key(cmd_name) {
210            return Err(RegistryError::DuplicateRegistration {
211                name: cmd_name.clone(),
212                suggestion: None,
213            }
214            .into());
215        }
216
217        // Check if command name conflicts with existing alias
218        if self.aliases.contains_key(cmd_name) {
219            let existing_cmd = self.aliases.get(cmd_name).unwrap();
220            return Err(RegistryError::DuplicateAlias {
221                alias: cmd_name.clone(),
222                existing_command: existing_cmd.clone(),
223                suggestion: None,
224            }
225            .into());
226        }
227
228        // Check all aliases for conflicts
229        for alias in &definition.aliases {
230            // Check if alias conflicts with existing command name
231            if self.commands.contains_key(alias) {
232                return Err(RegistryError::DuplicateAlias {
233                    alias: alias.clone(),
234                    existing_command: alias.clone(),
235                    suggestion: None,
236                }
237                .into());
238            }
239
240            // Check if alias conflicts with existing alias
241            if self.aliases.contains_key(alias) {
242                let existing_cmd = self.aliases.get(alias).unwrap();
243                return Err(RegistryError::DuplicateAlias {
244                    alias: alias.clone(),
245                    existing_command: existing_cmd.clone(),
246                    suggestion: None,
247                }
248                .into());
249            }
250        }
251
252        // Register all aliases
253        for alias in &definition.aliases {
254            self.aliases.insert(alias.clone(), cmd_name.clone());
255        }
256
257        // Register the command
258        self.commands
259            .insert(cmd_name.clone(), (definition, handler));
260
261        Ok(())
262    }
263
264    /// Resolve a name (command or alias) to the canonical command name
265    ///
266    /// This method checks if the given name is either:
267    /// - A registered command name (returns the name itself)
268    /// - An alias (returns the canonical command name)
269    ///
270    /// # Arguments
271    ///
272    /// * `name` - The name or alias to resolve
273    ///
274    /// # Returns
275    ///
276    /// - `Some(&str)` - The canonical command name
277    /// - `None` - If the name is not registered
278    ///
279    /// # Example
280    ///
281    /// ```
282    /// use dynamic_cli::registry::CommandRegistry;
283    /// # use dynamic_cli::config::schema::CommandDefinition;
284    /// # use dynamic_cli::executor::CommandHandler;
285    /// # use std::collections::HashMap;
286    ///
287    /// let mut registry = CommandRegistry::new();
288    ///
289    /// # let definition = CommandDefinition {
290    /// #     name: "hello".to_string(),
291    /// #     aliases: vec!["hi".to_string()],
292    /// #     description: "".to_string(),
293    /// #     required: false,
294    /// #     arguments: vec![],
295    /// #     options: vec![],
296    /// #     implementation: "".to_string(),
297    /// # };
298    /// # struct TestCmd;
299    /// # impl CommandHandler for TestCmd {
300    /// #     fn execute(&self, _: &mut dyn dynamic_cli::context::ExecutionContext, _: &HashMap<String, String>) -> dynamic_cli::Result<()> { Ok(()) }
301    /// # }
302    /// # registry.register(definition, Box::new(TestCmd)).unwrap();
303    /// // Resolve command name
304    /// assert_eq!(registry.resolve_name("hello"), Some("hello"));
305    ///
306    /// // Resolve alias
307    /// assert_eq!(registry.resolve_name("hi"), Some("hello"));
308    ///
309    /// // Unknown name
310    /// assert_eq!(registry.resolve_name("unknown"), None);
311    /// ```
312    pub fn resolve_name(&self, name: &str) -> Option<&str> {
313        // First check if it's a command name
314        // Return reference to the stored name, not the parameter
315        if let Some((cmd_def, _)) = self.commands.get(name) {
316            return Some(cmd_def.name.as_str());
317        }
318
319        // Then check if it's an alias
320        self.aliases.get(name).map(|s| s.as_str())
321    }
322
323    /// Get the definition of a command by name or alias
324    ///
325    /// # Arguments
326    ///
327    /// * `name` - The command name or alias
328    ///
329    /// # Returns
330    ///
331    /// - `Some(&CommandDefinition)` if the command exists
332    /// - `None` if the command is not registered
333    ///
334    /// # Example
335    ///
336    /// ```
337    /// # use dynamic_cli::registry::CommandRegistry;
338    /// # use dynamic_cli::config::schema::CommandDefinition;
339    /// # use dynamic_cli::executor::CommandHandler;
340    /// # use std::collections::HashMap;
341    /// # let mut registry = CommandRegistry::new();
342    /// # let definition = CommandDefinition {
343    /// #     name: "test".to_string(),
344    /// #     aliases: vec!["t".to_string()],
345    /// #     description: "Test command".to_string(),
346    /// #     required: false,
347    /// #     arguments: vec![],
348    /// #     options: vec![],
349    /// #     implementation: "".to_string(),
350    /// # };
351    /// # struct TestCmd;
352    /// # impl CommandHandler for TestCmd {
353    /// #     fn execute(&self, _: &mut dyn dynamic_cli::context::ExecutionContext, _: &HashMap<String, String>) -> dynamic_cli::Result<()> { Ok(()) }
354    /// # }
355    /// # registry.register(definition, Box::new(TestCmd)).unwrap();
356    /// // Get by name
357    /// if let Some(def) = registry.get_definition("test") {
358    ///     assert_eq!(def.name, "test");
359    ///     assert_eq!(def.description, "Test command");
360    /// }
361    ///
362    /// // Get by alias
363    /// if let Some(def) = registry.get_definition("t") {
364    ///     assert_eq!(def.name, "test");
365    /// }
366    /// ```
367    pub fn get_definition(&self, name: &str) -> Option<&CommandDefinition> {
368        let canonical_name = self.resolve_name(name)?;
369        self.commands.get(canonical_name).map(|(def, _)| def)
370    }
371
372    /// Get the handler of a command by name or alias
373    ///
374    /// This is the primary method used during command execution to
375    /// retrieve the handler that will execute the command.
376    ///
377    /// # Arguments
378    ///
379    /// * `name` - The command name or alias
380    ///
381    /// # Returns
382    ///
383    /// - `Some(&Box<dyn CommandHandler>)` if the command exists
384    /// - `None` if the command is not registered
385    ///
386    /// # Example
387    ///
388    /// ```
389    /// # use dynamic_cli::registry::CommandRegistry;
390    /// # use dynamic_cli::config::schema::CommandDefinition;
391    /// # use dynamic_cli::executor::CommandHandler;
392    /// # use std::collections::HashMap;
393    /// # let mut registry = CommandRegistry::new();
394    /// # let definition = CommandDefinition {
395    /// #     name: "exec".to_string(),
396    /// #     aliases: vec!["x".to_string()],
397    /// #     description: "".to_string(),
398    /// #     required: false,
399    /// #     arguments: vec![],
400    /// #     options: vec![],
401    /// #     implementation: "".to_string(),
402    /// # };
403    /// # struct ExecCmd;
404    /// # impl CommandHandler for ExecCmd {
405    /// #     fn execute(&self, _: &mut dyn dynamic_cli::context::ExecutionContext, _: &HashMap<String, String>) -> dynamic_cli::Result<()> { Ok(()) }
406    /// # }
407    /// # registry.register(definition, Box::new(ExecCmd)).unwrap();
408    /// // Get handler by name
409    /// if let Some(handler) = registry.get_handler("exec") {
410    ///     // Use handler for execution
411    /// }
412    ///
413    /// // Get handler by alias
414    /// if let Some(handler) = registry.get_handler("x") {
415    ///     // Same handler
416    /// }
417    /// ```
418    // The return type &Box<dyn CommandHandler> is intentional: callers receive a
419    // reference to the owned box, which preserves the indirection needed for
420    // dynamic dispatch without transferring ownership.
421    #[allow(clippy::borrowed_box)]
422    pub fn get_handler(&self, name: &str) -> Option<&Box<dyn CommandHandler>> {
423        let canonical_name = self.resolve_name(name)?;
424        self.commands
425            .get(canonical_name)
426            .map(|(_, handler)| handler)
427    }
428
429    /// List all registered command definitions
430    ///
431    /// Returns a vector of references to all command definitions in the registry.
432    /// The order is not guaranteed.
433    ///
434    /// # Returns
435    ///
436    /// Vector of command definition references
437    ///
438    /// # Example
439    ///
440    /// ```
441    /// # use dynamic_cli::registry::CommandRegistry;
442    /// # use dynamic_cli::config::schema::CommandDefinition;
443    /// # use dynamic_cli::executor::CommandHandler;
444    /// # use std::collections::HashMap;
445    /// # let mut registry = CommandRegistry::new();
446    /// # let def1 = CommandDefinition {
447    /// #     name: "cmd1".to_string(),
448    /// #     aliases: vec![],
449    /// #     description: "".to_string(),
450    /// #     required: false,
451    /// #     arguments: vec![],
452    /// #     options: vec![],
453    /// #     implementation: "".to_string(),
454    /// # };
455    /// # let def2 = CommandDefinition {
456    /// #     name: "cmd2".to_string(),
457    /// #     aliases: vec![],
458    /// #     description: "".to_string(),
459    /// #     required: false,
460    /// #     arguments: vec![],
461    /// #     options: vec![],
462    /// #     implementation: "".to_string(),
463    /// # };
464    /// # struct TestCmd;
465    /// # impl CommandHandler for TestCmd {
466    /// #     fn execute(&self, _: &mut dyn dynamic_cli::context::ExecutionContext, _: &HashMap<String, String>) -> dynamic_cli::Result<()> { Ok(()) }
467    /// # }
468    /// # registry.register(def1, Box::new(TestCmd)).unwrap();
469    /// # registry.register(def2, Box::new(TestCmd)).unwrap();
470    /// let commands = registry.list_commands();
471    /// assert_eq!(commands.len(), 2);
472    ///
473    /// // Use for help text, command completion, etc.
474    /// for cmd in commands {
475    ///     println!("{}: {}", cmd.name, cmd.description);
476    /// }
477    /// ```
478    pub fn list_commands(&self) -> Vec<&CommandDefinition> {
479        self.commands.values().map(|(def, _)| def).collect()
480    }
481
482    /// Get the number of registered commands
483    ///
484    /// # Example
485    ///
486    /// ```
487    /// use dynamic_cli::registry::CommandRegistry;
488    ///
489    /// let registry = CommandRegistry::new();
490    /// assert_eq!(registry.len(), 0);
491    /// ```
492    pub fn len(&self) -> usize {
493        self.commands.len()
494    }
495
496    /// Check if the registry is empty
497    ///
498    /// # Example
499    ///
500    /// ```
501    /// use dynamic_cli::registry::CommandRegistry;
502    ///
503    /// let registry = CommandRegistry::new();
504    /// assert!(registry.is_empty());
505    /// ```
506    pub fn is_empty(&self) -> bool {
507        self.commands.is_empty()
508    }
509
510    /// Check if a command is registered (by name or alias)
511    ///
512    /// # Example
513    ///
514    /// ```
515    /// # use dynamic_cli::registry::CommandRegistry;
516    /// # use dynamic_cli::config::schema::CommandDefinition;
517    /// # use dynamic_cli::executor::CommandHandler;
518    /// # use std::collections::HashMap;
519    /// # let mut registry = CommandRegistry::new();
520    /// # let definition = CommandDefinition {
521    /// #     name: "test".to_string(),
522    /// #     aliases: vec!["t".to_string()],
523    /// #     description: "".to_string(),
524    /// #     required: false,
525    /// #     arguments: vec![],
526    /// #     options: vec![],
527    /// #     implementation: "".to_string(),
528    /// # };
529    /// # struct TestCmd;
530    /// # impl CommandHandler for TestCmd {
531    /// #     fn execute(&self, _: &mut dyn dynamic_cli::context::ExecutionContext, _: &HashMap<String, String>) -> dynamic_cli::Result<()> { Ok(()) }
532    /// # }
533    /// # registry.register(definition, Box::new(TestCmd)).unwrap();
534    /// assert!(registry.contains("test"));
535    /// assert!(registry.contains("t"));
536    /// assert!(!registry.contains("unknown"));
537    /// ```
538    pub fn contains(&self, name: &str) -> bool {
539        self.resolve_name(name).is_some()
540    }
541}
542
543// Implement Default for convenience
544impl Default for CommandRegistry {
545    fn default() -> Self {
546        Self::new()
547    }
548}
549
550#[cfg(test)]
551mod tests {
552    use super::*;
553    use std::any::Any;
554
555    // Test fixtures
556    #[derive(Default)]
557    struct TestContext;
558
559    impl crate::context::ExecutionContext for TestContext {
560        fn as_any(&self) -> &dyn Any {
561            self
562        }
563        fn as_any_mut(&mut self) -> &mut dyn Any {
564            self
565        }
566    }
567
568    struct TestHandler;
569
570    impl CommandHandler for TestHandler {
571        fn execute(
572            &self,
573            _context: &mut dyn crate::context::ExecutionContext,
574            _args: &HashMap<String, String>,
575        ) -> crate::error::Result<()> {
576            Ok(())
577        }
578    }
579
580    fn create_test_definition(name: &str, aliases: Vec<&str>) -> CommandDefinition {
581        CommandDefinition {
582            name: name.to_string(),
583            aliases: aliases.iter().map(|s| s.to_string()).collect(),
584            description: format!("{} command", name),
585            required: false,
586            arguments: vec![],
587            options: vec![],
588            implementation: format!("{}_handler", name),
589        }
590    }
591
592    // Basic functionality tests
593    #[test]
594    fn test_new_registry_is_empty() {
595        let registry = CommandRegistry::new();
596        assert!(registry.is_empty());
597        assert_eq!(registry.len(), 0);
598        assert_eq!(registry.list_commands().len(), 0);
599    }
600
601    #[test]
602    fn test_register_command() {
603        let mut registry = CommandRegistry::new();
604        let definition = create_test_definition("test", vec![]);
605
606        let result = registry.register(definition, Box::new(TestHandler));
607
608        assert!(result.is_ok());
609        assert_eq!(registry.len(), 1);
610        assert!(!registry.is_empty());
611    }
612
613    #[test]
614    fn test_register_command_with_aliases() {
615        let mut registry = CommandRegistry::new();
616        let definition = create_test_definition("hello", vec!["hi", "greet"]);
617
618        registry
619            .register(definition, Box::new(TestHandler))
620            .unwrap();
621
622        assert_eq!(registry.len(), 1);
623        assert!(registry.contains("hello"));
624        assert!(registry.contains("hi"));
625        assert!(registry.contains("greet"));
626    }
627
628    #[test]
629    fn test_register_duplicate_command_fails() {
630        let mut registry = CommandRegistry::new();
631        let def1 = create_test_definition("test", vec![]);
632        let def2 = create_test_definition("test", vec![]);
633
634        registry.register(def1, Box::new(TestHandler)).unwrap();
635        let result = registry.register(def2, Box::new(TestHandler));
636
637        assert!(result.is_err());
638        match result.unwrap_err() {
639            crate::error::DynamicCliError::Registry(RegistryError::DuplicateRegistration {
640                name,
641                ..
642            }) => {
643                assert_eq!(name, "test");
644            }
645            _ => panic!("Wrong error type"),
646        }
647    }
648
649    #[test]
650    fn test_register_duplicate_alias_fails() {
651        let mut registry = CommandRegistry::new();
652        let def1 = create_test_definition("cmd1", vec!["c"]);
653        let def2 = create_test_definition("cmd2", vec!["c"]);
654
655        registry.register(def1, Box::new(TestHandler)).unwrap();
656        let result = registry.register(def2, Box::new(TestHandler));
657
658        assert!(result.is_err());
659        match result.unwrap_err() {
660            crate::error::DynamicCliError::Registry(RegistryError::DuplicateAlias {
661                alias,
662                existing_command,
663                ..
664            }) => {
665                assert_eq!(alias, "c");
666                assert_eq!(existing_command, "cmd1");
667            }
668            _ => panic!("Wrong error type"),
669        }
670    }
671
672    #[test]
673    fn test_alias_conflicts_with_command_name() {
674        let mut registry = CommandRegistry::new();
675        let def1 = create_test_definition("test", vec![]);
676        let def2 = create_test_definition("other", vec!["test"]);
677
678        registry.register(def1, Box::new(TestHandler)).unwrap();
679        let result = registry.register(def2, Box::new(TestHandler));
680
681        assert!(result.is_err());
682    }
683
684    #[test]
685    fn test_command_name_conflicts_with_alias() {
686        let mut registry = CommandRegistry::new();
687        let def1 = create_test_definition("cmd1", vec!["other"]);
688        let def2 = create_test_definition("other", vec![]);
689
690        registry.register(def1, Box::new(TestHandler)).unwrap();
691        let result = registry.register(def2, Box::new(TestHandler));
692
693        assert!(result.is_err());
694    }
695
696    // Resolve name tests
697    #[test]
698    fn test_resolve_command_name() {
699        let mut registry = CommandRegistry::new();
700        let definition = create_test_definition("test", vec![]);
701
702        registry
703            .register(definition, Box::new(TestHandler))
704            .unwrap();
705
706        assert_eq!(registry.resolve_name("test"), Some("test"));
707    }
708
709    #[test]
710    fn test_resolve_alias() {
711        let mut registry = CommandRegistry::new();
712        let definition = create_test_definition("hello", vec!["hi", "greet"]);
713
714        registry
715            .register(definition, Box::new(TestHandler))
716            .unwrap();
717
718        assert_eq!(registry.resolve_name("hi"), Some("hello"));
719        assert_eq!(registry.resolve_name("greet"), Some("hello"));
720    }
721
722    #[test]
723    fn test_resolve_unknown_name() {
724        let registry = CommandRegistry::new();
725        assert_eq!(registry.resolve_name("unknown"), None);
726    }
727
728    // Get definition tests
729    #[test]
730    fn test_get_definition_by_name() {
731        let mut registry = CommandRegistry::new();
732        let definition = create_test_definition("test", vec![]);
733
734        registry
735            .register(definition, Box::new(TestHandler))
736            .unwrap();
737
738        let retrieved = registry.get_definition("test");
739        assert!(retrieved.is_some());
740        assert_eq!(retrieved.unwrap().name, "test");
741    }
742
743    #[test]
744    fn test_get_definition_by_alias() {
745        let mut registry = CommandRegistry::new();
746        let definition = create_test_definition("hello", vec!["hi"]);
747
748        registry
749            .register(definition, Box::new(TestHandler))
750            .unwrap();
751
752        let retrieved = registry.get_definition("hi");
753        assert!(retrieved.is_some());
754        assert_eq!(retrieved.unwrap().name, "hello");
755    }
756
757    #[test]
758    fn test_get_definition_unknown() {
759        let registry = CommandRegistry::new();
760        assert!(registry.get_definition("unknown").is_none());
761    }
762
763    // Get handler tests
764    #[test]
765    fn test_get_handler_by_name() {
766        let mut registry = CommandRegistry::new();
767        let definition = create_test_definition("test", vec![]);
768
769        registry
770            .register(definition, Box::new(TestHandler))
771            .unwrap();
772
773        let handler = registry.get_handler("test");
774        assert!(handler.is_some());
775    }
776
777    #[test]
778    fn test_get_handler_by_alias() {
779        let mut registry = CommandRegistry::new();
780        let definition = create_test_definition("hello", vec!["hi"]);
781
782        registry
783            .register(definition, Box::new(TestHandler))
784            .unwrap();
785
786        let handler = registry.get_handler("hi");
787        assert!(handler.is_some());
788    }
789
790    #[test]
791    fn test_get_handler_unknown() {
792        let registry = CommandRegistry::new();
793        assert!(registry.get_handler("unknown").is_none());
794    }
795
796    // List commands tests
797    #[test]
798    fn test_list_commands_empty() {
799        let registry = CommandRegistry::new();
800        let commands = registry.list_commands();
801        assert_eq!(commands.len(), 0);
802    }
803
804    #[test]
805    fn test_list_commands_multiple() {
806        let mut registry = CommandRegistry::new();
807
808        registry
809            .register(
810                create_test_definition("cmd1", vec![]),
811                Box::new(TestHandler),
812            )
813            .unwrap();
814        registry
815            .register(
816                create_test_definition("cmd2", vec![]),
817                Box::new(TestHandler),
818            )
819            .unwrap();
820        registry
821            .register(
822                create_test_definition("cmd3", vec![]),
823                Box::new(TestHandler),
824            )
825            .unwrap();
826
827        let commands = registry.list_commands();
828        assert_eq!(commands.len(), 3);
829
830        let names: Vec<&str> = commands.iter().map(|c| c.name.as_str()).collect();
831        assert!(names.contains(&"cmd1"));
832        assert!(names.contains(&"cmd2"));
833        assert!(names.contains(&"cmd3"));
834    }
835
836    // Integration tests
837    #[test]
838    fn test_complete_workflow() {
839        let mut registry = CommandRegistry::new();
840
841        // Register multiple commands with aliases
842        let def1 = create_test_definition("simulate", vec!["sim", "run"]);
843        let def2 = create_test_definition("validate", vec!["val", "check"]);
844        let def3 = create_test_definition("help", vec!["h", "?"]);
845
846        registry.register(def1, Box::new(TestHandler)).unwrap();
847        registry.register(def2, Box::new(TestHandler)).unwrap();
848        registry.register(def3, Box::new(TestHandler)).unwrap();
849
850        // Verify registry state
851        assert_eq!(registry.len(), 3);
852
853        // Verify all names resolve correctly
854        assert_eq!(registry.resolve_name("simulate"), Some("simulate"));
855        assert_eq!(registry.resolve_name("sim"), Some("simulate"));
856        assert_eq!(registry.resolve_name("validate"), Some("validate"));
857        assert_eq!(registry.resolve_name("val"), Some("validate"));
858
859        // Verify handlers are accessible
860        assert!(registry.get_handler("simulate").is_some());
861        assert!(registry.get_handler("sim").is_some());
862        assert!(registry.get_handler("h").is_some());
863
864        // Verify definitions are accessible
865        let sim_def = registry.get_definition("sim");
866        assert!(sim_def.is_some());
867        assert_eq!(sim_def.unwrap().name, "simulate");
868    }
869
870    #[test]
871    fn test_default_trait() {
872        let registry: CommandRegistry = Default::default();
873        assert!(registry.is_empty());
874    }
875
876    #[test]
877    fn test_contains_method() {
878        let mut registry = CommandRegistry::new();
879        let definition = create_test_definition("test", vec!["t"]);
880
881        registry
882            .register(definition, Box::new(TestHandler))
883            .unwrap();
884
885        assert!(registry.contains("test"));
886        assert!(registry.contains("t"));
887        assert!(!registry.contains("unknown"));
888    }
889
890    #[test]
891    fn test_multiple_aliases_same_command() {
892        let mut registry = CommandRegistry::new();
893        let definition = create_test_definition("command", vec!["c", "cmd", "com"]);
894
895        registry
896            .register(definition, Box::new(TestHandler))
897            .unwrap();
898
899        // All aliases should resolve to the same command
900        assert_eq!(registry.resolve_name("c"), Some("command"));
901        assert_eq!(registry.resolve_name("cmd"), Some("command"));
902        assert_eq!(registry.resolve_name("com"), Some("command"));
903
904        // All should return the same handler
905        let handler1 = registry.get_handler("c");
906        let handler2 = registry.get_handler("cmd");
907        assert!(handler1.is_some());
908        assert!(handler2.is_some());
909    }
910
911    #[test]
912    fn test_case_sensitivity() {
913        let mut registry = CommandRegistry::new();
914        let definition = create_test_definition("Test", vec![]);
915
916        registry
917            .register(definition, Box::new(TestHandler))
918            .unwrap();
919
920        // Case matters
921        assert!(registry.contains("Test"));
922        assert!(!registry.contains("test"));
923        assert!(!registry.contains("TEST"));
924    }
925
926    #[test]
927    fn test_empty_alias_list() {
928        let mut registry = CommandRegistry::new();
929        let definition = create_test_definition("test", vec![]);
930
931        let result = registry.register(definition, Box::new(TestHandler));
932
933        assert!(result.is_ok());
934        assert!(registry.contains("test"));
935    }
936}