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 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 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}