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