Skip to main content

standout_dispatch/
handler.rs

1use crate::artifact::{Artifact, ArtifactRun};
2use crate::hooks::HookPhase;
3use crate::verify::ExpectedArg;
4use clap::ArgMatches;
5use serde::Serialize;
6use std::any::{Any, TypeId};
7use std::collections::HashMap;
8use std::fmt;
9use std::rc::Rc;
10use std::sync::Arc;
11#[derive(Default)]
12pub struct Extensions {
13    map: HashMap<TypeId, Box<dyn Any>>,
14}
15impl Extensions {
16    pub fn new() -> Self {
17        Self::default()
18    }
19    pub fn insert<T: 'static>(&mut self, val: T) -> Option<T> {
20        self.map
21            .insert(TypeId::of::<T>(), Box::new(val))
22            .and_then(|boxed| boxed.downcast().ok().map(|b| *b))
23    }
24    pub fn get<T: 'static>(&self) -> Option<&T> {
25        self.map
26            .get(&TypeId::of::<T>())
27            .and_then(|boxed| boxed.downcast_ref())
28    }
29    pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> {
30        self.map
31            .get_mut(&TypeId::of::<T>())
32            .and_then(|boxed| boxed.downcast_mut())
33    }
34    pub fn get_required<T: 'static>(&self) -> Result<&T, anyhow::Error> {
35        self.get::<T>().ok_or_else(|| {
36            anyhow::anyhow!(
37                "Extension missing: type {} not found in context",
38                std::any::type_name::<T>()
39            )
40        })
41    }
42    pub fn get_mut_required<T: 'static>(&mut self) -> Result<&mut T, anyhow::Error> {
43        self.get_mut::<T>().ok_or_else(|| {
44            anyhow::anyhow!(
45                "Extension missing: type {} not found in context",
46                std::any::type_name::<T>()
47            )
48        })
49    }
50    pub fn remove<T: 'static>(&mut self) -> Option<T> {
51        self.map
52            .remove(&TypeId::of::<T>())
53            .and_then(|boxed| boxed.downcast().ok().map(|b| *b))
54    }
55    pub fn contains<T: 'static>(&self) -> bool {
56        self.map.contains_key(&TypeId::of::<T>())
57    }
58    pub fn len(&self) -> usize {
59        self.map.len()
60    }
61    pub fn is_empty(&self) -> bool {
62        self.map.is_empty()
63    }
64    pub fn clear(&mut self) {
65        self.map.clear();
66    }
67}
68impl fmt::Debug for Extensions {
69    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70        f.debug_struct("Extensions")
71            .field("len", &self.map.len())
72            .finish_non_exhaustive()
73    }
74}
75impl Clone for Extensions {
76    // Cloning always yields an empty container: `Box<dyn Any>` isn't `Clone`,
77    // so injected values are dropped rather than copied.
78    fn clone(&self) -> Self {
79        Self::new()
80    }
81}
82#[derive(Debug)]
83pub struct CommandContext {
84    pub command_path: Vec<String>,
85    pub app_state: Rc<Extensions>,
86    pub extensions: Extensions,
87}
88impl CommandContext {
89    pub fn new(command_path: Vec<String>, app_state: Rc<Extensions>) -> Self {
90        Self {
91            command_path,
92            app_state,
93            extensions: Extensions::new(),
94        }
95    }
96}
97impl Default for CommandContext {
98    fn default() -> Self {
99        Self {
100            command_path: Vec::new(),
101            app_state: Rc::new(Extensions::new()),
102            extensions: Extensions::new(),
103        }
104    }
105}
106#[derive(Debug)]
107#[non_exhaustive]
108pub enum Output<T: Serialize> {
109    Render(T),
110    Silent,
111    Binary { data: Vec<u8>, filename: String },
112    Artifact(Artifact<T>),
113}
114impl<T: Serialize> Output<T> {
115    pub fn is_render(&self) -> bool {
116        matches!(self, Output::Render(_))
117    }
118    pub fn is_silent(&self) -> bool {
119        matches!(self, Output::Silent)
120    }
121    pub fn is_binary(&self) -> bool {
122        matches!(self, Output::Binary { .. })
123    }
124    pub fn is_artifact(&self) -> bool {
125        matches!(self, Output::Artifact(_))
126    }
127}
128pub type HandlerResult<T> = Result<Output<T>, anyhow::Error>;
129pub trait IntoHandlerResult<T: Serialize> {
130    fn into_handler_result(self) -> HandlerResult<T>;
131}
132impl<T, E> IntoHandlerResult<T> for Result<T, E>
133where
134    T: Serialize,
135    E: Into<anyhow::Error>,
136{
137    fn into_handler_result(self) -> HandlerResult<T> {
138        self.map(Output::Render).map_err(Into::into)
139    }
140}
141impl<T: Serialize> IntoHandlerResult<T> for HandlerResult<T> {
142    fn into_handler_result(self) -> HandlerResult<T> {
143        self
144    }
145}
146#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
147pub struct ExitStatus(u8);
148impl ExitStatus {
149    pub const SUCCESS: Self = Self(0);
150    pub const FAILURE: Self = Self(1);
151    pub const USAGE_ERROR: Self = Self(2);
152    pub const fn code(self) -> u8 {
153        self.0
154    }
155}
156#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
157#[error("an external failure status must be nonzero")]
158pub struct InvalidExternalStatus;
159#[derive(Debug, Clone)]
160pub struct ExternalFailure {
161    status: ExitStatus,
162    diagnostic: String,
163    source: Option<Arc<dyn std::error::Error + Send + Sync + 'static>>,
164}
165impl ExternalFailure {
166    pub fn new(status: u8, diagnostic: impl Into<String>) -> Result<Self, InvalidExternalStatus> {
167        if status == 0 {
168            return Err(InvalidExternalStatus);
169        }
170        Ok(Self {
171            status: ExitStatus(status),
172            diagnostic: diagnostic.into(),
173            source: None,
174        })
175    }
176    pub const fn exit_status(&self) -> ExitStatus {
177        self.status
178    }
179    pub fn diagnostic(&self) -> &str {
180        &self.diagnostic
181    }
182    pub fn with_source<E>(mut self, source: E) -> Self
183    where
184        E: std::error::Error + Send + Sync + 'static,
185    {
186        self.source = Some(Arc::new(source));
187        self
188    }
189}
190impl fmt::Display for ExternalFailure {
191    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
192        f.write_str(self.diagnostic())
193    }
194}
195impl std::error::Error for ExternalFailure {
196    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
197        self.source
198            .as_deref()
199            .map(|source| source as &(dyn std::error::Error + 'static))
200    }
201}
202#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
203#[non_exhaustive]
204pub enum SuccessKind {
205    Command,
206    ClapHelp,
207    ClapVersion,
208    PagedHelp,
209}
210#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
211#[non_exhaustive]
212pub enum OutputKind {
213    Text,
214    Binary,
215    Artifact,
216}
217#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
218#[non_exhaustive]
219pub enum RunErrorKind {
220    ClapUsage,
221    DefaultCommand,
222    Handler,
223    Hook(HookPhase),
224    Render,
225    FinalWrite(OutputKind),
226    External,
227}
228#[derive(Debug, Clone)]
229pub struct RunOutput {
230    text: String,
231    kind: SuccessKind,
232}
233impl RunOutput {
234    pub fn command(text: impl Into<String>) -> Self {
235        Self {
236            text: text.into(),
237            kind: SuccessKind::Command,
238        }
239    }
240    pub fn clap_help(text: impl Into<String>) -> Self {
241        Self {
242            text: text.into(),
243            kind: SuccessKind::ClapHelp,
244        }
245    }
246    pub fn paged_help(text: impl Into<String>) -> Self {
247        Self {
248            text: text.into(),
249            kind: SuccessKind::PagedHelp,
250        }
251    }
252    pub fn clap_version(text: impl Into<String>) -> Self {
253        Self {
254            text: text.into(),
255            kind: SuccessKind::ClapVersion,
256        }
257    }
258    pub fn as_str(&self) -> &str {
259        &self.text
260    }
261    pub const fn kind(&self) -> SuccessKind {
262        self.kind
263    }
264    pub fn into_string(self) -> String {
265        self.text
266    }
267}
268impl std::ops::Deref for RunOutput {
269    type Target = str;
270    fn deref(&self) -> &Self::Target {
271        self.as_str()
272    }
273}
274impl AsRef<str> for RunOutput {
275    fn as_ref(&self) -> &str {
276        self.as_str()
277    }
278}
279impl fmt::Display for RunOutput {
280    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
281        f.write_str(self.as_str())
282    }
283}
284impl PartialEq<str> for RunOutput {
285    fn eq(&self, other: &str) -> bool {
286        self.as_str() == other
287    }
288}
289impl PartialEq<&str> for RunOutput {
290    fn eq(&self, other: &&str) -> bool {
291        self.as_str() == *other
292    }
293}
294impl PartialEq<String> for RunOutput {
295    fn eq(&self, other: &String) -> bool {
296        self.as_str() == other
297    }
298}
299impl From<String> for RunOutput {
300    fn from(text: String) -> Self {
301        Self::command(text)
302    }
303}
304impl From<&str> for RunOutput {
305    fn from(text: &str) -> Self {
306        Self::command(text)
307    }
308}
309impl From<RunOutput> for String {
310    fn from(output: RunOutput) -> Self {
311        output.into_string()
312    }
313}
314#[derive(Debug, Clone)]
315pub struct RunError {
316    message: String,
317    kind: RunErrorKind,
318    status: ExitStatus,
319    source: Option<Arc<dyn std::error::Error + Send + Sync + 'static>>,
320}
321impl RunError {
322    // Panics if `kind` is `RunErrorKind::External`; construct those via
323    // `ExternalFailure` instead so status and diagnostic stay consistent.
324    pub fn new(message: impl Into<String>, kind: RunErrorKind) -> Self {
325        assert!(
326            kind != RunErrorKind::External,
327            "external run errors must be constructed from ExternalFailure"
328        );
329        let status = match kind {
330            RunErrorKind::ClapUsage => ExitStatus::USAGE_ERROR,
331            _ => ExitStatus::FAILURE,
332        };
333        Self {
334            message: message.into(),
335            kind,
336            status,
337            source: None,
338        }
339    }
340    pub fn with_source<E>(mut self, source: E) -> Self
341    where
342        E: std::error::Error + Send + Sync + 'static,
343    {
344        self.source = Some(Arc::new(source));
345        self
346    }
347    pub fn as_str(&self) -> &str {
348        &self.message
349    }
350    pub const fn kind(&self) -> RunErrorKind {
351        self.kind
352    }
353    pub const fn exit_status(&self) -> ExitStatus {
354        self.status
355    }
356    pub fn into_string(self) -> String {
357        self.message
358    }
359}
360impl std::ops::Deref for RunError {
361    type Target = str;
362    fn deref(&self) -> &Self::Target {
363        self.as_str()
364    }
365}
366impl AsRef<str> for RunError {
367    fn as_ref(&self) -> &str {
368        self.as_str()
369    }
370}
371impl fmt::Display for RunError {
372    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
373        f.write_str(self.as_str())
374    }
375}
376impl std::error::Error for RunError {
377    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
378        self.source
379            .as_deref()
380            .map(|source| source as &(dyn std::error::Error + 'static))
381    }
382}
383impl From<ExternalFailure> for RunError {
384    fn from(failure: ExternalFailure) -> Self {
385        Self {
386            message: failure.diagnostic,
387            kind: RunErrorKind::External,
388            status: failure.status,
389            source: failure.source,
390        }
391    }
392}
393impl From<String> for RunError {
394    fn from(message: String) -> Self {
395        Self::new(message, RunErrorKind::Handler)
396    }
397}
398impl From<&str> for RunError {
399    fn from(message: &str) -> Self {
400        Self::new(message, RunErrorKind::Handler)
401    }
402}
403impl From<RunError> for String {
404    fn from(error: RunError) -> Self {
405        error.into_string()
406    }
407}
408#[derive(Debug)]
409#[non_exhaustive]
410pub enum DispatchResult {
411    Handled(RunOutput),
412    Binary(Vec<u8>, String),
413    Artifact(ArtifactRun),
414    Silent,
415    Error(RunError),
416    NoMatch(ArgMatches),
417}
418impl DispatchResult {
419    pub fn is_handled(&self) -> bool {
420        matches!(self, DispatchResult::Handled(_))
421    }
422    pub fn is_binary(&self) -> bool {
423        matches!(self, DispatchResult::Binary(_, _))
424    }
425    pub fn is_artifact(&self) -> bool {
426        matches!(self, DispatchResult::Artifact(_))
427    }
428    pub fn is_silent(&self) -> bool {
429        matches!(self, DispatchResult::Silent)
430    }
431    pub fn is_error(&self) -> bool {
432        matches!(self, DispatchResult::Error(_))
433    }
434    pub fn output(&self) -> Option<&str> {
435        match self {
436            DispatchResult::Handled(s) => Some(s),
437            _ => None,
438        }
439    }
440    pub fn error(&self) -> Option<&str> {
441        match self {
442            DispatchResult::Error(s) => Some(s),
443            _ => None,
444        }
445    }
446    pub fn success_kind(&self) -> Option<SuccessKind> {
447        match self {
448            DispatchResult::Handled(output) => Some(output.kind()),
449            DispatchResult::Binary(_, _) | DispatchResult::Artifact(_) | DispatchResult::Silent => {
450                Some(SuccessKind::Command)
451            }
452            _ => None,
453        }
454    }
455    pub fn error_kind(&self) -> Option<RunErrorKind> {
456        match self {
457            DispatchResult::Error(error) => Some(error.kind()),
458            _ => None,
459        }
460    }
461    pub fn exit_status(&self) -> Option<ExitStatus> {
462        match self {
463            DispatchResult::Handled(_)
464            | DispatchResult::Binary(_, _)
465            | DispatchResult::Artifact(_)
466            | DispatchResult::Silent => Some(ExitStatus::SUCCESS),
467            DispatchResult::Error(error) => Some(error.exit_status()),
468            DispatchResult::NoMatch(_) => None,
469        }
470    }
471    pub fn binary(&self) -> Option<(&[u8], &str)> {
472        match self {
473            DispatchResult::Binary(bytes, filename) => Some((bytes, filename)),
474            _ => None,
475        }
476    }
477    pub fn artifact(&self) -> Option<&ArtifactRun> {
478        match self {
479            DispatchResult::Artifact(run) => Some(run),
480            _ => None,
481        }
482    }
483    pub fn matches(&self) -> Option<&ArgMatches> {
484        match self {
485            DispatchResult::NoMatch(m) => Some(m),
486            _ => None,
487        }
488    }
489}
490pub trait Handler {
491    type Output: Serialize;
492    fn handle(&mut self, matches: &ArgMatches, ctx: &CommandContext)
493        -> HandlerResult<Self::Output>;
494    fn expected_args(&self) -> Vec<ExpectedArg> {
495        Vec::new()
496    }
497}
498pub struct FnHandler<F, T, R = HandlerResult<T>>
499where
500    T: Serialize,
501{
502    f: F,
503    _phantom: std::marker::PhantomData<fn() -> (T, R)>,
504}
505impl<F, T, R> FnHandler<F, T, R>
506where
507    F: FnMut(&ArgMatches, &CommandContext) -> R,
508    R: IntoHandlerResult<T>,
509    T: Serialize,
510{
511    pub fn new(f: F) -> Self {
512        Self {
513            f,
514            _phantom: std::marker::PhantomData,
515        }
516    }
517}
518impl<F, T, R> Handler for FnHandler<F, T, R>
519where
520    F: FnMut(&ArgMatches, &CommandContext) -> R,
521    R: IntoHandlerResult<T>,
522    T: Serialize,
523{
524    type Output = T;
525    fn handle(&mut self, matches: &ArgMatches, ctx: &CommandContext) -> HandlerResult<T> {
526        (self.f)(matches, ctx).into_handler_result()
527    }
528}
529pub struct SimpleFnHandler<F, T, R = HandlerResult<T>>
530where
531    T: Serialize,
532{
533    f: F,
534    _phantom: std::marker::PhantomData<fn() -> (T, R)>,
535}
536impl<F, T, R> SimpleFnHandler<F, T, R>
537where
538    F: FnMut(&ArgMatches) -> R,
539    R: IntoHandlerResult<T>,
540    T: Serialize,
541{
542    pub fn new(f: F) -> Self {
543        Self {
544            f,
545            _phantom: std::marker::PhantomData,
546        }
547    }
548}
549impl<F, T, R> Handler for SimpleFnHandler<F, T, R>
550where
551    F: FnMut(&ArgMatches) -> R,
552    R: IntoHandlerResult<T>,
553    T: Serialize,
554{
555    type Output = T;
556    fn handle(&mut self, matches: &ArgMatches, _ctx: &CommandContext) -> HandlerResult<T> {
557        (self.f)(matches).into_handler_result()
558    }
559}
560#[cfg(test)]
561mod tests {
562    use super::*;
563    use serde_json::json;
564    #[test]
565    fn test_command_context_creation() {
566        let ctx = CommandContext {
567            command_path: vec!["config".into(), "get".into()],
568            app_state: Rc::new(Extensions::new()),
569            extensions: Extensions::new(),
570        };
571        assert_eq!(ctx.command_path, vec!["config", "get"]);
572    }
573    #[test]
574    fn external_failure_rejects_success_and_preserves_metadata() {
575        assert_eq!(
576            ExternalFailure::new(0, "not a failure").unwrap_err(),
577            InvalidExternalStatus
578        );
579        let failure = ExternalFailure::new(128, "fatal: repository missing\n")
580            .unwrap()
581            .with_source(std::io::Error::other("git failed"));
582        assert_eq!(failure.exit_status().code(), 128);
583        assert_eq!(failure.diagnostic(), "fatal: repository missing\n");
584        assert_eq!(
585            std::error::Error::source(&failure).unwrap().to_string(),
586            "git failed"
587        );
588        let captured = RunError::from(failure);
589        assert_eq!(captured.kind(), RunErrorKind::External);
590        assert_eq!(captured.exit_status().code(), 128);
591        assert_eq!(captured.as_str(), "fatal: repository missing\n");
592        assert_eq!(
593            std::error::Error::source(&captured).unwrap().to_string(),
594            "git failed"
595        );
596    }
597    #[test]
598    #[should_panic(expected = "external run errors must be constructed from ExternalFailure")]
599    fn run_error_new_rejects_external_kind() {
600        let _ = RunError::new("inconsistent", RunErrorKind::External);
601    }
602    #[test]
603    fn test_command_context_default() {
604        let ctx = CommandContext::default();
605        assert!(ctx.command_path.is_empty());
606        assert!(ctx.extensions.is_empty());
607        assert!(ctx.app_state.is_empty());
608    }
609    #[test]
610    fn test_command_context_with_app_state() {
611        struct Database {
612            url: String,
613        }
614        struct Config {
615            debug: bool,
616        }
617        let mut app_state = Extensions::new();
618        app_state.insert(Database {
619            url: "postgres://localhost".into(),
620        });
621        app_state.insert(Config { debug: true });
622        let app_state = Rc::new(app_state);
623        let ctx = CommandContext {
624            command_path: vec!["list".into()],
625            app_state: app_state.clone(),
626            extensions: Extensions::new(),
627        };
628        let db = ctx.app_state.get::<Database>().unwrap();
629        assert_eq!(db.url, "postgres://localhost");
630        let config = ctx.app_state.get::<Config>().unwrap();
631        assert!(config.debug);
632        assert_eq!(Rc::strong_count(&ctx.app_state), 2);
633    }
634    #[test]
635    fn test_command_context_app_state_get_required() {
636        struct Present;
637        let mut app_state = Extensions::new();
638        app_state.insert(Present);
639        let ctx = CommandContext {
640            command_path: vec![],
641            app_state: Rc::new(app_state),
642            extensions: Extensions::new(),
643        };
644        assert!(ctx.app_state.get_required::<Present>().is_ok());
645        #[derive(Debug)]
646        struct Missing;
647        let err = ctx.app_state.get_required::<Missing>();
648        assert!(err.is_err());
649        assert!(err.unwrap_err().to_string().contains("Extension missing"));
650    }
651    #[test]
652    fn test_extensions_insert_and_get() {
653        struct MyState {
654            value: i32,
655        }
656        let mut ext = Extensions::new();
657        assert!(ext.is_empty());
658        ext.insert(MyState { value: 42 });
659        assert!(!ext.is_empty());
660        assert_eq!(ext.len(), 1);
661        let state = ext.get::<MyState>().unwrap();
662        assert_eq!(state.value, 42);
663    }
664    #[test]
665    fn test_extensions_get_mut() {
666        struct Counter {
667            count: i32,
668        }
669        let mut ext = Extensions::new();
670        ext.insert(Counter { count: 0 });
671        if let Some(counter) = ext.get_mut::<Counter>() {
672            counter.count += 1;
673        }
674        assert_eq!(ext.get::<Counter>().unwrap().count, 1);
675    }
676    #[test]
677    fn test_extensions_multiple_types() {
678        struct TypeA(i32);
679        struct TypeB(String);
680        let mut ext = Extensions::new();
681        ext.insert(TypeA(1));
682        ext.insert(TypeB("hello".into()));
683        assert_eq!(ext.len(), 2);
684        assert_eq!(ext.get::<TypeA>().unwrap().0, 1);
685        assert_eq!(ext.get::<TypeB>().unwrap().0, "hello");
686    }
687    #[test]
688    fn test_extensions_replace() {
689        struct Value(i32);
690        let mut ext = Extensions::new();
691        ext.insert(Value(1));
692        let old = ext.insert(Value(2));
693        assert_eq!(old.unwrap().0, 1);
694        assert_eq!(ext.get::<Value>().unwrap().0, 2);
695    }
696    #[test]
697    fn test_extensions_remove() {
698        struct Value(i32);
699        let mut ext = Extensions::new();
700        ext.insert(Value(42));
701        let removed = ext.remove::<Value>();
702        assert_eq!(removed.unwrap().0, 42);
703        assert!(ext.is_empty());
704        assert!(ext.get::<Value>().is_none());
705    }
706    #[test]
707    fn test_extensions_contains() {
708        struct Present;
709        struct Absent;
710        let mut ext = Extensions::new();
711        ext.insert(Present);
712        assert!(ext.contains::<Present>());
713        assert!(!ext.contains::<Absent>());
714    }
715    #[test]
716    fn test_extensions_clear() {
717        struct A;
718        struct B;
719        let mut ext = Extensions::new();
720        ext.insert(A);
721        ext.insert(B);
722        assert_eq!(ext.len(), 2);
723        ext.clear();
724        assert!(ext.is_empty());
725    }
726    #[test]
727    fn test_extensions_missing_type_returns_none() {
728        struct NotInserted;
729        let ext = Extensions::new();
730        assert!(ext.get::<NotInserted>().is_none());
731    }
732    #[test]
733    fn test_extensions_get_required() {
734        #[derive(Debug)]
735        struct Config {
736            value: i32,
737        }
738        let mut ext = Extensions::new();
739        ext.insert(Config { value: 100 });
740        let val = ext.get_required::<Config>();
741        assert!(val.is_ok());
742        assert_eq!(val.unwrap().value, 100);
743        #[derive(Debug)]
744        struct Missing;
745        let err = ext.get_required::<Missing>();
746        assert!(err.is_err());
747        assert!(err
748            .unwrap_err()
749            .to_string()
750            .contains("Extension missing: type"));
751    }
752    #[test]
753    fn test_extensions_get_mut_required() {
754        #[derive(Debug)]
755        struct State {
756            count: i32,
757        }
758        let mut ext = Extensions::new();
759        ext.insert(State { count: 0 });
760        {
761            let val = ext.get_mut_required::<State>();
762            assert!(val.is_ok());
763            val.unwrap().count += 1;
764        }
765        assert_eq!(ext.get_required::<State>().unwrap().count, 1);
766        #[derive(Debug)]
767        struct Missing;
768        let err = ext.get_mut_required::<Missing>();
769        assert!(err.is_err());
770    }
771    #[test]
772    fn test_extensions_clone_behavior() {
773        struct Data(#[allow(dead_code)] i32);
774        let mut original = Extensions::new();
775        original.insert(Data(42));
776        let cloned = original.clone();
777        assert!(original.get::<Data>().is_some());
778        assert!(cloned.is_empty());
779        assert!(cloned.get::<Data>().is_none());
780    }
781    #[test]
782    fn test_output_render() {
783        let output: Output<String> = Output::Render("success".into());
784        assert!(output.is_render());
785        assert!(!output.is_silent());
786        assert!(!output.is_binary());
787    }
788    #[test]
789    fn test_output_silent() {
790        let output: Output<String> = Output::Silent;
791        assert!(!output.is_render());
792        assert!(output.is_silent());
793        assert!(!output.is_binary());
794    }
795    #[test]
796    fn test_output_binary() {
797        let output: Output<String> = Output::Binary {
798            data: vec![0x25, 0x50, 0x44, 0x46],
799            filename: "report.pdf".into(),
800        };
801        assert!(!output.is_render());
802        assert!(!output.is_silent());
803        assert!(output.is_binary());
804    }
805    #[test]
806    fn test_run_result_handled() {
807        let result = DispatchResult::Handled("output".into());
808        assert!(result.is_handled());
809        assert!(!result.is_binary());
810        assert!(!result.is_silent());
811        assert_eq!(result.output(), Some("output"));
812        assert!(result.matches().is_none());
813    }
814    #[test]
815    fn test_run_result_silent() {
816        let result = DispatchResult::Silent;
817        assert!(!result.is_handled());
818        assert!(!result.is_binary());
819        assert!(result.is_silent());
820    }
821    #[test]
822    fn test_run_result_binary() {
823        let bytes = vec![0x25, 0x50, 0x44, 0x46];
824        let result = DispatchResult::Binary(bytes.clone(), "report.pdf".into());
825        assert!(!result.is_handled());
826        assert!(result.is_binary());
827        assert!(!result.is_silent());
828        let (data, filename) = result.binary().unwrap();
829        assert_eq!(data, &bytes);
830        assert_eq!(filename, "report.pdf");
831    }
832    #[test]
833    fn test_run_result_no_match() {
834        let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
835        let result = DispatchResult::NoMatch(matches);
836        assert!(!result.is_handled());
837        assert!(!result.is_binary());
838        assert!(result.matches().is_some());
839    }
840    #[test]
841    fn test_fn_handler() {
842        let mut handler = FnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| {
843            Ok(Output::Render(json!({"status": "ok"})))
844        });
845        let ctx = CommandContext::default();
846        let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
847        let result = handler.handle(&matches, &ctx);
848        assert!(result.is_ok());
849    }
850    #[test]
851    fn test_fn_handler_mutation() {
852        let mut counter = 0u32;
853        let mut handler = FnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| {
854            counter += 1;
855            Ok(Output::Render(counter))
856        });
857        let ctx = CommandContext::default();
858        let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
859        let _ = handler.handle(&matches, &ctx);
860        let _ = handler.handle(&matches, &ctx);
861        let result = handler.handle(&matches, &ctx);
862        assert!(result.is_ok());
863        if let Ok(Output::Render(count)) = result {
864            assert_eq!(count, 3);
865        }
866    }
867    #[test]
868    fn test_into_handler_result_from_result_ok() {
869        use super::IntoHandlerResult;
870        let result: Result<String, anyhow::Error> = Ok("hello".to_string());
871        let handler_result = result.into_handler_result();
872        assert!(handler_result.is_ok());
873        match handler_result.unwrap() {
874            Output::Render(s) => assert_eq!(s, "hello"),
875            _ => panic!("Expected Output::Render"),
876        }
877    }
878    #[test]
879    fn test_into_handler_result_from_result_err() {
880        use super::IntoHandlerResult;
881        let result: Result<String, anyhow::Error> = Err(anyhow::anyhow!("test error"));
882        let handler_result = result.into_handler_result();
883        assert!(handler_result.is_err());
884        assert!(handler_result
885            .unwrap_err()
886            .to_string()
887            .contains("test error"));
888    }
889    #[test]
890    fn test_into_handler_result_passthrough_render() {
891        use super::IntoHandlerResult;
892        let handler_result: HandlerResult<String> = Ok(Output::Render("hello".to_string()));
893        let result = handler_result.into_handler_result();
894        assert!(result.is_ok());
895        match result.unwrap() {
896            Output::Render(s) => assert_eq!(s, "hello"),
897            _ => panic!("Expected Output::Render"),
898        }
899    }
900    #[test]
901    fn test_into_handler_result_passthrough_silent() {
902        use super::IntoHandlerResult;
903        let handler_result: HandlerResult<String> = Ok(Output::Silent);
904        let result = handler_result.into_handler_result();
905        assert!(result.is_ok());
906        assert!(matches!(result.unwrap(), Output::Silent));
907    }
908    #[test]
909    fn test_into_handler_result_passthrough_binary() {
910        use super::IntoHandlerResult;
911        let handler_result: HandlerResult<String> = Ok(Output::Binary {
912            data: vec![1, 2, 3],
913            filename: "test.bin".to_string(),
914        });
915        let result = handler_result.into_handler_result();
916        assert!(result.is_ok());
917        match result.unwrap() {
918            Output::Binary { data, filename } => {
919                assert_eq!(data, vec![1, 2, 3]);
920                assert_eq!(filename, "test.bin");
921            }
922            _ => panic!("Expected Output::Binary"),
923        }
924    }
925    #[test]
926    fn test_fn_handler_with_auto_wrap() {
927        let mut handler = FnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| {
928            Ok::<_, anyhow::Error>("auto-wrapped".to_string())
929        });
930        let ctx = CommandContext::default();
931        let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
932        let result = handler.handle(&matches, &ctx);
933        assert!(result.is_ok());
934        match result.unwrap() {
935            Output::Render(s) => assert_eq!(s, "auto-wrapped"),
936            _ => panic!("Expected Output::Render"),
937        }
938    }
939    #[test]
940    fn test_fn_handler_with_explicit_output() {
941        let mut handler =
942            FnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| Ok(Output::<()>::Silent));
943        let ctx = CommandContext::default();
944        let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
945        let result = handler.handle(&matches, &ctx);
946        assert!(result.is_ok());
947        assert!(matches!(result.unwrap(), Output::Silent));
948    }
949    #[test]
950    fn test_fn_handler_with_custom_error_type() {
951        #[derive(Debug)]
952        struct CustomError(String);
953        impl std::fmt::Display for CustomError {
954            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
955                write!(f, "CustomError: {}", self.0)
956            }
957        }
958        impl std::error::Error for CustomError {}
959        let mut handler = FnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| {
960            Err::<String, CustomError>(CustomError("oops".to_string()))
961        });
962        let ctx = CommandContext::default();
963        let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
964        let result = handler.handle(&matches, &ctx);
965        assert!(result.is_err());
966        assert!(result
967            .unwrap_err()
968            .to_string()
969            .contains("CustomError: oops"));
970    }
971    #[test]
972    fn test_simple_fn_handler_basic() {
973        use super::SimpleFnHandler;
974        let mut handler = SimpleFnHandler::new(|_m: &ArgMatches| {
975            Ok::<_, anyhow::Error>("no context needed".to_string())
976        });
977        let ctx = CommandContext::default();
978        let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
979        let result = handler.handle(&matches, &ctx);
980        assert!(result.is_ok());
981        match result.unwrap() {
982            Output::Render(s) => assert_eq!(s, "no context needed"),
983            _ => panic!("Expected Output::Render"),
984        }
985    }
986    #[test]
987    fn test_simple_fn_handler_with_args() {
988        use super::SimpleFnHandler;
989        let mut handler = SimpleFnHandler::new(|m: &ArgMatches| {
990            let verbose = m.get_flag("verbose");
991            Ok::<_, anyhow::Error>(verbose)
992        });
993        let ctx = CommandContext::default();
994        let matches = clap::Command::new("test")
995            .arg(
996                clap::Arg::new("verbose")
997                    .short('v')
998                    .action(clap::ArgAction::SetTrue),
999            )
1000            .get_matches_from(vec!["test", "-v"]);
1001        let result = handler.handle(&matches, &ctx);
1002        assert!(result.is_ok());
1003        match result.unwrap() {
1004            Output::Render(v) => assert!(v),
1005            _ => panic!("Expected Output::Render"),
1006        }
1007    }
1008    #[test]
1009    fn test_simple_fn_handler_explicit_output() {
1010        use super::SimpleFnHandler;
1011        let mut handler = SimpleFnHandler::new(|_m: &ArgMatches| Ok(Output::<()>::Silent));
1012        let ctx = CommandContext::default();
1013        let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1014        let result = handler.handle(&matches, &ctx);
1015        assert!(result.is_ok());
1016        assert!(matches!(result.unwrap(), Output::Silent));
1017    }
1018    #[test]
1019    fn test_simple_fn_handler_error() {
1020        use super::SimpleFnHandler;
1021        let mut handler = SimpleFnHandler::new(|_m: &ArgMatches| {
1022            Err::<String, _>(anyhow::anyhow!("simple error"))
1023        });
1024        let ctx = CommandContext::default();
1025        let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1026        let result = handler.handle(&matches, &ctx);
1027        assert!(result.is_err());
1028        assert!(result.unwrap_err().to_string().contains("simple error"));
1029    }
1030    #[test]
1031    fn test_simple_fn_handler_mutation() {
1032        use super::SimpleFnHandler;
1033        let mut counter = 0u32;
1034        let mut handler = SimpleFnHandler::new(|_m: &ArgMatches| {
1035            counter += 1;
1036            Ok::<_, anyhow::Error>(counter)
1037        });
1038        let ctx = CommandContext::default();
1039        let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1040        let _ = handler.handle(&matches, &ctx);
1041        let _ = handler.handle(&matches, &ctx);
1042        let result = handler.handle(&matches, &ctx);
1043        assert!(result.is_ok());
1044        match result.unwrap() {
1045            Output::Render(n) => assert_eq!(n, 3),
1046            _ => panic!("Expected Output::Render"),
1047        }
1048    }
1049}