1use std::{error::Error, fmt};
2
3use crate::profile::{AllLanes, LaneProfile, NarrowDenied, NarrowTransient};
4
5use crate::lane::{Denied, Fatal, Lane, Transient};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
9pub enum Level {
10 Trace,
11 Debug,
12 Info,
13 Warn,
14 Error,
15}
16
17impl Level {
18 pub fn as_str(self) -> &'static str {
20 match self {
21 Level::Trace => "TRACE",
22 Level::Debug => "DEBUG",
23 Level::Info => "INFO",
24 Level::Warn => "WARN",
25 Level::Error => "ERROR",
26 }
27 }
28}
29
30#[cfg(feature = "tracing")]
31impl From<Level> for tracing::Level {
32 fn from(level: Level) -> Self {
33 match level {
34 Level::Trace => tracing::Level::TRACE,
35 Level::Debug => tracing::Level::DEBUG,
36 Level::Info => tracing::Level::INFO,
37 Level::Warn => tracing::Level::WARN,
38 Level::Error => tracing::Level::ERROR,
39 }
40 }
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
59pub struct CodeInfo {
60 pub code: &'static str,
62 pub description: Option<&'static str>,
66}
67
68pub trait RejectionCode:
85 Copy + Eq + std::hash::Hash + fmt::Debug + fmt::Display + Into<&'static str> + Send + Sync + 'static
86{
87 const CODES: &'static [CodeInfo];
92}
93
94#[doc(hidden)]
96pub mod __catalogue {
97 use super::CodeInfo;
98
99 const fn str_eq(a: &str, b: &str) -> bool {
100 let (a, b) = (a.as_bytes(), b.as_bytes());
101 if a.len() != b.len() {
102 return false;
103 }
104 let mut i = 0;
105 while i < a.len() {
106 if a[i] != b[i] {
107 return false;
108 }
109 i += 1;
110 }
111 true
112 }
113
114 const fn seen_before(parts: &[&[CodeInfo]], part: usize, idx: usize) -> bool {
117 let code = parts[part][idx].code;
118 let mut p = 0;
119 while p <= part {
120 let end = if p == part { idx } else { parts[p].len() };
121 let mut i = 0;
122 while i < end {
123 if str_eq(parts[p][i].code, code) {
124 return true;
125 }
126 i += 1;
127 }
128 p += 1;
129 }
130 false
131 }
132
133 pub const fn catalogue_len(parts: &[&[CodeInfo]]) -> usize {
135 let mut n = 0;
136 let mut p = 0;
137 while p < parts.len() {
138 let mut i = 0;
139 while i < parts[p].len() {
140 if !seen_before(parts, p, i) {
141 n += 1;
142 }
143 i += 1;
144 }
145 p += 1;
146 }
147 n
148 }
149
150 pub const fn catalogue<const N: usize>(parts: &[&[CodeInfo]]) -> [CodeInfo; N] {
153 assert!(catalogue_len(parts) == N, "catalogue length mismatch");
154 let mut out = [CodeInfo {
155 code: "",
156 description: None,
157 }; N];
158 let mut n = 0;
159 let mut p = 0;
160 while p < parts.len() {
161 let mut i = 0;
162 while i < parts[p].len() {
163 if !seen_before(parts, p, i) {
164 out[n] = parts[p][i];
165 n += 1;
166 }
167 i += 1;
168 }
169 p += 1;
170 }
171 out
172 }
173
174 pub const fn codes_of<const N: usize>(entries: &[CodeInfo]) -> [&'static str; N] {
176 assert!(entries.len() == N, "catalogue length mismatch");
177 let mut out = [""; N];
178 let mut i = 0;
179 while i < N {
180 out[i] = entries[i].code;
181 i += 1;
182 }
183 out
184 }
185}
186
187pub trait Rejection: Error + Send + Sync + 'static {
192 type Code: RejectionCode;
194
195 fn code(&self) -> Self::Code;
196
197 fn level(&self) -> Level {
199 Level::Warn
200 }
201}
202
203pub trait Lift<X>: Sized {
211 type Unmapped;
212 fn lift(x: X) -> Result<Self, Self::Unmapped>;
213}
214
215impl<X: Rejection, P: From<X>> Lift<X> for P {
222 type Unmapped = core::convert::Infallible;
223 fn lift(x: X) -> Result<Self, Self::Unmapped> {
224 Ok(P::from(x))
225 }
226}
227
228impl<P> Lift<core::convert::Infallible> for P {
232 type Unmapped = core::convert::Infallible;
233 fn lift(x: core::convert::Infallible) -> Result<Self, Self::Unmapped> {
234 match x {}
235 }
236}
237
238#[doc(hidden)]
241pub trait UnmappedInto<F> {
242 fn unmapped_into(self) -> F;
243}
244impl<F> UnmappedInto<F> for core::convert::Infallible {
245 fn unmapped_into(self) -> F {
246 match self {}
247 }
248}
249impl<R: Rejection> UnmappedInto<Fatal> for R {
250 fn unmapped_into(self) -> Fatal {
251 Fatal::from_error(crate::FatalKind::Invariant, self)
256 .with_context("unhandled rejection at partial lift")
257 .with_opaque_source()
258 }
259}
260
261#[derive(Debug, Clone)]
266pub enum Fault<L: LaneProfile = AllLanes> {
267 Denied(L::Denied),
268 Transient(L::Transient),
269 Fatal(L::Fatal),
270}
271
272impl<L: LaneProfile> Fault<L> {
273 pub fn lane(&self) -> Lane {
274 match self {
275 Fault::Denied(_) => Lane::Denied,
276 Fault::Transient(_) => Lane::Transient,
277 Fault::Fatal(_) => Lane::Fatal,
278 }
279 }
280
281 pub fn is_transient(&self) -> bool {
282 matches!(self, Fault::Transient(_))
283 }
284
285 pub fn is_fatal(&self) -> bool {
286 matches!(self, Fault::Fatal(_))
287 }
288
289 pub fn is_denied(&self) -> bool {
290 matches!(self, Fault::Denied(_))
291 }
292
293 pub fn message(&self) -> String {
299 self.lanes().message()
300 }
301
302 pub fn narrow_transient(self, attempts: u32) -> Fault<crate::profile::WithoutTransient<L>>
307 where
308 L::Transient: NarrowTransient<L::Fatal>,
309 {
310 match self {
311 Fault::Denied(d) => Fault::Denied(d),
312 Fault::Transient(last) => Fault::Fatal(last.narrow(attempts)),
313 Fault::Fatal(f) => Fault::Fatal(f),
314 }
315 }
316
317 pub fn narrow_denied(self) -> Fault<crate::profile::WithoutDenied<L>>
321 where
322 L::Denied: NarrowDenied<L::Fatal>,
323 {
324 match self {
325 Fault::Denied(d) => Fault::Fatal(d.narrow()),
326 Fault::Transient(t) => Fault::Transient(t),
327 Fault::Fatal(f) => Fault::Fatal(f),
328 }
329 }
330}
331
332impl<L: LaneProfile<Denied = Denied>> Fault<L> {
339 pub fn as_denied(&self) -> Option<&Denied> {
340 match self {
341 Fault::Denied(d) => Some(d),
342 _ => None,
343 }
344 }
345}
346
347impl<L: LaneProfile<Transient = Transient>> Fault<L> {
348 pub fn as_transient(&self) -> Option<&Transient> {
349 match self {
350 Fault::Transient(t) => Some(t),
351 _ => None,
352 }
353 }
354
355 pub fn is_congestion(&self) -> bool {
356 self.as_transient().is_some_and(Transient::is_congestion)
357 }
358
359 pub fn is_contention(&self) -> bool {
361 self.as_transient().is_some_and(Transient::is_contention)
362 }
363}
364
365impl<L: LaneProfile<Fatal = Fatal>> Fault<L> {
366 pub fn as_fatal(&self) -> Option<&Fatal> {
367 match self {
368 Fault::Fatal(f) => Some(f),
369 _ => None,
370 }
371 }
372}
373
374impl<D, L: LaneProfile<Denied = Denied>> Fail<D, L> {
375 pub fn as_denied(&self) -> Option<&Denied> {
376 match self {
377 Fail::Denied(d) => Some(d),
378 _ => None,
379 }
380 }
381}
382
383impl<D, L: LaneProfile<Transient = Transient>> Fail<D, L> {
384 pub fn as_transient(&self) -> Option<&Transient> {
385 match self {
386 Fail::Transient(t) => Some(t),
387 _ => None,
388 }
389 }
390
391 pub fn is_congestion(&self) -> bool {
392 self.as_transient().is_some_and(Transient::is_congestion)
393 }
394
395 pub fn is_contention(&self) -> bool {
397 self.as_transient().is_some_and(Transient::is_contention)
398 }
399}
400
401impl<D, L: LaneProfile<Fatal = Fatal>> Fail<D, L> {
402 pub fn as_fatal(&self) -> Option<&Fatal> {
403 match self {
404 Fail::Fatal(f) => Some(f),
405 _ => None,
406 }
407 }
408}
409
410impl<L: LaneProfile> fmt::Display for Fault<L> {
411 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
412 match self {
413 Fault::Denied(d) => write!(f, "{d}"),
414 Fault::Transient(t) => write!(f, "{t}"),
415 Fault::Fatal(x) => write!(f, "{x}"),
416 }
417 }
418}
419
420impl<L: LaneProfile> Error for Fault<L> {
421 fn source(&self) -> Option<&(dyn Error + 'static)> {
422 match self {
425 Fault::Denied(d) => Some(d),
426 Fault::Transient(t) => Some(t),
427 Fault::Fatal(x) => Some(x),
428 }
429 }
430}
431
432#[derive(Debug, Clone)]
442pub enum Fail<D, L: LaneProfile = AllLanes> {
443 Rejected(D),
444 Denied(L::Denied),
445 Transient(L::Transient),
446 Fatal(L::Fatal),
447}
448
449impl<D, L: LaneProfile> Fail<D, L> {
450 pub fn lane(&self) -> Lane {
451 match self {
452 Fail::Rejected(_) => Lane::Rejected,
453 Fail::Denied(_) => Lane::Denied,
454 Fail::Transient(_) => Lane::Transient,
455 Fail::Fatal(_) => Lane::Fatal,
456 }
457 }
458
459 pub fn lift<P: Lift<D>, M: LaneProfile>(self) -> Fail<P, M>
470 where
471 L::Denied: Into<M::Denied>,
472 L::Transient: Into<M::Transient>,
473 L::Fatal: Into<M::Fatal>,
474 P::Unmapped: UnmappedInto<M::Fatal>,
475 {
476 match self {
477 Self::Rejected(d) => match P::lift(d) {
478 Ok(mapped) => Fail::Rejected(mapped),
479 Err(unmapped) => Fail::Fatal(unmapped.unmapped_into()),
480 },
481 Self::Denied(d) => Fail::Denied(d.into()),
482 Self::Transient(t) => Fail::Transient(t.into()),
483 Self::Fatal(f) => Fail::Fatal(f.into()),
484 }
485 }
486
487 pub fn lift_with<P, M: LaneProfile<Fatal = Fatal>>(
488 self,
489 f: impl FnOnce(D) -> Result<P, Fatal>,
490 ) -> Fail<P, M>
491 where
492 L::Denied: Into<M::Denied>,
493 L::Transient: Into<M::Transient>,
494 L::Fatal: Into<M::Fatal>,
495 {
496 match self {
497 Self::Rejected(d) => match f(d) {
498 Ok(p) => Fail::Rejected(p),
499 Err(e) => Fail::Fatal(e),
500 },
501 Self::Denied(d) => Fail::Denied(d.into()),
502 Self::Transient(t) => Fail::Transient(t.into()),
503 Self::Fatal(f) => Fail::Fatal(f.into()),
504 }
505 }
506
507 pub fn map_rejected<D2>(self, f: impl FnOnce(D) -> D2) -> Fail<D2, L> {
508 match self {
509 Fail::Rejected(d) => Fail::Rejected(f(d)),
510 Fail::Denied(d) => Fail::Denied(d),
511 Fail::Transient(t) => Fail::Transient(t),
512 Fail::Fatal(fatal) => Fail::Fatal(fatal),
513 }
514 }
515
516 pub fn rejected(self) -> Result<D, Fault<L>> {
520 match self {
521 Fail::Rejected(d) => Ok(d),
522 Fail::Denied(d) => Err(Fault::Denied(d)),
523 Fail::Transient(t) => Err(Fault::Transient(t)),
524 Fail::Fatal(f) => Err(Fault::Fatal(f)),
525 }
526 }
527
528 pub fn as_rejected(&self) -> Option<&D> {
529 match self {
530 Fail::Rejected(d) => Some(d),
531 _ => None,
532 }
533 }
534
535 pub fn is_transient(&self) -> bool {
536 matches!(self, Fail::Transient(_))
537 }
538
539 pub fn is_fatal(&self) -> bool {
540 matches!(self, Fail::Fatal(_))
541 }
542
543 pub fn is_denied(&self) -> bool {
544 matches!(self, Fail::Denied(_))
545 }
546
547 pub fn message(&self) -> String
555 where
556 D: Rejection,
557 {
558 self.lanes().message()
559 }
560
561 pub fn narrow_transient(self, attempts: u32) -> Fail<D, crate::profile::WithoutTransient<L>>
566 where
567 L::Transient: NarrowTransient<L::Fatal>,
568 {
569 match self {
570 Fail::Rejected(d) => Fail::Rejected(d),
571 Fail::Denied(d) => Fail::Denied(d),
572 Fail::Transient(last) => Fail::Fatal(last.narrow(attempts)),
573 Fail::Fatal(f) => Fail::Fatal(f),
574 }
575 }
576
577 pub fn narrow_denied(self) -> Fail<D, crate::profile::WithoutDenied<L>>
581 where
582 L::Denied: NarrowDenied<L::Fatal>,
583 {
584 match self {
585 Fail::Rejected(d) => Fail::Rejected(d),
586 Fail::Denied(d) => Fail::Fatal(d.narrow()),
587 Fail::Transient(t) => Fail::Transient(t),
588 Fail::Fatal(f) => Fail::Fatal(f),
589 }
590 }
591
592 pub fn narrow_rejected(self) -> Fault<L>
602 where
603 D: Rejection,
604 L: LaneProfile<Fatal = Fatal>,
605 {
606 match self {
607 Fail::Rejected(d) => Fault::Fatal(invariant_from_rejection(d)),
608 Fail::Denied(d) => Fault::Denied(d),
609 Fail::Transient(t) => Fault::Transient(t),
610 Fail::Fatal(f) => Fault::Fatal(f),
611 }
612 }
613}
614
615pub(crate) fn invariant_from_rejection<D: Rejection>(d: D) -> Fatal {
621 Fatal::from_error(crate::FatalKind::Invariant, d)
622 .with_context("rejected with no caller to correct")
623 .with_opaque_source()
624}
625
626impl<D: fmt::Display, L: LaneProfile> fmt::Display for Fail<D, L> {
627 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
628 match self {
629 Fail::Rejected(d) => write!(f, "rejected: {d}"),
630 Fail::Denied(d) => write!(f, "{d}"),
631 Fail::Transient(t) => write!(f, "{t}"),
632 Fail::Fatal(x) => write!(f, "{x}"),
633 }
634 }
635}
636
637impl<D: Error + 'static, L: LaneProfile> Error for Fail<D, L> {
638 fn source(&self) -> Option<&(dyn Error + 'static)> {
639 match self {
640 Fail::Rejected(d) => Some(d),
643 Fail::Denied(d) => Some(d),
644 Fail::Transient(t) => Some(t),
645 Fail::Fatal(x) => Some(x),
646 }
647 }
648}
649
650pub(crate) mod sealed {
651 pub trait Sealed {}
652 impl<D: super::Rejection, L: super::LaneProfile> Sealed for super::Fail<D, L> {}
653 impl<L: super::LaneProfile> Sealed for super::Fault<L> {}
654 impl<C: crate::Carrier> Sealed for C {}
655}
656
657pub trait Laned: sealed::Sealed + Error + Send + Sync + 'static + Sized {
662 type WithoutTransient: Error + Send + Sync + 'static;
663
664 fn lane(&self) -> Lane;
665
666 fn level(&self) -> Level;
670
671 fn is_transient(&self) -> bool {
672 self.lane() == Lane::Transient
673 }
674
675 fn is_fatal(&self) -> bool {
676 self.lane() == Lane::Fatal
677 }
678
679 fn is_denied(&self) -> bool {
680 self.lane() == Lane::Denied
681 }
682
683 fn narrow_transient(self, attempts: u32) -> Self::WithoutTransient;
684
685 fn message(&self) -> String;
693
694 #[cfg(feature = "tracing")]
695 fn record(&self, span: &tracing::Span);
696
697 #[cfg(feature = "tracing")]
699 #[doc(hidden)]
700 fn emit_event(&self);
701}
702
703impl<D: Rejection, L: LaneProfile> Laned for Fail<D, L>
708where
709 L::Transient: NarrowTransient<L::Fatal>,
710{
711 type WithoutTransient = Fail<D, crate::profile::WithoutTransient<L>>;
712
713 fn lane(&self) -> Lane {
714 Fail::lane(self)
715 }
716
717 fn level(&self) -> Level {
718 self.lanes().level()
719 }
720
721 fn narrow_transient(self, attempts: u32) -> Self::WithoutTransient {
722 Fail::narrow_transient(self, attempts)
723 }
724
725 fn message(&self) -> String {
726 Fail::message(self)
727 }
728
729 #[cfg(feature = "tracing")]
730 fn record(&self, span: &tracing::Span) {
731 self.lanes().record(span);
732 }
733
734 #[cfg(feature = "tracing")]
735 fn emit_event(&self) {
736 self.lanes().emit();
737 }
738}
739
740impl<C: crate::Carrier> Laned for C
744where
745 C::Repr: Laned,
746 <C as crate::IntoLanes>::Rejected: crate::carrier::LaneRejected,
747{
748 type WithoutTransient = <C::Repr as Laned>::WithoutTransient;
749
750 fn lane(&self) -> Lane {
751 crate::Carrier::lanes(self).lane()
752 }
753
754 fn level(&self) -> Level {
755 crate::Carrier::lanes(self).level()
756 }
757
758 fn narrow_transient(self, attempts: u32) -> Self::WithoutTransient {
759 self.into_repr().narrow_transient(attempts)
760 }
761
762 fn message(&self) -> String {
763 crate::Carrier::lanes(self).message()
764 }
765
766 #[cfg(feature = "tracing")]
767 fn record(&self, span: &tracing::Span) {
768 crate::Carrier::lanes(self).record(span);
769 }
770
771 #[cfg(feature = "tracing")]
772 fn emit_event(&self) {
773 crate::Carrier::lanes(self).emit();
774 }
775}
776
777impl<L: LaneProfile> Laned for Fault<L>
778where
779 L::Transient: NarrowTransient<L::Fatal>,
780{
781 type WithoutTransient = Fault<crate::profile::WithoutTransient<L>>;
782
783 fn lane(&self) -> Lane {
784 Fault::lane(self)
785 }
786
787 fn level(&self) -> Level {
788 self.lanes().level()
789 }
790
791 fn narrow_transient(self, attempts: u32) -> Self::WithoutTransient {
792 Fault::narrow_transient(self, attempts)
793 }
794
795 fn message(&self) -> String {
796 Fault::message(self)
797 }
798
799 #[cfg(feature = "tracing")]
800 fn record(&self, span: &tracing::Span) {
801 self.lanes().record(span);
802 }
803
804 #[cfg(feature = "tracing")]
805 fn emit_event(&self) {
806 self.lanes().emit();
807 }
808}
809
810#[doc(hidden)]
812pub trait RejectionField<const VARIANT: u64, const FIELD: usize> {
813 type Type;
814}
815
816#[doc(hidden)]
818pub trait RejectionMetadata<const VARIANT: u64>: Rejection {
819 type Fields<'a>;
820 const CODES: &'static [CodeInfo];
823 fn field_code(fields: Self::Fields<'_>) -> Self::Code;
824 fn field_level(fields: Self::Fields<'_>) -> Level;
825}
826
827#[cfg(test)]
828mod tests {
829 use super::*;
830 use crate::lane::{Exhausted, FatalKind, TransientKind};
831
832 #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
833 struct SmallCode;
834 impl fmt::Display for SmallCode {
835 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
836 f.write_str("SMALL")
837 }
838 }
839 impl From<SmallCode> for &'static str {
840 fn from(_: SmallCode) -> Self {
841 "SMALL"
842 }
843 }
844 impl RejectionCode for SmallCode {
845 const CODES: &'static [CodeInfo] = &[CodeInfo {
846 code: "SMALL",
847 description: None,
848 }];
849 }
850
851 #[derive(Debug, Clone, PartialEq, Eq)]
852 struct Small;
853 impl fmt::Display for Small {
854 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
855 f.write_str("small")
856 }
857 }
858 impl Error for Small {}
859 impl Rejection for Small {
860 type Code = SmallCode;
861 fn code(&self) -> SmallCode {
862 SmallCode
863 }
864 }
865
866 #[derive(Debug, Clone, PartialEq, Eq)]
867 struct Big(Small);
868 impl fmt::Display for Big {
869 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
870 write!(f, "big({})", self.0)
871 }
872 }
873 impl Error for Big {}
874 impl From<Small> for Big {
875 fn from(s: Small) -> Self {
876 Big(s)
877 }
878 }
879 impl Rejection for Big {
880 type Code = SmallCode;
881 fn code(&self) -> SmallCode {
882 self.0.code()
883 }
884 }
885
886 #[test]
887 fn expand_preserves_the_lane() {
888 let f: Fail<Small> = Fail::Rejected(Small);
889 let converted: Fail<Big> = f.lift();
890 assert_eq!(converted.lane(), Lane::Rejected);
891 assert_eq!(converted.as_rejected(), Some(&Big(Small)));
892
893 let t: Fail<Small> = Transient::new(TransientKind::Deadlock).into();
894 let converted: Fail<Big> = t.lift();
895 assert_eq!(converted.lane(), Lane::Transient);
896 }
897
898 #[test]
899 fn expand_with_demotes_an_unmapped_rejection_to_fatal() {
900 let f: Fail<Small> = Fail::Rejected(Small);
901 let converted: Fail<Big> = f.lift_with(|_| Err(Fatal::invariant("unmapped")));
902 assert_eq!(converted.lane(), Lane::Fatal);
903
904 let f: Fail<Small> = Fail::Rejected(Small);
905 let converted: Fail<Big> = f.lift_with(|s| Ok(Big(s)));
906 assert_eq!(converted.lane(), Lane::Rejected);
907 }
908
909 #[test]
914 fn unmapped_rejection_demoted_to_fatal_does_not_leak_its_display() {
915 let fatal: Fatal = Small.unmapped_into();
916 assert_eq!(fatal.kind, FatalKind::Invariant);
917 assert!(
918 fatal.source().unwrap().downcast_ref::<Small>().is_some(),
919 "the rejection must still be reachable for a handler or test to downcast"
920 );
921 let message = crate::dynamic::message_chain(&fatal);
922 assert!(
923 !message.contains("small"),
924 "a partial lift's unmapped rejection's Display must never reach an \
925 operator-facing message; got {message:?}"
926 );
927 }
928
929 #[test]
932 fn narrow_rejected_does_not_leak_the_rejections_display() {
933 let f: Fail<Small> = Fail::Rejected(Small);
934 let narrowed = f.narrow_rejected();
935 match &narrowed {
936 Fault::Fatal(fatal) => {
937 assert_eq!(fatal.kind, FatalKind::Invariant);
938 assert!(fatal.source().unwrap().downcast_ref::<Small>().is_some());
939 }
940 other => panic!("expected Fatal(Invariant), got {other:?}"),
941 }
942 let message = narrowed.message();
943 assert!(
944 !message.contains("small"),
945 "narrow_rejected's rejection Display must never reach an operator-facing \
946 message; got {message:?}"
947 );
948 }
949
950 #[test]
951 fn narrow_transient_turns_transient_into_exhausted_and_leaves_everything_else_alone() {
952 let t: Fail<Small> = Transient::new(TransientKind::Deadlock).into();
953 match t.narrow_transient(3) {
954 Fail::Fatal(f) => {
955 assert_eq!(f.kind, FatalKind::Exhausted);
956 let e = Error::source(&f)
957 .and_then(|s| s.downcast_ref::<Exhausted>())
958 .expect("exhaustion is the fatal's source");
959 assert_eq!(e.attempts, 3);
960 assert_eq!(e.last.kind, TransientKind::Deadlock);
961 }
962 other => panic!("expected Fatal(Exhausted), got {other:?}"),
963 }
964
965 let r: Fail<Small> = Fail::Rejected(Small);
966 assert!(matches!(r.narrow_transient(1), Fail::Rejected(Small)));
967
968 let fatal: Fail<Small> = Fatal::new(FatalKind::Config).into();
969 assert!(matches!(fatal.narrow_transient(1), Fail::Fatal(_)));
970 }
971
972 #[test]
973 fn fault_converts_into_any_fail_via_the_real_blanket() {
974 let fault: Fault = Fatal::new(FatalKind::CorruptState).into();
975 let converted: Fail<Small> = fault.into();
976 assert_eq!(converted.lane(), Lane::Fatal);
977 }
978
979 #[test]
980 fn rejected_narrows_or_returns_the_fault() {
981 let f: Fail<Small> = Fail::Rejected(Small);
982 assert_eq!(f.rejected().unwrap(), Small);
983
984 let t: Fail<Small> = Transient::new(TransientKind::Deadlock).into();
985 assert!(t.rejected().is_err());
986 }
987
988 #[test]
989 fn fail_source_is_the_lane_payload_itself_so_lane_of_can_downcast_it() {
990 let t: Fail<Small> = Transient::new(TransientKind::Deadlock).into();
991 let source = std::error::Error::source(&t).expect("source present");
992 assert!(source.is::<Transient>());
993 }
994
995 #[test]
996 fn fault_source_is_the_lane_payload_itself_so_lane_of_can_downcast_it() {
997 let t: Fault = Transient::new(TransientKind::Deadlock).into();
998 let source = std::error::Error::source(&t).expect("source present");
999 assert!(source.is::<Transient>());
1000 }
1001
1002 #[test]
1003 fn laned_narrow_transient_agrees_between_fail_and_fault() {
1004 fn exhausted_attempts(e: &(dyn Error + 'static)) -> u32 {
1005 e.source()
1006 .and_then(|s| s.downcast_ref::<Exhausted>())
1007 .expect("exhaustion is the fatal's source")
1008 .attempts
1009 }
1010
1011 let t: Fail<Small> = Transient::new(TransientKind::Deadlock).into();
1012 let narrowed: Fail<Small, crate::profile::WithoutTransient<AllLanes>> =
1013 Laned::narrow_transient(t, 2);
1014 assert_eq!(narrowed.lane(), Lane::Fatal);
1015 assert_eq!(exhausted_attempts(narrowed.as_fatal().unwrap()), 2);
1016
1017 let t: Fault = Transient::new(TransientKind::Deadlock).into();
1018 let narrowed: Fault<crate::profile::WithoutTransient<AllLanes>> =
1019 Laned::narrow_transient(t, 2);
1020 assert_eq!(narrowed.lane(), Lane::Fatal);
1021 assert_eq!(exhausted_attempts(narrowed.as_fatal().unwrap()), 2);
1022 }
1023}