1use crate::verify::ExpectedArg;
68use clap::ArgMatches;
69use serde::Serialize;
70use std::any::{Any, TypeId};
71use std::collections::HashMap;
72use std::fmt;
73use std::rc::Rc;
74
75#[derive(Default)]
108pub struct Extensions {
109 map: HashMap<TypeId, Box<dyn Any>>,
110}
111
112impl Extensions {
113 pub fn new() -> Self {
115 Self::default()
116 }
117
118 pub fn insert<T: 'static>(&mut self, val: T) -> Option<T> {
122 self.map
123 .insert(TypeId::of::<T>(), Box::new(val))
124 .and_then(|boxed| boxed.downcast().ok().map(|b| *b))
125 }
126
127 pub fn get<T: 'static>(&self) -> Option<&T> {
131 self.map
132 .get(&TypeId::of::<T>())
133 .and_then(|boxed| boxed.downcast_ref())
134 }
135
136 pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> {
140 self.map
141 .get_mut(&TypeId::of::<T>())
142 .and_then(|boxed| boxed.downcast_mut())
143 }
144
145 pub fn get_required<T: 'static>(&self) -> Result<&T, anyhow::Error> {
149 self.get::<T>().ok_or_else(|| {
150 anyhow::anyhow!(
151 "Extension missing: type {} not found in context",
152 std::any::type_name::<T>()
153 )
154 })
155 }
156
157 pub fn get_mut_required<T: 'static>(&mut self) -> Result<&mut T, anyhow::Error> {
161 self.get_mut::<T>().ok_or_else(|| {
162 anyhow::anyhow!(
163 "Extension missing: type {} not found in context",
164 std::any::type_name::<T>()
165 )
166 })
167 }
168
169 pub fn remove<T: 'static>(&mut self) -> Option<T> {
171 self.map
172 .remove(&TypeId::of::<T>())
173 .and_then(|boxed| boxed.downcast().ok().map(|b| *b))
174 }
175
176 pub fn contains<T: 'static>(&self) -> bool {
178 self.map.contains_key(&TypeId::of::<T>())
179 }
180
181 pub fn len(&self) -> usize {
183 self.map.len()
184 }
185
186 pub fn is_empty(&self) -> bool {
188 self.map.is_empty()
189 }
190
191 pub fn clear(&mut self) {
193 self.map.clear();
194 }
195}
196
197impl fmt::Debug for Extensions {
198 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199 f.debug_struct("Extensions")
200 .field("len", &self.map.len())
201 .finish_non_exhaustive()
202 }
203}
204
205impl Clone for Extensions {
206 fn clone(&self) -> Self {
207 Self::new()
211 }
212}
213
214#[derive(Debug)]
294pub struct CommandContext {
295 pub command_path: Vec<String>,
297
298 pub app_state: Rc<Extensions>,
305
306 pub extensions: Extensions,
311}
312
313impl CommandContext {
314 pub fn new(command_path: Vec<String>, app_state: Rc<Extensions>) -> Self {
318 Self {
319 command_path,
320 app_state,
321 extensions: Extensions::new(),
322 }
323 }
324}
325
326impl Default for CommandContext {
327 fn default() -> Self {
328 Self {
329 command_path: Vec::new(),
330 app_state: Rc::new(Extensions::new()),
331 extensions: Extensions::new(),
332 }
333 }
334}
335
336#[derive(Debug)]
340pub enum Output<T: Serialize> {
341 Render(T),
343 Silent,
345 Binary {
347 data: Vec<u8>,
349 filename: String,
351 },
352}
353
354impl<T: Serialize> Output<T> {
355 pub fn is_render(&self) -> bool {
357 matches!(self, Output::Render(_))
358 }
359
360 pub fn is_silent(&self) -> bool {
362 matches!(self, Output::Silent)
363 }
364
365 pub fn is_binary(&self) -> bool {
367 matches!(self, Output::Binary { .. })
368 }
369}
370
371pub type HandlerResult<T> = Result<Output<T>, anyhow::Error>;
375
376pub trait IntoHandlerResult<T: Serialize> {
402 fn into_handler_result(self) -> HandlerResult<T>;
404}
405
406impl<T, E> IntoHandlerResult<T> for Result<T, E>
411where
412 T: Serialize,
413 E: Into<anyhow::Error>,
414{
415 fn into_handler_result(self) -> HandlerResult<T> {
416 self.map(Output::Render).map_err(Into::into)
417 }
418}
419
420impl<T: Serialize> IntoHandlerResult<T> for HandlerResult<T> {
425 fn into_handler_result(self) -> HandlerResult<T> {
426 self
427 }
428}
429
430#[derive(Debug)]
435pub enum RunResult {
436 Handled(String),
438 Binary(Vec<u8>, String),
440 Silent,
442 NoMatch(ArgMatches),
444}
445
446impl RunResult {
447 pub fn is_handled(&self) -> bool {
449 matches!(self, RunResult::Handled(_))
450 }
451
452 pub fn is_binary(&self) -> bool {
454 matches!(self, RunResult::Binary(_, _))
455 }
456
457 pub fn is_silent(&self) -> bool {
459 matches!(self, RunResult::Silent)
460 }
461
462 pub fn output(&self) -> Option<&str> {
464 match self {
465 RunResult::Handled(s) => Some(s),
466 _ => None,
467 }
468 }
469
470 pub fn binary(&self) -> Option<(&[u8], &str)> {
472 match self {
473 RunResult::Binary(bytes, filename) => Some((bytes, filename)),
474 _ => None,
475 }
476 }
477
478 pub fn matches(&self) -> Option<&ArgMatches> {
480 match self {
481 RunResult::NoMatch(m) => Some(m),
482 _ => None,
483 }
484 }
485}
486
487pub trait Handler {
511 type Output: Serialize;
513
514 fn handle(&mut self, matches: &ArgMatches, ctx: &CommandContext)
516 -> HandlerResult<Self::Output>;
517
518 fn expected_args(&self) -> Vec<ExpectedArg> {
523 Vec::new()
524 }
525}
526
527pub struct FnHandler<F, T, R = HandlerResult<T>>
550where
551 T: Serialize,
552{
553 f: F,
554 _phantom: std::marker::PhantomData<fn() -> (T, R)>,
555}
556
557impl<F, T, R> FnHandler<F, T, R>
558where
559 F: FnMut(&ArgMatches, &CommandContext) -> R,
560 R: IntoHandlerResult<T>,
561 T: Serialize,
562{
563 pub fn new(f: F) -> Self {
565 Self {
566 f,
567 _phantom: std::marker::PhantomData,
568 }
569 }
570}
571
572impl<F, T, R> Handler for FnHandler<F, T, R>
573where
574 F: FnMut(&ArgMatches, &CommandContext) -> R,
575 R: IntoHandlerResult<T>,
576 T: Serialize,
577{
578 type Output = T;
579
580 fn handle(&mut self, matches: &ArgMatches, ctx: &CommandContext) -> HandlerResult<T> {
581 (self.f)(matches, ctx).into_handler_result()
582 }
583}
584
585pub struct SimpleFnHandler<F, T, R = HandlerResult<T>>
612where
613 T: Serialize,
614{
615 f: F,
616 _phantom: std::marker::PhantomData<fn() -> (T, R)>,
617}
618
619impl<F, T, R> SimpleFnHandler<F, T, R>
620where
621 F: FnMut(&ArgMatches) -> R,
622 R: IntoHandlerResult<T>,
623 T: Serialize,
624{
625 pub fn new(f: F) -> Self {
627 Self {
628 f,
629 _phantom: std::marker::PhantomData,
630 }
631 }
632}
633
634impl<F, T, R> Handler for SimpleFnHandler<F, T, R>
635where
636 F: FnMut(&ArgMatches) -> R,
637 R: IntoHandlerResult<T>,
638 T: Serialize,
639{
640 type Output = T;
641
642 fn handle(&mut self, matches: &ArgMatches, _ctx: &CommandContext) -> HandlerResult<T> {
643 (self.f)(matches).into_handler_result()
644 }
645}
646
647#[cfg(test)]
648mod tests {
649 use super::*;
650 use serde_json::json;
651
652 #[test]
653 fn test_command_context_creation() {
654 let ctx = CommandContext {
655 command_path: vec!["config".into(), "get".into()],
656 app_state: Rc::new(Extensions::new()),
657 extensions: Extensions::new(),
658 };
659 assert_eq!(ctx.command_path, vec!["config", "get"]);
660 }
661
662 #[test]
663 fn test_command_context_default() {
664 let ctx = CommandContext::default();
665 assert!(ctx.command_path.is_empty());
666 assert!(ctx.extensions.is_empty());
667 assert!(ctx.app_state.is_empty());
668 }
669
670 #[test]
671 fn test_command_context_with_app_state() {
672 struct Database {
673 url: String,
674 }
675 struct Config {
676 debug: bool,
677 }
678
679 let mut app_state = Extensions::new();
681 app_state.insert(Database {
682 url: "postgres://localhost".into(),
683 });
684 app_state.insert(Config { debug: true });
685 let app_state = Rc::new(app_state);
686
687 let ctx = CommandContext {
689 command_path: vec!["list".into()],
690 app_state: app_state.clone(),
691 extensions: Extensions::new(),
692 };
693
694 let db = ctx.app_state.get::<Database>().unwrap();
696 assert_eq!(db.url, "postgres://localhost");
697
698 let config = ctx.app_state.get::<Config>().unwrap();
699 assert!(config.debug);
700
701 assert_eq!(Rc::strong_count(&ctx.app_state), 2);
703 }
704
705 #[test]
706 fn test_command_context_app_state_get_required() {
707 struct Present;
708
709 let mut app_state = Extensions::new();
710 app_state.insert(Present);
711
712 let ctx = CommandContext {
713 command_path: vec![],
714 app_state: Rc::new(app_state),
715 extensions: Extensions::new(),
716 };
717
718 assert!(ctx.app_state.get_required::<Present>().is_ok());
720
721 #[derive(Debug)]
723 struct Missing;
724 let err = ctx.app_state.get_required::<Missing>();
725 assert!(err.is_err());
726 assert!(err.unwrap_err().to_string().contains("Extension missing"));
727 }
728
729 #[test]
731 fn test_extensions_insert_and_get() {
732 struct MyState {
733 value: i32,
734 }
735
736 let mut ext = Extensions::new();
737 assert!(ext.is_empty());
738
739 ext.insert(MyState { value: 42 });
740 assert!(!ext.is_empty());
741 assert_eq!(ext.len(), 1);
742
743 let state = ext.get::<MyState>().unwrap();
744 assert_eq!(state.value, 42);
745 }
746
747 #[test]
748 fn test_extensions_get_mut() {
749 struct Counter {
750 count: i32,
751 }
752
753 let mut ext = Extensions::new();
754 ext.insert(Counter { count: 0 });
755
756 if let Some(counter) = ext.get_mut::<Counter>() {
757 counter.count += 1;
758 }
759
760 assert_eq!(ext.get::<Counter>().unwrap().count, 1);
761 }
762
763 #[test]
764 fn test_extensions_multiple_types() {
765 struct TypeA(i32);
766 struct TypeB(String);
767
768 let mut ext = Extensions::new();
769 ext.insert(TypeA(1));
770 ext.insert(TypeB("hello".into()));
771
772 assert_eq!(ext.len(), 2);
773 assert_eq!(ext.get::<TypeA>().unwrap().0, 1);
774 assert_eq!(ext.get::<TypeB>().unwrap().0, "hello");
775 }
776
777 #[test]
778 fn test_extensions_replace() {
779 struct Value(i32);
780
781 let mut ext = Extensions::new();
782 ext.insert(Value(1));
783
784 let old = ext.insert(Value(2));
785 assert_eq!(old.unwrap().0, 1);
786 assert_eq!(ext.get::<Value>().unwrap().0, 2);
787 }
788
789 #[test]
790 fn test_extensions_remove() {
791 struct Value(i32);
792
793 let mut ext = Extensions::new();
794 ext.insert(Value(42));
795
796 let removed = ext.remove::<Value>();
797 assert_eq!(removed.unwrap().0, 42);
798 assert!(ext.is_empty());
799 assert!(ext.get::<Value>().is_none());
800 }
801
802 #[test]
803 fn test_extensions_contains() {
804 struct Present;
805 struct Absent;
806
807 let mut ext = Extensions::new();
808 ext.insert(Present);
809
810 assert!(ext.contains::<Present>());
811 assert!(!ext.contains::<Absent>());
812 }
813
814 #[test]
815 fn test_extensions_clear() {
816 struct A;
817 struct B;
818
819 let mut ext = Extensions::new();
820 ext.insert(A);
821 ext.insert(B);
822 assert_eq!(ext.len(), 2);
823
824 ext.clear();
825 assert!(ext.is_empty());
826 }
827
828 #[test]
829 fn test_extensions_missing_type_returns_none() {
830 struct NotInserted;
831
832 let ext = Extensions::new();
833 assert!(ext.get::<NotInserted>().is_none());
834 }
835
836 #[test]
837 fn test_extensions_get_required() {
838 #[derive(Debug)]
839 struct Config {
840 value: i32,
841 }
842
843 let mut ext = Extensions::new();
844 ext.insert(Config { value: 100 });
845
846 let val = ext.get_required::<Config>();
848 assert!(val.is_ok());
849 assert_eq!(val.unwrap().value, 100);
850
851 #[derive(Debug)]
853 struct Missing;
854 let err = ext.get_required::<Missing>();
855 assert!(err.is_err());
856 assert!(err
857 .unwrap_err()
858 .to_string()
859 .contains("Extension missing: type"));
860 }
861
862 #[test]
863 fn test_extensions_get_mut_required() {
864 #[derive(Debug)]
865 struct State {
866 count: i32,
867 }
868
869 let mut ext = Extensions::new();
870 ext.insert(State { count: 0 });
871
872 {
874 let val = ext.get_mut_required::<State>();
875 assert!(val.is_ok());
876 val.unwrap().count += 1;
877 }
878 assert_eq!(ext.get_required::<State>().unwrap().count, 1);
879
880 #[derive(Debug)]
882 struct Missing;
883 let err = ext.get_mut_required::<Missing>();
884 assert!(err.is_err());
885 }
886
887 #[test]
888 fn test_extensions_clone_behavior() {
889 struct Data(i32);
891
892 let mut original = Extensions::new();
893 original.insert(Data(42));
894
895 let cloned = original.clone();
896
897 assert!(original.get::<Data>().is_some());
899
900 assert!(cloned.is_empty());
902 assert!(cloned.get::<Data>().is_none());
903 }
904
905 #[test]
906 fn test_output_render() {
907 let output: Output<String> = Output::Render("success".into());
908 assert!(output.is_render());
909 assert!(!output.is_silent());
910 assert!(!output.is_binary());
911 }
912
913 #[test]
914 fn test_output_silent() {
915 let output: Output<String> = Output::Silent;
916 assert!(!output.is_render());
917 assert!(output.is_silent());
918 assert!(!output.is_binary());
919 }
920
921 #[test]
922 fn test_output_binary() {
923 let output: Output<String> = Output::Binary {
924 data: vec![0x25, 0x50, 0x44, 0x46],
925 filename: "report.pdf".into(),
926 };
927 assert!(!output.is_render());
928 assert!(!output.is_silent());
929 assert!(output.is_binary());
930 }
931
932 #[test]
933 fn test_run_result_handled() {
934 let result = RunResult::Handled("output".into());
935 assert!(result.is_handled());
936 assert!(!result.is_binary());
937 assert!(!result.is_silent());
938 assert_eq!(result.output(), Some("output"));
939 assert!(result.matches().is_none());
940 }
941
942 #[test]
943 fn test_run_result_silent() {
944 let result = RunResult::Silent;
945 assert!(!result.is_handled());
946 assert!(!result.is_binary());
947 assert!(result.is_silent());
948 }
949
950 #[test]
951 fn test_run_result_binary() {
952 let bytes = vec![0x25, 0x50, 0x44, 0x46];
953 let result = RunResult::Binary(bytes.clone(), "report.pdf".into());
954 assert!(!result.is_handled());
955 assert!(result.is_binary());
956 assert!(!result.is_silent());
957
958 let (data, filename) = result.binary().unwrap();
959 assert_eq!(data, &bytes);
960 assert_eq!(filename, "report.pdf");
961 }
962
963 #[test]
964 fn test_run_result_no_match() {
965 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
966 let result = RunResult::NoMatch(matches);
967 assert!(!result.is_handled());
968 assert!(!result.is_binary());
969 assert!(result.matches().is_some());
970 }
971
972 #[test]
973 fn test_fn_handler() {
974 let mut handler = FnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| {
975 Ok(Output::Render(json!({"status": "ok"})))
976 });
977
978 let ctx = CommandContext::default();
979 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
980
981 let result = handler.handle(&matches, &ctx);
982 assert!(result.is_ok());
983 }
984
985 #[test]
986 fn test_fn_handler_mutation() {
987 let mut counter = 0u32;
988
989 let mut handler = FnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| {
990 counter += 1;
991 Ok(Output::Render(counter))
992 });
993
994 let ctx = CommandContext::default();
995 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
996
997 let _ = handler.handle(&matches, &ctx);
998 let _ = handler.handle(&matches, &ctx);
999 let result = handler.handle(&matches, &ctx);
1000
1001 assert!(result.is_ok());
1002 if let Ok(Output::Render(count)) = result {
1003 assert_eq!(count, 3);
1004 }
1005 }
1006
1007 #[test]
1009 fn test_into_handler_result_from_result_ok() {
1010 use super::IntoHandlerResult;
1011
1012 let result: Result<String, anyhow::Error> = Ok("hello".to_string());
1013 let handler_result = result.into_handler_result();
1014
1015 assert!(handler_result.is_ok());
1016 match handler_result.unwrap() {
1017 Output::Render(s) => assert_eq!(s, "hello"),
1018 _ => panic!("Expected Output::Render"),
1019 }
1020 }
1021
1022 #[test]
1023 fn test_into_handler_result_from_result_err() {
1024 use super::IntoHandlerResult;
1025
1026 let result: Result<String, anyhow::Error> = Err(anyhow::anyhow!("test error"));
1027 let handler_result = result.into_handler_result();
1028
1029 assert!(handler_result.is_err());
1030 assert!(handler_result
1031 .unwrap_err()
1032 .to_string()
1033 .contains("test error"));
1034 }
1035
1036 #[test]
1037 fn test_into_handler_result_passthrough_render() {
1038 use super::IntoHandlerResult;
1039
1040 let handler_result: HandlerResult<String> = Ok(Output::Render("hello".to_string()));
1041 let result = handler_result.into_handler_result();
1042
1043 assert!(result.is_ok());
1044 match result.unwrap() {
1045 Output::Render(s) => assert_eq!(s, "hello"),
1046 _ => panic!("Expected Output::Render"),
1047 }
1048 }
1049
1050 #[test]
1051 fn test_into_handler_result_passthrough_silent() {
1052 use super::IntoHandlerResult;
1053
1054 let handler_result: HandlerResult<String> = Ok(Output::Silent);
1055 let result = handler_result.into_handler_result();
1056
1057 assert!(result.is_ok());
1058 assert!(matches!(result.unwrap(), Output::Silent));
1059 }
1060
1061 #[test]
1062 fn test_into_handler_result_passthrough_binary() {
1063 use super::IntoHandlerResult;
1064
1065 let handler_result: HandlerResult<String> = Ok(Output::Binary {
1066 data: vec![1, 2, 3],
1067 filename: "test.bin".to_string(),
1068 });
1069 let result = handler_result.into_handler_result();
1070
1071 assert!(result.is_ok());
1072 match result.unwrap() {
1073 Output::Binary { data, filename } => {
1074 assert_eq!(data, vec![1, 2, 3]);
1075 assert_eq!(filename, "test.bin");
1076 }
1077 _ => panic!("Expected Output::Binary"),
1078 }
1079 }
1080
1081 #[test]
1082 fn test_fn_handler_with_auto_wrap() {
1083 let mut handler = FnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| {
1085 Ok::<_, anyhow::Error>("auto-wrapped".to_string())
1086 });
1087
1088 let ctx = CommandContext::default();
1089 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1090
1091 let result = handler.handle(&matches, &ctx);
1092 assert!(result.is_ok());
1093 match result.unwrap() {
1094 Output::Render(s) => assert_eq!(s, "auto-wrapped"),
1095 _ => panic!("Expected Output::Render"),
1096 }
1097 }
1098
1099 #[test]
1100 fn test_fn_handler_with_explicit_output() {
1101 let mut handler =
1103 FnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| Ok(Output::<()>::Silent));
1104
1105 let ctx = CommandContext::default();
1106 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1107
1108 let result = handler.handle(&matches, &ctx);
1109 assert!(result.is_ok());
1110 assert!(matches!(result.unwrap(), Output::Silent));
1111 }
1112
1113 #[test]
1114 fn test_fn_handler_with_custom_error_type() {
1115 #[derive(Debug)]
1117 struct CustomError(String);
1118
1119 impl std::fmt::Display for CustomError {
1120 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1121 write!(f, "CustomError: {}", self.0)
1122 }
1123 }
1124
1125 impl std::error::Error for CustomError {}
1126
1127 let mut handler = FnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| {
1128 Err::<String, CustomError>(CustomError("oops".to_string()))
1129 });
1130
1131 let ctx = CommandContext::default();
1132 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1133
1134 let result = handler.handle(&matches, &ctx);
1135 assert!(result.is_err());
1136 assert!(result
1137 .unwrap_err()
1138 .to_string()
1139 .contains("CustomError: oops"));
1140 }
1141
1142 #[test]
1144 fn test_simple_fn_handler_basic() {
1145 use super::SimpleFnHandler;
1146
1147 let mut handler = SimpleFnHandler::new(|_m: &ArgMatches| {
1148 Ok::<_, anyhow::Error>("no context needed".to_string())
1149 });
1150
1151 let ctx = CommandContext::default();
1152 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1153
1154 let result = handler.handle(&matches, &ctx);
1155 assert!(result.is_ok());
1156 match result.unwrap() {
1157 Output::Render(s) => assert_eq!(s, "no context needed"),
1158 _ => panic!("Expected Output::Render"),
1159 }
1160 }
1161
1162 #[test]
1163 fn test_simple_fn_handler_with_args() {
1164 use super::SimpleFnHandler;
1165
1166 let mut handler = SimpleFnHandler::new(|m: &ArgMatches| {
1167 let verbose = m.get_flag("verbose");
1168 Ok::<_, anyhow::Error>(verbose)
1169 });
1170
1171 let ctx = CommandContext::default();
1172 let matches = clap::Command::new("test")
1173 .arg(
1174 clap::Arg::new("verbose")
1175 .short('v')
1176 .action(clap::ArgAction::SetTrue),
1177 )
1178 .get_matches_from(vec!["test", "-v"]);
1179
1180 let result = handler.handle(&matches, &ctx);
1181 assert!(result.is_ok());
1182 match result.unwrap() {
1183 Output::Render(v) => assert!(v),
1184 _ => panic!("Expected Output::Render"),
1185 }
1186 }
1187
1188 #[test]
1189 fn test_simple_fn_handler_explicit_output() {
1190 use super::SimpleFnHandler;
1191
1192 let mut handler = SimpleFnHandler::new(|_m: &ArgMatches| Ok(Output::<()>::Silent));
1193
1194 let ctx = CommandContext::default();
1195 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1196
1197 let result = handler.handle(&matches, &ctx);
1198 assert!(result.is_ok());
1199 assert!(matches!(result.unwrap(), Output::Silent));
1200 }
1201
1202 #[test]
1203 fn test_simple_fn_handler_error() {
1204 use super::SimpleFnHandler;
1205
1206 let mut handler = SimpleFnHandler::new(|_m: &ArgMatches| {
1207 Err::<String, _>(anyhow::anyhow!("simple error"))
1208 });
1209
1210 let ctx = CommandContext::default();
1211 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1212
1213 let result = handler.handle(&matches, &ctx);
1214 assert!(result.is_err());
1215 assert!(result.unwrap_err().to_string().contains("simple error"));
1216 }
1217
1218 #[test]
1219 fn test_simple_fn_handler_mutation() {
1220 use super::SimpleFnHandler;
1221
1222 let mut counter = 0u32;
1223 let mut handler = SimpleFnHandler::new(|_m: &ArgMatches| {
1224 counter += 1;
1225 Ok::<_, anyhow::Error>(counter)
1226 });
1227
1228 let ctx = CommandContext::default();
1229 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1230
1231 let _ = handler.handle(&matches, &ctx);
1232 let _ = handler.handle(&matches, &ctx);
1233 let result = handler.handle(&matches, &ctx);
1234
1235 assert!(result.is_ok());
1236 match result.unwrap() {
1237 Output::Render(n) => assert_eq!(n, 3),
1238 _ => panic!("Expected Output::Render"),
1239 }
1240 }
1241}