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 }
213 .into());
214 }
215
216 // Check if command name conflicts with existing alias
217 if self.aliases.contains_key(cmd_name) {
218 let existing_cmd = self.aliases.get(cmd_name).unwrap();
219 return Err(RegistryError::DuplicateAlias {
220 alias: cmd_name.clone(),
221 existing_command: existing_cmd.clone(),
222 }
223 .into());
224 }
225
226 // Check all aliases for conflicts
227 for alias in &definition.aliases {
228 // Check if alias conflicts with existing command name
229 if self.commands.contains_key(alias) {
230 return Err(RegistryError::DuplicateAlias {
231 alias: alias.clone(),
232 existing_command: alias.clone(),
233 }
234 .into());
235 }
236
237 // Check if alias conflicts with existing alias
238 if self.aliases.contains_key(alias) {
239 let existing_cmd = self.aliases.get(alias).unwrap();
240 return Err(RegistryError::DuplicateAlias {
241 alias: alias.clone(),
242 existing_command: existing_cmd.clone(),
243 }
244 .into());
245 }
246 }
247
248 // Register all aliases
249 for alias in &definition.aliases {
250 self.aliases.insert(alias.clone(), cmd_name.clone());
251 }
252
253 // Register the command
254 self.commands
255 .insert(cmd_name.clone(), (definition, handler));
256
257 Ok(())
258 }
259
260 /// Resolve a name (command or alias) to the canonical command name
261 ///
262 /// This method checks if the given name is either:
263 /// - A registered command name (returns the name itself)
264 /// - An alias (returns the canonical command name)
265 ///
266 /// # Arguments
267 ///
268 /// * `name` - The name or alias to resolve
269 ///
270 /// # Returns
271 ///
272 /// - `Some(&str)` - The canonical command name
273 /// - `None` - If the name is not registered
274 ///
275 /// # Example
276 ///
277 /// ```
278 /// use dynamic_cli::registry::CommandRegistry;
279 /// # use dynamic_cli::config::schema::CommandDefinition;
280 /// # use dynamic_cli::executor::CommandHandler;
281 /// # use std::collections::HashMap;
282 ///
283 /// let mut registry = CommandRegistry::new();
284 ///
285 /// # let definition = CommandDefinition {
286 /// # name: "hello".to_string(),
287 /// # aliases: vec!["hi".to_string()],
288 /// # description: "".to_string(),
289 /// # required: false,
290 /// # arguments: vec![],
291 /// # options: vec![],
292 /// # implementation: "".to_string(),
293 /// # };
294 /// # struct TestCmd;
295 /// # impl CommandHandler for TestCmd {
296 /// # fn execute(&self, _: &mut dyn dynamic_cli::context::ExecutionContext, _: &HashMap<String, String>) -> dynamic_cli::Result<()> { Ok(()) }
297 /// # }
298 /// # registry.register(definition, Box::new(TestCmd)).unwrap();
299 /// // Resolve command name
300 /// assert_eq!(registry.resolve_name("hello"), Some("hello"));
301 ///
302 /// // Resolve alias
303 /// assert_eq!(registry.resolve_name("hi"), Some("hello"));
304 ///
305 /// // Unknown name
306 /// assert_eq!(registry.resolve_name("unknown"), None);
307 /// ```
308 pub fn resolve_name(&self, name: &str) -> Option<&str> {
309 // First check if it's a command name
310 // Return reference to the stored name, not the parameter
311 if let Some((cmd_def, _)) = self.commands.get(name) {
312 return Some(cmd_def.name.as_str());
313 }
314
315 // Then check if it's an alias
316 self.aliases.get(name).map(|s| s.as_str())
317 }
318
319 /// Get the definition of a command by name or alias
320 ///
321 /// # Arguments
322 ///
323 /// * `name` - The command name or alias
324 ///
325 /// # Returns
326 ///
327 /// - `Some(&CommandDefinition)` if the command exists
328 /// - `None` if the command is not registered
329 ///
330 /// # Example
331 ///
332 /// ```
333 /// # use dynamic_cli::registry::CommandRegistry;
334 /// # use dynamic_cli::config::schema::CommandDefinition;
335 /// # use dynamic_cli::executor::CommandHandler;
336 /// # use std::collections::HashMap;
337 /// # let mut registry = CommandRegistry::new();
338 /// # let definition = CommandDefinition {
339 /// # name: "test".to_string(),
340 /// # aliases: vec!["t".to_string()],
341 /// # description: "Test command".to_string(),
342 /// # required: false,
343 /// # arguments: vec![],
344 /// # options: vec![],
345 /// # implementation: "".to_string(),
346 /// # };
347 /// # struct TestCmd;
348 /// # impl CommandHandler for TestCmd {
349 /// # fn execute(&self, _: &mut dyn dynamic_cli::context::ExecutionContext, _: &HashMap<String, String>) -> dynamic_cli::Result<()> { Ok(()) }
350 /// # }
351 /// # registry.register(definition, Box::new(TestCmd)).unwrap();
352 /// // Get by name
353 /// if let Some(def) = registry.get_definition("test") {
354 /// assert_eq!(def.name, "test");
355 /// assert_eq!(def.description, "Test command");
356 /// }
357 ///
358 /// // Get by alias
359 /// if let Some(def) = registry.get_definition("t") {
360 /// assert_eq!(def.name, "test");
361 /// }
362 /// ```
363 pub fn get_definition(&self, name: &str) -> Option<&CommandDefinition> {
364 let canonical_name = self.resolve_name(name)?;
365 self.commands.get(canonical_name).map(|(def, _)| def)
366 }
367
368 /// Get the handler of a command by name or alias
369 ///
370 /// This is the primary method used during command execution to
371 /// retrieve the handler that will execute the command.
372 ///
373 /// # Arguments
374 ///
375 /// * `name` - The command name or alias
376 ///
377 /// # Returns
378 ///
379 /// - `Some(&Box<dyn CommandHandler>)` if the command exists
380 /// - `None` if the command is not registered
381 ///
382 /// # Example
383 ///
384 /// ```
385 /// # use dynamic_cli::registry::CommandRegistry;
386 /// # use dynamic_cli::config::schema::CommandDefinition;
387 /// # use dynamic_cli::executor::CommandHandler;
388 /// # use std::collections::HashMap;
389 /// # let mut registry = CommandRegistry::new();
390 /// # let definition = CommandDefinition {
391 /// # name: "exec".to_string(),
392 /// # aliases: vec!["x".to_string()],
393 /// # description: "".to_string(),
394 /// # required: false,
395 /// # arguments: vec![],
396 /// # options: vec![],
397 /// # implementation: "".to_string(),
398 /// # };
399 /// # struct ExecCmd;
400 /// # impl CommandHandler for ExecCmd {
401 /// # fn execute(&self, _: &mut dyn dynamic_cli::context::ExecutionContext, _: &HashMap<String, String>) -> dynamic_cli::Result<()> { Ok(()) }
402 /// # }
403 /// # registry.register(definition, Box::new(ExecCmd)).unwrap();
404 /// // Get handler by name
405 /// if let Some(handler) = registry.get_handler("exec") {
406 /// // Use handler for execution
407 /// }
408 ///
409 /// // Get handler by alias
410 /// if let Some(handler) = registry.get_handler("x") {
411 /// // Same handler
412 /// }
413 /// ```
414 pub fn get_handler(&self, name: &str) -> Option<&Box<dyn CommandHandler>> {
415 let canonical_name = self.resolve_name(name)?;
416 self.commands
417 .get(canonical_name)
418 .map(|(_, handler)| handler)
419 }
420
421 /// List all registered command definitions
422 ///
423 /// Returns a vector of references to all command definitions in the registry.
424 /// The order is not guaranteed.
425 ///
426 /// # Returns
427 ///
428 /// Vector of command definition references
429 ///
430 /// # Example
431 ///
432 /// ```
433 /// # use dynamic_cli::registry::CommandRegistry;
434 /// # use dynamic_cli::config::schema::CommandDefinition;
435 /// # use dynamic_cli::executor::CommandHandler;
436 /// # use std::collections::HashMap;
437 /// # let mut registry = CommandRegistry::new();
438 /// # let def1 = CommandDefinition {
439 /// # name: "cmd1".to_string(),
440 /// # aliases: vec![],
441 /// # description: "".to_string(),
442 /// # required: false,
443 /// # arguments: vec![],
444 /// # options: vec![],
445 /// # implementation: "".to_string(),
446 /// # };
447 /// # let def2 = CommandDefinition {
448 /// # name: "cmd2".to_string(),
449 /// # aliases: vec![],
450 /// # description: "".to_string(),
451 /// # required: false,
452 /// # arguments: vec![],
453 /// # options: vec![],
454 /// # implementation: "".to_string(),
455 /// # };
456 /// # struct TestCmd;
457 /// # impl CommandHandler for TestCmd {
458 /// # fn execute(&self, _: &mut dyn dynamic_cli::context::ExecutionContext, _: &HashMap<String, String>) -> dynamic_cli::Result<()> { Ok(()) }
459 /// # }
460 /// # registry.register(def1, Box::new(TestCmd)).unwrap();
461 /// # registry.register(def2, Box::new(TestCmd)).unwrap();
462 /// let commands = registry.list_commands();
463 /// assert_eq!(commands.len(), 2);
464 ///
465 /// // Use for help text, command completion, etc.
466 /// for cmd in commands {
467 /// println!("{}: {}", cmd.name, cmd.description);
468 /// }
469 /// ```
470 pub fn list_commands(&self) -> Vec<&CommandDefinition> {
471 self.commands.values().map(|(def, _)| def).collect()
472 }
473
474 /// Get the number of registered commands
475 ///
476 /// # Example
477 ///
478 /// ```
479 /// use dynamic_cli::registry::CommandRegistry;
480 ///
481 /// let registry = CommandRegistry::new();
482 /// assert_eq!(registry.len(), 0);
483 /// ```
484 pub fn len(&self) -> usize {
485 self.commands.len()
486 }
487
488 /// Check if the registry is empty
489 ///
490 /// # Example
491 ///
492 /// ```
493 /// use dynamic_cli::registry::CommandRegistry;
494 ///
495 /// let registry = CommandRegistry::new();
496 /// assert!(registry.is_empty());
497 /// ```
498 pub fn is_empty(&self) -> bool {
499 self.commands.is_empty()
500 }
501
502 /// Check if a command is registered (by name or alias)
503 ///
504 /// # Example
505 ///
506 /// ```
507 /// # use dynamic_cli::registry::CommandRegistry;
508 /// # use dynamic_cli::config::schema::CommandDefinition;
509 /// # use dynamic_cli::executor::CommandHandler;
510 /// # use std::collections::HashMap;
511 /// # let mut registry = CommandRegistry::new();
512 /// # let definition = CommandDefinition {
513 /// # name: "test".to_string(),
514 /// # aliases: vec!["t".to_string()],
515 /// # description: "".to_string(),
516 /// # required: false,
517 /// # arguments: vec![],
518 /// # options: vec![],
519 /// # implementation: "".to_string(),
520 /// # };
521 /// # struct TestCmd;
522 /// # impl CommandHandler for TestCmd {
523 /// # fn execute(&self, _: &mut dyn dynamic_cli::context::ExecutionContext, _: &HashMap<String, String>) -> dynamic_cli::Result<()> { Ok(()) }
524 /// # }
525 /// # registry.register(definition, Box::new(TestCmd)).unwrap();
526 /// assert!(registry.contains("test"));
527 /// assert!(registry.contains("t"));
528 /// assert!(!registry.contains("unknown"));
529 /// ```
530 pub fn contains(&self, name: &str) -> bool {
531 self.resolve_name(name).is_some()
532 }
533}
534
535// Implement Default for convenience
536impl Default for CommandRegistry {
537 fn default() -> Self {
538 Self::new()
539 }
540}
541
542#[cfg(test)]
543mod tests {
544 use super::*;
545 use std::any::Any;
546
547 // Test fixtures
548 #[derive(Default)]
549 struct TestContext;
550
551 impl crate::context::ExecutionContext for TestContext {
552 fn as_any(&self) -> &dyn Any {
553 self
554 }
555 fn as_any_mut(&mut self) -> &mut dyn Any {
556 self
557 }
558 }
559
560 struct TestHandler;
561
562 impl CommandHandler for TestHandler {
563 fn execute(
564 &self,
565 _context: &mut dyn crate::context::ExecutionContext,
566 _args: &HashMap<String, String>,
567 ) -> crate::error::Result<()> {
568 Ok(())
569 }
570 }
571
572 fn create_test_definition(name: &str, aliases: Vec<&str>) -> CommandDefinition {
573 CommandDefinition {
574 name: name.to_string(),
575 aliases: aliases.iter().map(|s| s.to_string()).collect(),
576 description: format!("{} command", name),
577 required: false,
578 arguments: vec![],
579 options: vec![],
580 implementation: format!("{}_handler", name),
581 }
582 }
583
584 // Basic functionality tests
585 #[test]
586 fn test_new_registry_is_empty() {
587 let registry = CommandRegistry::new();
588 assert!(registry.is_empty());
589 assert_eq!(registry.len(), 0);
590 assert_eq!(registry.list_commands().len(), 0);
591 }
592
593 #[test]
594 fn test_register_command() {
595 let mut registry = CommandRegistry::new();
596 let definition = create_test_definition("test", vec![]);
597
598 let result = registry.register(definition, Box::new(TestHandler));
599
600 assert!(result.is_ok());
601 assert_eq!(registry.len(), 1);
602 assert!(!registry.is_empty());
603 }
604
605 #[test]
606 fn test_register_command_with_aliases() {
607 let mut registry = CommandRegistry::new();
608 let definition = create_test_definition("hello", vec!["hi", "greet"]);
609
610 registry
611 .register(definition, Box::new(TestHandler))
612 .unwrap();
613
614 assert_eq!(registry.len(), 1);
615 assert!(registry.contains("hello"));
616 assert!(registry.contains("hi"));
617 assert!(registry.contains("greet"));
618 }
619
620 #[test]
621 fn test_register_duplicate_command_fails() {
622 let mut registry = CommandRegistry::new();
623 let def1 = create_test_definition("test", vec![]);
624 let def2 = create_test_definition("test", vec![]);
625
626 registry.register(def1, Box::new(TestHandler)).unwrap();
627 let result = registry.register(def2, Box::new(TestHandler));
628
629 assert!(result.is_err());
630 match result.unwrap_err() {
631 crate::error::DynamicCliError::Registry(RegistryError::DuplicateRegistration {
632 name,
633 }) => {
634 assert_eq!(name, "test");
635 }
636 _ => panic!("Wrong error type"),
637 }
638 }
639
640 #[test]
641 fn test_register_duplicate_alias_fails() {
642 let mut registry = CommandRegistry::new();
643 let def1 = create_test_definition("cmd1", vec!["c"]);
644 let def2 = create_test_definition("cmd2", vec!["c"]);
645
646 registry.register(def1, Box::new(TestHandler)).unwrap();
647 let result = registry.register(def2, Box::new(TestHandler));
648
649 assert!(result.is_err());
650 match result.unwrap_err() {
651 crate::error::DynamicCliError::Registry(RegistryError::DuplicateAlias {
652 alias,
653 existing_command,
654 }) => {
655 assert_eq!(alias, "c");
656 assert_eq!(existing_command, "cmd1");
657 }
658 _ => panic!("Wrong error type"),
659 }
660 }
661
662 #[test]
663 fn test_alias_conflicts_with_command_name() {
664 let mut registry = CommandRegistry::new();
665 let def1 = create_test_definition("test", vec![]);
666 let def2 = create_test_definition("other", vec!["test"]);
667
668 registry.register(def1, Box::new(TestHandler)).unwrap();
669 let result = registry.register(def2, Box::new(TestHandler));
670
671 assert!(result.is_err());
672 }
673
674 #[test]
675 fn test_command_name_conflicts_with_alias() {
676 let mut registry = CommandRegistry::new();
677 let def1 = create_test_definition("cmd1", vec!["other"]);
678 let def2 = create_test_definition("other", vec![]);
679
680 registry.register(def1, Box::new(TestHandler)).unwrap();
681 let result = registry.register(def2, Box::new(TestHandler));
682
683 assert!(result.is_err());
684 }
685
686 // Resolve name tests
687 #[test]
688 fn test_resolve_command_name() {
689 let mut registry = CommandRegistry::new();
690 let definition = create_test_definition("test", vec![]);
691
692 registry
693 .register(definition, Box::new(TestHandler))
694 .unwrap();
695
696 assert_eq!(registry.resolve_name("test"), Some("test"));
697 }
698
699 #[test]
700 fn test_resolve_alias() {
701 let mut registry = CommandRegistry::new();
702 let definition = create_test_definition("hello", vec!["hi", "greet"]);
703
704 registry
705 .register(definition, Box::new(TestHandler))
706 .unwrap();
707
708 assert_eq!(registry.resolve_name("hi"), Some("hello"));
709 assert_eq!(registry.resolve_name("greet"), Some("hello"));
710 }
711
712 #[test]
713 fn test_resolve_unknown_name() {
714 let registry = CommandRegistry::new();
715 assert_eq!(registry.resolve_name("unknown"), None);
716 }
717
718 // Get definition tests
719 #[test]
720 fn test_get_definition_by_name() {
721 let mut registry = CommandRegistry::new();
722 let definition = create_test_definition("test", vec![]);
723
724 registry
725 .register(definition, Box::new(TestHandler))
726 .unwrap();
727
728 let retrieved = registry.get_definition("test");
729 assert!(retrieved.is_some());
730 assert_eq!(retrieved.unwrap().name, "test");
731 }
732
733 #[test]
734 fn test_get_definition_by_alias() {
735 let mut registry = CommandRegistry::new();
736 let definition = create_test_definition("hello", vec!["hi"]);
737
738 registry
739 .register(definition, Box::new(TestHandler))
740 .unwrap();
741
742 let retrieved = registry.get_definition("hi");
743 assert!(retrieved.is_some());
744 assert_eq!(retrieved.unwrap().name, "hello");
745 }
746
747 #[test]
748 fn test_get_definition_unknown() {
749 let registry = CommandRegistry::new();
750 assert!(registry.get_definition("unknown").is_none());
751 }
752
753 // Get handler tests
754 #[test]
755 fn test_get_handler_by_name() {
756 let mut registry = CommandRegistry::new();
757 let definition = create_test_definition("test", vec![]);
758
759 registry
760 .register(definition, Box::new(TestHandler))
761 .unwrap();
762
763 let handler = registry.get_handler("test");
764 assert!(handler.is_some());
765 }
766
767 #[test]
768 fn test_get_handler_by_alias() {
769 let mut registry = CommandRegistry::new();
770 let definition = create_test_definition("hello", vec!["hi"]);
771
772 registry
773 .register(definition, Box::new(TestHandler))
774 .unwrap();
775
776 let handler = registry.get_handler("hi");
777 assert!(handler.is_some());
778 }
779
780 #[test]
781 fn test_get_handler_unknown() {
782 let registry = CommandRegistry::new();
783 assert!(registry.get_handler("unknown").is_none());
784 }
785
786 // List commands tests
787 #[test]
788 fn test_list_commands_empty() {
789 let registry = CommandRegistry::new();
790 let commands = registry.list_commands();
791 assert_eq!(commands.len(), 0);
792 }
793
794 #[test]
795 fn test_list_commands_multiple() {
796 let mut registry = CommandRegistry::new();
797
798 registry
799 .register(
800 create_test_definition("cmd1", vec![]),
801 Box::new(TestHandler),
802 )
803 .unwrap();
804 registry
805 .register(
806 create_test_definition("cmd2", vec![]),
807 Box::new(TestHandler),
808 )
809 .unwrap();
810 registry
811 .register(
812 create_test_definition("cmd3", vec![]),
813 Box::new(TestHandler),
814 )
815 .unwrap();
816
817 let commands = registry.list_commands();
818 assert_eq!(commands.len(), 3);
819
820 let names: Vec<&str> = commands.iter().map(|c| c.name.as_str()).collect();
821 assert!(names.contains(&"cmd1"));
822 assert!(names.contains(&"cmd2"));
823 assert!(names.contains(&"cmd3"));
824 }
825
826 // Integration tests
827 #[test]
828 fn test_complete_workflow() {
829 let mut registry = CommandRegistry::new();
830
831 // Register multiple commands with aliases
832 let def1 = create_test_definition("simulate", vec!["sim", "run"]);
833 let def2 = create_test_definition("validate", vec!["val", "check"]);
834 let def3 = create_test_definition("help", vec!["h", "?"]);
835
836 registry.register(def1, Box::new(TestHandler)).unwrap();
837 registry.register(def2, Box::new(TestHandler)).unwrap();
838 registry.register(def3, Box::new(TestHandler)).unwrap();
839
840 // Verify registry state
841 assert_eq!(registry.len(), 3);
842
843 // Verify all names resolve correctly
844 assert_eq!(registry.resolve_name("simulate"), Some("simulate"));
845 assert_eq!(registry.resolve_name("sim"), Some("simulate"));
846 assert_eq!(registry.resolve_name("validate"), Some("validate"));
847 assert_eq!(registry.resolve_name("val"), Some("validate"));
848
849 // Verify handlers are accessible
850 assert!(registry.get_handler("simulate").is_some());
851 assert!(registry.get_handler("sim").is_some());
852 assert!(registry.get_handler("h").is_some());
853
854 // Verify definitions are accessible
855 let sim_def = registry.get_definition("sim");
856 assert!(sim_def.is_some());
857 assert_eq!(sim_def.unwrap().name, "simulate");
858 }
859
860 #[test]
861 fn test_default_trait() {
862 let registry: CommandRegistry = Default::default();
863 assert!(registry.is_empty());
864 }
865
866 #[test]
867 fn test_contains_method() {
868 let mut registry = CommandRegistry::new();
869 let definition = create_test_definition("test", vec!["t"]);
870
871 registry
872 .register(definition, Box::new(TestHandler))
873 .unwrap();
874
875 assert!(registry.contains("test"));
876 assert!(registry.contains("t"));
877 assert!(!registry.contains("unknown"));
878 }
879
880 #[test]
881 fn test_multiple_aliases_same_command() {
882 let mut registry = CommandRegistry::new();
883 let definition = create_test_definition("command", vec!["c", "cmd", "com"]);
884
885 registry
886 .register(definition, Box::new(TestHandler))
887 .unwrap();
888
889 // All aliases should resolve to the same command
890 assert_eq!(registry.resolve_name("c"), Some("command"));
891 assert_eq!(registry.resolve_name("cmd"), Some("command"));
892 assert_eq!(registry.resolve_name("com"), Some("command"));
893
894 // All should return the same handler
895 let handler1 = registry.get_handler("c");
896 let handler2 = registry.get_handler("cmd");
897 assert!(handler1.is_some());
898 assert!(handler2.is_some());
899 }
900
901 #[test]
902 fn test_case_sensitivity() {
903 let mut registry = CommandRegistry::new();
904 let definition = create_test_definition("Test", vec![]);
905
906 registry
907 .register(definition, Box::new(TestHandler))
908 .unwrap();
909
910 // Case matters
911 assert!(registry.contains("Test"));
912 assert!(!registry.contains("test"));
913 assert!(!registry.contains("TEST"));
914 }
915
916 #[test]
917 fn test_empty_alias_list() {
918 let mut registry = CommandRegistry::new();
919 let definition = create_test_definition("test", vec![]);
920
921 let result = registry.register(definition, Box::new(TestHandler));
922
923 assert!(result.is_ok());
924 assert!(registry.contains("test"));
925 }
926}