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}