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;
75use std::sync::Arc;
76
77#[derive(Default)]
110pub struct Extensions {
111 map: HashMap<TypeId, Box<dyn Any>>,
112}
113
114impl Extensions {
115 pub fn new() -> Self {
117 Self::default()
118 }
119
120 pub fn insert<T: 'static>(&mut self, val: T) -> Option<T> {
124 self.map
125 .insert(TypeId::of::<T>(), Box::new(val))
126 .and_then(|boxed| boxed.downcast().ok().map(|b| *b))
127 }
128
129 pub fn get<T: 'static>(&self) -> Option<&T> {
133 self.map
134 .get(&TypeId::of::<T>())
135 .and_then(|boxed| boxed.downcast_ref())
136 }
137
138 pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> {
142 self.map
143 .get_mut(&TypeId::of::<T>())
144 .and_then(|boxed| boxed.downcast_mut())
145 }
146
147 pub fn get_required<T: 'static>(&self) -> Result<&T, anyhow::Error> {
151 self.get::<T>().ok_or_else(|| {
152 anyhow::anyhow!(
153 "Extension missing: type {} not found in context",
154 std::any::type_name::<T>()
155 )
156 })
157 }
158
159 pub fn get_mut_required<T: 'static>(&mut self) -> Result<&mut T, anyhow::Error> {
163 self.get_mut::<T>().ok_or_else(|| {
164 anyhow::anyhow!(
165 "Extension missing: type {} not found in context",
166 std::any::type_name::<T>()
167 )
168 })
169 }
170
171 pub fn remove<T: 'static>(&mut self) -> Option<T> {
173 self.map
174 .remove(&TypeId::of::<T>())
175 .and_then(|boxed| boxed.downcast().ok().map(|b| *b))
176 }
177
178 pub fn contains<T: 'static>(&self) -> bool {
180 self.map.contains_key(&TypeId::of::<T>())
181 }
182
183 pub fn len(&self) -> usize {
185 self.map.len()
186 }
187
188 pub fn is_empty(&self) -> bool {
190 self.map.is_empty()
191 }
192
193 pub fn clear(&mut self) {
195 self.map.clear();
196 }
197}
198
199impl fmt::Debug for Extensions {
200 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201 f.debug_struct("Extensions")
202 .field("len", &self.map.len())
203 .finish_non_exhaustive()
204 }
205}
206
207impl Clone for Extensions {
208 fn clone(&self) -> Self {
209 Self::new()
213 }
214}
215
216#[derive(Debug)]
296pub struct CommandContext {
297 pub command_path: Vec<String>,
299
300 pub app_state: Rc<Extensions>,
307
308 pub extensions: Extensions,
313}
314
315impl CommandContext {
316 pub fn new(command_path: Vec<String>, app_state: Rc<Extensions>) -> Self {
320 Self {
321 command_path,
322 app_state,
323 extensions: Extensions::new(),
324 }
325 }
326}
327
328impl Default for CommandContext {
329 fn default() -> Self {
330 Self {
331 command_path: Vec::new(),
332 app_state: Rc::new(Extensions::new()),
333 extensions: Extensions::new(),
334 }
335 }
336}
337
338#[derive(Debug)]
342pub enum Output<T: Serialize> {
343 Render(T),
345 Silent,
347 Binary {
349 data: Vec<u8>,
351 filename: String,
353 },
354}
355
356impl<T: Serialize> Output<T> {
357 pub fn is_render(&self) -> bool {
359 matches!(self, Output::Render(_))
360 }
361
362 pub fn is_silent(&self) -> bool {
364 matches!(self, Output::Silent)
365 }
366
367 pub fn is_binary(&self) -> bool {
369 matches!(self, Output::Binary { .. })
370 }
371}
372
373pub type HandlerResult<T> = Result<Output<T>, anyhow::Error>;
377
378pub trait IntoHandlerResult<T: Serialize> {
404 fn into_handler_result(self) -> HandlerResult<T>;
406}
407
408impl<T, E> IntoHandlerResult<T> for Result<T, E>
413where
414 T: Serialize,
415 E: Into<anyhow::Error>,
416{
417 fn into_handler_result(self) -> HandlerResult<T> {
418 self.map(Output::Render).map_err(Into::into)
419 }
420}
421
422impl<T: Serialize> IntoHandlerResult<T> for HandlerResult<T> {
427 fn into_handler_result(self) -> HandlerResult<T> {
428 self
429 }
430}
431
432#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
434pub struct ExitStatus(u8);
435
436impl ExitStatus {
437 pub const SUCCESS: Self = Self(0);
439 pub const FAILURE: Self = Self(1);
441 pub const USAGE_ERROR: Self = Self(2);
443
444 pub const fn code(self) -> u8 {
446 self.0
447 }
448}
449
450#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
452#[error("an external failure status must be nonzero")]
453pub struct InvalidExternalStatus;
454
455#[derive(Debug, Clone)]
466pub struct ExternalFailure {
467 status: ExitStatus,
468 diagnostic: String,
469 source: Option<Arc<dyn std::error::Error + Send + Sync + 'static>>,
470}
471
472impl ExternalFailure {
473 pub fn new(status: u8, diagnostic: impl Into<String>) -> Result<Self, InvalidExternalStatus> {
476 if status == 0 {
477 return Err(InvalidExternalStatus);
478 }
479 Ok(Self {
480 status: ExitStatus(status),
481 diagnostic: diagnostic.into(),
482 source: None,
483 })
484 }
485
486 pub const fn exit_status(&self) -> ExitStatus {
488 self.status
489 }
490
491 pub fn diagnostic(&self) -> &str {
493 &self.diagnostic
494 }
495
496 pub fn with_source<E>(mut self, source: E) -> Self
498 where
499 E: std::error::Error + Send + Sync + 'static,
500 {
501 self.source = Some(Arc::new(source));
502 self
503 }
504}
505
506impl fmt::Display for ExternalFailure {
507 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
508 f.write_str(self.diagnostic())
509 }
510}
511
512impl std::error::Error for ExternalFailure {
513 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
514 self.source
515 .as_deref()
516 .map(|source| source as &(dyn std::error::Error + 'static))
517 }
518}
519
520#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
522#[non_exhaustive]
523pub enum SuccessKind {
524 Command,
526 ClapHelp,
528 ClapVersion,
530}
531
532#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
534#[non_exhaustive]
535pub enum OutputKind {
536 Text,
538 Binary,
540}
541
542#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
544#[non_exhaustive]
545pub enum RunErrorKind {
546 ClapUsage,
548 Handler,
550 Hook(HookPhase),
552 Render,
554 FinalWrite(OutputKind),
556 External,
558}
559
560#[derive(Debug, Clone)]
562pub struct RunOutput {
563 text: String,
564 kind: SuccessKind,
565}
566
567impl RunOutput {
568 pub fn command(text: impl Into<String>) -> Self {
570 Self {
571 text: text.into(),
572 kind: SuccessKind::Command,
573 }
574 }
575
576 pub fn clap_help(text: impl Into<String>) -> Self {
578 Self {
579 text: text.into(),
580 kind: SuccessKind::ClapHelp,
581 }
582 }
583
584 pub fn clap_version(text: impl Into<String>) -> Self {
586 Self {
587 text: text.into(),
588 kind: SuccessKind::ClapVersion,
589 }
590 }
591
592 pub fn as_str(&self) -> &str {
594 &self.text
595 }
596
597 pub const fn kind(&self) -> SuccessKind {
599 self.kind
600 }
601
602 pub fn into_string(self) -> String {
604 self.text
605 }
606}
607
608impl std::ops::Deref for RunOutput {
609 type Target = str;
610 fn deref(&self) -> &Self::Target {
611 self.as_str()
612 }
613}
614
615impl AsRef<str> for RunOutput {
616 fn as_ref(&self) -> &str {
617 self.as_str()
618 }
619}
620
621impl fmt::Display for RunOutput {
622 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
623 f.write_str(self.as_str())
624 }
625}
626
627impl PartialEq<str> for RunOutput {
628 fn eq(&self, other: &str) -> bool {
629 self.as_str() == other
630 }
631}
632
633impl PartialEq<&str> for RunOutput {
634 fn eq(&self, other: &&str) -> bool {
635 self.as_str() == *other
636 }
637}
638
639impl PartialEq<String> for RunOutput {
640 fn eq(&self, other: &String) -> bool {
641 self.as_str() == other
642 }
643}
644
645impl From<String> for RunOutput {
646 fn from(text: String) -> Self {
647 Self::command(text)
648 }
649}
650
651impl From<&str> for RunOutput {
652 fn from(text: &str) -> Self {
653 Self::command(text)
654 }
655}
656
657impl From<RunOutput> for String {
658 fn from(output: RunOutput) -> Self {
659 output.into_string()
660 }
661}
662
663#[derive(Debug, Clone)]
665pub struct RunError {
666 message: String,
667 kind: RunErrorKind,
668 status: ExitStatus,
669 source: Option<Arc<dyn std::error::Error + Send + Sync + 'static>>,
670}
671
672impl RunError {
673 pub fn new(message: impl Into<String>, kind: RunErrorKind) -> Self {
681 assert!(
682 kind != RunErrorKind::External,
683 "external run errors must be constructed from ExternalFailure"
684 );
685 let status = match kind {
686 RunErrorKind::ClapUsage => ExitStatus::USAGE_ERROR,
687 _ => ExitStatus::FAILURE,
688 };
689 Self {
690 message: message.into(),
691 kind,
692 status,
693 source: None,
694 }
695 }
696
697 pub fn with_source<E>(mut self, source: E) -> Self
699 where
700 E: std::error::Error + Send + Sync + 'static,
701 {
702 self.source = Some(Arc::new(source));
703 self
704 }
705
706 pub fn as_str(&self) -> &str {
708 &self.message
709 }
710
711 pub const fn kind(&self) -> RunErrorKind {
713 self.kind
714 }
715
716 pub const fn exit_status(&self) -> ExitStatus {
718 self.status
719 }
720
721 pub fn into_string(self) -> String {
723 self.message
724 }
725}
726
727impl std::ops::Deref for RunError {
728 type Target = str;
729 fn deref(&self) -> &Self::Target {
730 self.as_str()
731 }
732}
733
734impl AsRef<str> for RunError {
735 fn as_ref(&self) -> &str {
736 self.as_str()
737 }
738}
739
740impl fmt::Display for RunError {
741 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
742 f.write_str(self.as_str())
743 }
744}
745
746impl std::error::Error for RunError {
747 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
748 self.source
749 .as_deref()
750 .map(|source| source as &(dyn std::error::Error + 'static))
751 }
752}
753
754impl From<ExternalFailure> for RunError {
755 fn from(failure: ExternalFailure) -> Self {
756 Self {
757 message: failure.diagnostic,
758 kind: RunErrorKind::External,
759 status: failure.status,
760 source: failure.source,
761 }
762 }
763}
764
765impl From<String> for RunError {
766 fn from(message: String) -> Self {
767 Self::new(message, RunErrorKind::Handler)
768 }
769}
770
771impl From<&str> for RunError {
772 fn from(message: &str) -> Self {
773 Self::new(message, RunErrorKind::Handler)
774 }
775}
776
777impl From<RunError> for String {
778 fn from(error: RunError) -> Self {
779 error.into_string()
780 }
781}
782
783#[derive(Debug)]
791#[non_exhaustive]
792pub enum RunResult {
793 Handled(RunOutput),
795 Binary(Vec<u8>, String),
797 Silent,
799 Error(RunError),
802 NoMatch(ArgMatches),
804}
805
806impl RunResult {
807 pub fn is_handled(&self) -> bool {
809 matches!(self, RunResult::Handled(_))
810 }
811
812 pub fn is_binary(&self) -> bool {
814 matches!(self, RunResult::Binary(_, _))
815 }
816
817 pub fn is_silent(&self) -> bool {
819 matches!(self, RunResult::Silent)
820 }
821
822 pub fn is_error(&self) -> bool {
824 matches!(self, RunResult::Error(_))
825 }
826
827 pub fn output(&self) -> Option<&str> {
829 match self {
830 RunResult::Handled(s) => Some(s),
831 _ => None,
832 }
833 }
834
835 pub fn error(&self) -> Option<&str> {
837 match self {
838 RunResult::Error(s) => Some(s),
839 _ => None,
840 }
841 }
842
843 pub fn success_kind(&self) -> Option<SuccessKind> {
845 match self {
846 RunResult::Handled(output) => Some(output.kind()),
847 RunResult::Binary(_, _) | RunResult::Silent => Some(SuccessKind::Command),
848 _ => None,
849 }
850 }
851
852 pub fn error_kind(&self) -> Option<RunErrorKind> {
854 match self {
855 RunResult::Error(error) => Some(error.kind()),
856 _ => None,
857 }
858 }
859
860 pub fn exit_status(&self) -> Option<ExitStatus> {
865 match self {
866 RunResult::Handled(_) | RunResult::Binary(_, _) | RunResult::Silent => {
867 Some(ExitStatus::SUCCESS)
868 }
869 RunResult::Error(error) => Some(error.exit_status()),
870 RunResult::NoMatch(_) => None,
871 }
872 }
873
874 pub fn binary(&self) -> Option<(&[u8], &str)> {
876 match self {
877 RunResult::Binary(bytes, filename) => Some((bytes, filename)),
878 _ => None,
879 }
880 }
881
882 pub fn matches(&self) -> Option<&ArgMatches> {
884 match self {
885 RunResult::NoMatch(m) => Some(m),
886 _ => None,
887 }
888 }
889}
890
891pub trait Handler {
915 type Output: Serialize;
917
918 fn handle(&mut self, matches: &ArgMatches, ctx: &CommandContext)
920 -> HandlerResult<Self::Output>;
921
922 fn expected_args(&self) -> Vec<ExpectedArg> {
927 Vec::new()
928 }
929}
930
931pub struct FnHandler<F, T, R = HandlerResult<T>>
954where
955 T: Serialize,
956{
957 f: F,
958 _phantom: std::marker::PhantomData<fn() -> (T, R)>,
959}
960
961impl<F, T, R> FnHandler<F, T, R>
962where
963 F: FnMut(&ArgMatches, &CommandContext) -> R,
964 R: IntoHandlerResult<T>,
965 T: Serialize,
966{
967 pub fn new(f: F) -> Self {
969 Self {
970 f,
971 _phantom: std::marker::PhantomData,
972 }
973 }
974}
975
976impl<F, T, R> Handler for FnHandler<F, T, R>
977where
978 F: FnMut(&ArgMatches, &CommandContext) -> R,
979 R: IntoHandlerResult<T>,
980 T: Serialize,
981{
982 type Output = T;
983
984 fn handle(&mut self, matches: &ArgMatches, ctx: &CommandContext) -> HandlerResult<T> {
985 (self.f)(matches, ctx).into_handler_result()
986 }
987}
988
989pub struct SimpleFnHandler<F, T, R = HandlerResult<T>>
1016where
1017 T: Serialize,
1018{
1019 f: F,
1020 _phantom: std::marker::PhantomData<fn() -> (T, R)>,
1021}
1022
1023impl<F, T, R> SimpleFnHandler<F, T, R>
1024where
1025 F: FnMut(&ArgMatches) -> R,
1026 R: IntoHandlerResult<T>,
1027 T: Serialize,
1028{
1029 pub fn new(f: F) -> Self {
1031 Self {
1032 f,
1033 _phantom: std::marker::PhantomData,
1034 }
1035 }
1036}
1037
1038impl<F, T, R> Handler for SimpleFnHandler<F, T, R>
1039where
1040 F: FnMut(&ArgMatches) -> R,
1041 R: IntoHandlerResult<T>,
1042 T: Serialize,
1043{
1044 type Output = T;
1045
1046 fn handle(&mut self, matches: &ArgMatches, _ctx: &CommandContext) -> HandlerResult<T> {
1047 (self.f)(matches).into_handler_result()
1048 }
1049}
1050
1051#[cfg(test)]
1052mod tests {
1053 use super::*;
1054 use serde_json::json;
1055
1056 #[test]
1057 fn test_command_context_creation() {
1058 let ctx = CommandContext {
1059 command_path: vec!["config".into(), "get".into()],
1060 app_state: Rc::new(Extensions::new()),
1061 extensions: Extensions::new(),
1062 };
1063 assert_eq!(ctx.command_path, vec!["config", "get"]);
1064 }
1065
1066 #[test]
1067 fn external_failure_rejects_success_and_preserves_metadata() {
1068 assert_eq!(
1069 ExternalFailure::new(0, "not a failure").unwrap_err(),
1070 InvalidExternalStatus
1071 );
1072
1073 let failure = ExternalFailure::new(128, "fatal: repository missing\n")
1074 .unwrap()
1075 .with_source(std::io::Error::other("git failed"));
1076 assert_eq!(failure.exit_status().code(), 128);
1077 assert_eq!(failure.diagnostic(), "fatal: repository missing\n");
1078 assert_eq!(
1079 std::error::Error::source(&failure).unwrap().to_string(),
1080 "git failed"
1081 );
1082
1083 let captured = RunError::from(failure);
1084 assert_eq!(captured.kind(), RunErrorKind::External);
1085 assert_eq!(captured.exit_status().code(), 128);
1086 assert_eq!(captured.as_str(), "fatal: repository missing\n");
1087 assert_eq!(
1088 std::error::Error::source(&captured).unwrap().to_string(),
1089 "git failed"
1090 );
1091 }
1092
1093 #[test]
1094 #[should_panic(expected = "external run errors must be constructed from ExternalFailure")]
1095 fn run_error_new_rejects_external_kind() {
1096 let _ = RunError::new("inconsistent", RunErrorKind::External);
1097 }
1098
1099 #[test]
1100 fn test_command_context_default() {
1101 let ctx = CommandContext::default();
1102 assert!(ctx.command_path.is_empty());
1103 assert!(ctx.extensions.is_empty());
1104 assert!(ctx.app_state.is_empty());
1105 }
1106
1107 #[test]
1108 fn test_command_context_with_app_state() {
1109 struct Database {
1110 url: String,
1111 }
1112 struct Config {
1113 debug: bool,
1114 }
1115
1116 let mut app_state = Extensions::new();
1118 app_state.insert(Database {
1119 url: "postgres://localhost".into(),
1120 });
1121 app_state.insert(Config { debug: true });
1122 let app_state = Rc::new(app_state);
1123
1124 let ctx = CommandContext {
1126 command_path: vec!["list".into()],
1127 app_state: app_state.clone(),
1128 extensions: Extensions::new(),
1129 };
1130
1131 let db = ctx.app_state.get::<Database>().unwrap();
1133 assert_eq!(db.url, "postgres://localhost");
1134
1135 let config = ctx.app_state.get::<Config>().unwrap();
1136 assert!(config.debug);
1137
1138 assert_eq!(Rc::strong_count(&ctx.app_state), 2);
1140 }
1141
1142 #[test]
1143 fn test_command_context_app_state_get_required() {
1144 struct Present;
1145
1146 let mut app_state = Extensions::new();
1147 app_state.insert(Present);
1148
1149 let ctx = CommandContext {
1150 command_path: vec![],
1151 app_state: Rc::new(app_state),
1152 extensions: Extensions::new(),
1153 };
1154
1155 assert!(ctx.app_state.get_required::<Present>().is_ok());
1157
1158 #[derive(Debug)]
1160 struct Missing;
1161 let err = ctx.app_state.get_required::<Missing>();
1162 assert!(err.is_err());
1163 assert!(err.unwrap_err().to_string().contains("Extension missing"));
1164 }
1165
1166 #[test]
1168 fn test_extensions_insert_and_get() {
1169 struct MyState {
1170 value: i32,
1171 }
1172
1173 let mut ext = Extensions::new();
1174 assert!(ext.is_empty());
1175
1176 ext.insert(MyState { value: 42 });
1177 assert!(!ext.is_empty());
1178 assert_eq!(ext.len(), 1);
1179
1180 let state = ext.get::<MyState>().unwrap();
1181 assert_eq!(state.value, 42);
1182 }
1183
1184 #[test]
1185 fn test_extensions_get_mut() {
1186 struct Counter {
1187 count: i32,
1188 }
1189
1190 let mut ext = Extensions::new();
1191 ext.insert(Counter { count: 0 });
1192
1193 if let Some(counter) = ext.get_mut::<Counter>() {
1194 counter.count += 1;
1195 }
1196
1197 assert_eq!(ext.get::<Counter>().unwrap().count, 1);
1198 }
1199
1200 #[test]
1201 fn test_extensions_multiple_types() {
1202 struct TypeA(i32);
1203 struct TypeB(String);
1204
1205 let mut ext = Extensions::new();
1206 ext.insert(TypeA(1));
1207 ext.insert(TypeB("hello".into()));
1208
1209 assert_eq!(ext.len(), 2);
1210 assert_eq!(ext.get::<TypeA>().unwrap().0, 1);
1211 assert_eq!(ext.get::<TypeB>().unwrap().0, "hello");
1212 }
1213
1214 #[test]
1215 fn test_extensions_replace() {
1216 struct Value(i32);
1217
1218 let mut ext = Extensions::new();
1219 ext.insert(Value(1));
1220
1221 let old = ext.insert(Value(2));
1222 assert_eq!(old.unwrap().0, 1);
1223 assert_eq!(ext.get::<Value>().unwrap().0, 2);
1224 }
1225
1226 #[test]
1227 fn test_extensions_remove() {
1228 struct Value(i32);
1229
1230 let mut ext = Extensions::new();
1231 ext.insert(Value(42));
1232
1233 let removed = ext.remove::<Value>();
1234 assert_eq!(removed.unwrap().0, 42);
1235 assert!(ext.is_empty());
1236 assert!(ext.get::<Value>().is_none());
1237 }
1238
1239 #[test]
1240 fn test_extensions_contains() {
1241 struct Present;
1242 struct Absent;
1243
1244 let mut ext = Extensions::new();
1245 ext.insert(Present);
1246
1247 assert!(ext.contains::<Present>());
1248 assert!(!ext.contains::<Absent>());
1249 }
1250
1251 #[test]
1252 fn test_extensions_clear() {
1253 struct A;
1254 struct B;
1255
1256 let mut ext = Extensions::new();
1257 ext.insert(A);
1258 ext.insert(B);
1259 assert_eq!(ext.len(), 2);
1260
1261 ext.clear();
1262 assert!(ext.is_empty());
1263 }
1264
1265 #[test]
1266 fn test_extensions_missing_type_returns_none() {
1267 struct NotInserted;
1268
1269 let ext = Extensions::new();
1270 assert!(ext.get::<NotInserted>().is_none());
1271 }
1272
1273 #[test]
1274 fn test_extensions_get_required() {
1275 #[derive(Debug)]
1276 struct Config {
1277 value: i32,
1278 }
1279
1280 let mut ext = Extensions::new();
1281 ext.insert(Config { value: 100 });
1282
1283 let val = ext.get_required::<Config>();
1285 assert!(val.is_ok());
1286 assert_eq!(val.unwrap().value, 100);
1287
1288 #[derive(Debug)]
1290 struct Missing;
1291 let err = ext.get_required::<Missing>();
1292 assert!(err.is_err());
1293 assert!(err
1294 .unwrap_err()
1295 .to_string()
1296 .contains("Extension missing: type"));
1297 }
1298
1299 #[test]
1300 fn test_extensions_get_mut_required() {
1301 #[derive(Debug)]
1302 struct State {
1303 count: i32,
1304 }
1305
1306 let mut ext = Extensions::new();
1307 ext.insert(State { count: 0 });
1308
1309 {
1311 let val = ext.get_mut_required::<State>();
1312 assert!(val.is_ok());
1313 val.unwrap().count += 1;
1314 }
1315 assert_eq!(ext.get_required::<State>().unwrap().count, 1);
1316
1317 #[derive(Debug)]
1319 struct Missing;
1320 let err = ext.get_mut_required::<Missing>();
1321 assert!(err.is_err());
1322 }
1323
1324 #[test]
1325 fn test_extensions_clone_behavior() {
1326 struct Data(#[allow(dead_code)] i32);
1328
1329 let mut original = Extensions::new();
1330 original.insert(Data(42));
1331
1332 let cloned = original.clone();
1333
1334 assert!(original.get::<Data>().is_some());
1336
1337 assert!(cloned.is_empty());
1339 assert!(cloned.get::<Data>().is_none());
1340 }
1341
1342 #[test]
1343 fn test_output_render() {
1344 let output: Output<String> = Output::Render("success".into());
1345 assert!(output.is_render());
1346 assert!(!output.is_silent());
1347 assert!(!output.is_binary());
1348 }
1349
1350 #[test]
1351 fn test_output_silent() {
1352 let output: Output<String> = Output::Silent;
1353 assert!(!output.is_render());
1354 assert!(output.is_silent());
1355 assert!(!output.is_binary());
1356 }
1357
1358 #[test]
1359 fn test_output_binary() {
1360 let output: Output<String> = Output::Binary {
1361 data: vec![0x25, 0x50, 0x44, 0x46],
1362 filename: "report.pdf".into(),
1363 };
1364 assert!(!output.is_render());
1365 assert!(!output.is_silent());
1366 assert!(output.is_binary());
1367 }
1368
1369 #[test]
1370 fn test_run_result_handled() {
1371 let result = RunResult::Handled("output".into());
1372 assert!(result.is_handled());
1373 assert!(!result.is_binary());
1374 assert!(!result.is_silent());
1375 assert_eq!(result.output(), Some("output"));
1376 assert!(result.matches().is_none());
1377 }
1378
1379 #[test]
1380 fn test_run_result_silent() {
1381 let result = RunResult::Silent;
1382 assert!(!result.is_handled());
1383 assert!(!result.is_binary());
1384 assert!(result.is_silent());
1385 }
1386
1387 #[test]
1388 fn test_run_result_binary() {
1389 let bytes = vec![0x25, 0x50, 0x44, 0x46];
1390 let result = RunResult::Binary(bytes.clone(), "report.pdf".into());
1391 assert!(!result.is_handled());
1392 assert!(result.is_binary());
1393 assert!(!result.is_silent());
1394
1395 let (data, filename) = result.binary().unwrap();
1396 assert_eq!(data, &bytes);
1397 assert_eq!(filename, "report.pdf");
1398 }
1399
1400 #[test]
1401 fn test_run_result_no_match() {
1402 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1403 let result = RunResult::NoMatch(matches);
1404 assert!(!result.is_handled());
1405 assert!(!result.is_binary());
1406 assert!(result.matches().is_some());
1407 }
1408
1409 #[test]
1410 fn test_fn_handler() {
1411 let mut handler = FnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| {
1412 Ok(Output::Render(json!({"status": "ok"})))
1413 });
1414
1415 let ctx = CommandContext::default();
1416 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1417
1418 let result = handler.handle(&matches, &ctx);
1419 assert!(result.is_ok());
1420 }
1421
1422 #[test]
1423 fn test_fn_handler_mutation() {
1424 let mut counter = 0u32;
1425
1426 let mut handler = FnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| {
1427 counter += 1;
1428 Ok(Output::Render(counter))
1429 });
1430
1431 let ctx = CommandContext::default();
1432 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1433
1434 let _ = handler.handle(&matches, &ctx);
1435 let _ = handler.handle(&matches, &ctx);
1436 let result = handler.handle(&matches, &ctx);
1437
1438 assert!(result.is_ok());
1439 if let Ok(Output::Render(count)) = result {
1440 assert_eq!(count, 3);
1441 }
1442 }
1443
1444 #[test]
1446 fn test_into_handler_result_from_result_ok() {
1447 use super::IntoHandlerResult;
1448
1449 let result: Result<String, anyhow::Error> = Ok("hello".to_string());
1450 let handler_result = result.into_handler_result();
1451
1452 assert!(handler_result.is_ok());
1453 match handler_result.unwrap() {
1454 Output::Render(s) => assert_eq!(s, "hello"),
1455 _ => panic!("Expected Output::Render"),
1456 }
1457 }
1458
1459 #[test]
1460 fn test_into_handler_result_from_result_err() {
1461 use super::IntoHandlerResult;
1462
1463 let result: Result<String, anyhow::Error> = Err(anyhow::anyhow!("test error"));
1464 let handler_result = result.into_handler_result();
1465
1466 assert!(handler_result.is_err());
1467 assert!(handler_result
1468 .unwrap_err()
1469 .to_string()
1470 .contains("test error"));
1471 }
1472
1473 #[test]
1474 fn test_into_handler_result_passthrough_render() {
1475 use super::IntoHandlerResult;
1476
1477 let handler_result: HandlerResult<String> = Ok(Output::Render("hello".to_string()));
1478 let result = handler_result.into_handler_result();
1479
1480 assert!(result.is_ok());
1481 match result.unwrap() {
1482 Output::Render(s) => assert_eq!(s, "hello"),
1483 _ => panic!("Expected Output::Render"),
1484 }
1485 }
1486
1487 #[test]
1488 fn test_into_handler_result_passthrough_silent() {
1489 use super::IntoHandlerResult;
1490
1491 let handler_result: HandlerResult<String> = Ok(Output::Silent);
1492 let result = handler_result.into_handler_result();
1493
1494 assert!(result.is_ok());
1495 assert!(matches!(result.unwrap(), Output::Silent));
1496 }
1497
1498 #[test]
1499 fn test_into_handler_result_passthrough_binary() {
1500 use super::IntoHandlerResult;
1501
1502 let handler_result: HandlerResult<String> = Ok(Output::Binary {
1503 data: vec![1, 2, 3],
1504 filename: "test.bin".to_string(),
1505 });
1506 let result = handler_result.into_handler_result();
1507
1508 assert!(result.is_ok());
1509 match result.unwrap() {
1510 Output::Binary { data, filename } => {
1511 assert_eq!(data, vec![1, 2, 3]);
1512 assert_eq!(filename, "test.bin");
1513 }
1514 _ => panic!("Expected Output::Binary"),
1515 }
1516 }
1517
1518 #[test]
1519 fn test_fn_handler_with_auto_wrap() {
1520 let mut handler = FnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| {
1522 Ok::<_, anyhow::Error>("auto-wrapped".to_string())
1523 });
1524
1525 let ctx = CommandContext::default();
1526 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1527
1528 let result = handler.handle(&matches, &ctx);
1529 assert!(result.is_ok());
1530 match result.unwrap() {
1531 Output::Render(s) => assert_eq!(s, "auto-wrapped"),
1532 _ => panic!("Expected Output::Render"),
1533 }
1534 }
1535
1536 #[test]
1537 fn test_fn_handler_with_explicit_output() {
1538 let mut handler =
1540 FnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| Ok(Output::<()>::Silent));
1541
1542 let ctx = CommandContext::default();
1543 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1544
1545 let result = handler.handle(&matches, &ctx);
1546 assert!(result.is_ok());
1547 assert!(matches!(result.unwrap(), Output::Silent));
1548 }
1549
1550 #[test]
1551 fn test_fn_handler_with_custom_error_type() {
1552 #[derive(Debug)]
1554 struct CustomError(String);
1555
1556 impl std::fmt::Display for CustomError {
1557 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1558 write!(f, "CustomError: {}", self.0)
1559 }
1560 }
1561
1562 impl std::error::Error for CustomError {}
1563
1564 let mut handler = FnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| {
1565 Err::<String, CustomError>(CustomError("oops".to_string()))
1566 });
1567
1568 let ctx = CommandContext::default();
1569 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1570
1571 let result = handler.handle(&matches, &ctx);
1572 assert!(result.is_err());
1573 assert!(result
1574 .unwrap_err()
1575 .to_string()
1576 .contains("CustomError: oops"));
1577 }
1578
1579 #[test]
1581 fn test_simple_fn_handler_basic() {
1582 use super::SimpleFnHandler;
1583
1584 let mut handler = SimpleFnHandler::new(|_m: &ArgMatches| {
1585 Ok::<_, anyhow::Error>("no context needed".to_string())
1586 });
1587
1588 let ctx = CommandContext::default();
1589 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1590
1591 let result = handler.handle(&matches, &ctx);
1592 assert!(result.is_ok());
1593 match result.unwrap() {
1594 Output::Render(s) => assert_eq!(s, "no context needed"),
1595 _ => panic!("Expected Output::Render"),
1596 }
1597 }
1598
1599 #[test]
1600 fn test_simple_fn_handler_with_args() {
1601 use super::SimpleFnHandler;
1602
1603 let mut handler = SimpleFnHandler::new(|m: &ArgMatches| {
1604 let verbose = m.get_flag("verbose");
1605 Ok::<_, anyhow::Error>(verbose)
1606 });
1607
1608 let ctx = CommandContext::default();
1609 let matches = clap::Command::new("test")
1610 .arg(
1611 clap::Arg::new("verbose")
1612 .short('v')
1613 .action(clap::ArgAction::SetTrue),
1614 )
1615 .get_matches_from(vec!["test", "-v"]);
1616
1617 let result = handler.handle(&matches, &ctx);
1618 assert!(result.is_ok());
1619 match result.unwrap() {
1620 Output::Render(v) => assert!(v),
1621 _ => panic!("Expected Output::Render"),
1622 }
1623 }
1624
1625 #[test]
1626 fn test_simple_fn_handler_explicit_output() {
1627 use super::SimpleFnHandler;
1628
1629 let mut handler = SimpleFnHandler::new(|_m: &ArgMatches| Ok(Output::<()>::Silent));
1630
1631 let ctx = CommandContext::default();
1632 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1633
1634 let result = handler.handle(&matches, &ctx);
1635 assert!(result.is_ok());
1636 assert!(matches!(result.unwrap(), Output::Silent));
1637 }
1638
1639 #[test]
1640 fn test_simple_fn_handler_error() {
1641 use super::SimpleFnHandler;
1642
1643 let mut handler = SimpleFnHandler::new(|_m: &ArgMatches| {
1644 Err::<String, _>(anyhow::anyhow!("simple error"))
1645 });
1646
1647 let ctx = CommandContext::default();
1648 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1649
1650 let result = handler.handle(&matches, &ctx);
1651 assert!(result.is_err());
1652 assert!(result.unwrap_err().to_string().contains("simple error"));
1653 }
1654
1655 #[test]
1656 fn test_simple_fn_handler_mutation() {
1657 use super::SimpleFnHandler;
1658
1659 let mut counter = 0u32;
1660 let mut handler = SimpleFnHandler::new(|_m: &ArgMatches| {
1661 counter += 1;
1662 Ok::<_, anyhow::Error>(counter)
1663 });
1664
1665 let ctx = CommandContext::default();
1666 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1667
1668 let _ = handler.handle(&matches, &ctx);
1669 let _ = handler.handle(&matches, &ctx);
1670 let result = handler.handle(&matches, &ctx);
1671
1672 assert!(result.is_ok());
1673 match result.unwrap() {
1674 Output::Render(n) => assert_eq!(n, 3),
1675 _ => panic!("Expected Output::Render"),
1676 }
1677 }
1678}