Skip to main content

dynamic_cli/executor/
traits.rs

1//! Command handler trait and related types
2//!
3//! This module defines the core trait that all command implementations must implement.
4//! The trait is designed to be object-safe, meaning it can be used as a trait object
5//! (`&dyn CommandHandler`), which is critical for dynamic command registration.
6//!
7//! # Design Principles
8//!
9//! ## Object Safety
10//!
11//! The `CommandHandler` trait is intentionally kept simple and object-safe:
12//! - No generic methods (would prevent trait object usage)
13//! - No associated types with type parameters
14//! - All methods use concrete types or trait objects
15//!
16//! This allows the registry to store handlers as `Box<dyn CommandHandler>`,
17//! enabling dynamic command registration at runtime.
18//!
19//! ## Simple Type Signatures
20//!
21//! Arguments are passed as `HashMap<String, String>` rather than generic types.
22//! This design choice:
23//! - Maintains object safety
24//! - Provides flexibility in argument types
25//! - Delegates type parsing to the parser module
26//!
27//! ## Thread Safety
28//!
29//! All handlers must be `Send + Sync` to support:
30//! - Shared access across threads
31//! - Potential async execution in the future
32//! - Safe usage in multi-threaded contexts
33//!
34//! # Example
35//!
36//! ```
37//! use std::collections::HashMap;
38//! use dynamic_cli::executor::CommandHandler;
39//! use dynamic_cli::context::ExecutionContext;
40//! use dynamic_cli::Result;
41//!
42//! // Define a simple command handler
43//! struct HelloCommand;
44//!
45//! impl CommandHandler for HelloCommand {
46//!     fn execute(
47//!         &self,
48//!         _context: &mut dyn ExecutionContext,
49//!         args: &HashMap<String, String>,
50//!     ) -> Result<()> {
51//!         let name = args.get("name").map(|s| s.as_str()).unwrap_or("World");
52//!         println!("Hello, {}!", name);
53//!         Ok(())
54//!     }
55//! }
56//! ```
57
58use crate::context::ExecutionContext;
59use crate::error::Result;
60use async_trait::async_trait;
61use std::collections::HashMap;
62
63/// Trait for command implementations
64///
65/// Each command in the CLI/REPL application must implement this trait.
66/// The trait is designed to be object-safe, allowing commands to be
67/// stored and invoked dynamically through trait objects.
68///
69/// # Object Safety
70///
71/// This trait is intentionally object-safe (can be used as `dyn CommandHandler`).
72/// **Do not add methods with generic type parameters**, as this would break
73/// object safety and prevent dynamic dispatch.
74///
75/// # Thread Safety
76///
77/// Implementations must be `Send + Sync` to allow:
78/// - Sharing command handlers across threads
79/// - Safe concurrent access to the command registry
80/// - Future async execution support
81///
82/// # Execution Flow
83///
84/// 1. Parser converts user input to `HashMap<String, String>`
85/// 2. Validator checks argument constraints
86/// 3. `validate()` is called for custom validation (optional)
87/// 4. `execute()` is called with validated arguments
88///
89/// # Example
90///
91/// ```
92/// use std::collections::HashMap;
93/// use dynamic_cli::error::ExecutionError;
94/// use dynamic_cli::executor::CommandHandler;
95/// use dynamic_cli::context::ExecutionContext;
96/// use dynamic_cli::Result;
97///
98/// struct GreetCommand;
99///
100/// impl CommandHandler for GreetCommand {
101///     fn execute(
102///         &self,
103///         _context: &mut dyn ExecutionContext,
104///         args: &HashMap<String, String>,
105///     ) -> Result<()> {
106///         let name = args.get("name")
107///             .ok_or_else(|| {
108///                 ExecutionError::CommandFailed(
109///                     anyhow::anyhow!("Missing 'name' argument")
110///              )
111///          })?;
112///         
113///         let greeting = if let Some(formal) = args.get("formal") {
114///             if formal == "true" {
115///                 format!("Good day, {}.", name)
116///             } else {
117///                 format!("Hi, {}!", name)
118///             }
119///         } else {
120///             format!("Hello, {}!", name)
121///         };
122///         
123///         println!("{}", greeting);
124///         Ok(())
125///     }
126///     
127///     fn validate(&self, args: &HashMap<String, String>) -> Result<()> {
128///         // Custom validation: name must not be empty
129///         if let Some(name) = args.get("name") {
130///             if name.trim().is_empty() {
131///                 return Err(ExecutionError::CommandFailed(
132///                         anyhow::anyhow!("Name cannot be empty")
133///                 ).into());
134///             }
135///         }
136///         Ok(())
137///     }
138/// }
139/// ```
140pub trait CommandHandler: Send + Sync {
141    /// Execute the command with the given context and arguments
142    ///
143    /// This is the main entry point for command execution. It receives:
144    /// - A mutable reference to the execution context (for shared state)
145    /// - A map of argument names to their string values
146    ///
147    /// # Arguments
148    ///
149    /// * `context` - Mutable execution context for sharing state between commands.
150    ///   Use `downcast_ref` or `downcast_mut` from the `context` module
151    ///   to access your specific context type.
152    ///
153    /// * `args` - Parsed and validated arguments as name-value pairs.
154    ///   All values are strings; type conversion should be done
155    ///   within the handler if needed.
156    ///
157    /// # Returns
158    ///
159    /// - `Ok(())` if execution succeeds
160    /// - `Err(DynamicCliError)` if execution fails
161    ///
162    /// # Errors
163    ///
164    /// Implementations should return errors for:
165    /// - Invalid argument values (caught by validate, but can be rechecked)
166    /// - Execution failures (I/O errors, computation errors, etc.)
167    /// - Invalid context state
168    ///
169    /// Use `ExecutionError::CommandFailed` to wrap application-specific errors:
170    /// ```ignore
171    /// Err(ExecutionError::CommandFailed(anyhow::anyhow!("Details")).into())
172    /// ```
173    ///
174    /// # Example
175    ///
176    /// ```
177    /// # use std::collections::HashMap;
178    /// # use dynamic_cli::error::ExecutionError;
179    /// # use dynamic_cli::executor::CommandHandler;
180    /// # use dynamic_cli::context::ExecutionContext;
181    /// # use dynamic_cli::Result;
182    /// #
183    /// struct FileCommand;
184    ///
185    /// impl CommandHandler for FileCommand {
186    ///     fn execute(
187    ///         &self,
188    ///         _context: &mut dyn ExecutionContext,
189    ///         args: &HashMap<String, String>,
190    ///     ) -> Result<()> {
191    ///         let path = args.get("path")
192    ///             .ok_or_else(|| {
193    ///                ExecutionError::CommandFailed(
194    ///                       anyhow::anyhow!("Missing path argument")
195    ///             )
196    ///          })?;
197    ///         
198    ///         // Perform the actual work
199    ///         let content = std::fs::read_to_string(path)
200    ///             .map_err(|e| {
201    ///                ExecutionError::CommandFailed(anyhow::anyhow!("Failed to read file: {}", e))
202    ///         })?;
203    ///         
204    ///         println!("File contains {} bytes", content.len());
205    ///         Ok(())
206    ///     }
207    /// }
208    /// ```
209    fn execute(
210        &self,
211        context: &mut dyn ExecutionContext,
212        args: &HashMap<String, String>,
213    ) -> Result<()>;
214
215    /// Optional custom validation for arguments
216    ///
217    /// This method is called after the standard validation (type checking,
218    /// required arguments, etc.) but before execution. It allows commands
219    /// to implement custom validation logic.
220    ///
221    /// # Default Implementation
222    ///
223    /// The default implementation accepts all arguments (returns `Ok(())`).
224    /// Override this method only if you need custom validation.
225    ///
226    /// # Arguments
227    ///
228    /// * `args` - The arguments to validate
229    ///
230    /// # Returns
231    ///
232    /// - `Ok(())` if validation succeeds
233    /// - `Err(DynamicCliError)` if validation fails
234    ///
235    /// # Example
236    ///
237    /// ```
238    /// # use std::collections::HashMap;
239    /// # use dynamic_cli::executor::CommandHandler;
240    /// # use dynamic_cli::context::ExecutionContext;
241    /// # use dynamic_cli::error::ExecutionError;
242    /// # use dynamic_cli::Result;
243    /// #
244    /// struct RangeCommand;
245    ///
246    /// impl CommandHandler for RangeCommand {
247    ///     fn execute(
248    ///         &self,
249    ///         _context: &mut dyn ExecutionContext,
250    ///         args: &HashMap<String, String>,
251    ///     ) -> Result<()> {
252    ///         // Execution logic here
253    ///         Ok(())
254    ///     }
255    ///     
256    ///     fn validate(&self, args: &HashMap<String, String>) -> Result<()> {
257    ///         // Custom validation: ensure min < max
258    ///         if let (Some(min), Some(max)) = (args.get("min"), args.get("max")) {
259    ///             let min_val: f64 = min.parse()
260    ///                 .map_err(|_| {
261    ///                     ExecutionError::CommandFailed(anyhow::anyhow!("Invalid min value"))
262    ///             })?;
263    ///             let max_val: f64 = max.parse()
264    ///                 .map_err(|_| {ExecutionError::CommandFailed(anyhow::anyhow!("Invalid max value"))})?;
265    ///             
266    ///             if min_val >= max_val {
267    ///                 return Err(ExecutionError::CommandFailed(anyhow::anyhow!("min must be less than max")).into());
268    ///             }
269    ///         }
270    ///         Ok(())
271    ///     }
272    /// }
273    /// ```
274    fn validate(&self, _args: &HashMap<String, String>) -> Result<()> {
275        Ok(())
276    }
277}
278
279/// Async counterpart of [`CommandHandler`].
280///
281/// Additive to `CommandHandler` (see DD-022) β€” it does not replace it.
282/// Implementations use this trait when their command body needs to perform
283/// async I/O (network calls, streaming, etc.). The signatures deliberately
284/// mirror `CommandHandler` exactly, `execute`/`validate` aside from the
285/// `async` keyword, so that migrating a handler from sync to async is a
286/// mechanical change.
287///
288/// # Object Safety
289///
290/// Made `dyn`-compatible via `#[async_trait]` (which desugars `async fn` to
291/// a boxed, pinned future under the hood). Stored as `Box<dyn
292/// AsyncCommandHandler>` in the registry, exactly like `CommandHandler` is
293/// stored as `Box<dyn CommandHandler>`.
294///
295/// # Thread Safety
296///
297/// Same constraint as `CommandHandler`: `Send + Sync` is required so the
298/// handler can be shared across the registry and, transitively, across
299/// threads if the application needs it.
300///
301/// # Why a separate trait instead of an async `CommandHandler`?
302///
303/// Existing sync `CommandHandler` implementations (including downstream
304/// consumers) must keep compiling unchanged. See DD-022 for the full
305/// rationale, including why `tokio` is not a dependency of `dynamic-cli`
306/// itself and why driving the returned future via
307/// `futures::executor::block_on` at the dispatch site is safe.
308///
309/// # Example
310///
311/// ```
312/// use std::collections::HashMap;
313/// use async_trait::async_trait;
314/// use dynamic_cli::executor::AsyncCommandHandler;
315/// use dynamic_cli::context::ExecutionContext;
316/// use dynamic_cli::error::ExecutionError;
317/// use dynamic_cli::Result;
318///
319/// struct FetchCommand;
320///
321/// #[async_trait]
322/// impl AsyncCommandHandler for FetchCommand {
323///     async fn execute(
324///         &self,
325///         _context: &mut dyn ExecutionContext,
326///         args: &HashMap<String, String>,
327///     ) -> Result<()> {
328///         let url = args.get("url").ok_or_else(|| {
329///             ExecutionError::CommandFailed(anyhow::anyhow!("Missing 'url' argument"))
330///         })?;
331///         // Real implementations would `.await` an async HTTP call here.
332///         println!("Fetching {url}...");
333///         Ok(())
334///     }
335///
336///     async fn validate(&self, args: &HashMap<String, String>) -> Result<()> {
337///         if !args.contains_key("url") {
338///             return Err(ExecutionError::CommandFailed(anyhow::anyhow!("url is required")).into());
339///         }
340///         Ok(())
341///     }
342/// }
343/// ```
344#[async_trait]
345pub trait AsyncCommandHandler: Send + Sync {
346    /// Async equivalent of [`CommandHandler::execute`]. Same contract:
347    /// receives the mutable execution context and the parsed arguments,
348    /// returns `Ok(())` on success or a `DynamicCliError` on failure.
349    async fn execute(
350        &self,
351        context: &mut dyn ExecutionContext,
352        args: &HashMap<String, String>,
353    ) -> Result<()>;
354
355    /// Async equivalent of [`CommandHandler::validate`]. Same contract and
356    /// same default (accepts all arguments) β€” override only for custom
357    /// validation logic.
358    async fn validate(&self, _args: &HashMap<String, String>) -> Result<()> {
359        Ok(())
360    }
361}
362
363#[cfg(test)]
364mod tests {
365    use super::*;
366    use crate::error::ExecutionError;
367    use std::any::Any;
368    use std::sync::{Arc, Mutex};
369
370    // ============================================================================
371    // TEST FIXTURES
372    // ============================================================================
373
374    /// Simple test context for unit tests
375    #[derive(Default)]
376    struct TestContext {
377        state: String,
378    }
379
380    impl ExecutionContext for TestContext {
381        fn as_any(&self) -> &dyn Any {
382            self
383        }
384
385        fn as_any_mut(&mut self) -> &mut dyn Any {
386            self
387        }
388    }
389
390    /// Simple command that prints to context
391    struct HelloCommand;
392
393    impl CommandHandler for HelloCommand {
394        fn execute(
395            &self,
396            context: &mut dyn ExecutionContext,
397            args: &HashMap<String, String>,
398        ) -> Result<()> {
399            let ctx = crate::context::downcast_mut::<TestContext>(context).ok_or_else(|| {
400                ExecutionError::CommandFailed(anyhow::anyhow!("Wrong context type"))
401            })?;
402
403            let name = args.get("name").map(|s| s.as_str()).unwrap_or("World");
404            ctx.state = format!("Hello, {}!", name);
405            Ok(())
406        }
407    }
408
409    /// Command with custom validation
410    struct ValidatedCommand;
411
412    impl CommandHandler for ValidatedCommand {
413        fn execute(
414            &self,
415            _context: &mut dyn ExecutionContext,
416            _args: &HashMap<String, String>,
417        ) -> Result<()> {
418            Ok(())
419        }
420
421        fn validate(&self, args: &HashMap<String, String>) -> Result<()> {
422            // Require "count" argument to be present and > 0
423            if let Some(count) = args.get("count") {
424                let count_val: i32 = count.parse().map_err(|_| {
425                    ExecutionError::CommandFailed(anyhow::anyhow!("count must be an integer"))
426                })?;
427
428                if count_val <= 0 {
429                    return Err(ExecutionError::CommandFailed(anyhow::anyhow!(
430                        "count must be positive"
431                    ))
432                    .into());
433                }
434            } else {
435                return Err(
436                    ExecutionError::CommandFailed(anyhow::anyhow!("count is required")).into(),
437                );
438            }
439            Ok(())
440        }
441    }
442
443    /// Command that fails during execution
444    struct FailingCommand;
445
446    impl CommandHandler for FailingCommand {
447        fn execute(
448            &self,
449            _context: &mut dyn ExecutionContext,
450            _args: &HashMap<String, String>,
451        ) -> Result<()> {
452            Err(ExecutionError::CommandFailed(anyhow::anyhow!("Simulated failure")).into())
453        }
454    }
455
456    /// Command that modifies context
457    struct StatefulCommand;
458
459    impl CommandHandler for StatefulCommand {
460        fn execute(
461            &self,
462            context: &mut dyn ExecutionContext,
463            args: &HashMap<String, String>,
464        ) -> Result<()> {
465            let ctx = crate::context::downcast_mut::<TestContext>(context).ok_or_else(|| {
466                ExecutionError::CommandFailed(anyhow::anyhow!("Wrong context type"))
467            })?;
468
469            let value = args.get("value").map(|s| s.as_str()).unwrap_or("default");
470            ctx.state.push_str(value);
471            Ok(())
472        }
473    }
474
475    // ============================================================================
476    // BASIC FUNCTIONALITY TESTS
477    // ============================================================================
478
479    #[test]
480    fn test_basic_execution() {
481        let handler = HelloCommand;
482        let mut context = TestContext::default();
483        let mut args = HashMap::new();
484        args.insert("name".to_string(), "Rust".to_string());
485
486        let result = handler.execute(&mut context, &args);
487
488        assert!(result.is_ok());
489        assert_eq!(context.state, "Hello, Rust!");
490    }
491
492    #[test]
493    fn test_execution_without_args() {
494        let handler = HelloCommand;
495        let mut context = TestContext::default();
496        let args = HashMap::new();
497
498        let result = handler.execute(&mut context, &args);
499
500        assert!(result.is_ok());
501        assert_eq!(context.state, "Hello, World!");
502    }
503
504    #[test]
505    fn test_execution_with_empty_name() {
506        let handler = HelloCommand;
507        let mut context = TestContext::default();
508        let mut args = HashMap::new();
509        args.insert("name".to_string(), "".to_string());
510
511        let result = handler.execute(&mut context, &args);
512
513        assert!(result.is_ok());
514        assert_eq!(context.state, "Hello, !");
515    }
516
517    // ============================================================================
518    // VALIDATION TESTS
519    // ============================================================================
520
521    #[test]
522    fn test_default_validation_accepts_all() {
523        let handler = HelloCommand;
524        let mut args = HashMap::new();
525        args.insert("random".to_string(), "value".to_string());
526
527        let result = handler.validate(&args);
528
529        assert!(result.is_ok());
530    }
531
532    #[test]
533    fn test_custom_validation_success() {
534        let handler = ValidatedCommand;
535        let mut args = HashMap::new();
536        args.insert("count".to_string(), "5".to_string());
537
538        let result = handler.validate(&args);
539
540        assert!(result.is_ok());
541    }
542
543    #[test]
544    fn test_custom_validation_missing_arg() {
545        let handler = ValidatedCommand;
546        let args = HashMap::new();
547
548        let result = handler.validate(&args);
549
550        assert!(result.is_err());
551        let err_msg = format!("{}", result.unwrap_err());
552        assert!(err_msg.contains("required"));
553    }
554
555    #[test]
556    fn test_custom_validation_invalid_value() {
557        let handler = ValidatedCommand;
558        let mut args = HashMap::new();
559        args.insert("count".to_string(), "0".to_string());
560
561        let result = handler.validate(&args);
562
563        assert!(result.is_err());
564        let err_msg = format!("{}", result.unwrap_err());
565        assert!(err_msg.contains("positive"));
566    }
567
568    #[test]
569    fn test_custom_validation_non_integer() {
570        let handler = ValidatedCommand;
571        let mut args = HashMap::new();
572        args.insert("count".to_string(), "abc".to_string());
573
574        let result = handler.validate(&args);
575
576        assert!(result.is_err());
577        let err_msg = format!("{}", result.unwrap_err());
578        assert!(err_msg.contains("integer"));
579    }
580
581    // ============================================================================
582    // ERROR HANDLING TESTS
583    // ============================================================================
584
585    #[test]
586    fn test_execution_failure() {
587        let handler = FailingCommand;
588        let mut context = TestContext::default();
589        let args = HashMap::new();
590
591        let result = handler.execute(&mut context, &args);
592
593        assert!(result.is_err());
594        let err_msg = format!("{}", result.unwrap_err());
595        assert!(err_msg.contains("Simulated failure"));
596    }
597
598    #[test]
599    fn test_context_downcast_failure() {
600        // Use a different context type to trigger downcast failure
601        #[derive(Default)]
602        struct WrongContext;
603
604        impl ExecutionContext for WrongContext {
605            fn as_any(&self) -> &dyn Any {
606                self
607            }
608
609            fn as_any_mut(&mut self) -> &mut dyn Any {
610                self
611            }
612        }
613
614        let handler = HelloCommand;
615        let mut wrong_context = WrongContext::default();
616        let args = HashMap::new();
617
618        let result = handler.execute(&mut wrong_context, &args);
619
620        assert!(result.is_err());
621        let err_msg = format!("{}", result.unwrap_err());
622        assert!(err_msg.contains("Wrong context type"));
623    }
624
625    // ============================================================================
626    // STATE MODIFICATION TESTS
627    // ============================================================================
628
629    #[test]
630    fn test_context_state_modification() {
631        let handler = StatefulCommand;
632        let mut context = TestContext::default();
633        context.state = "initial".to_string();
634        let mut args = HashMap::new();
635        args.insert("value".to_string(), "_modified".to_string());
636
637        let result = handler.execute(&mut context, &args);
638
639        assert!(result.is_ok());
640        assert_eq!(context.state, "initial_modified");
641    }
642
643    #[test]
644    fn test_multiple_executions_preserve_state() {
645        let handler = StatefulCommand;
646        let mut context = TestContext::default();
647
648        // First execution
649        let mut args1 = HashMap::new();
650        args1.insert("value".to_string(), "first".to_string());
651        handler.execute(&mut context, &args1).unwrap();
652        assert_eq!(context.state, "first");
653
654        // Second execution
655        let mut args2 = HashMap::new();
656        args2.insert("value".to_string(), "_second".to_string());
657        handler.execute(&mut context, &args2).unwrap();
658        assert_eq!(context.state, "first_second");
659    }
660
661    // ============================================================================
662    // TRAIT OBJECT TESTS
663    // ============================================================================
664
665    #[test]
666    fn test_trait_object_usage() {
667        // Verify that CommandHandler can be used as a trait object
668        let handler: Box<dyn CommandHandler> = Box::new(HelloCommand);
669        let mut context = TestContext::default();
670        let mut args = HashMap::new();
671        args.insert("name".to_string(), "TraitObject".to_string());
672
673        let result = handler.execute(&mut context, &args);
674
675        assert!(result.is_ok());
676        assert_eq!(context.state, "Hello, TraitObject!");
677    }
678
679    #[test]
680    fn test_multiple_trait_objects() {
681        // Store multiple handlers as trait objects
682        let handlers: Vec<Box<dyn CommandHandler>> =
683            vec![Box::new(HelloCommand), Box::new(StatefulCommand)];
684
685        let mut context = TestContext::default();
686
687        // Execute first handler
688        let mut args1 = HashMap::new();
689        args1.insert("name".to_string(), "First".to_string());
690        handlers[0].execute(&mut context, &args1).unwrap();
691        assert_eq!(context.state, "Hello, First!");
692
693        // Execute second handler
694        context.state.clear();
695        let mut args2 = HashMap::new();
696        args2.insert("value".to_string(), "Second".to_string());
697        handlers[1].execute(&mut context, &args2).unwrap();
698        assert_eq!(context.state, "Second");
699    }
700
701    // ============================================================================
702    // THREAD SAFETY TESTS
703    // ============================================================================
704
705    #[test]
706    fn test_send_sync_requirement() {
707        // This test verifies that CommandHandler is Send + Sync
708        // by using it in a multi-threaded context
709        let handler: Arc<dyn CommandHandler> = Arc::new(HelloCommand);
710
711        // Clone the Arc to simulate sharing across threads
712        let handler_clone = handler.clone();
713
714        // This compilation test ensures Send + Sync are satisfied
715        let _ = std::thread::spawn(move || {
716            let _h = handler_clone;
717        });
718    }
719
720    #[test]
721    fn test_concurrent_validation() {
722        // Test that validation can be called from multiple threads
723        let handler = Arc::new(ValidatedCommand);
724        let handler_clone = handler.clone();
725
726        let handle = std::thread::spawn(move || {
727            let mut args = HashMap::new();
728            args.insert("count".to_string(), "10".to_string());
729            handler_clone.validate(&args)
730        });
731
732        let mut args = HashMap::new();
733        args.insert("count".to_string(), "5".to_string());
734        let result1 = handler.validate(&args);
735
736        let result2 = handle.join().unwrap();
737
738        assert!(result1.is_ok());
739        assert!(result2.is_ok());
740    }
741
742    // ============================================================================
743    // EDGE CASES
744    // ============================================================================
745
746    #[test]
747    fn test_empty_args() {
748        let handler = StatefulCommand;
749        let mut context = TestContext::default();
750        let args = HashMap::new();
751
752        // Should use default value
753        let result = handler.execute(&mut context, &args);
754
755        assert!(result.is_ok());
756        assert_eq!(context.state, "default");
757    }
758
759    #[test]
760    fn test_args_with_special_characters() {
761        let handler = HelloCommand;
762        let mut context = TestContext::default();
763        let mut args = HashMap::new();
764        args.insert("name".to_string(), "Hello, δΈ–η•Œ! 🌍".to_string());
765
766        let result = handler.execute(&mut context, &args);
767
768        assert!(result.is_ok());
769        assert_eq!(context.state, "Hello, Hello, δΈ–η•Œ! 🌍!");
770    }
771
772    #[test]
773    fn test_very_long_argument() {
774        let handler = HelloCommand;
775        let mut context = TestContext::default();
776        let mut args = HashMap::new();
777        let long_name = "x".repeat(10000);
778        args.insert("name".to_string(), long_name.clone());
779
780        let result = handler.execute(&mut context, &args);
781
782        assert!(result.is_ok());
783        assert!(context.state.contains(&long_name));
784    }
785
786    // ============================================================================
787    // SHARED STATE TESTS
788    // ============================================================================
789
790    #[test]
791    fn test_shared_mutable_context() {
792        // Test that context can be safely modified by multiple commands
793        let handler1 = StatefulCommand;
794        let handler2 = StatefulCommand;
795        let mut context = TestContext::default();
796
797        let mut args1 = HashMap::new();
798        args1.insert("value".to_string(), "A".to_string());
799        handler1.execute(&mut context, &args1).unwrap();
800
801        let mut args2 = HashMap::new();
802        args2.insert("value".to_string(), "B".to_string());
803        handler2.execute(&mut context, &args2).unwrap();
804
805        assert_eq!(context.state, "AB");
806    }
807
808    // Test to ensure the trait is indeed object-safe at compile time
809    #[test]
810    fn test_object_safety_compile_time() {
811        // This function signature requires CommandHandler to be object-safe
812        fn _accepts_trait_object(_: &dyn CommandHandler) {}
813
814        // If this compiles, the trait is object-safe
815        let handler = HelloCommand;
816        _accepts_trait_object(&handler);
817    }
818
819    // Test that demonstrates why we can't have generic methods
820    // (This is a documentation test, not an actual test that runs)
821    /// ```compile_fail
822    /// use dynamic_cli::executor::CommandHandler;
823    ///
824    /// trait BrokenHandler: CommandHandler {
825    ///     fn generic_method<T>(&self, value: T);
826    /// }
827    ///
828    /// // This would fail because trait objects can't have generic methods
829    /// fn use_as_trait_object(handler: &dyn BrokenHandler) {
830    ///     // Cannot call generic_method on trait object
831    /// }
832    /// ```
833    #[allow(dead_code)]
834    fn test_no_generic_methods_documentation() {}
835
836    // ============================================================================
837    // AsyncCommandHandler TESTS (DD-022)
838    // ============================================================================
839
840    /// Async command that writes to the test context, mirroring `HelloCommand`.
841    struct AsyncHelloCommand;
842
843    #[async_trait]
844    impl AsyncCommandHandler for AsyncHelloCommand {
845        async fn execute(
846            &self,
847            context: &mut dyn ExecutionContext,
848            args: &HashMap<String, String>,
849        ) -> Result<()> {
850            let ctx = crate::context::downcast_mut::<TestContext>(context).ok_or_else(|| {
851                ExecutionError::CommandFailed(anyhow::anyhow!("Wrong context type"))
852            })?;
853            let name = args.get("name").map(|s| s.as_str()).unwrap_or("World");
854            ctx.state = format!("Hello, {}!", name);
855            Ok(())
856        }
857    }
858
859    /// Async command with custom validation, mirroring `ValidatedCommand`.
860    struct AsyncValidatedCommand;
861
862    #[async_trait]
863    impl AsyncCommandHandler for AsyncValidatedCommand {
864        async fn execute(
865            &self,
866            _context: &mut dyn ExecutionContext,
867            _args: &HashMap<String, String>,
868        ) -> Result<()> {
869            Ok(())
870        }
871
872        async fn validate(&self, args: &HashMap<String, String>) -> Result<()> {
873            if !args.contains_key("count") {
874                return Err(
875                    ExecutionError::CommandFailed(anyhow::anyhow!("count is required")).into(),
876                );
877            }
878            Ok(())
879        }
880    }
881
882    /// Async command that fails during execution, mirroring `FailingCommand`.
883    struct AsyncFailingCommand;
884
885    #[async_trait]
886    impl AsyncCommandHandler for AsyncFailingCommand {
887        async fn execute(
888            &self,
889            _context: &mut dyn ExecutionContext,
890            _args: &HashMap<String, String>,
891        ) -> Result<()> {
892            Err(ExecutionError::CommandFailed(anyhow::anyhow!("Simulated async failure")).into())
893        }
894    }
895
896    #[test]
897    fn test_async_basic_execution() {
898        let handler = AsyncHelloCommand;
899        let mut context = TestContext::default();
900        let mut args = HashMap::new();
901        args.insert("name".to_string(), "Rust".to_string());
902
903        let result = futures::executor::block_on(handler.execute(&mut context, &args));
904
905        assert!(result.is_ok());
906        assert_eq!(context.state, "Hello, Rust!");
907    }
908
909    #[test]
910    fn test_async_default_validation_accepts_all() {
911        let handler = AsyncHelloCommand;
912        let mut args = HashMap::new();
913        args.insert("random".to_string(), "value".to_string());
914
915        let result = futures::executor::block_on(handler.validate(&args));
916
917        assert!(result.is_ok());
918    }
919
920    #[test]
921    fn test_async_custom_validation_missing_arg() {
922        let handler = AsyncValidatedCommand;
923        let args = HashMap::new();
924
925        let result = futures::executor::block_on(handler.validate(&args));
926
927        assert!(result.is_err());
928        let err_msg = format!("{}", result.unwrap_err());
929        assert!(err_msg.contains("required"));
930    }
931
932    #[test]
933    fn test_async_custom_validation_success() {
934        let handler = AsyncValidatedCommand;
935        let mut args = HashMap::new();
936        args.insert("count".to_string(), "5".to_string());
937
938        let result = futures::executor::block_on(handler.validate(&args));
939
940        assert!(result.is_ok());
941    }
942
943    #[test]
944    fn test_async_execution_failure() {
945        let handler = AsyncFailingCommand;
946        let mut context = TestContext::default();
947        let args = HashMap::new();
948
949        let result = futures::executor::block_on(handler.execute(&mut context, &args));
950
951        assert!(result.is_err());
952        let err_msg = format!("{}", result.unwrap_err());
953        assert!(err_msg.contains("Simulated async failure"));
954    }
955
956    #[test]
957    fn test_async_trait_object_usage() {
958        // Verify that AsyncCommandHandler can be used as a trait object β€”
959        // the core object-safety guarantee DD-022 depends on.
960        let handler: Box<dyn AsyncCommandHandler> = Box::new(AsyncHelloCommand);
961        let mut context = TestContext::default();
962        let mut args = HashMap::new();
963        args.insert("name".to_string(), "TraitObject".to_string());
964
965        let result = futures::executor::block_on(handler.execute(&mut context, &args));
966
967        assert!(result.is_ok());
968        assert_eq!(context.state, "Hello, TraitObject!");
969    }
970
971    #[test]
972    fn test_async_send_sync_requirement() {
973        // Verifies AsyncCommandHandler is Send + Sync by sharing it across
974        // threads via Arc β€” same pattern as test_send_sync_requirement above.
975        let handler: Arc<dyn AsyncCommandHandler> = Arc::new(AsyncHelloCommand);
976        let handler_clone = handler.clone();
977
978        let _ = std::thread::spawn(move || {
979            let _h = handler_clone;
980        });
981    }
982
983    #[test]
984    fn test_async_object_safety_compile_time() {
985        // If this compiles, AsyncCommandHandler is dyn-compatible.
986        fn _accepts_trait_object(_: &dyn AsyncCommandHandler) {}
987        _accepts_trait_object(&AsyncHelloCommand);
988    }
989}