Skip to main content

standout_dispatch/
handler.rs

1//! Command handler types.
2//!
3//! This module provides the core types for building logic handlers - the
4//! business logic layer in the dispatch pipeline.
5//!
6//! # Design Rationale
7//!
8//! Logic handlers are responsible for business logic only. They:
9//!
10//! - Receive parsed CLI arguments (`&ArgMatches`) and execution context
11//! - Perform application logic (database queries, file operations, etc.)
12//! - Return serializable data that will be passed to the render handler
13//!
14//! Handlers explicitly do not handle:
15//! - Output formatting (that's the render handler's job)
16//! - Template selection (that's configured at the framework level)
17//! - Theme/style decisions (that's the render handler's job)
18//!
19//! This separation keeps handlers focused and testable - you can unit test
20//! a handler by checking the data it returns, without worrying about rendering.
21//!
22//! # State Management: App State vs Extensions
23//!
24//! [`CommandContext`] provides two mechanisms for state injection:
25//!
26//! | Field | Mutability | Lifetime | Purpose |
27//! |-------|------------|----------|---------|
28//! | `app_state` | Immutable (`&`) | App lifetime (shared via Arc) | Database, Config, API clients |
29//! | `extensions` | Mutable (`&mut`) | Request lifetime | Per-request state, user scope |
30//!
31//! **App State** is configured at app build time via `AppBuilder::app_state()` and shared
32//! immutably across all command invocations. Use for long-lived resources:
33//!
34//! ```rust,ignore
35//! // At app build time
36//! App::builder()
37//!     .app_state(Database::connect()?)
38//!     .app_state(Config::load()?)
39//!     .build()?
40//!
41//! // In handlers
42//! fn list_handler(matches: &ArgMatches, ctx: &CommandContext) -> HandlerResult<Vec<User>> {
43//!     let db = ctx.app_state.get_required::<Database>()?;
44//!     Ok(Output::Render(db.list_users()?))
45//! }
46//! ```
47//!
48//! **Extensions** are injected per-request by pre-dispatch hooks. Use for request-scoped data:
49//!
50//! ```rust,ignore
51//! Hooks::new().pre_dispatch(|matches, ctx| {
52//!     let user_id = matches.get_one::<String>("user").unwrap();
53//!     ctx.extensions.insert(UserScope { user_id: user_id.clone() });
54//!     Ok(())
55//! })
56//! ```
57//!
58//! # Core Types
59//!
60//! - [`CommandContext`]: Environment information passed to handlers
61//! - [`Extensions`]: Type-safe container for injecting custom state
62//! - [`Output`]: What a handler produces (render data, silent, or binary)
63//! - [`HandlerResult`]: The result type for handlers (`Result<Output<T>, Error>`)
64//! - [`RunResult`]: The result of running the CLI dispatcher
65//! - [`Handler`]: Trait for command handlers (`&mut self`)
66
67use crate::verify::ExpectedArg;
68use clap::ArgMatches;
69use serde::Serialize;
70use std::any::{Any, TypeId};
71use std::collections::HashMap;
72use std::fmt;
73use std::rc::Rc;
74
75/// Type-safe container for injecting custom state into handlers.
76///
77/// Extensions allow pre-dispatch hooks to inject state that handlers can retrieve.
78/// This enables dependency injection without modifying handler signatures.
79///
80/// # Warning: Clone Behavior
81///
82/// `Extensions` is **not** cloned when the container is cloned. Cloning an `Extensions` instance
83/// results in a new, empty map. This is because the underlying `Box<dyn Any>` values cannot
84/// be cloned generically.
85///
86/// If you need to share state across threads/clones, use `Arc<T>` inside the extension.
87///
88/// # Example
89///
90/// ```rust
91/// use standout_dispatch::{Extensions, CommandContext};
92///
93/// // Define your state types
94/// struct ApiClient { base_url: String }
95/// struct UserScope { user_id: u64 }
96///
97/// // In a pre-dispatch hook, inject state
98/// let mut ctx = CommandContext::default();
99/// ctx.extensions.insert(ApiClient { base_url: "https://api.example.com".into() });
100/// ctx.extensions.insert(UserScope { user_id: 42 });
101///
102/// // In a handler, retrieve state
103/// let api = ctx.extensions.get_required::<ApiClient>()?;
104/// println!("API base: {}", api.base_url);
105/// # Ok::<(), anyhow::Error>(())
106/// ```
107#[derive(Default)]
108pub struct Extensions {
109    map: HashMap<TypeId, Box<dyn Any>>,
110}
111
112impl Extensions {
113    /// Creates a new empty extensions container.
114    pub fn new() -> Self {
115        Self::default()
116    }
117
118    /// Inserts a value into the extensions.
119    ///
120    /// If a value of this type already exists, it is replaced and returned.
121    pub fn insert<T: 'static>(&mut self, val: T) -> Option<T> {
122        self.map
123            .insert(TypeId::of::<T>(), Box::new(val))
124            .and_then(|boxed| boxed.downcast().ok().map(|b| *b))
125    }
126
127    /// Gets a reference to a value of the specified type.
128    ///
129    /// Returns `None` if no value of this type exists.
130    pub fn get<T: 'static>(&self) -> Option<&T> {
131        self.map
132            .get(&TypeId::of::<T>())
133            .and_then(|boxed| boxed.downcast_ref())
134    }
135
136    /// Gets a mutable reference to a value of the specified type.
137    ///
138    /// Returns `None` if no value of this type exists.
139    pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> {
140        self.map
141            .get_mut(&TypeId::of::<T>())
142            .and_then(|boxed| boxed.downcast_mut())
143    }
144
145    /// Gets a required reference to a value of the specified type.
146    ///
147    /// Returns an error if no value of this type exists.
148    pub fn get_required<T: 'static>(&self) -> Result<&T, anyhow::Error> {
149        self.get::<T>().ok_or_else(|| {
150            anyhow::anyhow!(
151                "Extension missing: type {} not found in context",
152                std::any::type_name::<T>()
153            )
154        })
155    }
156
157    /// Gets a required mutable reference to a value of the specified type.
158    ///
159    /// Returns an error if no value of this type exists.
160    pub fn get_mut_required<T: 'static>(&mut self) -> Result<&mut T, anyhow::Error> {
161        self.get_mut::<T>().ok_or_else(|| {
162            anyhow::anyhow!(
163                "Extension missing: type {} not found in context",
164                std::any::type_name::<T>()
165            )
166        })
167    }
168
169    /// Removes a value of the specified type, returning it if it existed.
170    pub fn remove<T: 'static>(&mut self) -> Option<T> {
171        self.map
172            .remove(&TypeId::of::<T>())
173            .and_then(|boxed| boxed.downcast().ok().map(|b| *b))
174    }
175
176    /// Returns `true` if the extensions contain a value of the specified type.
177    pub fn contains<T: 'static>(&self) -> bool {
178        self.map.contains_key(&TypeId::of::<T>())
179    }
180
181    /// Returns the number of extensions stored.
182    pub fn len(&self) -> usize {
183        self.map.len()
184    }
185
186    /// Returns `true` if no extensions are stored.
187    pub fn is_empty(&self) -> bool {
188        self.map.is_empty()
189    }
190
191    /// Removes all extensions.
192    pub fn clear(&mut self) {
193        self.map.clear();
194    }
195}
196
197impl fmt::Debug for Extensions {
198    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199        f.debug_struct("Extensions")
200            .field("len", &self.map.len())
201            .finish_non_exhaustive()
202    }
203}
204
205impl Clone for Extensions {
206    fn clone(&self) -> Self {
207        // Extensions cannot be cloned because Box<dyn Any> isn't Clone.
208        // Return empty extensions on clone - this is a limitation but
209        // matches the behavior of http::Extensions.
210        Self::new()
211    }
212}
213
214/// Context passed to command handlers.
215///
216/// Provides information about the execution environment plus two mechanisms
217/// for state injection:
218///
219/// - **`app_state`**: Immutable, app-lifetime state (Database, Config, API clients)
220/// - **`extensions`**: Mutable, per-request state (UserScope, RequestId)
221///
222/// Note that output format is deliberately not included here - format decisions
223/// are made by the render handler, not by logic handlers.
224///
225/// # App State (Immutable, Shared)
226///
227/// App state is configured at build time and shared across all dispatches:
228///
229/// ```rust,ignore
230/// use standout::cli::App;
231///
232/// struct Database { /* ... */ }
233/// struct Config { api_url: String }
234///
235/// App::builder()
236///     .app_state(Database::connect()?)
237///     .app_state(Config { api_url: "https://api.example.com".into() })
238///     .command("list", list_handler, "{{ items }}")
239///     .build()?
240/// ```
241///
242/// Handlers retrieve app state immutably:
243///
244/// ```rust,ignore
245/// fn list_handler(matches: &ArgMatches, ctx: &CommandContext) -> HandlerResult<Vec<Item>> {
246///     let db = ctx.app_state.get_required::<Database>()?;
247///     let config = ctx.app_state.get_required::<Config>()?;
248///     Ok(Output::Render(db.list_items(&config.api_url)?))
249/// }
250/// ```
251///
252/// ## Shared Mutable State
253///
254/// Since `app_state` is shared via `Arc`, it is immutable by default. To share mutable state
255/// (like counters or caches), use interior mutability primitives like `RwLock`, `Mutex`, or atomic types:
256///
257/// ```rust,ignore
258/// use std::sync::atomic::AtomicUsize;
259///
260/// struct Metrics { request_count: AtomicUsize }
261///
262/// // Builder
263/// App::builder().app_state(Metrics { request_count: AtomicUsize::new(0) });
264///
265/// // Handler
266/// let metrics = ctx.app_state.get_required::<Metrics>()?;
267/// metrics.request_count.fetch_add(1, Ordering::Relaxed);
268/// ```
269///
270/// # Extensions (Mutable, Per-Request)
271///
272/// Pre-dispatch hooks inject per-request state into `extensions`:
273///
274/// ```rust
275/// use standout_dispatch::{Hooks, HookError, CommandContext};
276///
277/// struct UserScope { user_id: String }
278///
279/// let hooks = Hooks::new()
280///     .pre_dispatch(|matches, ctx| {
281///         let user_id = matches.get_one::<String>("user").unwrap();
282///         ctx.extensions.insert(UserScope { user_id: user_id.clone() });
283///         Ok(())
284///     });
285///
286/// // In handler:
287/// fn my_handler(matches: &clap::ArgMatches, ctx: &CommandContext) -> anyhow::Result<()> {
288///     let scope = ctx.extensions.get_required::<UserScope>()?;
289///     // use scope.user_id...
290///     Ok(())
291/// }
292/// ```
293#[derive(Debug)]
294pub struct CommandContext {
295    /// The command path being executed (e.g., ["config", "get"])
296    pub command_path: Vec<String>,
297
298    /// Immutable app-level state shared across all dispatches.
299    ///
300    /// Configured via `AppBuilder::app_state()`. Contains long-lived resources
301    /// like database connections, configuration, and API clients.
302    ///
303    /// Use `get::<T>()` or `get_required::<T>()` to retrieve values.
304    pub app_state: Rc<Extensions>,
305
306    /// Mutable per-request state container.
307    ///
308    /// Pre-dispatch hooks can insert values that handlers retrieve.
309    /// Each dispatch gets a fresh Extensions instance.
310    pub extensions: Extensions,
311}
312
313impl CommandContext {
314    /// Creates a new CommandContext with the given path and shared app state.
315    ///
316    /// This is more efficient than `Default::default()` when you already have app_state.
317    pub fn new(command_path: Vec<String>, app_state: Rc<Extensions>) -> Self {
318        Self {
319            command_path,
320            app_state,
321            extensions: Extensions::new(),
322        }
323    }
324}
325
326impl Default for CommandContext {
327    fn default() -> Self {
328        Self {
329            command_path: Vec::new(),
330            app_state: Rc::new(Extensions::new()),
331            extensions: Extensions::new(),
332        }
333    }
334}
335
336/// What a handler produces.
337///
338/// This enum represents the different types of output a command handler can produce.
339#[derive(Debug)]
340pub enum Output<T: Serialize> {
341    /// Data to render with a template or serialize to JSON/YAML/etc.
342    Render(T),
343    /// Silent exit (no output produced)
344    Silent,
345    /// Binary output for file exports
346    Binary {
347        /// The binary data
348        data: Vec<u8>,
349        /// Suggested filename for the output
350        filename: String,
351    },
352}
353
354impl<T: Serialize> Output<T> {
355    /// Returns true if this is a render result.
356    pub fn is_render(&self) -> bool {
357        matches!(self, Output::Render(_))
358    }
359
360    /// Returns true if this is a silent result.
361    pub fn is_silent(&self) -> bool {
362        matches!(self, Output::Silent)
363    }
364
365    /// Returns true if this is a binary result.
366    pub fn is_binary(&self) -> bool {
367        matches!(self, Output::Binary { .. })
368    }
369}
370
371/// The result type for command handlers.
372///
373/// Enables use of the `?` operator for error propagation.
374pub type HandlerResult<T> = Result<Output<T>, anyhow::Error>;
375
376/// Trait for types that can be converted into a [`HandlerResult`].
377///
378/// This enables handlers to return either `Result<T, E>` directly (auto-wrapped
379/// in [`Output::Render`]) or the explicit [`HandlerResult<T>`] when fine-grained
380/// control is needed (for [`Output::Silent`] or [`Output::Binary`]).
381///
382/// # Example
383///
384/// ```rust
385/// use standout_dispatch::{HandlerResult, Output, IntoHandlerResult};
386///
387/// // Direct Result<T, E> is auto-wrapped in Output::Render
388/// fn simple() -> Result<String, anyhow::Error> {
389///     Ok("hello".to_string())
390/// }
391/// let result: HandlerResult<String> = simple().into_handler_result();
392/// assert!(matches!(result, Ok(Output::Render(_))));
393///
394/// // HandlerResult<T> passes through unchanged
395/// fn explicit() -> HandlerResult<String> {
396///     Ok(Output::Silent)
397/// }
398/// let result: HandlerResult<String> = explicit().into_handler_result();
399/// assert!(matches!(result, Ok(Output::Silent)));
400/// ```
401pub trait IntoHandlerResult<T: Serialize> {
402    /// Convert this type into a [`HandlerResult<T>`].
403    fn into_handler_result(self) -> HandlerResult<T>;
404}
405
406/// Implementation for `Result<T, E>` - auto-wraps successful values in [`Output::Render`].
407///
408/// This is the ergonomic path: handlers can return `Result<T, E>` directly
409/// and the framework wraps it appropriately.
410impl<T, E> IntoHandlerResult<T> for Result<T, E>
411where
412    T: Serialize,
413    E: Into<anyhow::Error>,
414{
415    fn into_handler_result(self) -> HandlerResult<T> {
416        self.map(Output::Render).map_err(Into::into)
417    }
418}
419
420/// Implementation for `HandlerResult<T>` - passes through unchanged.
421///
422/// This is the explicit path: handlers that need [`Output::Silent`] or
423/// [`Output::Binary`] can return `HandlerResult<T>` directly.
424impl<T: Serialize> IntoHandlerResult<T> for HandlerResult<T> {
425    fn into_handler_result(self) -> HandlerResult<T> {
426        self
427    }
428}
429
430/// Result of running the CLI dispatcher.
431///
432/// After processing arguments, the dispatcher either handles a command,
433/// surfaces an error, or falls through for manual handling.
434///
435/// Marked `#[non_exhaustive]` so future variants can be added without
436/// breaking exhaustive matchers.
437#[derive(Debug)]
438#[non_exhaustive]
439pub enum RunResult {
440    /// A handler processed the command successfully; contains the rendered output
441    Handled(String),
442    /// A handler produced binary output (bytes, suggested filename)
443    Binary(Vec<u8>, String),
444    /// Silent output (handler completed but produced no output)
445    Silent,
446    /// A handler, hook, or output step failed; contains the formatted error message.
447    /// Consumers should write this to stderr and exit non-zero.
448    Error(String),
449    /// No handler matched; contains the ArgMatches for manual handling
450    NoMatch(ArgMatches),
451}
452
453impl RunResult {
454    /// Returns true if a handler processed the command successfully (text output).
455    pub fn is_handled(&self) -> bool {
456        matches!(self, RunResult::Handled(_))
457    }
458
459    /// Returns true if the result is binary output.
460    pub fn is_binary(&self) -> bool {
461        matches!(self, RunResult::Binary(_, _))
462    }
463
464    /// Returns true if the result is silent.
465    pub fn is_silent(&self) -> bool {
466        matches!(self, RunResult::Silent)
467    }
468
469    /// Returns true if the result is an error.
470    pub fn is_error(&self) -> bool {
471        matches!(self, RunResult::Error(_))
472    }
473
474    /// Returns the output if handled, or None otherwise.
475    pub fn output(&self) -> Option<&str> {
476        match self {
477            RunResult::Handled(s) => Some(s),
478            _ => None,
479        }
480    }
481
482    /// Returns the error message if this is an error, or None otherwise.
483    pub fn error(&self) -> Option<&str> {
484        match self {
485            RunResult::Error(s) => Some(s),
486            _ => None,
487        }
488    }
489
490    /// Returns the binary data and filename if binary, or None otherwise.
491    pub fn binary(&self) -> Option<(&[u8], &str)> {
492        match self {
493            RunResult::Binary(bytes, filename) => Some((bytes, filename)),
494            _ => None,
495        }
496    }
497
498    /// Returns the matches if unhandled, or None if handled.
499    pub fn matches(&self) -> Option<&ArgMatches> {
500        match self {
501            RunResult::NoMatch(m) => Some(m),
502            _ => None,
503        }
504    }
505}
506
507/// Trait for command handlers.
508///
509/// Handlers take `&mut self` allowing direct mutation of internal state.
510/// This is the common case for CLI applications which are single-threaded.
511///
512/// # Example
513///
514/// ```rust
515/// use standout_dispatch::{Handler, HandlerResult, Output, CommandContext};
516/// use clap::ArgMatches;
517/// use serde::Serialize;
518///
519/// struct Counter { count: u32 }
520///
521/// impl Handler for Counter {
522///     type Output = u32;
523///
524///     fn handle(&mut self, _m: &ArgMatches, _ctx: &CommandContext) -> HandlerResult<u32> {
525///         self.count += 1;
526///         Ok(Output::Render(self.count))
527///     }
528/// }
529/// ```
530pub trait Handler {
531    /// The output type produced by this handler (must be serializable)
532    type Output: Serialize;
533
534    /// Execute the handler with the given matches and context.
535    fn handle(&mut self, matches: &ArgMatches, ctx: &CommandContext)
536        -> HandlerResult<Self::Output>;
537
538    /// Returns the arguments expected by this handler for verification.
539    ///
540    /// This is used to verify that the CLI command definition matches the handler's expectations.
541    /// Handlers generated by the `#[handler]` macro implement this automatically.
542    fn expected_args(&self) -> Vec<ExpectedArg> {
543        Vec::new()
544    }
545}
546
547/// A wrapper that implements Handler for FnMut closures.
548///
549/// The closure can return either:
550/// - `Result<T, E>` - automatically wrapped in [`Output::Render`]
551/// - `HandlerResult<T>` - passed through unchanged (for [`Output::Silent`] or [`Output::Binary`])
552///
553/// # Example
554///
555/// ```rust
556/// use standout_dispatch::{FnHandler, Handler, CommandContext, Output};
557/// use clap::ArgMatches;
558///
559/// // Returning Result<T, E> directly (auto-wrapped)
560/// let mut handler = FnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| {
561///     Ok::<_, anyhow::Error>("hello".to_string())
562/// });
563///
564/// // Returning HandlerResult<T> explicitly (for Silent/Binary)
565/// let mut silent_handler = FnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| {
566///     Ok(Output::<()>::Silent)
567/// });
568/// ```
569pub struct FnHandler<F, T, R = HandlerResult<T>>
570where
571    T: Serialize,
572{
573    f: F,
574    _phantom: std::marker::PhantomData<fn() -> (T, R)>,
575}
576
577impl<F, T, R> FnHandler<F, T, R>
578where
579    F: FnMut(&ArgMatches, &CommandContext) -> R,
580    R: IntoHandlerResult<T>,
581    T: Serialize,
582{
583    /// Creates a new FnHandler wrapping the given FnMut closure.
584    pub fn new(f: F) -> Self {
585        Self {
586            f,
587            _phantom: std::marker::PhantomData,
588        }
589    }
590}
591
592impl<F, T, R> Handler for FnHandler<F, T, R>
593where
594    F: FnMut(&ArgMatches, &CommandContext) -> R,
595    R: IntoHandlerResult<T>,
596    T: Serialize,
597{
598    type Output = T;
599
600    fn handle(&mut self, matches: &ArgMatches, ctx: &CommandContext) -> HandlerResult<T> {
601        (self.f)(matches, ctx).into_handler_result()
602    }
603}
604
605/// A handler wrapper for functions that don't need [`CommandContext`].
606///
607/// This is the simpler variant of [`FnHandler`] for handlers that only need
608/// [`ArgMatches`]. The context parameter is accepted but ignored internally.
609///
610/// The closure can return either:
611/// - `Result<T, E>` - automatically wrapped in [`Output::Render`]
612/// - `HandlerResult<T>` - passed through unchanged (for [`Output::Silent`] or [`Output::Binary`])
613///
614/// # Example
615///
616/// ```rust
617/// use standout_dispatch::{SimpleFnHandler, Handler, CommandContext, Output};
618/// use clap::ArgMatches;
619///
620/// // Handler that doesn't need context - just uses ArgMatches
621/// let mut handler = SimpleFnHandler::new(|_m: &ArgMatches| {
622///     Ok::<_, anyhow::Error>("Hello, world!".to_string())
623/// });
624///
625/// // Can still be used via Handler trait (context is ignored)
626/// let ctx = CommandContext::default();
627/// let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
628/// let result = handler.handle(&matches, &ctx);
629/// assert!(matches!(result, Ok(Output::Render(_))));
630/// ```
631pub struct SimpleFnHandler<F, T, R = HandlerResult<T>>
632where
633    T: Serialize,
634{
635    f: F,
636    _phantom: std::marker::PhantomData<fn() -> (T, R)>,
637}
638
639impl<F, T, R> SimpleFnHandler<F, T, R>
640where
641    F: FnMut(&ArgMatches) -> R,
642    R: IntoHandlerResult<T>,
643    T: Serialize,
644{
645    /// Creates a new SimpleFnHandler wrapping the given FnMut closure.
646    pub fn new(f: F) -> Self {
647        Self {
648            f,
649            _phantom: std::marker::PhantomData,
650        }
651    }
652}
653
654impl<F, T, R> Handler for SimpleFnHandler<F, T, R>
655where
656    F: FnMut(&ArgMatches) -> R,
657    R: IntoHandlerResult<T>,
658    T: Serialize,
659{
660    type Output = T;
661
662    fn handle(&mut self, matches: &ArgMatches, _ctx: &CommandContext) -> HandlerResult<T> {
663        (self.f)(matches).into_handler_result()
664    }
665}
666
667#[cfg(test)]
668mod tests {
669    use super::*;
670    use serde_json::json;
671
672    #[test]
673    fn test_command_context_creation() {
674        let ctx = CommandContext {
675            command_path: vec!["config".into(), "get".into()],
676            app_state: Rc::new(Extensions::new()),
677            extensions: Extensions::new(),
678        };
679        assert_eq!(ctx.command_path, vec!["config", "get"]);
680    }
681
682    #[test]
683    fn test_command_context_default() {
684        let ctx = CommandContext::default();
685        assert!(ctx.command_path.is_empty());
686        assert!(ctx.extensions.is_empty());
687        assert!(ctx.app_state.is_empty());
688    }
689
690    #[test]
691    fn test_command_context_with_app_state() {
692        struct Database {
693            url: String,
694        }
695        struct Config {
696            debug: bool,
697        }
698
699        // Build app state
700        let mut app_state = Extensions::new();
701        app_state.insert(Database {
702            url: "postgres://localhost".into(),
703        });
704        app_state.insert(Config { debug: true });
705        let app_state = Rc::new(app_state);
706
707        // Create context with app state
708        let ctx = CommandContext {
709            command_path: vec!["list".into()],
710            app_state: app_state.clone(),
711            extensions: Extensions::new(),
712        };
713
714        // Retrieve app state
715        let db = ctx.app_state.get::<Database>().unwrap();
716        assert_eq!(db.url, "postgres://localhost");
717
718        let config = ctx.app_state.get::<Config>().unwrap();
719        assert!(config.debug);
720
721        // App state is shared via Rc
722        assert_eq!(Rc::strong_count(&ctx.app_state), 2);
723    }
724
725    #[test]
726    fn test_command_context_app_state_get_required() {
727        struct Present;
728
729        let mut app_state = Extensions::new();
730        app_state.insert(Present);
731
732        let ctx = CommandContext {
733            command_path: vec![],
734            app_state: Rc::new(app_state),
735            extensions: Extensions::new(),
736        };
737
738        // Success case
739        assert!(ctx.app_state.get_required::<Present>().is_ok());
740
741        // Failure case
742        #[derive(Debug)]
743        struct Missing;
744        let err = ctx.app_state.get_required::<Missing>();
745        assert!(err.is_err());
746        assert!(err.unwrap_err().to_string().contains("Extension missing"));
747    }
748
749    // Extensions tests
750    #[test]
751    fn test_extensions_insert_and_get() {
752        struct MyState {
753            value: i32,
754        }
755
756        let mut ext = Extensions::new();
757        assert!(ext.is_empty());
758
759        ext.insert(MyState { value: 42 });
760        assert!(!ext.is_empty());
761        assert_eq!(ext.len(), 1);
762
763        let state = ext.get::<MyState>().unwrap();
764        assert_eq!(state.value, 42);
765    }
766
767    #[test]
768    fn test_extensions_get_mut() {
769        struct Counter {
770            count: i32,
771        }
772
773        let mut ext = Extensions::new();
774        ext.insert(Counter { count: 0 });
775
776        if let Some(counter) = ext.get_mut::<Counter>() {
777            counter.count += 1;
778        }
779
780        assert_eq!(ext.get::<Counter>().unwrap().count, 1);
781    }
782
783    #[test]
784    fn test_extensions_multiple_types() {
785        struct TypeA(i32);
786        struct TypeB(String);
787
788        let mut ext = Extensions::new();
789        ext.insert(TypeA(1));
790        ext.insert(TypeB("hello".into()));
791
792        assert_eq!(ext.len(), 2);
793        assert_eq!(ext.get::<TypeA>().unwrap().0, 1);
794        assert_eq!(ext.get::<TypeB>().unwrap().0, "hello");
795    }
796
797    #[test]
798    fn test_extensions_replace() {
799        struct Value(i32);
800
801        let mut ext = Extensions::new();
802        ext.insert(Value(1));
803
804        let old = ext.insert(Value(2));
805        assert_eq!(old.unwrap().0, 1);
806        assert_eq!(ext.get::<Value>().unwrap().0, 2);
807    }
808
809    #[test]
810    fn test_extensions_remove() {
811        struct Value(i32);
812
813        let mut ext = Extensions::new();
814        ext.insert(Value(42));
815
816        let removed = ext.remove::<Value>();
817        assert_eq!(removed.unwrap().0, 42);
818        assert!(ext.is_empty());
819        assert!(ext.get::<Value>().is_none());
820    }
821
822    #[test]
823    fn test_extensions_contains() {
824        struct Present;
825        struct Absent;
826
827        let mut ext = Extensions::new();
828        ext.insert(Present);
829
830        assert!(ext.contains::<Present>());
831        assert!(!ext.contains::<Absent>());
832    }
833
834    #[test]
835    fn test_extensions_clear() {
836        struct A;
837        struct B;
838
839        let mut ext = Extensions::new();
840        ext.insert(A);
841        ext.insert(B);
842        assert_eq!(ext.len(), 2);
843
844        ext.clear();
845        assert!(ext.is_empty());
846    }
847
848    #[test]
849    fn test_extensions_missing_type_returns_none() {
850        struct NotInserted;
851
852        let ext = Extensions::new();
853        assert!(ext.get::<NotInserted>().is_none());
854    }
855
856    #[test]
857    fn test_extensions_get_required() {
858        #[derive(Debug)]
859        struct Config {
860            value: i32,
861        }
862
863        let mut ext = Extensions::new();
864        ext.insert(Config { value: 100 });
865
866        // Success case
867        let val = ext.get_required::<Config>();
868        assert!(val.is_ok());
869        assert_eq!(val.unwrap().value, 100);
870
871        // Failure case
872        #[derive(Debug)]
873        struct Missing;
874        let err = ext.get_required::<Missing>();
875        assert!(err.is_err());
876        assert!(err
877            .unwrap_err()
878            .to_string()
879            .contains("Extension missing: type"));
880    }
881
882    #[test]
883    fn test_extensions_get_mut_required() {
884        #[derive(Debug)]
885        struct State {
886            count: i32,
887        }
888
889        let mut ext = Extensions::new();
890        ext.insert(State { count: 0 });
891
892        // Success case
893        {
894            let val = ext.get_mut_required::<State>();
895            assert!(val.is_ok());
896            val.unwrap().count += 1;
897        }
898        assert_eq!(ext.get_required::<State>().unwrap().count, 1);
899
900        // Failure case
901        #[derive(Debug)]
902        struct Missing;
903        let err = ext.get_mut_required::<Missing>();
904        assert!(err.is_err());
905    }
906
907    #[test]
908    fn test_extensions_clone_behavior() {
909        // Verify the documented behavior that Clone drops extensions
910        struct Data(i32);
911
912        let mut original = Extensions::new();
913        original.insert(Data(42));
914
915        let cloned = original.clone();
916
917        // Original has data
918        assert!(original.get::<Data>().is_some());
919
920        // Cloned is empty
921        assert!(cloned.is_empty());
922        assert!(cloned.get::<Data>().is_none());
923    }
924
925    #[test]
926    fn test_output_render() {
927        let output: Output<String> = Output::Render("success".into());
928        assert!(output.is_render());
929        assert!(!output.is_silent());
930        assert!(!output.is_binary());
931    }
932
933    #[test]
934    fn test_output_silent() {
935        let output: Output<String> = Output::Silent;
936        assert!(!output.is_render());
937        assert!(output.is_silent());
938        assert!(!output.is_binary());
939    }
940
941    #[test]
942    fn test_output_binary() {
943        let output: Output<String> = Output::Binary {
944            data: vec![0x25, 0x50, 0x44, 0x46],
945            filename: "report.pdf".into(),
946        };
947        assert!(!output.is_render());
948        assert!(!output.is_silent());
949        assert!(output.is_binary());
950    }
951
952    #[test]
953    fn test_run_result_handled() {
954        let result = RunResult::Handled("output".into());
955        assert!(result.is_handled());
956        assert!(!result.is_binary());
957        assert!(!result.is_silent());
958        assert_eq!(result.output(), Some("output"));
959        assert!(result.matches().is_none());
960    }
961
962    #[test]
963    fn test_run_result_silent() {
964        let result = RunResult::Silent;
965        assert!(!result.is_handled());
966        assert!(!result.is_binary());
967        assert!(result.is_silent());
968    }
969
970    #[test]
971    fn test_run_result_binary() {
972        let bytes = vec![0x25, 0x50, 0x44, 0x46];
973        let result = RunResult::Binary(bytes.clone(), "report.pdf".into());
974        assert!(!result.is_handled());
975        assert!(result.is_binary());
976        assert!(!result.is_silent());
977
978        let (data, filename) = result.binary().unwrap();
979        assert_eq!(data, &bytes);
980        assert_eq!(filename, "report.pdf");
981    }
982
983    #[test]
984    fn test_run_result_no_match() {
985        let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
986        let result = RunResult::NoMatch(matches);
987        assert!(!result.is_handled());
988        assert!(!result.is_binary());
989        assert!(result.matches().is_some());
990    }
991
992    #[test]
993    fn test_fn_handler() {
994        let mut handler = FnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| {
995            Ok(Output::Render(json!({"status": "ok"})))
996        });
997
998        let ctx = CommandContext::default();
999        let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1000
1001        let result = handler.handle(&matches, &ctx);
1002        assert!(result.is_ok());
1003    }
1004
1005    #[test]
1006    fn test_fn_handler_mutation() {
1007        let mut counter = 0u32;
1008
1009        let mut handler = FnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| {
1010            counter += 1;
1011            Ok(Output::Render(counter))
1012        });
1013
1014        let ctx = CommandContext::default();
1015        let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1016
1017        let _ = handler.handle(&matches, &ctx);
1018        let _ = handler.handle(&matches, &ctx);
1019        let result = handler.handle(&matches, &ctx);
1020
1021        assert!(result.is_ok());
1022        if let Ok(Output::Render(count)) = result {
1023            assert_eq!(count, 3);
1024        }
1025    }
1026
1027    // IntoHandlerResult tests
1028    #[test]
1029    fn test_into_handler_result_from_result_ok() {
1030        use super::IntoHandlerResult;
1031
1032        let result: Result<String, anyhow::Error> = Ok("hello".to_string());
1033        let handler_result = result.into_handler_result();
1034
1035        assert!(handler_result.is_ok());
1036        match handler_result.unwrap() {
1037            Output::Render(s) => assert_eq!(s, "hello"),
1038            _ => panic!("Expected Output::Render"),
1039        }
1040    }
1041
1042    #[test]
1043    fn test_into_handler_result_from_result_err() {
1044        use super::IntoHandlerResult;
1045
1046        let result: Result<String, anyhow::Error> = Err(anyhow::anyhow!("test error"));
1047        let handler_result = result.into_handler_result();
1048
1049        assert!(handler_result.is_err());
1050        assert!(handler_result
1051            .unwrap_err()
1052            .to_string()
1053            .contains("test error"));
1054    }
1055
1056    #[test]
1057    fn test_into_handler_result_passthrough_render() {
1058        use super::IntoHandlerResult;
1059
1060        let handler_result: HandlerResult<String> = Ok(Output::Render("hello".to_string()));
1061        let result = handler_result.into_handler_result();
1062
1063        assert!(result.is_ok());
1064        match result.unwrap() {
1065            Output::Render(s) => assert_eq!(s, "hello"),
1066            _ => panic!("Expected Output::Render"),
1067        }
1068    }
1069
1070    #[test]
1071    fn test_into_handler_result_passthrough_silent() {
1072        use super::IntoHandlerResult;
1073
1074        let handler_result: HandlerResult<String> = Ok(Output::Silent);
1075        let result = handler_result.into_handler_result();
1076
1077        assert!(result.is_ok());
1078        assert!(matches!(result.unwrap(), Output::Silent));
1079    }
1080
1081    #[test]
1082    fn test_into_handler_result_passthrough_binary() {
1083        use super::IntoHandlerResult;
1084
1085        let handler_result: HandlerResult<String> = Ok(Output::Binary {
1086            data: vec![1, 2, 3],
1087            filename: "test.bin".to_string(),
1088        });
1089        let result = handler_result.into_handler_result();
1090
1091        assert!(result.is_ok());
1092        match result.unwrap() {
1093            Output::Binary { data, filename } => {
1094                assert_eq!(data, vec![1, 2, 3]);
1095                assert_eq!(filename, "test.bin");
1096            }
1097            _ => panic!("Expected Output::Binary"),
1098        }
1099    }
1100
1101    #[test]
1102    fn test_fn_handler_with_auto_wrap() {
1103        // Handler that returns Result<T, E> directly (not HandlerResult)
1104        let mut handler = FnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| {
1105            Ok::<_, anyhow::Error>("auto-wrapped".to_string())
1106        });
1107
1108        let ctx = CommandContext::default();
1109        let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1110
1111        let result = handler.handle(&matches, &ctx);
1112        assert!(result.is_ok());
1113        match result.unwrap() {
1114            Output::Render(s) => assert_eq!(s, "auto-wrapped"),
1115            _ => panic!("Expected Output::Render"),
1116        }
1117    }
1118
1119    #[test]
1120    fn test_fn_handler_with_explicit_output() {
1121        // Handler that returns HandlerResult directly (for Silent/Binary)
1122        let mut handler =
1123            FnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| Ok(Output::<()>::Silent));
1124
1125        let ctx = CommandContext::default();
1126        let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1127
1128        let result = handler.handle(&matches, &ctx);
1129        assert!(result.is_ok());
1130        assert!(matches!(result.unwrap(), Output::Silent));
1131    }
1132
1133    #[test]
1134    fn test_fn_handler_with_custom_error_type() {
1135        // Custom error type that implements Into<anyhow::Error>
1136        #[derive(Debug)]
1137        struct CustomError(String);
1138
1139        impl std::fmt::Display for CustomError {
1140            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1141                write!(f, "CustomError: {}", self.0)
1142            }
1143        }
1144
1145        impl std::error::Error for CustomError {}
1146
1147        let mut handler = FnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| {
1148            Err::<String, CustomError>(CustomError("oops".to_string()))
1149        });
1150
1151        let ctx = CommandContext::default();
1152        let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1153
1154        let result = handler.handle(&matches, &ctx);
1155        assert!(result.is_err());
1156        assert!(result
1157            .unwrap_err()
1158            .to_string()
1159            .contains("CustomError: oops"));
1160    }
1161
1162    // SimpleFnHandler tests (no CommandContext)
1163    #[test]
1164    fn test_simple_fn_handler_basic() {
1165        use super::SimpleFnHandler;
1166
1167        let mut handler = SimpleFnHandler::new(|_m: &ArgMatches| {
1168            Ok::<_, anyhow::Error>("no context needed".to_string())
1169        });
1170
1171        let ctx = CommandContext::default();
1172        let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1173
1174        let result = handler.handle(&matches, &ctx);
1175        assert!(result.is_ok());
1176        match result.unwrap() {
1177            Output::Render(s) => assert_eq!(s, "no context needed"),
1178            _ => panic!("Expected Output::Render"),
1179        }
1180    }
1181
1182    #[test]
1183    fn test_simple_fn_handler_with_args() {
1184        use super::SimpleFnHandler;
1185
1186        let mut handler = SimpleFnHandler::new(|m: &ArgMatches| {
1187            let verbose = m.get_flag("verbose");
1188            Ok::<_, anyhow::Error>(verbose)
1189        });
1190
1191        let ctx = CommandContext::default();
1192        let matches = clap::Command::new("test")
1193            .arg(
1194                clap::Arg::new("verbose")
1195                    .short('v')
1196                    .action(clap::ArgAction::SetTrue),
1197            )
1198            .get_matches_from(vec!["test", "-v"]);
1199
1200        let result = handler.handle(&matches, &ctx);
1201        assert!(result.is_ok());
1202        match result.unwrap() {
1203            Output::Render(v) => assert!(v),
1204            _ => panic!("Expected Output::Render"),
1205        }
1206    }
1207
1208    #[test]
1209    fn test_simple_fn_handler_explicit_output() {
1210        use super::SimpleFnHandler;
1211
1212        let mut handler = SimpleFnHandler::new(|_m: &ArgMatches| Ok(Output::<()>::Silent));
1213
1214        let ctx = CommandContext::default();
1215        let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1216
1217        let result = handler.handle(&matches, &ctx);
1218        assert!(result.is_ok());
1219        assert!(matches!(result.unwrap(), Output::Silent));
1220    }
1221
1222    #[test]
1223    fn test_simple_fn_handler_error() {
1224        use super::SimpleFnHandler;
1225
1226        let mut handler = SimpleFnHandler::new(|_m: &ArgMatches| {
1227            Err::<String, _>(anyhow::anyhow!("simple error"))
1228        });
1229
1230        let ctx = CommandContext::default();
1231        let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1232
1233        let result = handler.handle(&matches, &ctx);
1234        assert!(result.is_err());
1235        assert!(result.unwrap_err().to_string().contains("simple error"));
1236    }
1237
1238    #[test]
1239    fn test_simple_fn_handler_mutation() {
1240        use super::SimpleFnHandler;
1241
1242        let mut counter = 0u32;
1243        let mut handler = SimpleFnHandler::new(|_m: &ArgMatches| {
1244            counter += 1;
1245            Ok::<_, anyhow::Error>(counter)
1246        });
1247
1248        let ctx = CommandContext::default();
1249        let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1250
1251        let _ = handler.handle(&matches, &ctx);
1252        let _ = handler.handle(&matches, &ctx);
1253        let result = handler.handle(&matches, &ctx);
1254
1255        assert!(result.is_ok());
1256        match result.unwrap() {
1257            Output::Render(n) => assert_eq!(n, 3),
1258            _ => panic!("Expected Output::Render"),
1259        }
1260    }
1261}