1use super::cancel::CancelReason;
101use core::fmt;
102use serde::{Deserialize, Serialize};
103
104#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
108pub struct PanicPayload {
109 message: String,
110}
111
112impl PanicPayload {
113 #[inline]
115 #[must_use]
116 pub fn new(message: impl Into<String>) -> Self {
117 Self {
118 message: message.into(),
119 }
120 }
121
122 #[inline]
124 #[must_use]
125 pub fn message(&self) -> &str {
126 &self.message
127 }
128}
129
130impl fmt::Display for PanicPayload {
131 #[inline]
132 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133 write!(f, "panic: {}", self.message)
134 }
135}
136
137#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
160pub enum Severity {
161 Ok = 0,
163 Err = 1,
165 Cancelled = 2,
167 Panicked = 3,
169}
170
171impl Severity {
172 #[inline]
176 #[must_use]
177 pub const fn as_u8(self) -> u8 {
178 self as u8
179 }
180
181 #[inline]
185 #[must_use]
186 pub const fn from_u8(value: u8) -> Option<Self> {
187 match value {
188 0 => Some(Self::Ok),
189 1 => Some(Self::Err),
190 2 => Some(Self::Cancelled),
191 3 => Some(Self::Panicked),
192 _ => None,
193 }
194 }
195}
196
197impl fmt::Display for Severity {
198 #[inline]
199 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
200 match self {
201 Self::Ok => write!(f, "ok"),
202 Self::Err => write!(f, "err"),
203 Self::Cancelled => write!(f, "cancelled"),
204 Self::Panicked => write!(f, "panicked"),
205 }
206 }
207}
208
209#[derive(Debug, Clone, Serialize, Deserialize)]
214pub enum Outcome<T, E> {
215 Ok(T),
217 Err(E),
219 Cancelled(CancelReason),
221 Panicked(PanicPayload),
223}
224
225impl<T, E> PartialEq for Outcome<T, E>
226where
227 T: PartialEq,
228 E: PartialEq,
229{
230 #[inline]
231 fn eq(&self, other: &Self) -> bool {
232 match (self, other) {
233 (Self::Ok(a), Self::Ok(b)) => a == b,
234 (Self::Err(a), Self::Err(b)) => a == b,
235 (Self::Cancelled(a), Self::Cancelled(b)) => a == b,
236 (Self::Panicked(a), Self::Panicked(b)) => a == b,
237 _ => false,
238 }
239 }
240}
241
242impl<T, E> Eq for Outcome<T, E>
243where
244 T: Eq,
245 E: Eq,
246{
247}
248
249impl<T, E> Outcome<T, E> {
250 #[inline]
266 #[must_use]
267 pub const fn ok(value: T) -> Self {
268 Self::Ok(value)
269 }
270
271 #[inline]
282 #[must_use]
283 pub const fn err(error: E) -> Self {
284 Self::Err(error)
285 }
286
287 #[inline]
298 #[must_use]
299 pub const fn cancelled(reason: CancelReason) -> Self {
300 Self::Cancelled(reason)
301 }
302
303 #[inline]
314 #[must_use]
315 pub const fn panicked(payload: PanicPayload) -> Self {
316 Self::Panicked(payload)
317 }
318
319 #[inline]
343 #[must_use]
344 pub const fn severity(&self) -> Severity {
345 match self {
346 Self::Ok(_) => Severity::Ok,
347 Self::Err(_) => Severity::Err,
348 Self::Cancelled(_) => Severity::Cancelled,
349 Self::Panicked(_) => Severity::Panicked,
350 }
351 }
352
353 #[inline]
357 #[must_use]
358 pub const fn severity_u8(&self) -> u8 {
359 self.severity().as_u8()
360 }
361
362 #[inline]
366 #[must_use]
367 pub const fn is_terminal(&self) -> bool {
368 true }
370
371 #[inline]
385 #[must_use]
386 pub const fn is_ok(&self) -> bool {
387 matches!(self, Self::Ok(_))
388 }
389
390 #[inline]
401 #[must_use]
402 pub const fn is_err(&self) -> bool {
403 matches!(self, Self::Err(_))
404 }
405
406 #[inline]
417 #[must_use]
418 pub const fn is_cancelled(&self) -> bool {
419 matches!(self, Self::Cancelled(_))
420 }
421
422 #[inline]
433 #[must_use]
434 pub const fn is_panicked(&self) -> bool {
435 matches!(self, Self::Panicked(_))
436 }
437
438 #[inline]
442 pub fn into_result(self) -> Result<T, OutcomeError<E>> {
443 match self {
444 Self::Ok(v) => Ok(v),
445 Self::Err(e) => Err(OutcomeError::Err(e)),
446 Self::Cancelled(r) => Err(OutcomeError::Cancelled(r)),
447 Self::Panicked(p) => Err(OutcomeError::Panicked(p)),
448 }
449 }
450
451 #[inline]
453 pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Outcome<U, E> {
454 match self {
455 Self::Ok(v) => Outcome::Ok(f(v)),
456 Self::Err(e) => Outcome::Err(e),
457 Self::Cancelled(r) => Outcome::Cancelled(r),
458 Self::Panicked(p) => Outcome::Panicked(p),
459 }
460 }
461
462 #[inline]
474 pub fn map_err<F2, G: FnOnce(E) -> F2>(self, g: G) -> Outcome<T, F2> {
475 match self {
476 Self::Ok(v) => Outcome::Ok(v),
477 Self::Err(e) => Outcome::Err(g(e)),
478 Self::Cancelled(r) => Outcome::Cancelled(r),
479 Self::Panicked(p) => Outcome::Panicked(p),
480 }
481 }
482
483 #[inline]
508 pub fn and_then<U, F: FnOnce(T) -> Outcome<U, E>>(self, f: F) -> Outcome<U, E> {
509 match self {
510 Self::Ok(v) => f(v),
511 Self::Err(e) => Outcome::Err(e),
512 Self::Cancelled(r) => Outcome::Cancelled(r),
513 Self::Panicked(p) => Outcome::Panicked(p),
514 }
515 }
516
517 #[inline]
541 pub fn ok_or_else<F2, G: FnOnce() -> F2>(self, f: G) -> Result<T, F2> {
542 match self {
543 Self::Ok(v) => Ok(v),
544 _ => Err(f()),
545 }
546 }
547
548 #[inline]
585 #[must_use]
586 pub fn join(self, other: Self) -> Self {
587 match (self, other) {
588 (Self::Cancelled(mut left), Self::Cancelled(right)) => {
589 if right.severity() > left.severity() {
590 left.strengthen(&right);
591 }
592 Self::Cancelled(left)
593 }
594 (left, right) => {
595 if left.severity() >= right.severity() {
596 left
597 } else {
598 right
599 }
600 }
601 }
602 }
603
604 #[inline]
614 #[track_caller]
615 pub fn unwrap(self) -> T
616 where
617 E: fmt::Debug,
618 {
619 match self {
620 Self::Ok(v) => v,
621 Self::Err(e) => panic!("called `Outcome::unwrap()` on an `Err` value: {e:?}"),
622 Self::Cancelled(r) => {
623 panic!("called `Outcome::unwrap()` on a `Cancelled` value: {r:?}")
624 }
625 Self::Panicked(p) => panic!("called `Outcome::unwrap()` on a `Panicked` value: {p}"),
626 }
627 }
628
629 #[inline]
631 pub fn unwrap_or(self, default: T) -> T {
632 match self {
633 Self::Ok(v) => v,
634 _ => default,
635 }
636 }
637
638 #[inline]
640 pub fn unwrap_or_else<F: FnOnce() -> T>(self, f: F) -> T {
641 match self {
642 Self::Ok(v) => v,
643 _ => f(),
644 }
645 }
646}
647
648impl<T, E> From<Result<T, E>> for Outcome<T, E> {
649 #[inline]
650 fn from(result: Result<T, E>) -> Self {
651 match result {
652 Ok(v) => Self::Ok(v),
653 Err(e) => Self::Err(e),
654 }
655 }
656}
657
658#[derive(Debug, Clone, Serialize, Deserialize)]
660pub enum OutcomeError<E> {
661 Err(E),
663 Cancelled(CancelReason),
665 Panicked(PanicPayload),
667}
668
669impl<E: fmt::Display> fmt::Display for OutcomeError<E> {
670 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
671 match self {
672 Self::Err(e) => write!(f, "{e}"),
673 Self::Cancelled(r) => write!(f, "cancelled: {r}"),
674 Self::Panicked(p) => write!(f, "{p}"),
675 }
676 }
677}
678
679impl<E: fmt::Debug + fmt::Display> std::error::Error for OutcomeError<E> {}
680
681#[inline]
688pub fn join_outcomes<T, E>(a: Outcome<T, E>, b: Outcome<T, E>) -> Outcome<T, E> {
689 a.join(b)
690}
691
692#[cfg(test)]
693mod tests {
694 use super::*;
695 use serde_json::{Value, json};
696
697 fn scrub_outcome_serde(value: Value) -> Value {
698 let mut scrubbed = value;
699
700 if let Some(message) = scrubbed.pointer_mut("/cancelled/Cancelled/message") {
701 *message = Value::String("[MESSAGE]".to_string());
702 }
703
704 scrubbed
705 }
706
707 fn scrub_outcome_json_ids(value: Value) -> Value {
708 let mut scrubbed = value;
709
710 if let Some(origin_region) = scrubbed.pointer_mut("/Cancelled/origin_region") {
711 *origin_region = json!("[REGION_ID]");
712 }
713
714 if let Some(origin_task) = scrubbed.pointer_mut("/Cancelled/origin_task") {
715 *origin_task = json!("[TASK_ID]");
716 }
717
718 scrubbed
719 }
720
721 #[test]
726 fn severity_ordering() {
727 let ok: Outcome<i32, &str> = Outcome::Ok(42);
728 let err: Outcome<i32, &str> = Outcome::Err("error");
729 let cancelled: Outcome<i32, &str> = Outcome::Cancelled(CancelReason::default());
730 let panicked: Outcome<i32, &str> = Outcome::Panicked(PanicPayload::new("panic"));
731
732 assert!(ok.severity() < err.severity());
733 assert!(err.severity() < cancelled.severity());
734 assert!(cancelled.severity() < panicked.severity());
735 }
736
737 #[test]
738 fn severity_values() {
739 let ok: Outcome<(), ()> = Outcome::Ok(());
740 let err: Outcome<(), ()> = Outcome::Err(());
741 let cancelled: Outcome<(), ()> = Outcome::Cancelled(CancelReason::default());
742 let panicked: Outcome<(), ()> = Outcome::Panicked(PanicPayload::new("test"));
743
744 assert_eq!(ok.severity(), Severity::Ok);
745 assert_eq!(err.severity(), Severity::Err);
746 assert_eq!(cancelled.severity(), Severity::Cancelled);
747 assert_eq!(panicked.severity(), Severity::Panicked);
748 }
749
750 #[test]
755 fn is_ok_predicate() {
756 let ok: Outcome<i32, &str> = Outcome::Ok(42);
757 let err: Outcome<i32, &str> = Outcome::Err("error");
758
759 assert!(ok.is_ok());
760 assert!(!err.is_ok());
761 }
762
763 #[test]
764 fn is_err_predicate() {
765 let ok: Outcome<i32, &str> = Outcome::Ok(42);
766 let err: Outcome<i32, &str> = Outcome::Err("error");
767
768 assert!(!ok.is_err());
769 assert!(err.is_err());
770 }
771
772 #[test]
773 fn is_cancelled_predicate() {
774 let ok: Outcome<i32, &str> = Outcome::Ok(42);
775 let cancelled: Outcome<i32, &str> = Outcome::Cancelled(CancelReason::default());
776
777 assert!(!ok.is_cancelled());
778 assert!(cancelled.is_cancelled());
779 }
780
781 #[test]
782 fn is_panicked_predicate() {
783 let ok: Outcome<i32, &str> = Outcome::Ok(42);
784 let panicked: Outcome<i32, &str> = Outcome::Panicked(PanicPayload::new("oops"));
785
786 assert!(!ok.is_panicked());
787 assert!(panicked.is_panicked());
788 }
789
790 #[test]
791 fn is_terminal_always_true() {
792 let ok: Outcome<i32, &str> = Outcome::Ok(42);
794 let err: Outcome<i32, &str> = Outcome::Err("error");
795 let cancelled: Outcome<i32, &str> = Outcome::Cancelled(CancelReason::default());
796 let panicked: Outcome<i32, &str> = Outcome::Panicked(PanicPayload::new("panic"));
797
798 assert!(ok.is_terminal());
799 assert!(err.is_terminal());
800 assert!(cancelled.is_terminal());
801 assert!(panicked.is_terminal());
802 }
803
804 #[test]
809 fn join_takes_worse() {
810 let ok: Outcome<i32, &str> = Outcome::Ok(1);
811 let err: Outcome<i32, &str> = Outcome::Err("error");
812
813 let joined = join_outcomes(ok, err);
814 assert!(joined.is_err());
815 }
816
817 #[test]
818 fn join_ok_with_ok_returns_first() {
819 let a: Outcome<i32, &str> = Outcome::Ok(1);
820 let b: Outcome<i32, &str> = Outcome::Ok(2);
821
822 let result = join_outcomes(a, b);
824 assert!(matches!(result, Outcome::Ok(1)));
825 }
826
827 #[test]
828 fn join_err_with_cancelled_returns_cancelled() {
829 let err: Outcome<i32, &str> = Outcome::Err("error");
830 let cancelled: Outcome<i32, &str> = Outcome::Cancelled(CancelReason::default());
831
832 let result = join_outcomes(err, cancelled);
833 assert!(result.is_cancelled());
834 }
835
836 #[test]
837 fn join_panicked_dominates_all() {
838 let ok: Outcome<i32, &str> = Outcome::Ok(1);
839 let err: Outcome<i32, &str> = Outcome::Err("error");
840 let cancelled: Outcome<i32, &str> = Outcome::Cancelled(CancelReason::default());
841 let panicked: Outcome<i32, &str> = Outcome::Panicked(PanicPayload::new("panic"));
842
843 assert!(join_outcomes(ok, panicked.clone()).is_panicked());
844 assert!(join_outcomes(err, panicked.clone()).is_panicked());
845 assert!(join_outcomes(cancelled, panicked).is_panicked());
846 }
847
848 #[test]
849 fn join_cancelled_strengthens_to_worst_reason() {
850 let user: Outcome<i32, &str> = Outcome::Cancelled(CancelReason::user("soft"));
851 let shutdown: Outcome<i32, &str> = Outcome::Cancelled(CancelReason::shutdown());
852
853 let left_first = user.clone().join(shutdown.clone());
854 let right_first = shutdown.join(user);
855
856 match left_first {
857 Outcome::Cancelled(reason) => assert!(reason.is_shutdown()),
858 other => panic!("expected cancelled outcome, got {other:?}"),
859 }
860
861 match right_first {
862 Outcome::Cancelled(reason) => assert!(reason.is_shutdown()),
863 other => panic!("expected cancelled outcome, got {other:?}"),
864 }
865 }
866
867 #[test]
868 fn join_outcomes_cancelled_strengthens_to_worst_reason() {
869 let user: Outcome<i32, &str> = Outcome::Cancelled(CancelReason::user("soft"));
870 let shutdown: Outcome<i32, &str> = Outcome::Cancelled(CancelReason::shutdown());
871
872 let joined = join_outcomes(user, shutdown);
873
874 match joined {
875 Outcome::Cancelled(reason) => assert!(reason.is_shutdown()),
876 other => panic!("expected cancelled outcome, got {other:?}"),
877 }
878 }
879
880 #[test]
881 fn join_cancelled_equal_severity_is_left_biased() {
882 use crate::types::CancelKind;
883 let left: Outcome<i32, &str> = Outcome::Cancelled(CancelReason::user("z-left"));
884 let right: Outcome<i32, &str> = Outcome::Cancelled(CancelReason::user("a-right"));
885
886 let joined = left.join(right);
887
888 match joined {
889 Outcome::Cancelled(reason) => {
890 assert!(reason.is_kind(CancelKind::User));
891 assert_eq!(reason.message(), Some("z-left"));
892 }
893 other => panic!("expected cancelled outcome, got {other:?}"),
894 }
895 }
896
897 #[test]
898 fn join_cancelled_equal_rank_kinds_is_left_biased() {
899 use crate::types::CancelKind;
900 let left: Outcome<i32, &str> = Outcome::Cancelled(CancelReason::timeout());
901 let right: Outcome<i32, &str> = Outcome::Cancelled(CancelReason::deadline());
902
903 let joined = left.join(right);
904
905 match joined {
906 Outcome::Cancelled(reason) => assert!(reason.is_kind(CancelKind::Timeout)),
907 other => panic!("expected cancelled outcome, got {other:?}"),
908 }
909 }
910
911 #[test]
916 fn map_transforms_ok_value() {
917 let ok: Outcome<i32, &str> = Outcome::Ok(21);
918 let mapped = ok.map(|x| x * 2);
919 assert!(matches!(mapped, Outcome::Ok(42)));
920 }
921
922 #[test]
923 fn map_preserves_err() {
924 let err: Outcome<i32, &str> = Outcome::Err("error");
925 let mapped = err.map(|x| x * 2);
926 assert!(matches!(mapped, Outcome::Err("error")));
927 }
928
929 #[test]
930 fn map_preserves_cancelled() {
931 let cancelled: Outcome<i32, &str> = Outcome::Cancelled(CancelReason::default());
932 let mapped = cancelled.map(|x| x * 2);
933 assert!(mapped.is_cancelled());
934 }
935
936 #[test]
937 fn map_preserves_panicked() {
938 let panicked: Outcome<i32, &str> = Outcome::Panicked(PanicPayload::new("oops"));
939 let mapped = panicked.map(|x| x * 2);
940 assert!(mapped.is_panicked());
941 }
942
943 #[test]
944 fn map_err_transforms_err_value() {
945 let err: Outcome<i32, &str> = Outcome::Err("short");
946 let mapped = err.map_err(str::len);
947 assert!(matches!(mapped, Outcome::Err(5)));
948 }
949
950 #[test]
951 fn map_err_preserves_ok() {
952 let ok: Outcome<i32, &str> = Outcome::Ok(42);
953 let mapped = ok.map_err(str::len);
954 assert!(matches!(mapped, Outcome::Ok(42)));
955 }
956
957 #[test]
962 fn unwrap_returns_value_on_ok() {
963 let ok: Outcome<i32, &str> = Outcome::Ok(42);
964 assert_eq!(ok.unwrap(), 42);
965 }
966
967 #[test]
968 #[should_panic(expected = "called `Outcome::unwrap()` on an `Err` value")]
969 fn unwrap_panics_on_err() {
970 let err: Outcome<i32, &str> = Outcome::Err("error");
971 let _ = err.unwrap();
972 }
973
974 #[test]
975 #[should_panic(expected = "called `Outcome::unwrap()` on a `Cancelled` value")]
976 fn unwrap_panics_on_cancelled() {
977 let cancelled: Outcome<i32, &str> = Outcome::Cancelled(CancelReason::default());
978 let _ = cancelled.unwrap();
979 }
980
981 #[test]
982 #[should_panic(expected = "called `Outcome::unwrap()` on a `Panicked` value")]
983 fn unwrap_panics_on_panicked() {
984 let panicked: Outcome<i32, &str> = Outcome::Panicked(PanicPayload::new("oops"));
985 let _ = panicked.unwrap();
986 }
987
988 #[test]
989 fn unwrap_or_returns_value_on_ok() {
990 let ok: Outcome<i32, &str> = Outcome::Ok(42);
991 assert_eq!(ok.unwrap_or(0), 42);
992 }
993
994 #[test]
995 fn unwrap_or_returns_default_on_err() {
996 let err: Outcome<i32, &str> = Outcome::Err("error");
997 assert_eq!(err.unwrap_or(0), 0);
998 }
999
1000 #[test]
1001 fn unwrap_or_returns_default_on_cancelled() {
1002 let cancelled: Outcome<i32, &str> = Outcome::Cancelled(CancelReason::default());
1003 assert_eq!(cancelled.unwrap_or(99), 99);
1004 }
1005
1006 #[test]
1007 fn unwrap_or_else_computes_default_lazily() {
1008 let err: Outcome<i32, &str> = Outcome::Err("error");
1009 let mut called = false;
1010 let result = err.unwrap_or_else(|| {
1011 called = true;
1012 42
1013 });
1014 assert!(called);
1015 assert_eq!(result, 42);
1016 }
1017
1018 #[test]
1019 fn unwrap_or_else_doesnt_call_closure_on_ok() {
1020 let ok: Outcome<i32, &str> = Outcome::Ok(42);
1021 let result = ok.unwrap_or_else(|| panic!("should not be called"));
1022 assert_eq!(result, 42);
1023 }
1024
1025 #[test]
1026 fn ok_or_else_collapses_non_ok_variants_to_fallback() {
1027 let cancelled: Outcome<i32, &str> = Outcome::Cancelled(CancelReason::default());
1028 let panicked: Outcome<i32, &str> = Outcome::Panicked(PanicPayload::new("oops"));
1029
1030 assert_eq!(cancelled.ok_or_else(|| "fallback"), Err("fallback"));
1031 assert_eq!(panicked.ok_or_else(|| "fallback"), Err("fallback"));
1032 }
1033
1034 #[test]
1035 fn ok_or_else_doesnt_call_closure_on_ok() {
1036 let ok: Outcome<i32, &str> = Outcome::Ok(42);
1037 let result = ok.ok_or_else(|| panic!("should not be called"));
1038 assert_eq!(result, Ok(42));
1039 }
1040
1041 #[test]
1046 fn into_result_ok() {
1047 let ok: Outcome<i32, &str> = Outcome::Ok(42);
1048 let result = ok.into_result();
1049 assert!(matches!(result, Ok(42)));
1050 }
1051
1052 #[test]
1053 fn into_result_err() {
1054 let err: Outcome<i32, &str> = Outcome::Err("error");
1055 let result = err.into_result();
1056 assert!(matches!(result, Err(OutcomeError::Err("error"))));
1057 }
1058
1059 #[test]
1060 fn into_result_cancelled() {
1061 let cancelled: Outcome<i32, &str> = Outcome::Cancelled(CancelReason::default());
1062 let result = cancelled.into_result();
1063 assert!(matches!(result, Err(OutcomeError::Cancelled(_))));
1064 }
1065
1066 #[test]
1067 fn into_result_panicked() {
1068 let panicked: Outcome<i32, &str> = Outcome::Panicked(PanicPayload::new("oops"));
1069 let result = panicked.into_result();
1070 assert!(matches!(result, Err(OutcomeError::Panicked(_))));
1071 }
1072
1073 #[test]
1078 fn from_result_ok() {
1079 let result: Result<i32, &str> = Ok(42);
1080 let outcome: Outcome<i32, &str> = Outcome::from(result);
1081 assert!(matches!(outcome, Outcome::Ok(42)));
1082 }
1083
1084 #[test]
1085 fn from_result_err() {
1086 let result: Result<i32, &str> = Err("error");
1087 let outcome: Outcome<i32, &str> = Outcome::from(result);
1088 assert!(matches!(outcome, Outcome::Err("error")));
1089 }
1090
1091 #[test]
1096 fn panic_payload_display() {
1097 let payload = PanicPayload::new("something went wrong");
1098 let display = format!("{payload}");
1099 assert_eq!(display, "panic: something went wrong");
1100 }
1101
1102 #[test]
1103 fn panic_payload_message() {
1104 let payload = PanicPayload::new("test message");
1105 assert_eq!(payload.message(), "test message");
1106 }
1107
1108 #[test]
1109 fn outcome_error_display_err() {
1110 let error: OutcomeError<&str> = OutcomeError::Err("application error");
1111 let display = format!("{error}");
1112 assert_eq!(display, "application error");
1113 }
1114
1115 #[test]
1116 fn outcome_error_display_cancelled() {
1117 let error: OutcomeError<&str> = OutcomeError::Cancelled(CancelReason::default());
1118 let display = format!("{error}");
1119 assert!(display.contains("cancelled"));
1120 }
1121
1122 #[test]
1123 fn outcome_error_display_cancelled_uses_human_readable_reason() {
1124 let error: OutcomeError<&str> =
1125 OutcomeError::Cancelled(CancelReason::timeout().with_message("budget elapsed"));
1126 let display = format!("{error}");
1127 assert_eq!(display, "cancelled: timeout: budget elapsed");
1128 assert!(!display.contains("CancelReason"));
1129 }
1130
1131 #[test]
1132 fn outcome_error_display_panicked() {
1133 let error: OutcomeError<&str> = OutcomeError::Panicked(PanicPayload::new("oops"));
1134 let display = format!("{error}");
1135 assert!(display.contains("panic"));
1136 assert!(display.contains("oops"));
1137 }
1138
1139 #[test]
1140 fn severity_debug_clone_copy_hash() {
1141 use std::collections::HashSet;
1142 let a = Severity::Cancelled;
1143 let b = a; let c = a;
1145 assert_eq!(a, b);
1146 assert_eq!(a, c);
1147 let dbg = format!("{a:?}");
1148 assert!(dbg.contains("Cancelled"));
1149 let mut set = HashSet::new();
1150 set.insert(a);
1151 assert!(set.contains(&b));
1152 assert!(!set.contains(&Severity::Ok));
1153 }
1154
1155 #[test]
1156 fn panic_payload_debug_clone_eq() {
1157 let a = PanicPayload::new("boom");
1158 let b = a.clone();
1159 assert_eq!(a, b);
1160 assert_ne!(a, PanicPayload::new("other"));
1161 let dbg = format!("{a:?}");
1162 assert!(dbg.contains("PanicPayload"));
1163 }
1164
1165 #[test]
1166 fn outcome_serde_snapshot_scrubbed() {
1167 insta::assert_json_snapshot!(
1168 "outcome_serde_scrubbed",
1169 scrub_outcome_serde(json!({
1170 "ok": Outcome::<u8, &str>::ok(7),
1171 "err": Outcome::<u8, &str>::err("denied"),
1172 "cancelled": Outcome::<u8, &str>::cancelled(CancelReason::user("req-9f4c36b1")),
1173 "panicked": OutcomeError::<&str>::Panicked(PanicPayload::new("boom")),
1174 }))
1175 );
1176 }
1177
1178 #[test]
1179 fn outcome_json_snapshot_scrubs_ids_only() {
1180 let cancelled: Outcome<(), ()> = Outcome::cancelled(
1181 CancelReason::linked_exit()
1182 .with_region(crate::types::RegionId::new_for_test(42, 7))
1183 .with_task(crate::types::TaskId::new_for_test(9, 3))
1184 .with_timestamp(crate::types::Time::from_nanos(55))
1185 .with_message("upstream closed"),
1186 );
1187
1188 insta::assert_json_snapshot!(
1189 "outcome_json_scrubbed_ids",
1190 scrub_outcome_json_ids(serde_json::to_value(cancelled).expect("serialize outcome"))
1191 );
1192 }
1193}