1use crate::artifact::{Artifact, ArtifactRun};
68use crate::hooks::HookPhase;
69use crate::verify::ExpectedArg;
70use clap::ArgMatches;
71use serde::Serialize;
72use std::any::{Any, TypeId};
73use std::collections::HashMap;
74use std::fmt;
75use std::rc::Rc;
76use std::sync::Arc;
77
78#[derive(Default)]
111pub struct Extensions {
112 map: HashMap<TypeId, Box<dyn Any>>,
113}
114
115impl Extensions {
116 pub fn new() -> Self {
118 Self::default()
119 }
120
121 pub fn insert<T: 'static>(&mut self, val: T) -> Option<T> {
125 self.map
126 .insert(TypeId::of::<T>(), Box::new(val))
127 .and_then(|boxed| boxed.downcast().ok().map(|b| *b))
128 }
129
130 pub fn get<T: 'static>(&self) -> Option<&T> {
134 self.map
135 .get(&TypeId::of::<T>())
136 .and_then(|boxed| boxed.downcast_ref())
137 }
138
139 pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> {
143 self.map
144 .get_mut(&TypeId::of::<T>())
145 .and_then(|boxed| boxed.downcast_mut())
146 }
147
148 pub fn get_required<T: 'static>(&self) -> Result<&T, anyhow::Error> {
152 self.get::<T>().ok_or_else(|| {
153 anyhow::anyhow!(
154 "Extension missing: type {} not found in context",
155 std::any::type_name::<T>()
156 )
157 })
158 }
159
160 pub fn get_mut_required<T: 'static>(&mut self) -> Result<&mut T, anyhow::Error> {
164 self.get_mut::<T>().ok_or_else(|| {
165 anyhow::anyhow!(
166 "Extension missing: type {} not found in context",
167 std::any::type_name::<T>()
168 )
169 })
170 }
171
172 pub fn remove<T: 'static>(&mut self) -> Option<T> {
174 self.map
175 .remove(&TypeId::of::<T>())
176 .and_then(|boxed| boxed.downcast().ok().map(|b| *b))
177 }
178
179 pub fn contains<T: 'static>(&self) -> bool {
181 self.map.contains_key(&TypeId::of::<T>())
182 }
183
184 pub fn len(&self) -> usize {
186 self.map.len()
187 }
188
189 pub fn is_empty(&self) -> bool {
191 self.map.is_empty()
192 }
193
194 pub fn clear(&mut self) {
196 self.map.clear();
197 }
198}
199
200impl fmt::Debug for Extensions {
201 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
202 f.debug_struct("Extensions")
203 .field("len", &self.map.len())
204 .finish_non_exhaustive()
205 }
206}
207
208impl Clone for Extensions {
209 fn clone(&self) -> Self {
210 Self::new()
214 }
215}
216
217#[derive(Debug)]
297pub struct CommandContext {
298 pub command_path: Vec<String>,
300
301 pub app_state: Rc<Extensions>,
308
309 pub extensions: Extensions,
314}
315
316impl CommandContext {
317 pub fn new(command_path: Vec<String>, app_state: Rc<Extensions>) -> Self {
321 Self {
322 command_path,
323 app_state,
324 extensions: Extensions::new(),
325 }
326 }
327}
328
329impl Default for CommandContext {
330 fn default() -> Self {
331 Self {
332 command_path: Vec::new(),
333 app_state: Rc::new(Extensions::new()),
334 extensions: Extensions::new(),
335 }
336 }
337}
338
339#[derive(Debug)]
358#[non_exhaustive]
359pub enum Output<T: Serialize> {
360 Render(T),
362 Silent,
364 Binary {
366 data: Vec<u8>,
368 filename: String,
370 },
371 Artifact(Artifact<T>),
374}
375
376impl<T: Serialize> Output<T> {
377 pub fn is_render(&self) -> bool {
379 matches!(self, Output::Render(_))
380 }
381
382 pub fn is_silent(&self) -> bool {
384 matches!(self, Output::Silent)
385 }
386
387 pub fn is_binary(&self) -> bool {
389 matches!(self, Output::Binary { .. })
390 }
391
392 pub fn is_artifact(&self) -> bool {
394 matches!(self, Output::Artifact(_))
395 }
396}
397
398pub type HandlerResult<T> = Result<Output<T>, anyhow::Error>;
402
403pub trait IntoHandlerResult<T: Serialize> {
429 fn into_handler_result(self) -> HandlerResult<T>;
431}
432
433impl<T, E> IntoHandlerResult<T> for Result<T, E>
438where
439 T: Serialize,
440 E: Into<anyhow::Error>,
441{
442 fn into_handler_result(self) -> HandlerResult<T> {
443 self.map(Output::Render).map_err(Into::into)
444 }
445}
446
447impl<T: Serialize> IntoHandlerResult<T> for HandlerResult<T> {
452 fn into_handler_result(self) -> HandlerResult<T> {
453 self
454 }
455}
456
457#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
459pub struct ExitStatus(u8);
460
461impl ExitStatus {
462 pub const SUCCESS: Self = Self(0);
464 pub const FAILURE: Self = Self(1);
466 pub const USAGE_ERROR: Self = Self(2);
468
469 pub const fn code(self) -> u8 {
471 self.0
472 }
473}
474
475#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
477#[error("an external failure status must be nonzero")]
478pub struct InvalidExternalStatus;
479
480#[derive(Debug, Clone)]
491pub struct ExternalFailure {
492 status: ExitStatus,
493 diagnostic: String,
494 source: Option<Arc<dyn std::error::Error + Send + Sync + 'static>>,
495}
496
497impl ExternalFailure {
498 pub fn new(status: u8, diagnostic: impl Into<String>) -> Result<Self, InvalidExternalStatus> {
501 if status == 0 {
502 return Err(InvalidExternalStatus);
503 }
504 Ok(Self {
505 status: ExitStatus(status),
506 diagnostic: diagnostic.into(),
507 source: None,
508 })
509 }
510
511 pub const fn exit_status(&self) -> ExitStatus {
513 self.status
514 }
515
516 pub fn diagnostic(&self) -> &str {
518 &self.diagnostic
519 }
520
521 pub fn with_source<E>(mut self, source: E) -> Self
523 where
524 E: std::error::Error + Send + Sync + 'static,
525 {
526 self.source = Some(Arc::new(source));
527 self
528 }
529}
530
531impl fmt::Display for ExternalFailure {
532 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
533 f.write_str(self.diagnostic())
534 }
535}
536
537impl std::error::Error for ExternalFailure {
538 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
539 self.source
540 .as_deref()
541 .map(|source| source as &(dyn std::error::Error + 'static))
542 }
543}
544
545#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
547#[non_exhaustive]
548pub enum SuccessKind {
549 Command,
551 ClapHelp,
553 ClapVersion,
555}
556
557#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
559#[non_exhaustive]
560pub enum OutputKind {
561 Text,
563 Binary,
565 Artifact,
569}
570
571#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
573#[non_exhaustive]
574pub enum RunErrorKind {
575 ClapUsage,
577 DefaultCommand,
579 Handler,
581 Hook(HookPhase),
583 Render,
585 FinalWrite(OutputKind),
587 External,
589}
590
591#[derive(Debug, Clone)]
593pub struct RunOutput {
594 text: String,
595 kind: SuccessKind,
596}
597
598impl RunOutput {
599 pub fn command(text: impl Into<String>) -> Self {
601 Self {
602 text: text.into(),
603 kind: SuccessKind::Command,
604 }
605 }
606
607 pub fn clap_help(text: impl Into<String>) -> Self {
609 Self {
610 text: text.into(),
611 kind: SuccessKind::ClapHelp,
612 }
613 }
614
615 pub fn clap_version(text: impl Into<String>) -> Self {
617 Self {
618 text: text.into(),
619 kind: SuccessKind::ClapVersion,
620 }
621 }
622
623 pub fn as_str(&self) -> &str {
625 &self.text
626 }
627
628 pub const fn kind(&self) -> SuccessKind {
630 self.kind
631 }
632
633 pub fn into_string(self) -> String {
635 self.text
636 }
637}
638
639impl std::ops::Deref for RunOutput {
640 type Target = str;
641 fn deref(&self) -> &Self::Target {
642 self.as_str()
643 }
644}
645
646impl AsRef<str> for RunOutput {
647 fn as_ref(&self) -> &str {
648 self.as_str()
649 }
650}
651
652impl fmt::Display for RunOutput {
653 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
654 f.write_str(self.as_str())
655 }
656}
657
658impl PartialEq<str> for RunOutput {
659 fn eq(&self, other: &str) -> bool {
660 self.as_str() == other
661 }
662}
663
664impl PartialEq<&str> for RunOutput {
665 fn eq(&self, other: &&str) -> bool {
666 self.as_str() == *other
667 }
668}
669
670impl PartialEq<String> for RunOutput {
671 fn eq(&self, other: &String) -> bool {
672 self.as_str() == other
673 }
674}
675
676impl From<String> for RunOutput {
677 fn from(text: String) -> Self {
678 Self::command(text)
679 }
680}
681
682impl From<&str> for RunOutput {
683 fn from(text: &str) -> Self {
684 Self::command(text)
685 }
686}
687
688impl From<RunOutput> for String {
689 fn from(output: RunOutput) -> Self {
690 output.into_string()
691 }
692}
693
694#[derive(Debug, Clone)]
696pub struct RunError {
697 message: String,
698 kind: RunErrorKind,
699 status: ExitStatus,
700 source: Option<Arc<dyn std::error::Error + Send + Sync + 'static>>,
701}
702
703impl RunError {
704 pub fn new(message: impl Into<String>, kind: RunErrorKind) -> Self {
712 assert!(
713 kind != RunErrorKind::External,
714 "external run errors must be constructed from ExternalFailure"
715 );
716 let status = match kind {
717 RunErrorKind::ClapUsage => ExitStatus::USAGE_ERROR,
718 _ => ExitStatus::FAILURE,
719 };
720 Self {
721 message: message.into(),
722 kind,
723 status,
724 source: None,
725 }
726 }
727
728 pub fn with_source<E>(mut self, source: E) -> Self
730 where
731 E: std::error::Error + Send + Sync + 'static,
732 {
733 self.source = Some(Arc::new(source));
734 self
735 }
736
737 pub fn as_str(&self) -> &str {
739 &self.message
740 }
741
742 pub const fn kind(&self) -> RunErrorKind {
744 self.kind
745 }
746
747 pub const fn exit_status(&self) -> ExitStatus {
749 self.status
750 }
751
752 pub fn into_string(self) -> String {
754 self.message
755 }
756}
757
758impl std::ops::Deref for RunError {
759 type Target = str;
760 fn deref(&self) -> &Self::Target {
761 self.as_str()
762 }
763}
764
765impl AsRef<str> for RunError {
766 fn as_ref(&self) -> &str {
767 self.as_str()
768 }
769}
770
771impl fmt::Display for RunError {
772 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
773 f.write_str(self.as_str())
774 }
775}
776
777impl std::error::Error for RunError {
778 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
779 self.source
780 .as_deref()
781 .map(|source| source as &(dyn std::error::Error + 'static))
782 }
783}
784
785impl From<ExternalFailure> for RunError {
786 fn from(failure: ExternalFailure) -> Self {
787 Self {
788 message: failure.diagnostic,
789 kind: RunErrorKind::External,
790 status: failure.status,
791 source: failure.source,
792 }
793 }
794}
795
796impl From<String> for RunError {
797 fn from(message: String) -> Self {
798 Self::new(message, RunErrorKind::Handler)
799 }
800}
801
802impl From<&str> for RunError {
803 fn from(message: &str) -> Self {
804 Self::new(message, RunErrorKind::Handler)
805 }
806}
807
808impl From<RunError> for String {
809 fn from(error: RunError) -> Self {
810 error.into_string()
811 }
812}
813
814#[derive(Debug)]
822#[non_exhaustive]
823pub enum RunResult {
824 Handled(RunOutput),
826 Binary(Vec<u8>, String),
828 Artifact(ArtifactRun),
835 Silent,
837 Error(RunError),
840 NoMatch(ArgMatches),
842}
843
844impl RunResult {
845 pub fn is_handled(&self) -> bool {
847 matches!(self, RunResult::Handled(_))
848 }
849
850 pub fn is_binary(&self) -> bool {
852 matches!(self, RunResult::Binary(_, _))
853 }
854
855 pub fn is_artifact(&self) -> bool {
857 matches!(self, RunResult::Artifact(_))
858 }
859
860 pub fn is_silent(&self) -> bool {
862 matches!(self, RunResult::Silent)
863 }
864
865 pub fn is_error(&self) -> bool {
867 matches!(self, RunResult::Error(_))
868 }
869
870 pub fn output(&self) -> Option<&str> {
872 match self {
873 RunResult::Handled(s) => Some(s),
874 _ => None,
875 }
876 }
877
878 pub fn error(&self) -> Option<&str> {
880 match self {
881 RunResult::Error(s) => Some(s),
882 _ => None,
883 }
884 }
885
886 pub fn success_kind(&self) -> Option<SuccessKind> {
888 match self {
889 RunResult::Handled(output) => Some(output.kind()),
890 RunResult::Binary(_, _) | RunResult::Artifact(_) | RunResult::Silent => {
891 Some(SuccessKind::Command)
892 }
893 _ => None,
894 }
895 }
896
897 pub fn error_kind(&self) -> Option<RunErrorKind> {
899 match self {
900 RunResult::Error(error) => Some(error.kind()),
901 _ => None,
902 }
903 }
904
905 pub fn exit_status(&self) -> Option<ExitStatus> {
910 match self {
911 RunResult::Handled(_)
912 | RunResult::Binary(_, _)
913 | RunResult::Artifact(_)
914 | RunResult::Silent => Some(ExitStatus::SUCCESS),
915 RunResult::Error(error) => Some(error.exit_status()),
916 RunResult::NoMatch(_) => None,
917 }
918 }
919
920 pub fn binary(&self) -> Option<(&[u8], &str)> {
922 match self {
923 RunResult::Binary(bytes, filename) => Some((bytes, filename)),
924 _ => None,
925 }
926 }
927
928 pub fn artifact(&self) -> Option<&ArtifactRun> {
930 match self {
931 RunResult::Artifact(run) => Some(run),
932 _ => None,
933 }
934 }
935
936 pub fn matches(&self) -> Option<&ArgMatches> {
938 match self {
939 RunResult::NoMatch(m) => Some(m),
940 _ => None,
941 }
942 }
943}
944
945pub trait Handler {
969 type Output: Serialize;
971
972 fn handle(&mut self, matches: &ArgMatches, ctx: &CommandContext)
974 -> HandlerResult<Self::Output>;
975
976 fn expected_args(&self) -> Vec<ExpectedArg> {
981 Vec::new()
982 }
983}
984
985pub struct FnHandler<F, T, R = HandlerResult<T>>
1008where
1009 T: Serialize,
1010{
1011 f: F,
1012 _phantom: std::marker::PhantomData<fn() -> (T, R)>,
1013}
1014
1015impl<F, T, R> FnHandler<F, T, R>
1016where
1017 F: FnMut(&ArgMatches, &CommandContext) -> R,
1018 R: IntoHandlerResult<T>,
1019 T: Serialize,
1020{
1021 pub fn new(f: F) -> Self {
1023 Self {
1024 f,
1025 _phantom: std::marker::PhantomData,
1026 }
1027 }
1028}
1029
1030impl<F, T, R> Handler for FnHandler<F, T, R>
1031where
1032 F: FnMut(&ArgMatches, &CommandContext) -> R,
1033 R: IntoHandlerResult<T>,
1034 T: Serialize,
1035{
1036 type Output = T;
1037
1038 fn handle(&mut self, matches: &ArgMatches, ctx: &CommandContext) -> HandlerResult<T> {
1039 (self.f)(matches, ctx).into_handler_result()
1040 }
1041}
1042
1043pub struct SimpleFnHandler<F, T, R = HandlerResult<T>>
1070where
1071 T: Serialize,
1072{
1073 f: F,
1074 _phantom: std::marker::PhantomData<fn() -> (T, R)>,
1075}
1076
1077impl<F, T, R> SimpleFnHandler<F, T, R>
1078where
1079 F: FnMut(&ArgMatches) -> R,
1080 R: IntoHandlerResult<T>,
1081 T: Serialize,
1082{
1083 pub fn new(f: F) -> Self {
1085 Self {
1086 f,
1087 _phantom: std::marker::PhantomData,
1088 }
1089 }
1090}
1091
1092impl<F, T, R> Handler for SimpleFnHandler<F, T, R>
1093where
1094 F: FnMut(&ArgMatches) -> R,
1095 R: IntoHandlerResult<T>,
1096 T: Serialize,
1097{
1098 type Output = T;
1099
1100 fn handle(&mut self, matches: &ArgMatches, _ctx: &CommandContext) -> HandlerResult<T> {
1101 (self.f)(matches).into_handler_result()
1102 }
1103}
1104
1105#[cfg(test)]
1106mod tests {
1107 use super::*;
1108 use serde_json::json;
1109
1110 #[test]
1111 fn test_command_context_creation() {
1112 let ctx = CommandContext {
1113 command_path: vec!["config".into(), "get".into()],
1114 app_state: Rc::new(Extensions::new()),
1115 extensions: Extensions::new(),
1116 };
1117 assert_eq!(ctx.command_path, vec!["config", "get"]);
1118 }
1119
1120 #[test]
1121 fn external_failure_rejects_success_and_preserves_metadata() {
1122 assert_eq!(
1123 ExternalFailure::new(0, "not a failure").unwrap_err(),
1124 InvalidExternalStatus
1125 );
1126
1127 let failure = ExternalFailure::new(128, "fatal: repository missing\n")
1128 .unwrap()
1129 .with_source(std::io::Error::other("git failed"));
1130 assert_eq!(failure.exit_status().code(), 128);
1131 assert_eq!(failure.diagnostic(), "fatal: repository missing\n");
1132 assert_eq!(
1133 std::error::Error::source(&failure).unwrap().to_string(),
1134 "git failed"
1135 );
1136
1137 let captured = RunError::from(failure);
1138 assert_eq!(captured.kind(), RunErrorKind::External);
1139 assert_eq!(captured.exit_status().code(), 128);
1140 assert_eq!(captured.as_str(), "fatal: repository missing\n");
1141 assert_eq!(
1142 std::error::Error::source(&captured).unwrap().to_string(),
1143 "git failed"
1144 );
1145 }
1146
1147 #[test]
1148 #[should_panic(expected = "external run errors must be constructed from ExternalFailure")]
1149 fn run_error_new_rejects_external_kind() {
1150 let _ = RunError::new("inconsistent", RunErrorKind::External);
1151 }
1152
1153 #[test]
1154 fn test_command_context_default() {
1155 let ctx = CommandContext::default();
1156 assert!(ctx.command_path.is_empty());
1157 assert!(ctx.extensions.is_empty());
1158 assert!(ctx.app_state.is_empty());
1159 }
1160
1161 #[test]
1162 fn test_command_context_with_app_state() {
1163 struct Database {
1164 url: String,
1165 }
1166 struct Config {
1167 debug: bool,
1168 }
1169
1170 let mut app_state = Extensions::new();
1172 app_state.insert(Database {
1173 url: "postgres://localhost".into(),
1174 });
1175 app_state.insert(Config { debug: true });
1176 let app_state = Rc::new(app_state);
1177
1178 let ctx = CommandContext {
1180 command_path: vec!["list".into()],
1181 app_state: app_state.clone(),
1182 extensions: Extensions::new(),
1183 };
1184
1185 let db = ctx.app_state.get::<Database>().unwrap();
1187 assert_eq!(db.url, "postgres://localhost");
1188
1189 let config = ctx.app_state.get::<Config>().unwrap();
1190 assert!(config.debug);
1191
1192 assert_eq!(Rc::strong_count(&ctx.app_state), 2);
1194 }
1195
1196 #[test]
1197 fn test_command_context_app_state_get_required() {
1198 struct Present;
1199
1200 let mut app_state = Extensions::new();
1201 app_state.insert(Present);
1202
1203 let ctx = CommandContext {
1204 command_path: vec![],
1205 app_state: Rc::new(app_state),
1206 extensions: Extensions::new(),
1207 };
1208
1209 assert!(ctx.app_state.get_required::<Present>().is_ok());
1211
1212 #[derive(Debug)]
1214 struct Missing;
1215 let err = ctx.app_state.get_required::<Missing>();
1216 assert!(err.is_err());
1217 assert!(err.unwrap_err().to_string().contains("Extension missing"));
1218 }
1219
1220 #[test]
1222 fn test_extensions_insert_and_get() {
1223 struct MyState {
1224 value: i32,
1225 }
1226
1227 let mut ext = Extensions::new();
1228 assert!(ext.is_empty());
1229
1230 ext.insert(MyState { value: 42 });
1231 assert!(!ext.is_empty());
1232 assert_eq!(ext.len(), 1);
1233
1234 let state = ext.get::<MyState>().unwrap();
1235 assert_eq!(state.value, 42);
1236 }
1237
1238 #[test]
1239 fn test_extensions_get_mut() {
1240 struct Counter {
1241 count: i32,
1242 }
1243
1244 let mut ext = Extensions::new();
1245 ext.insert(Counter { count: 0 });
1246
1247 if let Some(counter) = ext.get_mut::<Counter>() {
1248 counter.count += 1;
1249 }
1250
1251 assert_eq!(ext.get::<Counter>().unwrap().count, 1);
1252 }
1253
1254 #[test]
1255 fn test_extensions_multiple_types() {
1256 struct TypeA(i32);
1257 struct TypeB(String);
1258
1259 let mut ext = Extensions::new();
1260 ext.insert(TypeA(1));
1261 ext.insert(TypeB("hello".into()));
1262
1263 assert_eq!(ext.len(), 2);
1264 assert_eq!(ext.get::<TypeA>().unwrap().0, 1);
1265 assert_eq!(ext.get::<TypeB>().unwrap().0, "hello");
1266 }
1267
1268 #[test]
1269 fn test_extensions_replace() {
1270 struct Value(i32);
1271
1272 let mut ext = Extensions::new();
1273 ext.insert(Value(1));
1274
1275 let old = ext.insert(Value(2));
1276 assert_eq!(old.unwrap().0, 1);
1277 assert_eq!(ext.get::<Value>().unwrap().0, 2);
1278 }
1279
1280 #[test]
1281 fn test_extensions_remove() {
1282 struct Value(i32);
1283
1284 let mut ext = Extensions::new();
1285 ext.insert(Value(42));
1286
1287 let removed = ext.remove::<Value>();
1288 assert_eq!(removed.unwrap().0, 42);
1289 assert!(ext.is_empty());
1290 assert!(ext.get::<Value>().is_none());
1291 }
1292
1293 #[test]
1294 fn test_extensions_contains() {
1295 struct Present;
1296 struct Absent;
1297
1298 let mut ext = Extensions::new();
1299 ext.insert(Present);
1300
1301 assert!(ext.contains::<Present>());
1302 assert!(!ext.contains::<Absent>());
1303 }
1304
1305 #[test]
1306 fn test_extensions_clear() {
1307 struct A;
1308 struct B;
1309
1310 let mut ext = Extensions::new();
1311 ext.insert(A);
1312 ext.insert(B);
1313 assert_eq!(ext.len(), 2);
1314
1315 ext.clear();
1316 assert!(ext.is_empty());
1317 }
1318
1319 #[test]
1320 fn test_extensions_missing_type_returns_none() {
1321 struct NotInserted;
1322
1323 let ext = Extensions::new();
1324 assert!(ext.get::<NotInserted>().is_none());
1325 }
1326
1327 #[test]
1328 fn test_extensions_get_required() {
1329 #[derive(Debug)]
1330 struct Config {
1331 value: i32,
1332 }
1333
1334 let mut ext = Extensions::new();
1335 ext.insert(Config { value: 100 });
1336
1337 let val = ext.get_required::<Config>();
1339 assert!(val.is_ok());
1340 assert_eq!(val.unwrap().value, 100);
1341
1342 #[derive(Debug)]
1344 struct Missing;
1345 let err = ext.get_required::<Missing>();
1346 assert!(err.is_err());
1347 assert!(err
1348 .unwrap_err()
1349 .to_string()
1350 .contains("Extension missing: type"));
1351 }
1352
1353 #[test]
1354 fn test_extensions_get_mut_required() {
1355 #[derive(Debug)]
1356 struct State {
1357 count: i32,
1358 }
1359
1360 let mut ext = Extensions::new();
1361 ext.insert(State { count: 0 });
1362
1363 {
1365 let val = ext.get_mut_required::<State>();
1366 assert!(val.is_ok());
1367 val.unwrap().count += 1;
1368 }
1369 assert_eq!(ext.get_required::<State>().unwrap().count, 1);
1370
1371 #[derive(Debug)]
1373 struct Missing;
1374 let err = ext.get_mut_required::<Missing>();
1375 assert!(err.is_err());
1376 }
1377
1378 #[test]
1379 fn test_extensions_clone_behavior() {
1380 struct Data(#[allow(dead_code)] i32);
1382
1383 let mut original = Extensions::new();
1384 original.insert(Data(42));
1385
1386 let cloned = original.clone();
1387
1388 assert!(original.get::<Data>().is_some());
1390
1391 assert!(cloned.is_empty());
1393 assert!(cloned.get::<Data>().is_none());
1394 }
1395
1396 #[test]
1397 fn test_output_render() {
1398 let output: Output<String> = Output::Render("success".into());
1399 assert!(output.is_render());
1400 assert!(!output.is_silent());
1401 assert!(!output.is_binary());
1402 }
1403
1404 #[test]
1405 fn test_output_silent() {
1406 let output: Output<String> = Output::Silent;
1407 assert!(!output.is_render());
1408 assert!(output.is_silent());
1409 assert!(!output.is_binary());
1410 }
1411
1412 #[test]
1413 fn test_output_binary() {
1414 let output: Output<String> = Output::Binary {
1415 data: vec![0x25, 0x50, 0x44, 0x46],
1416 filename: "report.pdf".into(),
1417 };
1418 assert!(!output.is_render());
1419 assert!(!output.is_silent());
1420 assert!(output.is_binary());
1421 }
1422
1423 #[test]
1424 fn test_run_result_handled() {
1425 let result = RunResult::Handled("output".into());
1426 assert!(result.is_handled());
1427 assert!(!result.is_binary());
1428 assert!(!result.is_silent());
1429 assert_eq!(result.output(), Some("output"));
1430 assert!(result.matches().is_none());
1431 }
1432
1433 #[test]
1434 fn test_run_result_silent() {
1435 let result = RunResult::Silent;
1436 assert!(!result.is_handled());
1437 assert!(!result.is_binary());
1438 assert!(result.is_silent());
1439 }
1440
1441 #[test]
1442 fn test_run_result_binary() {
1443 let bytes = vec![0x25, 0x50, 0x44, 0x46];
1444 let result = RunResult::Binary(bytes.clone(), "report.pdf".into());
1445 assert!(!result.is_handled());
1446 assert!(result.is_binary());
1447 assert!(!result.is_silent());
1448
1449 let (data, filename) = result.binary().unwrap();
1450 assert_eq!(data, &bytes);
1451 assert_eq!(filename, "report.pdf");
1452 }
1453
1454 #[test]
1455 fn test_run_result_no_match() {
1456 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1457 let result = RunResult::NoMatch(matches);
1458 assert!(!result.is_handled());
1459 assert!(!result.is_binary());
1460 assert!(result.matches().is_some());
1461 }
1462
1463 #[test]
1464 fn test_fn_handler() {
1465 let mut handler = FnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| {
1466 Ok(Output::Render(json!({"status": "ok"})))
1467 });
1468
1469 let ctx = CommandContext::default();
1470 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1471
1472 let result = handler.handle(&matches, &ctx);
1473 assert!(result.is_ok());
1474 }
1475
1476 #[test]
1477 fn test_fn_handler_mutation() {
1478 let mut counter = 0u32;
1479
1480 let mut handler = FnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| {
1481 counter += 1;
1482 Ok(Output::Render(counter))
1483 });
1484
1485 let ctx = CommandContext::default();
1486 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1487
1488 let _ = handler.handle(&matches, &ctx);
1489 let _ = handler.handle(&matches, &ctx);
1490 let result = handler.handle(&matches, &ctx);
1491
1492 assert!(result.is_ok());
1493 if let Ok(Output::Render(count)) = result {
1494 assert_eq!(count, 3);
1495 }
1496 }
1497
1498 #[test]
1500 fn test_into_handler_result_from_result_ok() {
1501 use super::IntoHandlerResult;
1502
1503 let result: Result<String, anyhow::Error> = Ok("hello".to_string());
1504 let handler_result = result.into_handler_result();
1505
1506 assert!(handler_result.is_ok());
1507 match handler_result.unwrap() {
1508 Output::Render(s) => assert_eq!(s, "hello"),
1509 _ => panic!("Expected Output::Render"),
1510 }
1511 }
1512
1513 #[test]
1514 fn test_into_handler_result_from_result_err() {
1515 use super::IntoHandlerResult;
1516
1517 let result: Result<String, anyhow::Error> = Err(anyhow::anyhow!("test error"));
1518 let handler_result = result.into_handler_result();
1519
1520 assert!(handler_result.is_err());
1521 assert!(handler_result
1522 .unwrap_err()
1523 .to_string()
1524 .contains("test error"));
1525 }
1526
1527 #[test]
1528 fn test_into_handler_result_passthrough_render() {
1529 use super::IntoHandlerResult;
1530
1531 let handler_result: HandlerResult<String> = Ok(Output::Render("hello".to_string()));
1532 let result = handler_result.into_handler_result();
1533
1534 assert!(result.is_ok());
1535 match result.unwrap() {
1536 Output::Render(s) => assert_eq!(s, "hello"),
1537 _ => panic!("Expected Output::Render"),
1538 }
1539 }
1540
1541 #[test]
1542 fn test_into_handler_result_passthrough_silent() {
1543 use super::IntoHandlerResult;
1544
1545 let handler_result: HandlerResult<String> = Ok(Output::Silent);
1546 let result = handler_result.into_handler_result();
1547
1548 assert!(result.is_ok());
1549 assert!(matches!(result.unwrap(), Output::Silent));
1550 }
1551
1552 #[test]
1553 fn test_into_handler_result_passthrough_binary() {
1554 use super::IntoHandlerResult;
1555
1556 let handler_result: HandlerResult<String> = Ok(Output::Binary {
1557 data: vec![1, 2, 3],
1558 filename: "test.bin".to_string(),
1559 });
1560 let result = handler_result.into_handler_result();
1561
1562 assert!(result.is_ok());
1563 match result.unwrap() {
1564 Output::Binary { data, filename } => {
1565 assert_eq!(data, vec![1, 2, 3]);
1566 assert_eq!(filename, "test.bin");
1567 }
1568 _ => panic!("Expected Output::Binary"),
1569 }
1570 }
1571
1572 #[test]
1573 fn test_fn_handler_with_auto_wrap() {
1574 let mut handler = FnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| {
1576 Ok::<_, anyhow::Error>("auto-wrapped".to_string())
1577 });
1578
1579 let ctx = CommandContext::default();
1580 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1581
1582 let result = handler.handle(&matches, &ctx);
1583 assert!(result.is_ok());
1584 match result.unwrap() {
1585 Output::Render(s) => assert_eq!(s, "auto-wrapped"),
1586 _ => panic!("Expected Output::Render"),
1587 }
1588 }
1589
1590 #[test]
1591 fn test_fn_handler_with_explicit_output() {
1592 let mut handler =
1594 FnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| Ok(Output::<()>::Silent));
1595
1596 let ctx = CommandContext::default();
1597 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1598
1599 let result = handler.handle(&matches, &ctx);
1600 assert!(result.is_ok());
1601 assert!(matches!(result.unwrap(), Output::Silent));
1602 }
1603
1604 #[test]
1605 fn test_fn_handler_with_custom_error_type() {
1606 #[derive(Debug)]
1608 struct CustomError(String);
1609
1610 impl std::fmt::Display for CustomError {
1611 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1612 write!(f, "CustomError: {}", self.0)
1613 }
1614 }
1615
1616 impl std::error::Error for CustomError {}
1617
1618 let mut handler = FnHandler::new(|_m: &ArgMatches, _ctx: &CommandContext| {
1619 Err::<String, CustomError>(CustomError("oops".to_string()))
1620 });
1621
1622 let ctx = CommandContext::default();
1623 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1624
1625 let result = handler.handle(&matches, &ctx);
1626 assert!(result.is_err());
1627 assert!(result
1628 .unwrap_err()
1629 .to_string()
1630 .contains("CustomError: oops"));
1631 }
1632
1633 #[test]
1635 fn test_simple_fn_handler_basic() {
1636 use super::SimpleFnHandler;
1637
1638 let mut handler = SimpleFnHandler::new(|_m: &ArgMatches| {
1639 Ok::<_, anyhow::Error>("no context needed".to_string())
1640 });
1641
1642 let ctx = CommandContext::default();
1643 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1644
1645 let result = handler.handle(&matches, &ctx);
1646 assert!(result.is_ok());
1647 match result.unwrap() {
1648 Output::Render(s) => assert_eq!(s, "no context needed"),
1649 _ => panic!("Expected Output::Render"),
1650 }
1651 }
1652
1653 #[test]
1654 fn test_simple_fn_handler_with_args() {
1655 use super::SimpleFnHandler;
1656
1657 let mut handler = SimpleFnHandler::new(|m: &ArgMatches| {
1658 let verbose = m.get_flag("verbose");
1659 Ok::<_, anyhow::Error>(verbose)
1660 });
1661
1662 let ctx = CommandContext::default();
1663 let matches = clap::Command::new("test")
1664 .arg(
1665 clap::Arg::new("verbose")
1666 .short('v')
1667 .action(clap::ArgAction::SetTrue),
1668 )
1669 .get_matches_from(vec!["test", "-v"]);
1670
1671 let result = handler.handle(&matches, &ctx);
1672 assert!(result.is_ok());
1673 match result.unwrap() {
1674 Output::Render(v) => assert!(v),
1675 _ => panic!("Expected Output::Render"),
1676 }
1677 }
1678
1679 #[test]
1680 fn test_simple_fn_handler_explicit_output() {
1681 use super::SimpleFnHandler;
1682
1683 let mut handler = SimpleFnHandler::new(|_m: &ArgMatches| Ok(Output::<()>::Silent));
1684
1685 let ctx = CommandContext::default();
1686 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1687
1688 let result = handler.handle(&matches, &ctx);
1689 assert!(result.is_ok());
1690 assert!(matches!(result.unwrap(), Output::Silent));
1691 }
1692
1693 #[test]
1694 fn test_simple_fn_handler_error() {
1695 use super::SimpleFnHandler;
1696
1697 let mut handler = SimpleFnHandler::new(|_m: &ArgMatches| {
1698 Err::<String, _>(anyhow::anyhow!("simple error"))
1699 });
1700
1701 let ctx = CommandContext::default();
1702 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1703
1704 let result = handler.handle(&matches, &ctx);
1705 assert!(result.is_err());
1706 assert!(result.unwrap_err().to_string().contains("simple error"));
1707 }
1708
1709 #[test]
1710 fn test_simple_fn_handler_mutation() {
1711 use super::SimpleFnHandler;
1712
1713 let mut counter = 0u32;
1714 let mut handler = SimpleFnHandler::new(|_m: &ArgMatches| {
1715 counter += 1;
1716 Ok::<_, anyhow::Error>(counter)
1717 });
1718
1719 let ctx = CommandContext::default();
1720 let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
1721
1722 let _ = handler.handle(&matches, &ctx);
1723 let _ = handler.handle(&matches, &ctx);
1724 let result = handler.handle(&matches, &ctx);
1725
1726 assert!(result.is_ok());
1727 match result.unwrap() {
1728 Output::Render(n) => assert_eq!(n, 3),
1729 _ => panic!("Expected Output::Render"),
1730 }
1731 }
1732}