1use clap::ArgMatches;
69use serde::Serialize;
70use std::any::{Any, TypeId};
71use std::collections::HashMap;
72use std::fmt;
73use std::sync::Arc;
74
75#[derive(Default)]
108pub struct Extensions {
109 map: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
110}
111
112impl Extensions {
113 pub fn new() -> Self {
115 Self::default()
116 }
117
118 pub fn insert<T: Send + Sync + '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: Arc<Extensions>,
305
306 pub extensions: Extensions,
311}
312
313impl CommandContext {
314 pub fn new(command_path: Vec<String>, app_state: Arc<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: Arc::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: Send + Sync {
491 type Output: Serialize;
493
494 fn handle(&self, matches: &ArgMatches, ctx: &CommandContext) -> HandlerResult<Self::Output>;
496}
497
498pub struct FnHandler<F, T, R = HandlerResult<T>>
521where
522 T: Serialize + Send + Sync,
523{
524 f: F,
525 _phantom: std::marker::PhantomData<fn() -> (T, R)>,
526}
527
528impl<F, T, R> FnHandler<F, T, R>
529where
530 F: Fn(&ArgMatches, &CommandContext) -> R + Send + Sync,
531 R: IntoHandlerResult<T>,
532 T: Serialize + Send + Sync,
533{
534 pub fn new(f: F) -> Self {
536 Self {
537 f,
538 _phantom: std::marker::PhantomData,
539 }
540 }
541}
542
543impl<F, T, R> Handler for FnHandler<F, T, R>
544where
545 F: Fn(&ArgMatches, &CommandContext) -> R + Send + Sync,
546 R: IntoHandlerResult<T>,
547 T: Serialize + Send + Sync,
548{
549 type Output = T;
550
551 fn handle(&self, matches: &ArgMatches, ctx: &CommandContext) -> HandlerResult<T> {
552 (self.f)(matches, ctx).into_handler_result()
553 }
554}
555
556pub trait LocalHandler {
563 type Output: Serialize;
565
566 fn handle(&mut self, matches: &ArgMatches, ctx: &CommandContext)
568 -> HandlerResult<Self::Output>;
569}
570
571pub struct LocalFnHandler<F, T, R = HandlerResult<T>>
593where
594 T: Serialize,
595{
596 f: F,
597 _phantom: std::marker::PhantomData<fn() -> (T, R)>,
598}
599
600impl<F, T, R> LocalFnHandler<F, T, R>
601where
602 F: FnMut(&ArgMatches, &CommandContext) -> R,
603 R: IntoHandlerResult<T>,
604 T: Serialize,
605{
606 pub fn new(f: F) -> Self {
608 Self {
609 f,
610 _phantom: std::marker::PhantomData,
611 }
612 }
613}
614
615impl<F, T, R> LocalHandler for LocalFnHandler<F, T, R>
616where
617 F: FnMut(&ArgMatches, &CommandContext) -> R,
618 R: IntoHandlerResult<T>,
619 T: Serialize,
620{
621 type Output = T;
622
623 fn handle(&mut self, matches: &ArgMatches, ctx: &CommandContext) -> HandlerResult<T> {
624 (self.f)(matches, ctx).into_handler_result()
625 }
626}
627
628pub struct SimpleFnHandler<F, T, R = HandlerResult<T>>
655where
656 T: Serialize + Send + Sync,
657{
658 f: F,
659 _phantom: std::marker::PhantomData<fn() -> (T, R)>,
660}
661
662impl<F, T, R> SimpleFnHandler<F, T, R>
663where
664 F: Fn(&ArgMatches) -> R + Send + Sync,
665 R: IntoHandlerResult<T>,
666 T: Serialize + Send + Sync,
667{
668 pub fn new(f: F) -> Self {
670 Self {
671 f,
672 _phantom: std::marker::PhantomData,
673 }
674 }
675}
676
677impl<F, T, R> Handler for SimpleFnHandler<F, T, R>
678where
679 F: Fn(&ArgMatches) -> R + Send + Sync,
680 R: IntoHandlerResult<T>,
681 T: Serialize + Send + Sync,
682{
683 type Output = T;
684
685 fn handle(&self, matches: &ArgMatches, _ctx: &CommandContext) -> HandlerResult<T> {
686 (self.f)(matches).into_handler_result()
687 }
688}
689
690pub struct LocalSimpleFnHandler<F, T, R = HandlerResult<T>>
712where
713 T: Serialize,
714{
715 f: F,
716 _phantom: std::marker::PhantomData<fn() -> (T, R)>,
717}
718
719impl<F, T, R> LocalSimpleFnHandler<F, T, R>
720where
721 F: FnMut(&ArgMatches) -> R,
722 R: IntoHandlerResult<T>,
723 T: Serialize,
724{
725 pub fn new(f: F) -> Self {
727 Self {
728 f,
729 _phantom: std::marker::PhantomData,
730 }
731 }
732}
733
734impl<F, T, R> LocalHandler for LocalSimpleFnHandler<F, T, R>
735where
736 F: FnMut(&ArgMatches) -> R,
737 R: IntoHandlerResult<T>,
738 T: Serialize,
739{
740 type Output = T;
741
742 fn handle(&mut self, matches: &ArgMatches, _ctx: &CommandContext) -> HandlerResult<T> {
743 (self.f)(matches).into_handler_result()
744 }
745}
746
747#[cfg(test)]
748mod tests {
749 use super::*;
750 use serde_json::json;
751
752 #[test]
753 fn test_command_context_creation() {
754 let ctx = CommandContext {
755 command_path: vec!["config".into(), "get".into()],
756 app_state: Arc::new(Extensions::new()),
757 extensions: Extensions::new(),
758 };
759 assert_eq!(ctx.command_path, vec!["config", "get"]);
760 }
761
762 #[test]
763 fn test_command_context_default() {
764 let ctx = CommandContext::default();
765 assert!(ctx.command_path.is_empty());
766 assert!(ctx.extensions.is_empty());
767 assert!(ctx.app_state.is_empty());
768 }
769
770 #[test]
771 fn test_command_context_with_app_state() {
772 struct Database {
773 url: String,
774 }
775 struct Config {
776 debug: bool,
777 }
778
779 let mut app_state = Extensions::new();
781 app_state.insert(Database {
782 url: "postgres://localhost".into(),
783 });
784 app_state.insert(Config { debug: true });
785 let app_state = Arc::new(app_state);
786
787 let ctx = CommandContext {
789 command_path: vec!["list".into()],
790 app_state: app_state.clone(),
791 extensions: Extensions::new(),
792 };
793
794 let db = ctx.app_state.get::<Database>().unwrap();
796 assert_eq!(db.url, "postgres://localhost");
797
798 let config = ctx.app_state.get::<Config>().unwrap();
799 assert!(config.debug);
800
801 assert_eq!(Arc::strong_count(&ctx.app_state), 2);
803 }
804
805 #[test]
806 fn test_command_context_app_state_get_required() {
807 struct Present;
808
809 let mut app_state = Extensions::new();
810 app_state.insert(Present);
811
812 let ctx = CommandContext {
813 command_path: vec![],
814 app_state: Arc::new(app_state),
815 extensions: Extensions::new(),
816 };
817
818 assert!(ctx.app_state.get_required::<Present>().is_ok());
820
821 #[derive(Debug)]
823 struct Missing;
824 let err = ctx.app_state.get_required::<Missing>();
825 assert!(err.is_err());
826 assert!(err.unwrap_err().to_string().contains("Extension missing"));
827 }
828
829 #[test]
831 fn test_extensions_insert_and_get() {
832 struct MyState {
833 value: i32,
834 }
835
836 let mut ext = Extensions::new();
837 assert!(ext.is_empty());
838
839 ext.insert(MyState { value: 42 });
840 assert!(!ext.is_empty());
841 assert_eq!(ext.len(), 1);
842
843 let state = ext.get::<MyState>().unwrap();
844 assert_eq!(state.value, 42);
845 }
846
847 #[test]
848 fn test_extensions_get_mut() {
849 struct Counter {
850 count: i32,
851 }
852
853 let mut ext = Extensions::new();
854 ext.insert(Counter { count: 0 });
855
856 if let Some(counter) = ext.get_mut::<Counter>() {
857 counter.count += 1;
858 }
859
860 assert_eq!(ext.get::<Counter>().unwrap().count, 1);
861 }
862
863 #[test]
864 fn test_extensions_multiple_types() {
865 struct TypeA(i32);
866 struct TypeB(String);
867
868 let mut ext = Extensions::new();
869 ext.insert(TypeA(1));
870 ext.insert(TypeB("hello".into()));
871
872 assert_eq!(ext.len(), 2);
873 assert_eq!(ext.get::<TypeA>().unwrap().0, 1);
874 assert_eq!(ext.get::<TypeB>().unwrap().0, "hello");
875 }
876
877 #[test]
878 fn test_extensions_replace() {
879 struct Value(i32);
880
881 let mut ext = Extensions::new();
882 ext.insert(Value(1));
883
884 let old = ext.insert(Value(2));
885 assert_eq!(old.unwrap().0, 1);
886 assert_eq!(ext.get::<Value>().unwrap().0, 2);
887 }
888
889 #[test]
890 fn test_extensions_remove() {
891 struct Value(i32);
892
893 let mut ext = Extensions::new();
894 ext.insert(Value(42));
895
896 let removed = ext.remove::<Value>();
897 assert_eq!(removed.unwrap().0, 42);
898 assert!(ext.is_empty());
899 assert!(ext.get::<Value>().is_none());
900 }
901
902 #[test]
903 fn test_extensions_contains() {
904 struct Present;
905 struct Absent;
906
907 let mut ext = Extensions::new();
908 ext.insert(Present);
909
910 assert!(ext.contains::<Present>());
911 assert!(!ext.contains::<Absent>());
912 }
913
914 #[test]
915 fn test_extensions_clear() {
916 struct A;
917 struct B;
918
919 let mut ext = Extensions::new();
920 ext.insert(A);
921 ext.insert(B);
922 assert_eq!(ext.len(), 2);
923
924 ext.clear();
925 assert!(ext.is_empty());
926 }
927
928 #[test]
929 fn test_extensions_missing_type_returns_none() {
930 struct NotInserted;
931
932 let ext = Extensions::new();
933 assert!(ext.get::<NotInserted>().is_none());
934 }
935
936 #[test]
937 fn test_extensions_get_required() {
938 #[derive(Debug)]
939 struct Config {
940 value: i32,
941 }
942
943 let mut ext = Extensions::new();
944 ext.insert(Config { value: 100 });
945
946 let val = ext.get_required::<Config>();
948 assert!(val.is_ok());
949 assert_eq!(val.unwrap().value, 100);
950
951 #[derive(Debug)]
953 struct Missing;
954 let err = ext.get_required::<Missing>();
955 assert!(err.is_err());
956 assert!(err
957 .unwrap_err()
958 .to_string()
959 .contains("Extension missing: type"));
960 }
961
962 #[test]
963 fn test_extensions_get_mut_required() {
964 #[derive(Debug)]
965 struct State {
966 count: i32,
967 }
968
969 let mut ext = Extensions::new();
970 ext.insert(State { count: 0 });
971
972 {
974 let val = ext.get_mut_required::<State>();
975 assert!(val.is_ok());
976 val.unwrap().count += 1;
977 }
978 assert_eq!(ext.get_required::<State>().unwrap().count, 1);
979
980 #[derive(Debug)]
982 struct Missing;
983 let err = ext.get_mut_required::<Missing>();
984 assert!(err.is_err());
985 }
986
987 #[test]
988 fn test_extensions_clone_behavior() {
989 struct Data(i32);
991
992 let mut original = Extensions::new();
993 original.insert(Data(42));
994
995 let cloned = original.clone();
996
997 assert!(original.get::<Data>().is_some());
999
1000 assert!(cloned.is_empty());
1002 assert!(cloned.get::<Data>().is_none());
1003 }
1004
1005 #[test]
1006 fn test_output_render() {
1007 let output: Output<String> = Output::Render("success".into());
1008 assert!(output.is_render());
1009 assert!(!output.is_silent());
1010 assert!(!output.is_binary());
1011 }
1012
1013 #[test]
1014 fn test_output_silent() {
1015 let output: Output<String> = Output::Silent;
1016 assert!(!output.is_render());
1017 assert!(output.is_silent());
1018 assert!(!output.is_binary());
1019 }
1020
1021 #[test]
1022 fn test_output_binary() {
1023 let output: Output<String> = Output::Binary {
1024 data: vec![0x25, 0x50, 0x44, 0x46],
1025 filename: "report.pdf".into(),
1026 };
1027 assert!(!output.is_render());
1028 assert!(!output.is_silent());
1029 assert!(output.is_binary());
1030 }
1031
1032 #[test]
1033 fn test_run_result_handled() {
1034 let result = RunResult::Handled("output".into());
1035 assert!(result.is_handled());
1036 assert!(!result.is_binary());
1037 assert!(!result.is_silent());
1038 assert_eq!(result.output(), Some("output"));
1039 assert!(result.matches().is_none());
1040 }
1041
1042 #[test]
1043 fn test_run_result_silent() {
1044 let result = RunResult::Silent;
1045 assert!(!result.is_handled());
1046 assert!(!result.is_binary());
1047 assert!(result.is_silent());
1048 }
1049
1050 #[test]
1051 fn test_run_result_binary() {
1052 let bytes = vec![0x25, 0x50, 0x44, 0x46];
1053 let result = RunResult::Binary(bytes.clone(), "report.pdf".into());
1054 assert!(!result.is_handled());
1055 assert!(result.is_binary());
1056 assert!(!result.is_silent());
1057
1058 let (data, filename) = result.binary().unwrap();
1059 assert_eq!(data, &bytes);
1060 assert_eq!(filename, "report.pdf");
1061 }
1062
1063 #[test]
1064 fn test_run_result_no_match() {
1065 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1066 let result = RunResult::NoMatch(matches);
1067 assert!(!result.is_handled());
1068 assert!(!result.is_binary());
1069 assert!(result.matches().is_some());
1070 }
1071
1072 #[test]
1073 fn test_fn_handler() {
1074 let handler = FnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| {
1075 Ok(Output::Render(json!({"status": "ok"})))
1076 });
1077
1078 let ctx = CommandContext::default();
1079 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1080
1081 let result = handler.handle(&matches, &ctx);
1082 assert!(result.is_ok());
1083 }
1084
1085 #[test]
1086 fn test_local_fn_handler_mutation() {
1087 let mut counter = 0u32;
1088
1089 let mut handler = LocalFnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| {
1090 counter += 1;
1091 Ok(Output::Render(counter))
1092 });
1093
1094 let ctx = CommandContext::default();
1095 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1096
1097 let _ = handler.handle(&matches, &ctx);
1098 let _ = handler.handle(&matches, &ctx);
1099 let result = handler.handle(&matches, &ctx);
1100
1101 assert!(result.is_ok());
1102 if let Ok(Output::Render(count)) = result {
1103 assert_eq!(count, 3);
1104 }
1105 }
1106
1107 #[test]
1109 fn test_into_handler_result_from_result_ok() {
1110 use super::IntoHandlerResult;
1111
1112 let result: Result<String, anyhow::Error> = Ok("hello".to_string());
1113 let handler_result = result.into_handler_result();
1114
1115 assert!(handler_result.is_ok());
1116 match handler_result.unwrap() {
1117 Output::Render(s) => assert_eq!(s, "hello"),
1118 _ => panic!("Expected Output::Render"),
1119 }
1120 }
1121
1122 #[test]
1123 fn test_into_handler_result_from_result_err() {
1124 use super::IntoHandlerResult;
1125
1126 let result: Result<String, anyhow::Error> = Err(anyhow::anyhow!("test error"));
1127 let handler_result = result.into_handler_result();
1128
1129 assert!(handler_result.is_err());
1130 assert!(handler_result
1131 .unwrap_err()
1132 .to_string()
1133 .contains("test error"));
1134 }
1135
1136 #[test]
1137 fn test_into_handler_result_passthrough_render() {
1138 use super::IntoHandlerResult;
1139
1140 let handler_result: HandlerResult<String> = Ok(Output::Render("hello".to_string()));
1141 let result = handler_result.into_handler_result();
1142
1143 assert!(result.is_ok());
1144 match result.unwrap() {
1145 Output::Render(s) => assert_eq!(s, "hello"),
1146 _ => panic!("Expected Output::Render"),
1147 }
1148 }
1149
1150 #[test]
1151 fn test_into_handler_result_passthrough_silent() {
1152 use super::IntoHandlerResult;
1153
1154 let handler_result: HandlerResult<String> = Ok(Output::Silent);
1155 let result = handler_result.into_handler_result();
1156
1157 assert!(result.is_ok());
1158 assert!(matches!(result.unwrap(), Output::Silent));
1159 }
1160
1161 #[test]
1162 fn test_into_handler_result_passthrough_binary() {
1163 use super::IntoHandlerResult;
1164
1165 let handler_result: HandlerResult<String> = Ok(Output::Binary {
1166 data: vec![1, 2, 3],
1167 filename: "test.bin".to_string(),
1168 });
1169 let result = handler_result.into_handler_result();
1170
1171 assert!(result.is_ok());
1172 match result.unwrap() {
1173 Output::Binary { data, filename } => {
1174 assert_eq!(data, vec![1, 2, 3]);
1175 assert_eq!(filename, "test.bin");
1176 }
1177 _ => panic!("Expected Output::Binary"),
1178 }
1179 }
1180
1181 #[test]
1182 fn test_fn_handler_with_auto_wrap() {
1183 let handler = FnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| {
1185 Ok::<_, anyhow::Error>("auto-wrapped".to_string())
1186 });
1187
1188 let ctx = CommandContext::default();
1189 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1190
1191 let result = handler.handle(&matches, &ctx);
1192 assert!(result.is_ok());
1193 match result.unwrap() {
1194 Output::Render(s) => assert_eq!(s, "auto-wrapped"),
1195 _ => panic!("Expected Output::Render"),
1196 }
1197 }
1198
1199 #[test]
1200 fn test_fn_handler_with_explicit_output() {
1201 let handler =
1203 FnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| Ok(Output::<()>::Silent));
1204
1205 let ctx = CommandContext::default();
1206 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1207
1208 let result = handler.handle(&matches, &ctx);
1209 assert!(result.is_ok());
1210 assert!(matches!(result.unwrap(), Output::Silent));
1211 }
1212
1213 #[test]
1214 fn test_local_fn_handler_with_auto_wrap() {
1215 let mut handler = LocalFnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| {
1216 Ok::<_, anyhow::Error>(42i32)
1217 });
1218
1219 let ctx = CommandContext::default();
1220 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1221
1222 let result = handler.handle(&matches, &ctx);
1223 assert!(result.is_ok());
1224 match result.unwrap() {
1225 Output::Render(n) => assert_eq!(n, 42),
1226 _ => panic!("Expected Output::Render"),
1227 }
1228 }
1229
1230 #[test]
1231 fn test_fn_handler_with_custom_error_type() {
1232 #[derive(Debug)]
1234 struct CustomError(String);
1235
1236 impl std::fmt::Display for CustomError {
1237 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1238 write!(f, "CustomError: {}", self.0)
1239 }
1240 }
1241
1242 impl std::error::Error for CustomError {}
1243
1244 let handler = FnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| {
1245 Err::<String, CustomError>(CustomError("oops".to_string()))
1246 });
1247
1248 let ctx = CommandContext::default();
1249 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1250
1251 let result = handler.handle(&matches, &ctx);
1252 assert!(result.is_err());
1253 assert!(result
1254 .unwrap_err()
1255 .to_string()
1256 .contains("CustomError: oops"));
1257 }
1258
1259 #[test]
1261 fn test_simple_fn_handler_basic() {
1262 use super::SimpleFnHandler;
1263
1264 let handler = SimpleFnHandler::new(|_m: &ArgMatches| {
1265 Ok::<_, anyhow::Error>("no context needed".to_string())
1266 });
1267
1268 let ctx = CommandContext::default();
1269 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1270
1271 let result = handler.handle(&matches, &ctx);
1272 assert!(result.is_ok());
1273 match result.unwrap() {
1274 Output::Render(s) => assert_eq!(s, "no context needed"),
1275 _ => panic!("Expected Output::Render"),
1276 }
1277 }
1278
1279 #[test]
1280 fn test_simple_fn_handler_with_args() {
1281 use super::SimpleFnHandler;
1282
1283 let handler = SimpleFnHandler::new(|m: &ArgMatches| {
1284 let verbose = m.get_flag("verbose");
1285 Ok::<_, anyhow::Error>(verbose)
1286 });
1287
1288 let ctx = CommandContext::default();
1289 let matches = clap::Command::new("test")
1290 .arg(
1291 clap::Arg::new("verbose")
1292 .short('v')
1293 .action(clap::ArgAction::SetTrue),
1294 )
1295 .get_matches_from(vec!["test", "-v"]);
1296
1297 let result = handler.handle(&matches, &ctx);
1298 assert!(result.is_ok());
1299 match result.unwrap() {
1300 Output::Render(v) => assert!(v),
1301 _ => panic!("Expected Output::Render"),
1302 }
1303 }
1304
1305 #[test]
1306 fn test_simple_fn_handler_explicit_output() {
1307 use super::SimpleFnHandler;
1308
1309 let handler = SimpleFnHandler::new(|_m: &ArgMatches| Ok(Output::<()>::Silent));
1310
1311 let ctx = CommandContext::default();
1312 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1313
1314 let result = handler.handle(&matches, &ctx);
1315 assert!(result.is_ok());
1316 assert!(matches!(result.unwrap(), Output::Silent));
1317 }
1318
1319 #[test]
1320 fn test_simple_fn_handler_error() {
1321 use super::SimpleFnHandler;
1322
1323 let handler = SimpleFnHandler::new(|_m: &ArgMatches| {
1324 Err::<String, _>(anyhow::anyhow!("simple error"))
1325 });
1326
1327 let ctx = CommandContext::default();
1328 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1329
1330 let result = handler.handle(&matches, &ctx);
1331 assert!(result.is_err());
1332 assert!(result.unwrap_err().to_string().contains("simple error"));
1333 }
1334
1335 #[test]
1337 fn test_local_simple_fn_handler_basic() {
1338 use super::LocalSimpleFnHandler;
1339
1340 let mut handler = LocalSimpleFnHandler::new(|_m: &ArgMatches| {
1341 Ok::<_, anyhow::Error>("local no context".to_string())
1342 });
1343
1344 let ctx = CommandContext::default();
1345 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1346
1347 let result = handler.handle(&matches, &ctx);
1348 assert!(result.is_ok());
1349 match result.unwrap() {
1350 Output::Render(s) => assert_eq!(s, "local no context"),
1351 _ => panic!("Expected Output::Render"),
1352 }
1353 }
1354
1355 #[test]
1356 fn test_local_simple_fn_handler_mutation() {
1357 use super::LocalSimpleFnHandler;
1358
1359 let mut counter = 0u32;
1360 let mut handler = LocalSimpleFnHandler::new(|_m: &ArgMatches| {
1361 counter += 1;
1362 Ok::<_, anyhow::Error>(counter)
1363 });
1364
1365 let ctx = CommandContext::default();
1366 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1367
1368 let _ = handler.handle(&matches, &ctx);
1369 let _ = handler.handle(&matches, &ctx);
1370 let result = handler.handle(&matches, &ctx);
1371
1372 assert!(result.is_ok());
1373 match result.unwrap() {
1374 Output::Render(n) => assert_eq!(n, 3),
1375 _ => panic!("Expected Output::Render"),
1376 }
1377 }
1378
1379 #[test]
1380 fn test_local_simple_fn_handler_explicit_output() {
1381 use super::LocalSimpleFnHandler;
1382
1383 let mut handler = LocalSimpleFnHandler::new(|_m: &ArgMatches| Ok(Output::<()>::Silent));
1384
1385 let ctx = CommandContext::default();
1386 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1387
1388 let result = handler.handle(&matches, &ctx);
1389 assert!(result.is_ok());
1390 assert!(matches!(result.unwrap(), Output::Silent));
1391 }
1392}