1use crate::hooks::HookPhase;
68use crate::verify::ExpectedArg;
69use clap::ArgMatches;
70use serde::Serialize;
71use std::any::{Any, TypeId};
72use std::collections::HashMap;
73use std::fmt;
74use std::rc::Rc;
75
76#[derive(Default)]
109pub struct Extensions {
110 map: HashMap<TypeId, Box<dyn Any>>,
111}
112
113impl Extensions {
114 pub fn new() -> Self {
116 Self::default()
117 }
118
119 pub fn insert<T: 'static>(&mut self, val: T) -> Option<T> {
123 self.map
124 .insert(TypeId::of::<T>(), Box::new(val))
125 .and_then(|boxed| boxed.downcast().ok().map(|b| *b))
126 }
127
128 pub fn get<T: 'static>(&self) -> Option<&T> {
132 self.map
133 .get(&TypeId::of::<T>())
134 .and_then(|boxed| boxed.downcast_ref())
135 }
136
137 pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> {
141 self.map
142 .get_mut(&TypeId::of::<T>())
143 .and_then(|boxed| boxed.downcast_mut())
144 }
145
146 pub fn get_required<T: 'static>(&self) -> Result<&T, anyhow::Error> {
150 self.get::<T>().ok_or_else(|| {
151 anyhow::anyhow!(
152 "Extension missing: type {} not found in context",
153 std::any::type_name::<T>()
154 )
155 })
156 }
157
158 pub fn get_mut_required<T: 'static>(&mut self) -> Result<&mut T, anyhow::Error> {
162 self.get_mut::<T>().ok_or_else(|| {
163 anyhow::anyhow!(
164 "Extension missing: type {} not found in context",
165 std::any::type_name::<T>()
166 )
167 })
168 }
169
170 pub fn remove<T: 'static>(&mut self) -> Option<T> {
172 self.map
173 .remove(&TypeId::of::<T>())
174 .and_then(|boxed| boxed.downcast().ok().map(|b| *b))
175 }
176
177 pub fn contains<T: 'static>(&self) -> bool {
179 self.map.contains_key(&TypeId::of::<T>())
180 }
181
182 pub fn len(&self) -> usize {
184 self.map.len()
185 }
186
187 pub fn is_empty(&self) -> bool {
189 self.map.is_empty()
190 }
191
192 pub fn clear(&mut self) {
194 self.map.clear();
195 }
196}
197
198impl fmt::Debug for Extensions {
199 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
200 f.debug_struct("Extensions")
201 .field("len", &self.map.len())
202 .finish_non_exhaustive()
203 }
204}
205
206impl Clone for Extensions {
207 fn clone(&self) -> Self {
208 Self::new()
212 }
213}
214
215#[derive(Debug)]
295pub struct CommandContext {
296 pub command_path: Vec<String>,
298
299 pub app_state: Rc<Extensions>,
306
307 pub extensions: Extensions,
312}
313
314impl CommandContext {
315 pub fn new(command_path: Vec<String>, app_state: Rc<Extensions>) -> Self {
319 Self {
320 command_path,
321 app_state,
322 extensions: Extensions::new(),
323 }
324 }
325}
326
327impl Default for CommandContext {
328 fn default() -> Self {
329 Self {
330 command_path: Vec::new(),
331 app_state: Rc::new(Extensions::new()),
332 extensions: Extensions::new(),
333 }
334 }
335}
336
337#[derive(Debug)]
341pub enum Output<T: Serialize> {
342 Render(T),
344 Silent,
346 Binary {
348 data: Vec<u8>,
350 filename: String,
352 },
353}
354
355impl<T: Serialize> Output<T> {
356 pub fn is_render(&self) -> bool {
358 matches!(self, Output::Render(_))
359 }
360
361 pub fn is_silent(&self) -> bool {
363 matches!(self, Output::Silent)
364 }
365
366 pub fn is_binary(&self) -> bool {
368 matches!(self, Output::Binary { .. })
369 }
370}
371
372pub type HandlerResult<T> = Result<Output<T>, anyhow::Error>;
376
377pub trait IntoHandlerResult<T: Serialize> {
403 fn into_handler_result(self) -> HandlerResult<T>;
405}
406
407impl<T, E> IntoHandlerResult<T> for Result<T, E>
412where
413 T: Serialize,
414 E: Into<anyhow::Error>,
415{
416 fn into_handler_result(self) -> HandlerResult<T> {
417 self.map(Output::Render).map_err(Into::into)
418 }
419}
420
421impl<T: Serialize> IntoHandlerResult<T> for HandlerResult<T> {
426 fn into_handler_result(self) -> HandlerResult<T> {
427 self
428 }
429}
430
431#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
433pub struct ExitStatus(u8);
434
435impl ExitStatus {
436 pub const SUCCESS: Self = Self(0);
438 pub const FAILURE: Self = Self(1);
440 pub const USAGE_ERROR: Self = Self(2);
442
443 pub const fn code(self) -> u8 {
445 self.0
446 }
447}
448
449#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
451#[non_exhaustive]
452pub enum SuccessKind {
453 Command,
455 ClapHelp,
457 ClapVersion,
459}
460
461#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
463#[non_exhaustive]
464pub enum OutputKind {
465 Text,
467 Binary,
469}
470
471#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
473#[non_exhaustive]
474pub enum RunErrorKind {
475 ClapUsage,
477 Handler,
479 Hook(HookPhase),
481 Render,
483 FinalWrite(OutputKind),
485}
486
487#[derive(Debug, Clone)]
489pub struct RunOutput {
490 text: String,
491 kind: SuccessKind,
492}
493
494impl RunOutput {
495 pub fn command(text: impl Into<String>) -> Self {
497 Self {
498 text: text.into(),
499 kind: SuccessKind::Command,
500 }
501 }
502
503 pub fn clap_help(text: impl Into<String>) -> Self {
505 Self {
506 text: text.into(),
507 kind: SuccessKind::ClapHelp,
508 }
509 }
510
511 pub fn clap_version(text: impl Into<String>) -> Self {
513 Self {
514 text: text.into(),
515 kind: SuccessKind::ClapVersion,
516 }
517 }
518
519 pub fn as_str(&self) -> &str {
521 &self.text
522 }
523
524 pub const fn kind(&self) -> SuccessKind {
526 self.kind
527 }
528
529 pub fn into_string(self) -> String {
531 self.text
532 }
533}
534
535impl std::ops::Deref for RunOutput {
536 type Target = str;
537 fn deref(&self) -> &Self::Target {
538 self.as_str()
539 }
540}
541
542impl AsRef<str> for RunOutput {
543 fn as_ref(&self) -> &str {
544 self.as_str()
545 }
546}
547
548impl fmt::Display for RunOutput {
549 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
550 f.write_str(self.as_str())
551 }
552}
553
554impl PartialEq<str> for RunOutput {
555 fn eq(&self, other: &str) -> bool {
556 self.as_str() == other
557 }
558}
559
560impl PartialEq<&str> for RunOutput {
561 fn eq(&self, other: &&str) -> bool {
562 self.as_str() == *other
563 }
564}
565
566impl PartialEq<String> for RunOutput {
567 fn eq(&self, other: &String) -> bool {
568 self.as_str() == other
569 }
570}
571
572impl From<String> for RunOutput {
573 fn from(text: String) -> Self {
574 Self::command(text)
575 }
576}
577
578impl From<&str> for RunOutput {
579 fn from(text: &str) -> Self {
580 Self::command(text)
581 }
582}
583
584impl From<RunOutput> for String {
585 fn from(output: RunOutput) -> Self {
586 output.into_string()
587 }
588}
589
590#[derive(Debug, Clone)]
592pub struct RunError {
593 message: String,
594 kind: RunErrorKind,
595}
596
597impl RunError {
598 pub fn new(message: impl Into<String>, kind: RunErrorKind) -> Self {
600 Self {
601 message: message.into(),
602 kind,
603 }
604 }
605
606 pub fn as_str(&self) -> &str {
608 &self.message
609 }
610
611 pub const fn kind(&self) -> RunErrorKind {
613 self.kind
614 }
615
616 pub const fn exit_status(&self) -> ExitStatus {
618 match self.kind {
619 RunErrorKind::ClapUsage => ExitStatus::USAGE_ERROR,
620 _ => ExitStatus::FAILURE,
621 }
622 }
623
624 pub fn into_string(self) -> String {
626 self.message
627 }
628}
629
630impl std::ops::Deref for RunError {
631 type Target = str;
632 fn deref(&self) -> &Self::Target {
633 self.as_str()
634 }
635}
636
637impl AsRef<str> for RunError {
638 fn as_ref(&self) -> &str {
639 self.as_str()
640 }
641}
642
643impl fmt::Display for RunError {
644 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
645 f.write_str(self.as_str())
646 }
647}
648
649impl From<String> for RunError {
650 fn from(message: String) -> Self {
651 Self::new(message, RunErrorKind::Handler)
652 }
653}
654
655impl From<&str> for RunError {
656 fn from(message: &str) -> Self {
657 Self::new(message, RunErrorKind::Handler)
658 }
659}
660
661impl From<RunError> for String {
662 fn from(error: RunError) -> Self {
663 error.into_string()
664 }
665}
666
667#[derive(Debug)]
675#[non_exhaustive]
676pub enum RunResult {
677 Handled(RunOutput),
679 Binary(Vec<u8>, String),
681 Silent,
683 Error(RunError),
686 NoMatch(ArgMatches),
688}
689
690impl RunResult {
691 pub fn is_handled(&self) -> bool {
693 matches!(self, RunResult::Handled(_))
694 }
695
696 pub fn is_binary(&self) -> bool {
698 matches!(self, RunResult::Binary(_, _))
699 }
700
701 pub fn is_silent(&self) -> bool {
703 matches!(self, RunResult::Silent)
704 }
705
706 pub fn is_error(&self) -> bool {
708 matches!(self, RunResult::Error(_))
709 }
710
711 pub fn output(&self) -> Option<&str> {
713 match self {
714 RunResult::Handled(s) => Some(s),
715 _ => None,
716 }
717 }
718
719 pub fn error(&self) -> Option<&str> {
721 match self {
722 RunResult::Error(s) => Some(s),
723 _ => None,
724 }
725 }
726
727 pub fn success_kind(&self) -> Option<SuccessKind> {
729 match self {
730 RunResult::Handled(output) => Some(output.kind()),
731 RunResult::Binary(_, _) | RunResult::Silent => Some(SuccessKind::Command),
732 _ => None,
733 }
734 }
735
736 pub fn error_kind(&self) -> Option<RunErrorKind> {
738 match self {
739 RunResult::Error(error) => Some(error.kind()),
740 _ => None,
741 }
742 }
743
744 pub fn exit_status(&self) -> Option<ExitStatus> {
749 match self {
750 RunResult::Handled(_) | RunResult::Binary(_, _) | RunResult::Silent => {
751 Some(ExitStatus::SUCCESS)
752 }
753 RunResult::Error(error) => Some(error.exit_status()),
754 RunResult::NoMatch(_) => None,
755 }
756 }
757
758 pub fn binary(&self) -> Option<(&[u8], &str)> {
760 match self {
761 RunResult::Binary(bytes, filename) => Some((bytes, filename)),
762 _ => None,
763 }
764 }
765
766 pub fn matches(&self) -> Option<&ArgMatches> {
768 match self {
769 RunResult::NoMatch(m) => Some(m),
770 _ => None,
771 }
772 }
773}
774
775pub trait Handler {
799 type Output: Serialize;
801
802 fn handle(&mut self, matches: &ArgMatches, ctx: &CommandContext)
804 -> HandlerResult<Self::Output>;
805
806 fn expected_args(&self) -> Vec<ExpectedArg> {
811 Vec::new()
812 }
813}
814
815pub struct FnHandler<F, T, R = HandlerResult<T>>
838where
839 T: Serialize,
840{
841 f: F,
842 _phantom: std::marker::PhantomData<fn() -> (T, R)>,
843}
844
845impl<F, T, R> FnHandler<F, T, R>
846where
847 F: FnMut(&ArgMatches, &CommandContext) -> R,
848 R: IntoHandlerResult<T>,
849 T: Serialize,
850{
851 pub fn new(f: F) -> Self {
853 Self {
854 f,
855 _phantom: std::marker::PhantomData,
856 }
857 }
858}
859
860impl<F, T, R> Handler for FnHandler<F, T, R>
861where
862 F: FnMut(&ArgMatches, &CommandContext) -> R,
863 R: IntoHandlerResult<T>,
864 T: Serialize,
865{
866 type Output = T;
867
868 fn handle(&mut self, matches: &ArgMatches, ctx: &CommandContext) -> HandlerResult<T> {
869 (self.f)(matches, ctx).into_handler_result()
870 }
871}
872
873pub struct SimpleFnHandler<F, T, R = HandlerResult<T>>
900where
901 T: Serialize,
902{
903 f: F,
904 _phantom: std::marker::PhantomData<fn() -> (T, R)>,
905}
906
907impl<F, T, R> SimpleFnHandler<F, T, R>
908where
909 F: FnMut(&ArgMatches) -> R,
910 R: IntoHandlerResult<T>,
911 T: Serialize,
912{
913 pub fn new(f: F) -> Self {
915 Self {
916 f,
917 _phantom: std::marker::PhantomData,
918 }
919 }
920}
921
922impl<F, T, R> Handler for SimpleFnHandler<F, T, R>
923where
924 F: FnMut(&ArgMatches) -> R,
925 R: IntoHandlerResult<T>,
926 T: Serialize,
927{
928 type Output = T;
929
930 fn handle(&mut self, matches: &ArgMatches, _ctx: &CommandContext) -> HandlerResult<T> {
931 (self.f)(matches).into_handler_result()
932 }
933}
934
935#[cfg(test)]
936mod tests {
937 use super::*;
938 use serde_json::json;
939
940 #[test]
941 fn test_command_context_creation() {
942 let ctx = CommandContext {
943 command_path: vec!["config".into(), "get".into()],
944 app_state: Rc::new(Extensions::new()),
945 extensions: Extensions::new(),
946 };
947 assert_eq!(ctx.command_path, vec!["config", "get"]);
948 }
949
950 #[test]
951 fn test_command_context_default() {
952 let ctx = CommandContext::default();
953 assert!(ctx.command_path.is_empty());
954 assert!(ctx.extensions.is_empty());
955 assert!(ctx.app_state.is_empty());
956 }
957
958 #[test]
959 fn test_command_context_with_app_state() {
960 struct Database {
961 url: String,
962 }
963 struct Config {
964 debug: bool,
965 }
966
967 let mut app_state = Extensions::new();
969 app_state.insert(Database {
970 url: "postgres://localhost".into(),
971 });
972 app_state.insert(Config { debug: true });
973 let app_state = Rc::new(app_state);
974
975 let ctx = CommandContext {
977 command_path: vec!["list".into()],
978 app_state: app_state.clone(),
979 extensions: Extensions::new(),
980 };
981
982 let db = ctx.app_state.get::<Database>().unwrap();
984 assert_eq!(db.url, "postgres://localhost");
985
986 let config = ctx.app_state.get::<Config>().unwrap();
987 assert!(config.debug);
988
989 assert_eq!(Rc::strong_count(&ctx.app_state), 2);
991 }
992
993 #[test]
994 fn test_command_context_app_state_get_required() {
995 struct Present;
996
997 let mut app_state = Extensions::new();
998 app_state.insert(Present);
999
1000 let ctx = CommandContext {
1001 command_path: vec![],
1002 app_state: Rc::new(app_state),
1003 extensions: Extensions::new(),
1004 };
1005
1006 assert!(ctx.app_state.get_required::<Present>().is_ok());
1008
1009 #[derive(Debug)]
1011 struct Missing;
1012 let err = ctx.app_state.get_required::<Missing>();
1013 assert!(err.is_err());
1014 assert!(err.unwrap_err().to_string().contains("Extension missing"));
1015 }
1016
1017 #[test]
1019 fn test_extensions_insert_and_get() {
1020 struct MyState {
1021 value: i32,
1022 }
1023
1024 let mut ext = Extensions::new();
1025 assert!(ext.is_empty());
1026
1027 ext.insert(MyState { value: 42 });
1028 assert!(!ext.is_empty());
1029 assert_eq!(ext.len(), 1);
1030
1031 let state = ext.get::<MyState>().unwrap();
1032 assert_eq!(state.value, 42);
1033 }
1034
1035 #[test]
1036 fn test_extensions_get_mut() {
1037 struct Counter {
1038 count: i32,
1039 }
1040
1041 let mut ext = Extensions::new();
1042 ext.insert(Counter { count: 0 });
1043
1044 if let Some(counter) = ext.get_mut::<Counter>() {
1045 counter.count += 1;
1046 }
1047
1048 assert_eq!(ext.get::<Counter>().unwrap().count, 1);
1049 }
1050
1051 #[test]
1052 fn test_extensions_multiple_types() {
1053 struct TypeA(i32);
1054 struct TypeB(String);
1055
1056 let mut ext = Extensions::new();
1057 ext.insert(TypeA(1));
1058 ext.insert(TypeB("hello".into()));
1059
1060 assert_eq!(ext.len(), 2);
1061 assert_eq!(ext.get::<TypeA>().unwrap().0, 1);
1062 assert_eq!(ext.get::<TypeB>().unwrap().0, "hello");
1063 }
1064
1065 #[test]
1066 fn test_extensions_replace() {
1067 struct Value(i32);
1068
1069 let mut ext = Extensions::new();
1070 ext.insert(Value(1));
1071
1072 let old = ext.insert(Value(2));
1073 assert_eq!(old.unwrap().0, 1);
1074 assert_eq!(ext.get::<Value>().unwrap().0, 2);
1075 }
1076
1077 #[test]
1078 fn test_extensions_remove() {
1079 struct Value(i32);
1080
1081 let mut ext = Extensions::new();
1082 ext.insert(Value(42));
1083
1084 let removed = ext.remove::<Value>();
1085 assert_eq!(removed.unwrap().0, 42);
1086 assert!(ext.is_empty());
1087 assert!(ext.get::<Value>().is_none());
1088 }
1089
1090 #[test]
1091 fn test_extensions_contains() {
1092 struct Present;
1093 struct Absent;
1094
1095 let mut ext = Extensions::new();
1096 ext.insert(Present);
1097
1098 assert!(ext.contains::<Present>());
1099 assert!(!ext.contains::<Absent>());
1100 }
1101
1102 #[test]
1103 fn test_extensions_clear() {
1104 struct A;
1105 struct B;
1106
1107 let mut ext = Extensions::new();
1108 ext.insert(A);
1109 ext.insert(B);
1110 assert_eq!(ext.len(), 2);
1111
1112 ext.clear();
1113 assert!(ext.is_empty());
1114 }
1115
1116 #[test]
1117 fn test_extensions_missing_type_returns_none() {
1118 struct NotInserted;
1119
1120 let ext = Extensions::new();
1121 assert!(ext.get::<NotInserted>().is_none());
1122 }
1123
1124 #[test]
1125 fn test_extensions_get_required() {
1126 #[derive(Debug)]
1127 struct Config {
1128 value: i32,
1129 }
1130
1131 let mut ext = Extensions::new();
1132 ext.insert(Config { value: 100 });
1133
1134 let val = ext.get_required::<Config>();
1136 assert!(val.is_ok());
1137 assert_eq!(val.unwrap().value, 100);
1138
1139 #[derive(Debug)]
1141 struct Missing;
1142 let err = ext.get_required::<Missing>();
1143 assert!(err.is_err());
1144 assert!(err
1145 .unwrap_err()
1146 .to_string()
1147 .contains("Extension missing: type"));
1148 }
1149
1150 #[test]
1151 fn test_extensions_get_mut_required() {
1152 #[derive(Debug)]
1153 struct State {
1154 count: i32,
1155 }
1156
1157 let mut ext = Extensions::new();
1158 ext.insert(State { count: 0 });
1159
1160 {
1162 let val = ext.get_mut_required::<State>();
1163 assert!(val.is_ok());
1164 val.unwrap().count += 1;
1165 }
1166 assert_eq!(ext.get_required::<State>().unwrap().count, 1);
1167
1168 #[derive(Debug)]
1170 struct Missing;
1171 let err = ext.get_mut_required::<Missing>();
1172 assert!(err.is_err());
1173 }
1174
1175 #[test]
1176 fn test_extensions_clone_behavior() {
1177 struct Data(#[allow(dead_code)] i32);
1179
1180 let mut original = Extensions::new();
1181 original.insert(Data(42));
1182
1183 let cloned = original.clone();
1184
1185 assert!(original.get::<Data>().is_some());
1187
1188 assert!(cloned.is_empty());
1190 assert!(cloned.get::<Data>().is_none());
1191 }
1192
1193 #[test]
1194 fn test_output_render() {
1195 let output: Output<String> = Output::Render("success".into());
1196 assert!(output.is_render());
1197 assert!(!output.is_silent());
1198 assert!(!output.is_binary());
1199 }
1200
1201 #[test]
1202 fn test_output_silent() {
1203 let output: Output<String> = Output::Silent;
1204 assert!(!output.is_render());
1205 assert!(output.is_silent());
1206 assert!(!output.is_binary());
1207 }
1208
1209 #[test]
1210 fn test_output_binary() {
1211 let output: Output<String> = Output::Binary {
1212 data: vec![0x25, 0x50, 0x44, 0x46],
1213 filename: "report.pdf".into(),
1214 };
1215 assert!(!output.is_render());
1216 assert!(!output.is_silent());
1217 assert!(output.is_binary());
1218 }
1219
1220 #[test]
1221 fn test_run_result_handled() {
1222 let result = RunResult::Handled("output".into());
1223 assert!(result.is_handled());
1224 assert!(!result.is_binary());
1225 assert!(!result.is_silent());
1226 assert_eq!(result.output(), Some("output"));
1227 assert!(result.matches().is_none());
1228 }
1229
1230 #[test]
1231 fn test_run_result_silent() {
1232 let result = RunResult::Silent;
1233 assert!(!result.is_handled());
1234 assert!(!result.is_binary());
1235 assert!(result.is_silent());
1236 }
1237
1238 #[test]
1239 fn test_run_result_binary() {
1240 let bytes = vec![0x25, 0x50, 0x44, 0x46];
1241 let result = RunResult::Binary(bytes.clone(), "report.pdf".into());
1242 assert!(!result.is_handled());
1243 assert!(result.is_binary());
1244 assert!(!result.is_silent());
1245
1246 let (data, filename) = result.binary().unwrap();
1247 assert_eq!(data, &bytes);
1248 assert_eq!(filename, "report.pdf");
1249 }
1250
1251 #[test]
1252 fn test_run_result_no_match() {
1253 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1254 let result = RunResult::NoMatch(matches);
1255 assert!(!result.is_handled());
1256 assert!(!result.is_binary());
1257 assert!(result.matches().is_some());
1258 }
1259
1260 #[test]
1261 fn test_fn_handler() {
1262 let mut handler = FnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| {
1263 Ok(Output::Render(json!({"status": "ok"})))
1264 });
1265
1266 let ctx = CommandContext::default();
1267 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1268
1269 let result = handler.handle(&matches, &ctx);
1270 assert!(result.is_ok());
1271 }
1272
1273 #[test]
1274 fn test_fn_handler_mutation() {
1275 let mut counter = 0u32;
1276
1277 let mut handler = FnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| {
1278 counter += 1;
1279 Ok(Output::Render(counter))
1280 });
1281
1282 let ctx = CommandContext::default();
1283 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1284
1285 let _ = handler.handle(&matches, &ctx);
1286 let _ = handler.handle(&matches, &ctx);
1287 let result = handler.handle(&matches, &ctx);
1288
1289 assert!(result.is_ok());
1290 if let Ok(Output::Render(count)) = result {
1291 assert_eq!(count, 3);
1292 }
1293 }
1294
1295 #[test]
1297 fn test_into_handler_result_from_result_ok() {
1298 use super::IntoHandlerResult;
1299
1300 let result: Result<String, anyhow::Error> = Ok("hello".to_string());
1301 let handler_result = result.into_handler_result();
1302
1303 assert!(handler_result.is_ok());
1304 match handler_result.unwrap() {
1305 Output::Render(s) => assert_eq!(s, "hello"),
1306 _ => panic!("Expected Output::Render"),
1307 }
1308 }
1309
1310 #[test]
1311 fn test_into_handler_result_from_result_err() {
1312 use super::IntoHandlerResult;
1313
1314 let result: Result<String, anyhow::Error> = Err(anyhow::anyhow!("test error"));
1315 let handler_result = result.into_handler_result();
1316
1317 assert!(handler_result.is_err());
1318 assert!(handler_result
1319 .unwrap_err()
1320 .to_string()
1321 .contains("test error"));
1322 }
1323
1324 #[test]
1325 fn test_into_handler_result_passthrough_render() {
1326 use super::IntoHandlerResult;
1327
1328 let handler_result: HandlerResult<String> = Ok(Output::Render("hello".to_string()));
1329 let result = handler_result.into_handler_result();
1330
1331 assert!(result.is_ok());
1332 match result.unwrap() {
1333 Output::Render(s) => assert_eq!(s, "hello"),
1334 _ => panic!("Expected Output::Render"),
1335 }
1336 }
1337
1338 #[test]
1339 fn test_into_handler_result_passthrough_silent() {
1340 use super::IntoHandlerResult;
1341
1342 let handler_result: HandlerResult<String> = Ok(Output::Silent);
1343 let result = handler_result.into_handler_result();
1344
1345 assert!(result.is_ok());
1346 assert!(matches!(result.unwrap(), Output::Silent));
1347 }
1348
1349 #[test]
1350 fn test_into_handler_result_passthrough_binary() {
1351 use super::IntoHandlerResult;
1352
1353 let handler_result: HandlerResult<String> = Ok(Output::Binary {
1354 data: vec![1, 2, 3],
1355 filename: "test.bin".to_string(),
1356 });
1357 let result = handler_result.into_handler_result();
1358
1359 assert!(result.is_ok());
1360 match result.unwrap() {
1361 Output::Binary { data, filename } => {
1362 assert_eq!(data, vec![1, 2, 3]);
1363 assert_eq!(filename, "test.bin");
1364 }
1365 _ => panic!("Expected Output::Binary"),
1366 }
1367 }
1368
1369 #[test]
1370 fn test_fn_handler_with_auto_wrap() {
1371 let mut handler = FnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| {
1373 Ok::<_, anyhow::Error>("auto-wrapped".to_string())
1374 });
1375
1376 let ctx = CommandContext::default();
1377 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1378
1379 let result = handler.handle(&matches, &ctx);
1380 assert!(result.is_ok());
1381 match result.unwrap() {
1382 Output::Render(s) => assert_eq!(s, "auto-wrapped"),
1383 _ => panic!("Expected Output::Render"),
1384 }
1385 }
1386
1387 #[test]
1388 fn test_fn_handler_with_explicit_output() {
1389 let mut handler =
1391 FnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| Ok(Output::<()>::Silent));
1392
1393 let ctx = CommandContext::default();
1394 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1395
1396 let result = handler.handle(&matches, &ctx);
1397 assert!(result.is_ok());
1398 assert!(matches!(result.unwrap(), Output::Silent));
1399 }
1400
1401 #[test]
1402 fn test_fn_handler_with_custom_error_type() {
1403 #[derive(Debug)]
1405 struct CustomError(String);
1406
1407 impl std::fmt::Display for CustomError {
1408 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1409 write!(f, "CustomError: {}", self.0)
1410 }
1411 }
1412
1413 impl std::error::Error for CustomError {}
1414
1415 let mut handler = FnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| {
1416 Err::<String, CustomError>(CustomError("oops".to_string()))
1417 });
1418
1419 let ctx = CommandContext::default();
1420 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1421
1422 let result = handler.handle(&matches, &ctx);
1423 assert!(result.is_err());
1424 assert!(result
1425 .unwrap_err()
1426 .to_string()
1427 .contains("CustomError: oops"));
1428 }
1429
1430 #[test]
1432 fn test_simple_fn_handler_basic() {
1433 use super::SimpleFnHandler;
1434
1435 let mut handler = SimpleFnHandler::new(|_m: &ArgMatches| {
1436 Ok::<_, anyhow::Error>("no context needed".to_string())
1437 });
1438
1439 let ctx = CommandContext::default();
1440 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1441
1442 let result = handler.handle(&matches, &ctx);
1443 assert!(result.is_ok());
1444 match result.unwrap() {
1445 Output::Render(s) => assert_eq!(s, "no context needed"),
1446 _ => panic!("Expected Output::Render"),
1447 }
1448 }
1449
1450 #[test]
1451 fn test_simple_fn_handler_with_args() {
1452 use super::SimpleFnHandler;
1453
1454 let mut handler = SimpleFnHandler::new(|m: &ArgMatches| {
1455 let verbose = m.get_flag("verbose");
1456 Ok::<_, anyhow::Error>(verbose)
1457 });
1458
1459 let ctx = CommandContext::default();
1460 let matches = clap::Command::new("test")
1461 .arg(
1462 clap::Arg::new("verbose")
1463 .short('v')
1464 .action(clap::ArgAction::SetTrue),
1465 )
1466 .get_matches_from(vec!["test", "-v"]);
1467
1468 let result = handler.handle(&matches, &ctx);
1469 assert!(result.is_ok());
1470 match result.unwrap() {
1471 Output::Render(v) => assert!(v),
1472 _ => panic!("Expected Output::Render"),
1473 }
1474 }
1475
1476 #[test]
1477 fn test_simple_fn_handler_explicit_output() {
1478 use super::SimpleFnHandler;
1479
1480 let mut handler = SimpleFnHandler::new(|_m: &ArgMatches| Ok(Output::<()>::Silent));
1481
1482 let ctx = CommandContext::default();
1483 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1484
1485 let result = handler.handle(&matches, &ctx);
1486 assert!(result.is_ok());
1487 assert!(matches!(result.unwrap(), Output::Silent));
1488 }
1489
1490 #[test]
1491 fn test_simple_fn_handler_error() {
1492 use super::SimpleFnHandler;
1493
1494 let mut handler = SimpleFnHandler::new(|_m: &ArgMatches| {
1495 Err::<String, _>(anyhow::anyhow!("simple error"))
1496 });
1497
1498 let ctx = CommandContext::default();
1499 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1500
1501 let result = handler.handle(&matches, &ctx);
1502 assert!(result.is_err());
1503 assert!(result.unwrap_err().to_string().contains("simple error"));
1504 }
1505
1506 #[test]
1507 fn test_simple_fn_handler_mutation() {
1508 use super::SimpleFnHandler;
1509
1510 let mut counter = 0u32;
1511 let mut handler = SimpleFnHandler::new(|_m: &ArgMatches| {
1512 counter += 1;
1513 Ok::<_, anyhow::Error>(counter)
1514 });
1515
1516 let ctx = CommandContext::default();
1517 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1518
1519 let _ = handler.handle(&matches, &ctx);
1520 let _ = handler.handle(&matches, &ctx);
1521 let result = handler.handle(&matches, &ctx);
1522
1523 assert!(result.is_ok());
1524 match result.unwrap() {
1525 Output::Render(n) => assert_eq!(n, 3),
1526 _ => panic!("Expected Output::Render"),
1527 }
1528 }
1529}