1use std::{error::Error, fmt};
2
3use crate::profile::{AllLanes, LaneProfile, NarrowDenied, NarrowTransient};
4
5use crate::lane::{Denied, Exhausted, 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
17#[cfg(feature = "tracing")]
18impl From<Level> for tracing::Level {
19 fn from(level: Level) -> Self {
20 match level {
21 Level::Trace => tracing::Level::TRACE,
22 Level::Debug => tracing::Level::DEBUG,
23 Level::Info => tracing::Level::INFO,
24 Level::Warn => tracing::Level::WARN,
25 Level::Error => tracing::Level::ERROR,
26 }
27 }
28}
29
30pub trait Rejection: Error + Send + Sync + 'static {
35 type Code: Copy
37 + Eq
38 + std::hash::Hash
39 + fmt::Debug
40 + fmt::Display
41 + Into<&'static str>
42 + Send
43 + Sync
44 + 'static;
45
46 fn code(&self) -> Self::Code;
47
48 fn level(&self) -> Level {
50 Level::Info
51 }
52}
53
54pub trait Lift<X>: Sized {
62 type Unmapped;
63 fn lift(x: X) -> Result<Self, Self::Unmapped>;
64}
65
66impl<X: Rejection, P: From<X>> Lift<X> for P {
73 type Unmapped = core::convert::Infallible;
74 fn lift(x: X) -> Result<Self, Self::Unmapped> {
75 Ok(P::from(x))
76 }
77}
78
79impl<P> Lift<core::convert::Infallible> for P {
83 type Unmapped = core::convert::Infallible;
84 fn lift(x: core::convert::Infallible) -> Result<Self, Self::Unmapped> {
85 match x {}
86 }
87}
88
89#[doc(hidden)]
92pub trait UnmappedInto<F> {
93 fn unmapped_into(self) -> F;
94}
95impl<F> UnmappedInto<F> for core::convert::Infallible {
96 fn unmapped_into(self) -> F {
97 match self {}
98 }
99}
100impl<R: Rejection> UnmappedInto<Fatal> for R {
101 fn unmapped_into(self) -> Fatal {
102 Fatal::from_error(crate::FatalKind::Invariant, self)
107 .with_context("unhandled rejection at partial lift")
108 .with_opaque_source()
109 }
110}
111
112#[derive(Debug, Clone)]
117pub enum Fault<L: LaneProfile = AllLanes> {
118 Denied(L::Denied),
119 Transient(L::Transient),
120 Fatal(L::Fatal),
121}
122
123impl<L: LaneProfile> Fault<L> {
124 pub fn lane(&self) -> Lane {
125 match self {
126 Fault::Denied(_) => Lane::Denied,
127 Fault::Transient(_) => Lane::Transient,
128 Fault::Fatal(_) => Lane::Fatal,
129 }
130 }
131
132 pub fn is_transient(&self) -> bool {
133 matches!(self, Fault::Transient(_))
134 }
135
136 pub fn is_fatal(&self) -> bool {
137 matches!(self, Fault::Fatal(_))
138 }
139
140 pub fn is_denied(&self) -> bool {
141 matches!(self, Fault::Denied(_))
142 }
143
144 pub fn message(&self) -> String {
150 match self {
151 Fault::Denied(d) => d.to_string(),
152 Fault::Transient(t) => crate::dynamic::message_chain(t),
153 Fault::Fatal(x) => crate::dynamic::message_chain(x),
154 }
155 }
156
157 pub fn narrow_transient(self, attempts: u32) -> Fault<crate::profile::WithoutTransient<L>>
162 where
163 L::Transient: NarrowTransient<L::Fatal>,
164 {
165 match self {
166 Fault::Denied(d) => Fault::Denied(d),
167 Fault::Transient(last) => Fault::Fatal(last.narrow(attempts)),
168 Fault::Fatal(f) => Fault::Fatal(f),
169 }
170 }
171
172 pub fn narrow_denied(self) -> Fault<crate::profile::WithoutDenied<L>>
176 where
177 L::Denied: NarrowDenied<L::Fatal>,
178 {
179 match self {
180 Fault::Denied(d) => Fault::Fatal(d.narrow()),
181 Fault::Transient(t) => Fault::Transient(t),
182 Fault::Fatal(f) => Fault::Fatal(f),
183 }
184 }
185}
186
187impl<L: LaneProfile<Denied = Denied>> Fault<L> {
194 pub fn as_denied(&self) -> Option<&Denied> {
195 match self {
196 Fault::Denied(d) => Some(d),
197 _ => None,
198 }
199 }
200}
201
202impl<L: LaneProfile<Transient = Transient>> Fault<L> {
203 pub fn as_transient(&self) -> Option<&Transient> {
204 match self {
205 Fault::Transient(t) => Some(t),
206 _ => None,
207 }
208 }
209
210 pub fn is_congestion(&self) -> bool {
211 self.as_transient().is_some_and(Transient::is_congestion)
212 }
213
214 pub fn is_contention(&self) -> bool {
216 self.as_transient().is_some_and(Transient::is_contention)
217 }
218}
219
220impl<L: LaneProfile<Fatal = Fatal>> Fault<L> {
221 pub fn as_fatal(&self) -> Option<&Fatal> {
222 match self {
223 Fault::Fatal(f) => Some(f),
224 _ => None,
225 }
226 }
227}
228
229impl<D, L: LaneProfile<Denied = Denied>> Fail<D, L> {
230 pub fn as_denied(&self) -> Option<&Denied> {
231 match self {
232 Fail::Denied(d) => Some(d),
233 _ => None,
234 }
235 }
236}
237
238impl<D, L: LaneProfile<Transient = Transient>> Fail<D, L> {
239 pub fn as_transient(&self) -> Option<&Transient> {
240 match self {
241 Fail::Transient(t) => Some(t),
242 _ => None,
243 }
244 }
245
246 pub fn is_congestion(&self) -> bool {
247 self.as_transient().is_some_and(Transient::is_congestion)
248 }
249
250 pub fn is_contention(&self) -> bool {
252 self.as_transient().is_some_and(Transient::is_contention)
253 }
254}
255
256impl<D, L: LaneProfile<Fatal = Fatal>> Fail<D, L> {
257 pub fn as_fatal(&self) -> Option<&Fatal> {
258 match self {
259 Fail::Fatal(f) => Some(f),
260 _ => None,
261 }
262 }
263}
264
265impl<L: LaneProfile> fmt::Display for Fault<L> {
266 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
267 match self {
268 Fault::Denied(d) => write!(f, "{d}"),
269 Fault::Transient(t) => write!(f, "{t}"),
270 Fault::Fatal(x) => write!(f, "{x}"),
271 }
272 }
273}
274
275impl<L: LaneProfile> Error for Fault<L> {
276 fn source(&self) -> Option<&(dyn Error + 'static)> {
277 match self {
280 Fault::Denied(d) => Some(d),
281 Fault::Transient(t) => Some(t),
282 Fault::Fatal(x) => Some(x),
283 }
284 }
285}
286
287impl<L: LaneProfile> From<Transient> for Fault<L>
288where
289 L: LaneProfile<Transient = Transient>,
290{
291 fn from(t: Transient) -> Self {
292 Fault::Transient(t)
293 }
294}
295
296impl<L: LaneProfile> From<Fatal> for Fault<L>
297where
298 L: LaneProfile<Fatal = Fatal>,
299{
300 fn from(f: Fatal) -> Self {
301 Fault::Fatal(f)
302 }
303}
304
305impl<L: LaneProfile> From<Denied> for Fault<L>
306where
307 L: LaneProfile<Denied = Denied>,
308{
309 fn from(d: Denied) -> Self {
310 Fault::Denied(d)
311 }
312}
313
314impl<L: LaneProfile> From<Exhausted> for Fault<L>
315where
316 L: LaneProfile<Fatal = Fatal>,
317{
318 fn from(e: Exhausted) -> Self {
319 Fault::Fatal(Fatal::from_error(crate::lane::FatalKind::Exhausted, e))
320 }
321}
322
323impl<L: LaneProfile> From<core::convert::Infallible> for Fault<L> {
326 fn from(e: core::convert::Infallible) -> Self {
327 match e {}
328 }
329}
330
331#[derive(Debug, Clone)]
341pub enum Fail<D, L: LaneProfile = AllLanes> {
342 Rejected(D),
343 Denied(L::Denied),
344 Transient(L::Transient),
345 Fatal(L::Fatal),
346}
347
348impl<D, L: LaneProfile> Fail<D, L> {
349 pub fn lane(&self) -> Lane {
350 match self {
351 Fail::Rejected(_) => Lane::Rejected,
352 Fail::Denied(_) => Lane::Denied,
353 Fail::Transient(_) => Lane::Transient,
354 Fail::Fatal(_) => Lane::Fatal,
355 }
356 }
357
358 pub fn widen<P: Lift<D>, M: LaneProfile>(self) -> Fail<P, M>
368 where
369 L::Denied: Into<M::Denied>,
370 L::Transient: Into<M::Transient>,
371 L::Fatal: Into<M::Fatal>,
372 P::Unmapped: UnmappedInto<M::Fatal>,
373 {
374 match self {
375 Self::Rejected(d) => match P::lift(d) {
376 Ok(mapped) => Fail::Rejected(mapped),
377 Err(unmapped) => Fail::Fatal(unmapped.unmapped_into()),
378 },
379 Self::Denied(d) => Fail::Denied(d.into()),
380 Self::Transient(t) => Fail::Transient(t.into()),
381 Self::Fatal(f) => Fail::Fatal(f.into()),
382 }
383 }
384
385 pub fn widen_with<P, M: LaneProfile<Fatal = Fatal>>(
386 self,
387 f: impl FnOnce(D) -> Result<P, Fatal>,
388 ) -> Fail<P, M>
389 where
390 L::Denied: Into<M::Denied>,
391 L::Transient: Into<M::Transient>,
392 L::Fatal: Into<M::Fatal>,
393 {
394 match self {
395 Self::Rejected(d) => match f(d) {
396 Ok(p) => Fail::Rejected(p),
397 Err(e) => Fail::Fatal(e),
398 },
399 Self::Denied(d) => Fail::Denied(d.into()),
400 Self::Transient(t) => Fail::Transient(t.into()),
401 Self::Fatal(f) => Fail::Fatal(f.into()),
402 }
403 }
404
405 pub fn map_rejected<D2>(self, f: impl FnOnce(D) -> D2) -> Fail<D2, L> {
406 match self {
407 Fail::Rejected(d) => Fail::Rejected(f(d)),
408 Fail::Denied(d) => Fail::Denied(d),
409 Fail::Transient(t) => Fail::Transient(t),
410 Fail::Fatal(fatal) => Fail::Fatal(fatal),
411 }
412 }
413
414 pub fn rejected(self) -> Result<D, Fault<L>> {
418 match self {
419 Fail::Rejected(d) => Ok(d),
420 Fail::Denied(d) => Err(Fault::Denied(d)),
421 Fail::Transient(t) => Err(Fault::Transient(t)),
422 Fail::Fatal(f) => Err(Fault::Fatal(f)),
423 }
424 }
425
426 pub fn as_rejected(&self) -> Option<&D> {
427 match self {
428 Fail::Rejected(d) => Some(d),
429 _ => None,
430 }
431 }
432
433 pub fn is_transient(&self) -> bool {
434 matches!(self, Fail::Transient(_))
435 }
436
437 pub fn is_fatal(&self) -> bool {
438 matches!(self, Fail::Fatal(_))
439 }
440
441 pub fn is_denied(&self) -> bool {
442 matches!(self, Fail::Denied(_))
443 }
444
445 pub fn message(&self) -> String
453 where
454 D: Rejection,
455 {
456 match self {
457 Fail::Rejected(d) => d.code().to_string(),
458 Fail::Denied(d) => d.to_string(),
459 Fail::Transient(t) => crate::dynamic::message_chain(t),
460 Fail::Fatal(x) => crate::dynamic::message_chain(x),
461 }
462 }
463
464 pub fn narrow_transient(self, attempts: u32) -> Fail<D, crate::profile::WithoutTransient<L>>
469 where
470 L::Transient: NarrowTransient<L::Fatal>,
471 {
472 match self {
473 Fail::Rejected(d) => Fail::Rejected(d),
474 Fail::Denied(d) => Fail::Denied(d),
475 Fail::Transient(last) => Fail::Fatal(last.narrow(attempts)),
476 Fail::Fatal(f) => Fail::Fatal(f),
477 }
478 }
479
480 pub fn narrow_denied(self) -> Fail<D, crate::profile::WithoutDenied<L>>
484 where
485 L::Denied: NarrowDenied<L::Fatal>,
486 {
487 match self {
488 Fail::Rejected(d) => Fail::Rejected(d),
489 Fail::Denied(d) => Fail::Fatal(d.narrow()),
490 Fail::Transient(t) => Fail::Transient(t),
491 Fail::Fatal(f) => Fail::Fatal(f),
492 }
493 }
494
495 pub fn narrow_rejected(self) -> Fault<L>
505 where
506 D: Rejection,
507 L: LaneProfile<Fatal = Fatal>,
508 {
509 match self {
510 Fail::Rejected(d) => Fault::Fatal(invariant_from_rejection(d)),
511 Fail::Denied(d) => Fault::Denied(d),
512 Fail::Transient(t) => Fault::Transient(t),
513 Fail::Fatal(f) => Fault::Fatal(f),
514 }
515 }
516}
517
518pub(crate) fn invariant_from_rejection<D: Rejection>(d: D) -> Fatal {
524 Fatal::from_error(crate::FatalKind::Invariant, d)
525 .with_context("rejected with no caller to correct")
526 .with_opaque_source()
527}
528
529impl<D, S: LaneProfile, L: LaneProfile> From<Fault<S>> for Fail<D, L>
532where
533 S::Denied: Into<L::Denied>,
534 S::Transient: Into<L::Transient>,
535 S::Fatal: Into<L::Fatal>,
536{
537 fn from(f: Fault<S>) -> Self {
538 match f {
539 Fault::Denied(d) => Fail::Denied(d.into()),
540 Fault::Transient(t) => Fail::Transient(t.into()),
541 Fault::Fatal(x) => Fail::Fatal(x.into()),
542 }
543 }
544}
545
546impl<D, L: LaneProfile> From<Transient> for Fail<D, L>
547where
548 L: LaneProfile<Transient = Transient>,
549{
550 fn from(t: Transient) -> Self {
551 Fail::Transient(t)
552 }
553}
554
555impl<D, L: LaneProfile> From<Fatal> for Fail<D, L>
556where
557 L: LaneProfile<Fatal = Fatal>,
558{
559 fn from(f: Fatal) -> Self {
560 Fail::Fatal(f)
561 }
562}
563
564impl<D, L: LaneProfile> From<Denied> for Fail<D, L>
565where
566 L: LaneProfile<Denied = Denied>,
567{
568 fn from(d: Denied) -> Self {
569 Fail::Denied(d)
570 }
571}
572
573impl<D, L: LaneProfile> From<Exhausted> for Fail<D, L>
574where
575 L: LaneProfile<Fatal = Fatal>,
576{
577 fn from(e: Exhausted) -> Self {
578 Fail::Fatal(Fatal::from_error(crate::lane::FatalKind::Exhausted, e))
579 }
580}
581
582impl<D: fmt::Display, L: LaneProfile> fmt::Display for Fail<D, L> {
583 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
584 match self {
585 Fail::Rejected(d) => write!(f, "rejected: {d}"),
586 Fail::Denied(d) => write!(f, "{d}"),
587 Fail::Transient(t) => write!(f, "{t}"),
588 Fail::Fatal(x) => write!(f, "{x}"),
589 }
590 }
591}
592
593impl<D: Error + 'static, L: LaneProfile> Error for Fail<D, L> {
594 fn source(&self) -> Option<&(dyn Error + 'static)> {
595 match self {
596 Fail::Rejected(d) => Some(d),
599 Fail::Denied(d) => Some(d),
600 Fail::Transient(t) => Some(t),
601 Fail::Fatal(x) => Some(x),
602 }
603 }
604}
605
606pub trait Failure: Error + Send + Sync + Sized + 'static {
610 type Rejection: Rejection;
611 type Lanes: LaneProfile;
612
613 fn into_fail(self) -> Fail<Self::Rejection, Self::Lanes>;
614 fn from_fail(f: Fail<Self::Rejection, Self::Lanes>) -> Self;
615 fn as_fail(&self) -> &Fail<Self::Rejection, Self::Lanes>;
616
617 fn lane(&self) -> Lane {
618 self.as_fail().lane()
619 }
620
621 fn is_transient(&self) -> bool {
622 matches!(self.lane(), Lane::Transient)
623 }
624}
625
626impl<D: Rejection, L: LaneProfile> Failure for Fail<D, L> {
627 type Rejection = D;
628 type Lanes = L;
629
630 fn into_fail(self) -> Fail<Self::Rejection, Self::Lanes> {
631 self
632 }
633
634 fn from_fail(f: Fail<Self::Rejection, Self::Lanes>) -> Self {
635 f
636 }
637
638 fn as_fail(&self) -> &Fail<Self::Rejection, Self::Lanes> {
639 self
640 }
641}
642
643pub(crate) mod sealed {
644 pub trait Sealed {}
645 impl<F: super::Failure> Sealed for F {}
646 impl<L: super::LaneProfile> Sealed for super::Fault<L> {}
647}
648
649pub trait Laned: sealed::Sealed + Error + Send + Sync + 'static + Sized {
654 type WithoutTransient: Error + Send + Sync + 'static;
655
656 fn lane(&self) -> Lane;
657
658 fn is_transient(&self) -> bool {
659 self.lane() == Lane::Transient
660 }
661
662 fn is_fatal(&self) -> bool {
663 self.lane() == Lane::Fatal
664 }
665
666 fn is_denied(&self) -> bool {
667 self.lane() == Lane::Denied
668 }
669
670 fn narrow_transient(self, attempts: u32) -> Self::WithoutTransient;
671
672 fn message(&self) -> String;
680
681 #[cfg(feature = "tracing")]
682 fn record(&self, span: &tracing::Span);
683}
684
685impl<F: Failure> Laned for F
690where
691 <F::Lanes as LaneProfile>::Transient: NarrowTransient<<F::Lanes as LaneProfile>::Fatal>,
692{
693 type WithoutTransient = Fail<F::Rejection, crate::profile::WithoutTransient<F::Lanes>>;
694
695 fn lane(&self) -> Lane {
696 Failure::lane(self)
697 }
698
699 fn narrow_transient(self, attempts: u32) -> Self::WithoutTransient {
700 self.into_fail().narrow_transient(attempts)
701 }
702
703 fn message(&self) -> String {
704 self.as_fail().message()
705 }
706
707 #[cfg(feature = "tracing")]
708 fn record(&self, span: &tracing::Span) {
709 crate::record::record_fail(span, self.as_fail());
710 }
711}
712
713impl<L: LaneProfile> Laned for Fault<L>
714where
715 L::Transient: NarrowTransient<L::Fatal>,
716{
717 type WithoutTransient = Fault<crate::profile::WithoutTransient<L>>;
718
719 fn lane(&self) -> Lane {
720 Fault::lane(self)
721 }
722
723 fn narrow_transient(self, attempts: u32) -> Self::WithoutTransient {
724 Fault::narrow_transient(self, attempts)
725 }
726
727 fn message(&self) -> String {
728 Fault::message(self)
729 }
730
731 #[cfg(feature = "tracing")]
732 fn record(&self, span: &tracing::Span) {
733 crate::record::record_fault(span, self);
734 }
735}
736
737#[doc(hidden)]
743pub trait WidenResult<T, E>: Sized {
744 fn widen(self) -> Result<T, E>;
745}
746
747impl<T, L: LaneProfile, M: LaneProfile> WidenResult<T, Fault<M>> for Result<T, Fault<L>>
748where
749 L::Denied: Into<M::Denied>,
750 L::Transient: Into<M::Transient>,
751 L::Fatal: Into<M::Fatal>,
752{
753 fn widen(self) -> Result<T, Fault<M>> {
754 self.map_err(Fault::widen)
755 }
756}
757
758impl<T, R, P: Lift<R>, L: LaneProfile, M: LaneProfile> WidenResult<T, Fail<P, M>>
766 for Result<T, Fail<R, L>>
767where
768 L::Denied: Into<M::Denied>,
769 L::Transient: Into<M::Transient>,
770 L::Fatal: Into<M::Fatal>,
771 P::Unmapped: UnmappedInto<M::Fatal>,
772{
773 fn widen(self) -> Result<T, Fail<P, M>> {
774 self.map_err(Fail::widen)
775 }
776}
777
778impl<L: LaneProfile> Fault<L> {
784 pub fn widen<M: LaneProfile>(self) -> Fault<M>
785 where
786 L::Denied: Into<M::Denied>,
787 L::Transient: Into<M::Transient>,
788 L::Fatal: Into<M::Fatal>,
789 {
790 match self {
791 Self::Denied(d) => Fault::Denied(d.into()),
792 Self::Transient(t) => Fault::Transient(t.into()),
793 Self::Fatal(f) => Fault::Fatal(f.into()),
794 }
795 }
796}
797
798#[doc(hidden)]
800pub trait RejectionField<const VARIANT: u64, const FIELD: usize> {
801 type Type;
802}
803
804#[doc(hidden)]
806pub trait RejectionMetadata<const VARIANT: u64>: Rejection {
807 type Fields<'a>;
808 fn field_code(fields: Self::Fields<'_>) -> Self::Code;
809 fn field_level(fields: Self::Fields<'_>) -> Level;
810}
811
812#[cfg(test)]
813mod tests {
814 use super::*;
815 use crate::lane::{FatalKind, TransientKind};
816
817 #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
818 struct SmallCode;
819 impl fmt::Display for SmallCode {
820 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
821 f.write_str("SMALL")
822 }
823 }
824 impl From<SmallCode> for &'static str {
825 fn from(_: SmallCode) -> Self {
826 "SMALL"
827 }
828 }
829
830 #[derive(Debug, Clone, PartialEq, Eq)]
831 struct Small;
832 impl fmt::Display for Small {
833 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
834 f.write_str("small")
835 }
836 }
837 impl Error for Small {}
838 impl Rejection for Small {
839 type Code = SmallCode;
840 fn code(&self) -> SmallCode {
841 SmallCode
842 }
843 }
844
845 #[derive(Debug, Clone, PartialEq, Eq)]
846 struct Big(Small);
847 impl fmt::Display for Big {
848 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
849 write!(f, "big({})", self.0)
850 }
851 }
852 impl Error for Big {}
853 impl From<Small> for Big {
854 fn from(s: Small) -> Self {
855 Big(s)
856 }
857 }
858 impl Rejection for Big {
859 type Code = SmallCode;
860 fn code(&self) -> SmallCode {
861 self.0.code()
862 }
863 }
864
865 #[test]
866 fn widen_preserves_the_lane() {
867 let f: Fail<Small> = Fail::Rejected(Small);
868 let widened: Fail<Big> = f.widen();
869 assert_eq!(widened.lane(), Lane::Rejected);
870 assert_eq!(widened.as_rejected(), Some(&Big(Small)));
871
872 let t: Fail<Small> = Transient::new(TransientKind::Deadlock).into();
873 let widened: Fail<Big> = t.widen();
874 assert_eq!(widened.lane(), Lane::Transient);
875 }
876
877 #[test]
878 fn widen_with_demotes_an_unmapped_rejection_to_fatal() {
879 let f: Fail<Small> = Fail::Rejected(Small);
880 let widened: Fail<Big> = f.widen_with(|_| Err(Fatal::invariant("unmapped")));
881 assert_eq!(widened.lane(), Lane::Fatal);
882
883 let f: Fail<Small> = Fail::Rejected(Small);
884 let widened: Fail<Big> = f.widen_with(|s| Ok(Big(s)));
885 assert_eq!(widened.lane(), Lane::Rejected);
886 }
887
888 #[test]
893 fn unmapped_rejection_demoted_to_fatal_does_not_leak_its_display() {
894 let fatal: Fatal = Small.unmapped_into();
895 assert_eq!(fatal.kind, FatalKind::Invariant);
896 assert!(
897 fatal.source().unwrap().downcast_ref::<Small>().is_some(),
898 "the rejection must still be reachable for a handler or test to downcast"
899 );
900 let message = crate::dynamic::message_chain(&fatal);
901 assert!(
902 !message.contains("small"),
903 "a partial lift's unmapped rejection's Display must never reach an \
904 operator-facing message; got {message:?}"
905 );
906 }
907
908 #[test]
911 fn narrow_rejected_does_not_leak_the_rejections_display() {
912 let f: Fail<Small> = Fail::Rejected(Small);
913 let narrowed = f.narrow_rejected();
914 match &narrowed {
915 Fault::Fatal(fatal) => {
916 assert_eq!(fatal.kind, FatalKind::Invariant);
917 assert!(fatal.source().unwrap().downcast_ref::<Small>().is_some());
918 }
919 other => panic!("expected Fatal(Invariant), got {other:?}"),
920 }
921 let message = narrowed.message();
922 assert!(
923 !message.contains("small"),
924 "narrow_rejected's rejection Display must never reach an operator-facing \
925 message; got {message:?}"
926 );
927 }
928
929 #[test]
930 fn narrow_transient_turns_transient_into_exhausted_and_leaves_everything_else_alone() {
931 let t: Fail<Small> = Transient::new(TransientKind::Deadlock).into();
932 match t.narrow_transient(3) {
933 Fail::Fatal(f) => {
934 assert_eq!(f.kind, FatalKind::Exhausted);
935 let e = Error::source(&f)
936 .and_then(|s| s.downcast_ref::<Exhausted>())
937 .expect("exhaustion is the fatal's source");
938 assert_eq!(e.attempts, 3);
939 assert_eq!(e.last.kind, TransientKind::Deadlock);
940 }
941 other => panic!("expected Fatal(Exhausted), got {other:?}"),
942 }
943
944 let r: Fail<Small> = Fail::Rejected(Small);
945 assert!(matches!(r.narrow_transient(1), Fail::Rejected(Small)));
946
947 let fatal: Fail<Small> = Fatal::new(FatalKind::Config).into();
948 assert!(matches!(fatal.narrow_transient(1), Fail::Fatal(_)));
949 }
950
951 #[test]
952 fn fault_converts_into_any_fail_via_the_real_blanket() {
953 let fault: Fault = Fatal::new(FatalKind::CorruptState).into();
954 let widened: Fail<Small> = fault.into();
955 assert_eq!(widened.lane(), Lane::Fatal);
956 }
957
958 #[test]
959 fn rejected_narrows_or_returns_the_fault() {
960 let f: Fail<Small> = Fail::Rejected(Small);
961 assert_eq!(f.rejected().unwrap(), Small);
962
963 let t: Fail<Small> = Transient::new(TransientKind::Deadlock).into();
964 assert!(t.rejected().is_err());
965 }
966
967 #[test]
968 fn fail_source_is_the_lane_payload_itself_so_lane_of_can_downcast_it() {
969 let t: Fail<Small> = Transient::new(TransientKind::Deadlock).into();
970 let source = std::error::Error::source(&t).expect("source present");
971 assert!(source.is::<Transient>());
972 }
973
974 #[test]
975 fn fault_source_is_the_lane_payload_itself_so_lane_of_can_downcast_it() {
976 let t: Fault = Transient::new(TransientKind::Deadlock).into();
977 let source = std::error::Error::source(&t).expect("source present");
978 assert!(source.is::<Transient>());
979 }
980
981 #[test]
982 fn laned_narrow_transient_agrees_between_fail_and_fault() {
983 fn exhausted_attempts(e: &(dyn Error + 'static)) -> u32 {
984 e.source()
985 .and_then(|s| s.downcast_ref::<Exhausted>())
986 .expect("exhaustion is the fatal's source")
987 .attempts
988 }
989
990 let t: Fail<Small> = Transient::new(TransientKind::Deadlock).into();
991 let narrowed: Fail<Small, crate::profile::WithoutTransient<AllLanes>> =
992 Laned::narrow_transient(t, 2);
993 assert_eq!(narrowed.lane(), Lane::Fatal);
994 assert_eq!(exhausted_attempts(narrowed.as_fatal().unwrap()), 2);
995
996 let t: Fault = Transient::new(TransientKind::Deadlock).into();
997 let narrowed: Fault<crate::profile::WithoutTransient<AllLanes>> =
998 Laned::narrow_transient(t, 2);
999 assert_eq!(narrowed.lane(), Lane::Fatal);
1000 assert_eq!(exhausted_attempts(narrowed.as_fatal().unwrap()), 2);
1001 }
1002}