Skip to main content

standout_dispatch/
handler.rs

1//! Command handler types.
2//!
3//! This module provides the core types for building CLI handler adapters in the
4//! dispatch pipeline.
5//!
6//! # Design Rationale
7//!
8//! Handler adapters are responsible for the CLI-to-application seam. They:
9//!
10//! - Receive parsed CLI arguments (`&ArgMatches`) and execution context
11//! - Call a CLI-free application library
12//! - Map library results into serializable CLI view data for 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::hooks::HookPhase;
68use crate::verify::ExpectedArg;
69use clap::ArgMatches;
70use serde::Serialize;
71use std::any::{Any, TypeId};
72use std::collections::HashMap;
73use std::fmt;
74use std::rc::Rc;
75
76/// Type-safe container for injecting custom state into handlers.
77///
78/// Extensions allow pre-dispatch hooks to inject state that handlers can retrieve.
79/// This enables dependency injection without modifying handler signatures.
80///
81/// # Warning: Clone Behavior
82///
83/// `Extensions` is **not** cloned when the container is cloned. Cloning an `Extensions` instance
84/// results in a new, empty map. This is because the underlying `Box<dyn Any>` values cannot
85/// be cloned generically.
86///
87/// If you need to share state across threads/clones, use `Arc<T>` inside the extension.
88///
89/// # Example
90///
91/// ```rust
92/// use standout_dispatch::{Extensions, CommandContext};
93///
94/// // Define your state types
95/// struct ApiClient { base_url: String }
96/// struct UserScope { user_id: u64 }
97///
98/// // In a pre-dispatch hook, inject state
99/// let mut ctx = CommandContext::default();
100/// ctx.extensions.insert(ApiClient { base_url: "https://api.example.com".into() });
101/// ctx.extensions.insert(UserScope { user_id: 42 });
102///
103/// // In a handler, retrieve state
104/// let api = ctx.extensions.get_required::<ApiClient>()?;
105/// println!("API base: {}", api.base_url);
106/// # Ok::<(), anyhow::Error>(())
107/// ```
108#[derive(Default)]
109pub struct Extensions {
110    map: HashMap<TypeId, Box<dyn Any>>,
111}
112
113impl Extensions {
114    /// Creates a new empty extensions container.
115    pub fn new() -> Self {
116        Self::default()
117    }
118
119    /// Inserts a value into the extensions.
120    ///
121    /// If a value of this type already exists, it is replaced and returned.
122    pub fn insert<T: 'static>(&mut self, val: T) -> Option<T> {
123        self.map
124            .insert(TypeId::of::<T>(), Box::new(val))
125            .and_then(|boxed| boxed.downcast().ok().map(|b| *b))
126    }
127
128    /// Gets a reference to a value of the specified type.
129    ///
130    /// Returns `None` if no value of this type exists.
131    pub fn get<T: 'static>(&self) -> Option<&T> {
132        self.map
133            .get(&TypeId::of::<T>())
134            .and_then(|boxed| boxed.downcast_ref())
135    }
136
137    /// Gets a mutable reference to a value of the specified type.
138    ///
139    /// Returns `None` if no value of this type exists.
140    pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> {
141        self.map
142            .get_mut(&TypeId::of::<T>())
143            .and_then(|boxed| boxed.downcast_mut())
144    }
145
146    /// Gets a required reference to a value of the specified type.
147    ///
148    /// Returns an error if no value of this type exists.
149    pub fn get_required<T: 'static>(&self) -> Result<&T, anyhow::Error> {
150        self.get::<T>().ok_or_else(|| {
151            anyhow::anyhow!(
152                "Extension missing: type {} not found in context",
153                std::any::type_name::<T>()
154            )
155        })
156    }
157
158    /// Gets a required mutable reference to a value of the specified type.
159    ///
160    /// Returns an error if no value of this type exists.
161    pub fn get_mut_required<T: 'static>(&mut self) -> Result<&mut T, anyhow::Error> {
162        self.get_mut::<T>().ok_or_else(|| {
163            anyhow::anyhow!(
164                "Extension missing: type {} not found in context",
165                std::any::type_name::<T>()
166            )
167        })
168    }
169
170    /// Removes a value of the specified type, returning it if it existed.
171    pub fn remove<T: 'static>(&mut self) -> Option<T> {
172        self.map
173            .remove(&TypeId::of::<T>())
174            .and_then(|boxed| boxed.downcast().ok().map(|b| *b))
175    }
176
177    /// Returns `true` if the extensions contain a value of the specified type.
178    pub fn contains<T: 'static>(&self) -> bool {
179        self.map.contains_key(&TypeId::of::<T>())
180    }
181
182    /// Returns the number of extensions stored.
183    pub fn len(&self) -> usize {
184        self.map.len()
185    }
186
187    /// Returns `true` if no extensions are stored.
188    pub fn is_empty(&self) -> bool {
189        self.map.is_empty()
190    }
191
192    /// Removes all extensions.
193    pub fn clear(&mut self) {
194        self.map.clear();
195    }
196}
197
198impl fmt::Debug for Extensions {
199    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
200        f.debug_struct("Extensions")
201            .field("len", &self.map.len())
202            .finish_non_exhaustive()
203    }
204}
205
206impl Clone for Extensions {
207    fn clone(&self) -> Self {
208        // Extensions cannot be cloned because Box<dyn Any> isn't Clone.
209        // Return empty extensions on clone - this is a limitation but
210        // matches the behavior of http::Extensions.
211        Self::new()
212    }
213}
214
215/// Context passed to command handlers.
216///
217/// Provides information about the execution environment plus two mechanisms
218/// for state injection:
219///
220/// - **`app_state`**: Immutable, app-lifetime state (Database, Config, API clients)
221/// - **`extensions`**: Mutable, per-request state (UserScope, RequestId)
222///
223/// Note that output format is deliberately not included here - format decisions
224/// are made by the render handler, not by logic handlers.
225///
226/// # App State (Immutable, Shared)
227///
228/// App state is configured at build time and shared across all dispatches:
229///
230/// ```rust,ignore
231/// use standout::cli::App;
232///
233/// struct Database { /* ... */ }
234/// struct Config { api_url: String }
235///
236/// App::builder()
237///     .app_state(Database::connect()?)
238///     .app_state(Config { api_url: "https://api.example.com".into() })
239///     .command("list", list_handler, "{{ items }}")
240///     .build()?
241/// ```
242///
243/// Handlers retrieve app state immutably:
244///
245/// ```rust,ignore
246/// fn list_handler(matches: &ArgMatches, ctx: &CommandContext) -> HandlerResult<Vec<Item>> {
247///     let db = ctx.app_state.get_required::<Database>()?;
248///     let config = ctx.app_state.get_required::<Config>()?;
249///     Ok(Output::Render(db.list_items(&config.api_url)?))
250/// }
251/// ```
252///
253/// ## Shared Mutable State
254///
255/// Since `app_state` is shared via `Arc`, it is immutable by default. To share mutable state
256/// (like counters or caches), use interior mutability primitives like `RwLock`, `Mutex`, or atomic types:
257///
258/// ```rust,ignore
259/// use std::sync::atomic::AtomicUsize;
260///
261/// struct Metrics { request_count: AtomicUsize }
262///
263/// // Builder
264/// App::builder().app_state(Metrics { request_count: AtomicUsize::new(0) });
265///
266/// // Handler
267/// let metrics = ctx.app_state.get_required::<Metrics>()?;
268/// metrics.request_count.fetch_add(1, Ordering::Relaxed);
269/// ```
270///
271/// # Extensions (Mutable, Per-Request)
272///
273/// Pre-dispatch hooks inject per-request state into `extensions`:
274///
275/// ```rust
276/// use standout_dispatch::{Hooks, HookError, CommandContext};
277///
278/// struct UserScope { user_id: String }
279///
280/// let hooks = Hooks::new()
281///     .pre_dispatch(|matches, ctx| {
282///         let user_id = matches.get_one::<String>("user").unwrap();
283///         ctx.extensions.insert(UserScope { user_id: user_id.clone() });
284///         Ok(())
285///     });
286///
287/// // In handler:
288/// fn my_handler(matches: &clap::ArgMatches, ctx: &CommandContext) -> anyhow::Result<()> {
289///     let scope = ctx.extensions.get_required::<UserScope>()?;
290///     // use scope.user_id...
291///     Ok(())
292/// }
293/// ```
294#[derive(Debug)]
295pub struct CommandContext {
296    /// The command path being executed (e.g., ["config", "get"])
297    pub command_path: Vec<String>,
298
299    /// Immutable app-level state shared across all dispatches.
300    ///
301    /// Configured via `AppBuilder::app_state()`. Contains long-lived resources
302    /// like database connections, configuration, and API clients.
303    ///
304    /// Use `get::<T>()` or `get_required::<T>()` to retrieve values.
305    pub app_state: Rc<Extensions>,
306
307    /// Mutable per-request state container.
308    ///
309    /// Pre-dispatch hooks can insert values that handlers retrieve.
310    /// Each dispatch gets a fresh Extensions instance.
311    pub extensions: Extensions,
312}
313
314impl CommandContext {
315    /// Creates a new CommandContext with the given path and shared app state.
316    ///
317    /// This is more efficient than `Default::default()` when you already have app_state.
318    pub fn new(command_path: Vec<String>, app_state: Rc<Extensions>) -> Self {
319        Self {
320            command_path,
321            app_state,
322            extensions: Extensions::new(),
323        }
324    }
325}
326
327impl Default for CommandContext {
328    fn default() -> Self {
329        Self {
330            command_path: Vec::new(),
331            app_state: Rc::new(Extensions::new()),
332            extensions: Extensions::new(),
333        }
334    }
335}
336
337/// What a handler produces.
338///
339/// This enum represents the different types of output a command handler can produce.
340#[derive(Debug)]
341pub enum Output<T: Serialize> {
342    /// Data to render with a template or serialize to JSON/YAML/etc.
343    Render(T),
344    /// Silent exit (no output produced)
345    Silent,
346    /// Binary output for file exports
347    Binary {
348        /// The binary data
349        data: Vec<u8>,
350        /// Suggested filename for the output
351        filename: String,
352    },
353}
354
355impl<T: Serialize> Output<T> {
356    /// Returns true if this is a render result.
357    pub fn is_render(&self) -> bool {
358        matches!(self, Output::Render(_))
359    }
360
361    /// Returns true if this is a silent result.
362    pub fn is_silent(&self) -> bool {
363        matches!(self, Output::Silent)
364    }
365
366    /// Returns true if this is a binary result.
367    pub fn is_binary(&self) -> bool {
368        matches!(self, Output::Binary { .. })
369    }
370}
371
372/// The result type for command handlers.
373///
374/// Enables use of the `?` operator for error propagation.
375pub type HandlerResult<T> = Result<Output<T>, anyhow::Error>;
376
377/// Trait for types that can be converted into a [`HandlerResult`].
378///
379/// This enables handlers to return either `Result<T, E>` directly (auto-wrapped
380/// in [`Output::Render`]) or the explicit [`HandlerResult<T>`] when fine-grained
381/// control is needed (for [`Output::Silent`] or [`Output::Binary`]).
382///
383/// # Example
384///
385/// ```rust
386/// use standout_dispatch::{HandlerResult, Output, IntoHandlerResult};
387///
388/// // Direct Result<T, E> is auto-wrapped in Output::Render
389/// fn simple() -> Result<String, anyhow::Error> {
390///     Ok("hello".to_string())
391/// }
392/// let result: HandlerResult<String> = simple().into_handler_result();
393/// assert!(matches!(result, Ok(Output::Render(_))));
394///
395/// // HandlerResult<T> passes through unchanged
396/// fn explicit() -> HandlerResult<String> {
397///     Ok(Output::Silent)
398/// }
399/// let result: HandlerResult<String> = explicit().into_handler_result();
400/// assert!(matches!(result, Ok(Output::Silent)));
401/// ```
402pub trait IntoHandlerResult<T: Serialize> {
403    /// Convert this type into a [`HandlerResult<T>`].
404    fn into_handler_result(self) -> HandlerResult<T>;
405}
406
407/// Implementation for `Result<T, E>` - auto-wraps successful values in [`Output::Render`].
408///
409/// This is the ergonomic path: handlers can return `Result<T, E>` directly
410/// and the framework wraps it appropriately.
411impl<T, E> IntoHandlerResult<T> for Result<T, E>
412where
413    T: Serialize,
414    E: Into<anyhow::Error>,
415{
416    fn into_handler_result(self) -> HandlerResult<T> {
417        self.map(Output::Render).map_err(Into::into)
418    }
419}
420
421/// Implementation for `HandlerResult<T>` - passes through unchanged.
422///
423/// This is the explicit path: handlers that need [`Output::Silent`] or
424/// [`Output::Binary`] can return `HandlerResult<T>` directly.
425impl<T: Serialize> IntoHandlerResult<T> for HandlerResult<T> {
426    fn into_handler_result(self) -> HandlerResult<T> {
427        self
428    }
429}
430
431/// Process exit status selected by Standout's execution policy.
432#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
433pub struct ExitStatus(u8);
434
435impl ExitStatus {
436    /// Successful command, help, or version display.
437    pub const SUCCESS: Self = Self(0);
438    /// Runtime or final-write failure.
439    pub const FAILURE: Self = Self(1);
440    /// Clap command-line usage error.
441    pub const USAGE_ERROR: Self = Self(2);
442
443    /// Returns the numeric status reported to the operating system.
444    pub const fn code(self) -> u8 {
445        self.0
446    }
447}
448
449/// Successful text outcome carried by [`RunResult::Handled`].
450#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
451#[non_exhaustive]
452pub enum SuccessKind {
453    /// A registered command completed successfully.
454    Command,
455    /// Clap requested a help display.
456    ClapHelp,
457    /// Clap requested a version display.
458    ClapVersion,
459}
460
461/// The final payload whose write failed.
462#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
463#[non_exhaustive]
464pub enum OutputKind {
465    /// Rendered text output.
466    Text,
467    /// Binary output.
468    Binary,
469}
470
471/// Typed origin for an unsuccessful framework run.
472#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
473#[non_exhaustive]
474pub enum RunErrorKind {
475    /// Clap rejected the command line.
476    ClapUsage,
477    /// The application handler returned an error.
478    Handler,
479    /// A hook failed in the identified phase. Pipe failures are post-output hooks.
480    Hook(HookPhase),
481    /// Handler data serialization or presentation rendering failed.
482    Render,
483    /// A framework-owned file/stdout write failed.
484    FinalWrite(OutputKind),
485}
486
487/// Metadata-bearing successful text compatible with string-oriented access.
488#[derive(Debug, Clone)]
489pub struct RunOutput {
490    text: String,
491    kind: SuccessKind,
492}
493
494impl RunOutput {
495    /// Creates a successful command output.
496    pub fn command(text: impl Into<String>) -> Self {
497        Self {
498            text: text.into(),
499            kind: SuccessKind::Command,
500        }
501    }
502
503    /// Creates a Clap help display.
504    pub fn clap_help(text: impl Into<String>) -> Self {
505        Self {
506            text: text.into(),
507            kind: SuccessKind::ClapHelp,
508        }
509    }
510
511    /// Creates a Clap version display.
512    pub fn clap_version(text: impl Into<String>) -> Self {
513        Self {
514            text: text.into(),
515            kind: SuccessKind::ClapVersion,
516        }
517    }
518
519    /// Returns the captured text.
520    pub fn as_str(&self) -> &str {
521        &self.text
522    }
523
524    /// Returns the typed success origin.
525    pub const fn kind(&self) -> SuccessKind {
526        self.kind
527    }
528
529    /// Consumes the wrapper and returns the captured text.
530    pub fn into_string(self) -> String {
531        self.text
532    }
533}
534
535impl std::ops::Deref for RunOutput {
536    type Target = str;
537    fn deref(&self) -> &Self::Target {
538        self.as_str()
539    }
540}
541
542impl AsRef<str> for RunOutput {
543    fn as_ref(&self) -> &str {
544        self.as_str()
545    }
546}
547
548impl fmt::Display for RunOutput {
549    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
550        f.write_str(self.as_str())
551    }
552}
553
554impl PartialEq<str> for RunOutput {
555    fn eq(&self, other: &str) -> bool {
556        self.as_str() == other
557    }
558}
559
560impl PartialEq<&str> for RunOutput {
561    fn eq(&self, other: &&str) -> bool {
562        self.as_str() == *other
563    }
564}
565
566impl PartialEq<String> for RunOutput {
567    fn eq(&self, other: &String) -> bool {
568        self.as_str() == other
569    }
570}
571
572impl From<String> for RunOutput {
573    fn from(text: String) -> Self {
574        Self::command(text)
575    }
576}
577
578impl From<&str> for RunOutput {
579    fn from(text: &str) -> Self {
580        Self::command(text)
581    }
582}
583
584impl From<RunOutput> for String {
585    fn from(output: RunOutput) -> Self {
586        output.into_string()
587    }
588}
589
590/// Metadata-bearing failure compatible with string-oriented access.
591#[derive(Debug, Clone)]
592pub struct RunError {
593    message: String,
594    kind: RunErrorKind,
595}
596
597impl RunError {
598    /// Creates a failure with its framework origin.
599    pub fn new(message: impl Into<String>, kind: RunErrorKind) -> Self {
600        Self {
601            message: message.into(),
602            kind,
603        }
604    }
605
606    /// Returns the existing human-readable diagnostic.
607    pub fn as_str(&self) -> &str {
608        &self.message
609    }
610
611    /// Returns the typed failure origin.
612    pub const fn kind(&self) -> RunErrorKind {
613        self.kind
614    }
615
616    /// Returns the shell status selected for this failure.
617    pub const fn exit_status(&self) -> ExitStatus {
618        match self.kind {
619            RunErrorKind::ClapUsage => ExitStatus::USAGE_ERROR,
620            _ => ExitStatus::FAILURE,
621        }
622    }
623
624    /// Consumes the wrapper and returns the diagnostic text.
625    pub fn into_string(self) -> String {
626        self.message
627    }
628}
629
630impl std::ops::Deref for RunError {
631    type Target = str;
632    fn deref(&self) -> &Self::Target {
633        self.as_str()
634    }
635}
636
637impl AsRef<str> for RunError {
638    fn as_ref(&self) -> &str {
639        self.as_str()
640    }
641}
642
643impl fmt::Display for RunError {
644    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
645        f.write_str(self.as_str())
646    }
647}
648
649impl From<String> for RunError {
650    fn from(message: String) -> Self {
651        Self::new(message, RunErrorKind::Handler)
652    }
653}
654
655impl From<&str> for RunError {
656    fn from(message: &str) -> Self {
657        Self::new(message, RunErrorKind::Handler)
658    }
659}
660
661impl From<RunError> for String {
662    fn from(error: RunError) -> Self {
663        error.into_string()
664    }
665}
666
667/// Result of running the CLI dispatcher.
668///
669/// After processing arguments, the dispatcher either handles a command,
670/// surfaces an error, or falls through for manual handling.
671///
672/// Marked `#[non_exhaustive]` so future variants can be added without
673/// breaking exhaustive matchers.
674#[derive(Debug)]
675#[non_exhaustive]
676pub enum RunResult {
677    /// A handler processed the command successfully; contains the rendered output
678    Handled(RunOutput),
679    /// A handler produced binary output (bytes, suggested filename)
680    Binary(Vec<u8>, String),
681    /// Silent output (handler completed but produced no output)
682    Silent,
683    /// A handler, hook, or output step failed; contains the formatted error message.
684    /// Consumers should write this to stderr and exit non-zero.
685    Error(RunError),
686    /// No handler matched; contains the ArgMatches for manual handling
687    NoMatch(ArgMatches),
688}
689
690impl RunResult {
691    /// Returns true if a handler processed the command successfully (text output).
692    pub fn is_handled(&self) -> bool {
693        matches!(self, RunResult::Handled(_))
694    }
695
696    /// Returns true if the result is binary output.
697    pub fn is_binary(&self) -> bool {
698        matches!(self, RunResult::Binary(_, _))
699    }
700
701    /// Returns true if the result is silent.
702    pub fn is_silent(&self) -> bool {
703        matches!(self, RunResult::Silent)
704    }
705
706    /// Returns true if the result is an error.
707    pub fn is_error(&self) -> bool {
708        matches!(self, RunResult::Error(_))
709    }
710
711    /// Returns the output if handled, or None otherwise.
712    pub fn output(&self) -> Option<&str> {
713        match self {
714            RunResult::Handled(s) => Some(s),
715            _ => None,
716        }
717    }
718
719    /// Returns the error message if this is an error, or None otherwise.
720    pub fn error(&self) -> Option<&str> {
721        match self {
722            RunResult::Error(s) => Some(s),
723            _ => None,
724        }
725    }
726
727    /// Returns the typed success origin for captured text.
728    pub fn success_kind(&self) -> Option<SuccessKind> {
729        match self {
730            RunResult::Handled(output) => Some(output.kind()),
731            RunResult::Binary(_, _) | RunResult::Silent => Some(SuccessKind::Command),
732            _ => None,
733        }
734    }
735
736    /// Returns the typed error origin, if this run failed.
737    pub fn error_kind(&self) -> Option<RunErrorKind> {
738        match self {
739            RunResult::Error(error) => Some(error.kind()),
740            _ => None,
741        }
742    }
743
744    /// Returns the completed run's shell status.
745    ///
746    /// `NoMatch` returns `None`: it is a fallback handoff, not a completed
747    /// framework execution and is deliberately not treated as a usage error.
748    pub fn exit_status(&self) -> Option<ExitStatus> {
749        match self {
750            RunResult::Handled(_) | RunResult::Binary(_, _) | RunResult::Silent => {
751                Some(ExitStatus::SUCCESS)
752            }
753            RunResult::Error(error) => Some(error.exit_status()),
754            RunResult::NoMatch(_) => None,
755        }
756    }
757
758    /// Returns the binary data and filename if binary, or None otherwise.
759    pub fn binary(&self) -> Option<(&[u8], &str)> {
760        match self {
761            RunResult::Binary(bytes, filename) => Some((bytes, filename)),
762            _ => None,
763        }
764    }
765
766    /// Returns the matches if unhandled, or None if handled.
767    pub fn matches(&self) -> Option<&ArgMatches> {
768        match self {
769            RunResult::NoMatch(m) => Some(m),
770            _ => None,
771        }
772    }
773}
774
775/// Trait for command handlers.
776///
777/// Handlers take `&mut self` allowing direct mutation of internal state.
778/// This is the common case for CLI applications which are single-threaded.
779///
780/// # Example
781///
782/// ```rust
783/// use standout_dispatch::{Handler, HandlerResult, Output, CommandContext};
784/// use clap::ArgMatches;
785/// use serde::Serialize;
786///
787/// struct Counter { count: u32 }
788///
789/// impl Handler for Counter {
790///     type Output = u32;
791///
792///     fn handle(&mut self, _m: &ArgMatches, _ctx: &CommandContext) -> HandlerResult<u32> {
793///         self.count += 1;
794///         Ok(Output::Render(self.count))
795///     }
796/// }
797/// ```
798pub trait Handler {
799    /// The output type produced by this handler (must be serializable)
800    type Output: Serialize;
801
802    /// Execute the handler with the given matches and context.
803    fn handle(&mut self, matches: &ArgMatches, ctx: &CommandContext)
804        -> HandlerResult<Self::Output>;
805
806    /// Returns the arguments expected by this handler for verification.
807    ///
808    /// This is used to verify that the CLI command definition matches the handler's expectations.
809    /// Handlers generated by the `#[handler]` macro implement this automatically.
810    fn expected_args(&self) -> Vec<ExpectedArg> {
811        Vec::new()
812    }
813}
814
815/// A wrapper that implements Handler for FnMut closures.
816///
817/// The closure can return either:
818/// - `Result<T, E>` - automatically wrapped in [`Output::Render`]
819/// - `HandlerResult<T>` - passed through unchanged (for [`Output::Silent`] or [`Output::Binary`])
820///
821/// # Example
822///
823/// ```rust
824/// use standout_dispatch::{FnHandler, Handler, CommandContext, Output};
825/// use clap::ArgMatches;
826///
827/// // Returning Result<T, E> directly (auto-wrapped)
828/// let mut handler = FnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| {
829///     Ok::<_, anyhow::Error>("hello".to_string())
830/// });
831///
832/// // Returning HandlerResult<T> explicitly (for Silent/Binary)
833/// let mut silent_handler = FnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| {
834///     Ok(Output::<()>::Silent)
835/// });
836/// ```
837pub struct FnHandler<F, T, R = HandlerResult<T>>
838where
839    T: Serialize,
840{
841    f: F,
842    _phantom: std::marker::PhantomData<fn() -> (T, R)>,
843}
844
845impl<F, T, R> FnHandler<F, T, R>
846where
847    F: FnMut(&ArgMatches, &CommandContext) -> R,
848    R: IntoHandlerResult<T>,
849    T: Serialize,
850{
851    /// Creates a new FnHandler wrapping the given FnMut closure.
852    pub fn new(f: F) -> Self {
853        Self {
854            f,
855            _phantom: std::marker::PhantomData,
856        }
857    }
858}
859
860impl<F, T, R> Handler for FnHandler<F, T, R>
861where
862    F: FnMut(&ArgMatches, &CommandContext) -> R,
863    R: IntoHandlerResult<T>,
864    T: Serialize,
865{
866    type Output = T;
867
868    fn handle(&mut self, matches: &ArgMatches, ctx: &CommandContext) -> HandlerResult<T> {
869        (self.f)(matches, ctx).into_handler_result()
870    }
871}
872
873/// A handler wrapper for functions that don't need [`CommandContext`].
874///
875/// This is the simpler variant of [`FnHandler`] for handlers that only need
876/// [`ArgMatches`]. The context parameter is accepted but ignored internally.
877///
878/// The closure can return either:
879/// - `Result<T, E>` - automatically wrapped in [`Output::Render`]
880/// - `HandlerResult<T>` - passed through unchanged (for [`Output::Silent`] or [`Output::Binary`])
881///
882/// # Example
883///
884/// ```rust
885/// use standout_dispatch::{SimpleFnHandler, Handler, CommandContext, Output};
886/// use clap::ArgMatches;
887///
888/// // Handler that doesn't need context - just uses ArgMatches
889/// let mut handler = SimpleFnHandler::new(|_m: &ArgMatches| {
890///     Ok::<_, anyhow::Error>("Hello, world!".to_string())
891/// });
892///
893/// // Can still be used via Handler trait (context is ignored)
894/// let ctx = CommandContext::default();
895/// let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
896/// let result = handler.handle(&matches, &ctx);
897/// assert!(matches!(result, Ok(Output::Render(_))));
898/// ```
899pub struct SimpleFnHandler<F, T, R = HandlerResult<T>>
900where
901    T: Serialize,
902{
903    f: F,
904    _phantom: std::marker::PhantomData<fn() -> (T, R)>,
905}
906
907impl<F, T, R> SimpleFnHandler<F, T, R>
908where
909    F: FnMut(&ArgMatches) -> R,
910    R: IntoHandlerResult<T>,
911    T: Serialize,
912{
913    /// Creates a new SimpleFnHandler wrapping the given FnMut closure.
914    pub fn new(f: F) -> Self {
915        Self {
916            f,
917            _phantom: std::marker::PhantomData,
918        }
919    }
920}
921
922impl<F, T, R> Handler for SimpleFnHandler<F, T, R>
923where
924    F: FnMut(&ArgMatches) -> R,
925    R: IntoHandlerResult<T>,
926    T: Serialize,
927{
928    type Output = T;
929
930    fn handle(&mut self, matches: &ArgMatches, _ctx: &CommandContext) -> HandlerResult<T> {
931        (self.f)(matches).into_handler_result()
932    }
933}
934
935#[cfg(test)]
936mod tests {
937    use super::*;
938    use serde_json::json;
939
940    #[test]
941    fn test_command_context_creation() {
942        let ctx = CommandContext {
943            command_path: vec!["config".into(), "get".into()],
944            app_state: Rc::new(Extensions::new()),
945            extensions: Extensions::new(),
946        };
947        assert_eq!(ctx.command_path, vec!["config", "get"]);
948    }
949
950    #[test]
951    fn test_command_context_default() {
952        let ctx = CommandContext::default();
953        assert!(ctx.command_path.is_empty());
954        assert!(ctx.extensions.is_empty());
955        assert!(ctx.app_state.is_empty());
956    }
957
958    #[test]
959    fn test_command_context_with_app_state() {
960        struct Database {
961            url: String,
962        }
963        struct Config {
964            debug: bool,
965        }
966
967        // Build app state
968        let mut app_state = Extensions::new();
969        app_state.insert(Database {
970            url: "postgres://localhost".into(),
971        });
972        app_state.insert(Config { debug: true });
973        let app_state = Rc::new(app_state);
974
975        // Create context with app state
976        let ctx = CommandContext {
977            command_path: vec!["list".into()],
978            app_state: app_state.clone(),
979            extensions: Extensions::new(),
980        };
981
982        // Retrieve app state
983        let db = ctx.app_state.get::<Database>().unwrap();
984        assert_eq!(db.url, "postgres://localhost");
985
986        let config = ctx.app_state.get::<Config>().unwrap();
987        assert!(config.debug);
988
989        // App state is shared via Rc
990        assert_eq!(Rc::strong_count(&ctx.app_state), 2);
991    }
992
993    #[test]
994    fn test_command_context_app_state_get_required() {
995        struct Present;
996
997        let mut app_state = Extensions::new();
998        app_state.insert(Present);
999
1000        let ctx = CommandContext {
1001            command_path: vec![],
1002            app_state: Rc::new(app_state),
1003            extensions: Extensions::new(),
1004        };
1005
1006        // Success case
1007        assert!(ctx.app_state.get_required::<Present>().is_ok());
1008
1009        // Failure case
1010        #[derive(Debug)]
1011        struct Missing;
1012        let err = ctx.app_state.get_required::<Missing>();
1013        assert!(err.is_err());
1014        assert!(err.unwrap_err().to_string().contains("Extension missing"));
1015    }
1016
1017    // Extensions tests
1018    #[test]
1019    fn test_extensions_insert_and_get() {
1020        struct MyState {
1021            value: i32,
1022        }
1023
1024        let mut ext = Extensions::new();
1025        assert!(ext.is_empty());
1026
1027        ext.insert(MyState { value: 42 });
1028        assert!(!ext.is_empty());
1029        assert_eq!(ext.len(), 1);
1030
1031        let state = ext.get::<MyState>().unwrap();
1032        assert_eq!(state.value, 42);
1033    }
1034
1035    #[test]
1036    fn test_extensions_get_mut() {
1037        struct Counter {
1038            count: i32,
1039        }
1040
1041        let mut ext = Extensions::new();
1042        ext.insert(Counter { count: 0 });
1043
1044        if let Some(counter) = ext.get_mut::<Counter>() {
1045            counter.count += 1;
1046        }
1047
1048        assert_eq!(ext.get::<Counter>().unwrap().count, 1);
1049    }
1050
1051    #[test]
1052    fn test_extensions_multiple_types() {
1053        struct TypeA(i32);
1054        struct TypeB(String);
1055
1056        let mut ext = Extensions::new();
1057        ext.insert(TypeA(1));
1058        ext.insert(TypeB("hello".into()));
1059
1060        assert_eq!(ext.len(), 2);
1061        assert_eq!(ext.get::<TypeA>().unwrap().0, 1);
1062        assert_eq!(ext.get::<TypeB>().unwrap().0, "hello");
1063    }
1064
1065    #[test]
1066    fn test_extensions_replace() {
1067        struct Value(i32);
1068
1069        let mut ext = Extensions::new();
1070        ext.insert(Value(1));
1071
1072        let old = ext.insert(Value(2));
1073        assert_eq!(old.unwrap().0, 1);
1074        assert_eq!(ext.get::<Value>().unwrap().0, 2);
1075    }
1076
1077    #[test]
1078    fn test_extensions_remove() {
1079        struct Value(i32);
1080
1081        let mut ext = Extensions::new();
1082        ext.insert(Value(42));
1083
1084        let removed = ext.remove::<Value>();
1085        assert_eq!(removed.unwrap().0, 42);
1086        assert!(ext.is_empty());
1087        assert!(ext.get::<Value>().is_none());
1088    }
1089
1090    #[test]
1091    fn test_extensions_contains() {
1092        struct Present;
1093        struct Absent;
1094
1095        let mut ext = Extensions::new();
1096        ext.insert(Present);
1097
1098        assert!(ext.contains::<Present>());
1099        assert!(!ext.contains::<Absent>());
1100    }
1101
1102    #[test]
1103    fn test_extensions_clear() {
1104        struct A;
1105        struct B;
1106
1107        let mut ext = Extensions::new();
1108        ext.insert(A);
1109        ext.insert(B);
1110        assert_eq!(ext.len(), 2);
1111
1112        ext.clear();
1113        assert!(ext.is_empty());
1114    }
1115
1116    #[test]
1117    fn test_extensions_missing_type_returns_none() {
1118        struct NotInserted;
1119
1120        let ext = Extensions::new();
1121        assert!(ext.get::<NotInserted>().is_none());
1122    }
1123
1124    #[test]
1125    fn test_extensions_get_required() {
1126        #[derive(Debug)]
1127        struct Config {
1128            value: i32,
1129        }
1130
1131        let mut ext = Extensions::new();
1132        ext.insert(Config { value: 100 });
1133
1134        // Success case
1135        let val = ext.get_required::<Config>();
1136        assert!(val.is_ok());
1137        assert_eq!(val.unwrap().value, 100);
1138
1139        // Failure case
1140        #[derive(Debug)]
1141        struct Missing;
1142        let err = ext.get_required::<Missing>();
1143        assert!(err.is_err());
1144        assert!(err
1145            .unwrap_err()
1146            .to_string()
1147            .contains("Extension missing: type"));
1148    }
1149
1150    #[test]
1151    fn test_extensions_get_mut_required() {
1152        #[derive(Debug)]
1153        struct State {
1154            count: i32,
1155        }
1156
1157        let mut ext = Extensions::new();
1158        ext.insert(State { count: 0 });
1159
1160        // Success case
1161        {
1162            let val = ext.get_mut_required::<State>();
1163            assert!(val.is_ok());
1164            val.unwrap().count += 1;
1165        }
1166        assert_eq!(ext.get_required::<State>().unwrap().count, 1);
1167
1168        // Failure case
1169        #[derive(Debug)]
1170        struct Missing;
1171        let err = ext.get_mut_required::<Missing>();
1172        assert!(err.is_err());
1173    }
1174
1175    #[test]
1176    fn test_extensions_clone_behavior() {
1177        // Verify the documented behavior that Clone drops extensions
1178        struct Data(#[allow(dead_code)] i32);
1179
1180        let mut original = Extensions::new();
1181        original.insert(Data(42));
1182
1183        let cloned = original.clone();
1184
1185        // Original has data
1186        assert!(original.get::<Data>().is_some());
1187
1188        // Cloned is empty
1189        assert!(cloned.is_empty());
1190        assert!(cloned.get::<Data>().is_none());
1191    }
1192
1193    #[test]
1194    fn test_output_render() {
1195        let output: Output<String> = Output::Render("success".into());
1196        assert!(output.is_render());
1197        assert!(!output.is_silent());
1198        assert!(!output.is_binary());
1199    }
1200
1201    #[test]
1202    fn test_output_silent() {
1203        let output: Output<String> = Output::Silent;
1204        assert!(!output.is_render());
1205        assert!(output.is_silent());
1206        assert!(!output.is_binary());
1207    }
1208
1209    #[test]
1210    fn test_output_binary() {
1211        let output: Output<String> = Output::Binary {
1212            data: vec![0x25, 0x50, 0x44, 0x46],
1213            filename: "report.pdf".into(),
1214        };
1215        assert!(!output.is_render());
1216        assert!(!output.is_silent());
1217        assert!(output.is_binary());
1218    }
1219
1220    #[test]
1221    fn test_run_result_handled() {
1222        let result = RunResult::Handled("output".into());
1223        assert!(result.is_handled());
1224        assert!(!result.is_binary());
1225        assert!(!result.is_silent());
1226        assert_eq!(result.output(), Some("output"));
1227        assert!(result.matches().is_none());
1228    }
1229
1230    #[test]
1231    fn test_run_result_silent() {
1232        let result = RunResult::Silent;
1233        assert!(!result.is_handled());
1234        assert!(!result.is_binary());
1235        assert!(result.is_silent());
1236    }
1237
1238    #[test]
1239    fn test_run_result_binary() {
1240        let bytes = vec![0x25, 0x50, 0x44, 0x46];
1241        let result = RunResult::Binary(bytes.clone(), "report.pdf".into());
1242        assert!(!result.is_handled());
1243        assert!(result.is_binary());
1244        assert!(!result.is_silent());
1245
1246        let (data, filename) = result.binary().unwrap();
1247        assert_eq!(data, &bytes);
1248        assert_eq!(filename, "report.pdf");
1249    }
1250
1251    #[test]
1252    fn test_run_result_no_match() {
1253        let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1254        let result = RunResult::NoMatch(matches);
1255        assert!(!result.is_handled());
1256        assert!(!result.is_binary());
1257        assert!(result.matches().is_some());
1258    }
1259
1260    #[test]
1261    fn test_fn_handler() {
1262        let mut handler = FnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| {
1263            Ok(Output::Render(json!({"status": "ok"})))
1264        });
1265
1266        let ctx = CommandContext::default();
1267        let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1268
1269        let result = handler.handle(&matches, &ctx);
1270        assert!(result.is_ok());
1271    }
1272
1273    #[test]
1274    fn test_fn_handler_mutation() {
1275        let mut counter = 0u32;
1276
1277        let mut handler = FnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| {
1278            counter += 1;
1279            Ok(Output::Render(counter))
1280        });
1281
1282        let ctx = CommandContext::default();
1283        let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1284
1285        let _ = handler.handle(&matches, &ctx);
1286        let _ = handler.handle(&matches, &ctx);
1287        let result = handler.handle(&matches, &ctx);
1288
1289        assert!(result.is_ok());
1290        if let Ok(Output::Render(count)) = result {
1291            assert_eq!(count, 3);
1292        }
1293    }
1294
1295    // IntoHandlerResult tests
1296    #[test]
1297    fn test_into_handler_result_from_result_ok() {
1298        use super::IntoHandlerResult;
1299
1300        let result: Result<String, anyhow::Error> = Ok("hello".to_string());
1301        let handler_result = result.into_handler_result();
1302
1303        assert!(handler_result.is_ok());
1304        match handler_result.unwrap() {
1305            Output::Render(s) => assert_eq!(s, "hello"),
1306            _ => panic!("Expected Output::Render"),
1307        }
1308    }
1309
1310    #[test]
1311    fn test_into_handler_result_from_result_err() {
1312        use super::IntoHandlerResult;
1313
1314        let result: Result<String, anyhow::Error> = Err(anyhow::anyhow!("test error"));
1315        let handler_result = result.into_handler_result();
1316
1317        assert!(handler_result.is_err());
1318        assert!(handler_result
1319            .unwrap_err()
1320            .to_string()
1321            .contains("test error"));
1322    }
1323
1324    #[test]
1325    fn test_into_handler_result_passthrough_render() {
1326        use super::IntoHandlerResult;
1327
1328        let handler_result: HandlerResult<String> = Ok(Output::Render("hello".to_string()));
1329        let result = handler_result.into_handler_result();
1330
1331        assert!(result.is_ok());
1332        match result.unwrap() {
1333            Output::Render(s) => assert_eq!(s, "hello"),
1334            _ => panic!("Expected Output::Render"),
1335        }
1336    }
1337
1338    #[test]
1339    fn test_into_handler_result_passthrough_silent() {
1340        use super::IntoHandlerResult;
1341
1342        let handler_result: HandlerResult<String> = Ok(Output::Silent);
1343        let result = handler_result.into_handler_result();
1344
1345        assert!(result.is_ok());
1346        assert!(matches!(result.unwrap(), Output::Silent));
1347    }
1348
1349    #[test]
1350    fn test_into_handler_result_passthrough_binary() {
1351        use super::IntoHandlerResult;
1352
1353        let handler_result: HandlerResult<String> = Ok(Output::Binary {
1354            data: vec![1, 2, 3],
1355            filename: "test.bin".to_string(),
1356        });
1357        let result = handler_result.into_handler_result();
1358
1359        assert!(result.is_ok());
1360        match result.unwrap() {
1361            Output::Binary { data, filename } => {
1362                assert_eq!(data, vec![1, 2, 3]);
1363                assert_eq!(filename, "test.bin");
1364            }
1365            _ => panic!("Expected Output::Binary"),
1366        }
1367    }
1368
1369    #[test]
1370    fn test_fn_handler_with_auto_wrap() {
1371        // Handler that returns Result<T, E> directly (not HandlerResult)
1372        let mut handler = FnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| {
1373            Ok::<_, anyhow::Error>("auto-wrapped".to_string())
1374        });
1375
1376        let ctx = CommandContext::default();
1377        let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1378
1379        let result = handler.handle(&matches, &ctx);
1380        assert!(result.is_ok());
1381        match result.unwrap() {
1382            Output::Render(s) => assert_eq!(s, "auto-wrapped"),
1383            _ => panic!("Expected Output::Render"),
1384        }
1385    }
1386
1387    #[test]
1388    fn test_fn_handler_with_explicit_output() {
1389        // Handler that returns HandlerResult directly (for Silent/Binary)
1390        let mut handler =
1391            FnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| Ok(Output::<()>::Silent));
1392
1393        let ctx = CommandContext::default();
1394        let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1395
1396        let result = handler.handle(&matches, &ctx);
1397        assert!(result.is_ok());
1398        assert!(matches!(result.unwrap(), Output::Silent));
1399    }
1400
1401    #[test]
1402    fn test_fn_handler_with_custom_error_type() {
1403        // Custom error type that implements Into<anyhow::Error>
1404        #[derive(Debug)]
1405        struct CustomError(String);
1406
1407        impl std::fmt::Display for CustomError {
1408            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1409                write!(f, "CustomError: {}", self.0)
1410            }
1411        }
1412
1413        impl std::error::Error for CustomError {}
1414
1415        let mut handler = FnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| {
1416            Err::<String, CustomError>(CustomError("oops".to_string()))
1417        });
1418
1419        let ctx = CommandContext::default();
1420        let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1421
1422        let result = handler.handle(&matches, &ctx);
1423        assert!(result.is_err());
1424        assert!(result
1425            .unwrap_err()
1426            .to_string()
1427            .contains("CustomError: oops"));
1428    }
1429
1430    // SimpleFnHandler tests (no CommandContext)
1431    #[test]
1432    fn test_simple_fn_handler_basic() {
1433        use super::SimpleFnHandler;
1434
1435        let mut handler = SimpleFnHandler::new(|_m: &ArgMatches| {
1436            Ok::<_, anyhow::Error>("no context needed".to_string())
1437        });
1438
1439        let ctx = CommandContext::default();
1440        let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1441
1442        let result = handler.handle(&matches, &ctx);
1443        assert!(result.is_ok());
1444        match result.unwrap() {
1445            Output::Render(s) => assert_eq!(s, "no context needed"),
1446            _ => panic!("Expected Output::Render"),
1447        }
1448    }
1449
1450    #[test]
1451    fn test_simple_fn_handler_with_args() {
1452        use super::SimpleFnHandler;
1453
1454        let mut handler = SimpleFnHandler::new(|m: &ArgMatches| {
1455            let verbose = m.get_flag("verbose");
1456            Ok::<_, anyhow::Error>(verbose)
1457        });
1458
1459        let ctx = CommandContext::default();
1460        let matches = clap::Command::new("test")
1461            .arg(
1462                clap::Arg::new("verbose")
1463                    .short('v')
1464                    .action(clap::ArgAction::SetTrue),
1465            )
1466            .get_matches_from(vec!["test", "-v"]);
1467
1468        let result = handler.handle(&matches, &ctx);
1469        assert!(result.is_ok());
1470        match result.unwrap() {
1471            Output::Render(v) => assert!(v),
1472            _ => panic!("Expected Output::Render"),
1473        }
1474    }
1475
1476    #[test]
1477    fn test_simple_fn_handler_explicit_output() {
1478        use super::SimpleFnHandler;
1479
1480        let mut handler = SimpleFnHandler::new(|_m: &ArgMatches| Ok(Output::<()>::Silent));
1481
1482        let ctx = CommandContext::default();
1483        let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1484
1485        let result = handler.handle(&matches, &ctx);
1486        assert!(result.is_ok());
1487        assert!(matches!(result.unwrap(), Output::Silent));
1488    }
1489
1490    #[test]
1491    fn test_simple_fn_handler_error() {
1492        use super::SimpleFnHandler;
1493
1494        let mut handler = SimpleFnHandler::new(|_m: &ArgMatches| {
1495            Err::<String, _>(anyhow::anyhow!("simple error"))
1496        });
1497
1498        let ctx = CommandContext::default();
1499        let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1500
1501        let result = handler.handle(&matches, &ctx);
1502        assert!(result.is_err());
1503        assert!(result.unwrap_err().to_string().contains("simple error"));
1504    }
1505
1506    #[test]
1507    fn test_simple_fn_handler_mutation() {
1508        use super::SimpleFnHandler;
1509
1510        let mut counter = 0u32;
1511        let mut handler = SimpleFnHandler::new(|_m: &ArgMatches| {
1512            counter += 1;
1513            Ok::<_, anyhow::Error>(counter)
1514        });
1515
1516        let ctx = CommandContext::default();
1517        let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1518
1519        let _ = handler.handle(&matches, &ctx);
1520        let _ = handler.handle(&matches, &ctx);
1521        let result = handler.handle(&matches, &ctx);
1522
1523        assert!(result.is_ok());
1524        match result.unwrap() {
1525            Output::Render(n) => assert_eq!(n, 3),
1526            _ => panic!("Expected Output::Render"),
1527        }
1528    }
1529}