Skip to main content

errlanes/
profile.rs

1//! Sealed compile-time subsets of the three fault lanes.
2use std::{convert::Infallible, error::Error, fmt::Debug};
3
4use crate::{Denied, Exhausted, Fatal, Transient};
5
6mod sealed {
7    pub trait Sealed {}
8}
9
10/// A standard lane marker, or an uninhabited disabled slot.
11#[doc(hidden)]
12pub trait Slot<M>: From<Infallible> + Error + Clone + Send + Sync + 'static {
13    fn marker(&self) -> &M;
14}
15impl<M: From<Infallible> + Error + Clone + Send + Sync + 'static> Slot<M> for M {
16    fn marker(&self) -> &M {
17        self
18    }
19}
20macro_rules! disabled {
21    ($($marker:ty),*) => {$ (
22        impl Slot<$marker> for Infallible { fn marker(&self) -> &$marker { match *self {} } }
23        impl From<Infallible> for $marker { fn from(value: Infallible) -> Self { match value {} } }
24    )*};
25}
26disabled!(Denied, Transient, Fatal, Exhausted);
27
28/// How a transient slot is narrowed away when retries stop: an enabled
29/// `Transient` becomes `Fatal(Exhausted)` carrying the attempt count and the
30/// last transient as its source; a disabled slot has no value to consume.
31///
32/// There is deliberately no `NarrowTransient<Infallible> for Transient`: a
33/// profile that admits `Transient` but not `Fatal` claims an operation can be
34/// retried but can never fail permanently, which is not true of anything
35/// worth retrying. Such a profile is therefore not narrowable, and — through
36/// the [`crate::Laned`] impls — not retryable either.
37#[doc(hidden)]
38pub trait NarrowTransient<F> {
39    fn narrow(self, attempts: u32) -> F;
40}
41impl<F> NarrowTransient<F> for Infallible {
42    fn narrow(self, _: u32) -> F {
43        match self {}
44    }
45}
46impl NarrowTransient<Fatal> for Transient {
47    fn narrow(self, attempts: u32) -> Fatal {
48        Fatal::from_error(
49            crate::FatalKind::Exhausted,
50            Exhausted {
51                attempts,
52                last: self,
53            },
54        )
55    }
56}
57
58/// How a denied slot is narrowed away at a boundary with no subject: an
59/// enabled `Denied` becomes `Fatal(Denied)` carrying itself as the source; a
60/// disabled slot has no value to consume. As with `NarrowTransient`, there is
61/// no `NarrowDenied<Infallible> for Denied`: a profile that can deny but can
62/// never fail permanently has nowhere to put the narrowing.
63#[doc(hidden)]
64pub trait NarrowDenied<F> {
65    fn narrow(self) -> F;
66}
67impl<F> NarrowDenied<F> for Infallible {
68    fn narrow(self) -> F {
69        match self {}
70    }
71}
72impl NarrowDenied<Fatal> for Denied {
73    fn narrow(self) -> Fatal {
74        self.into_fatal()
75    }
76}
77
78/// Sealed profile: disabled slots are `Infallible`, enabled slots are the
79/// established markers. Use [`crate::lanes!`] to select a subset.
80pub trait LaneProfile: sealed::Sealed + Debug + Clone + Send + Sync + 'static {
81    type Denied: Slot<Denied>;
82    type Transient: Slot<Transient>;
83    type Fatal: Slot<Fatal>;
84
85    /// This profile with the transient lane narrowed away: the projection
86    /// `narrow_transient` and `retry` return through, spelled
87    /// [`crate::profile::WithoutTransient`].
88    type WithoutTransient: LaneProfile<Denied = Self::Denied, Transient = Infallible, Fatal = Self::Fatal>;
89
90    /// This profile with the denied lane narrowed away: the projection
91    /// `narrow_denied` returns through, spelled
92    /// [`crate::profile::WithoutDenied`].
93    type WithoutDenied: LaneProfile<Denied = Infallible, Transient = Self::Transient, Fatal = Self::Fatal>;
94
95    /// This profile with the denied lane enabled; see [`crate::WithDenied`].
96    type WithDenied: LaneProfile<Denied = Denied, Transient = Self::Transient, Fatal = Self::Fatal>;
97
98    /// This profile with the transient lane enabled; see [`crate::WithTransient`].
99    type WithTransient: LaneProfile<Denied = Self::Denied, Transient = Transient, Fatal = Self::Fatal>;
100
101    /// This profile with the fatal lane enabled; see [`crate::WithFatal`].
102    type WithFatal: LaneProfile<Denied = Self::Denied, Transient = Self::Transient, Fatal = Fatal>;
103}
104
105#[derive(Debug, Clone, Copy)]
106pub struct Profile<const D: bool, const T: bool, const F: bool>;
107macro_rules! profile {
108    ($d:literal, $t:literal, $f:literal; $denied:ty, $transient:ty, $fatal:ty) => {
109        impl sealed::Sealed for Profile<$d, $t, $f> {}
110        impl LaneProfile for Profile<$d, $t, $f> {
111            type Denied = $denied;
112            type Transient = $transient;
113            type Fatal = $fatal;
114            type WithoutTransient = Profile<$d, false, $f>;
115            type WithoutDenied = Profile<false, $t, $f>;
116            type WithDenied = Profile<true, $t, $f>;
117            type WithTransient = Profile<$d, true, $f>;
118            type WithFatal = Profile<$d, $t, true>;
119        }
120        impl crate::Reshape for Profile<$d, $t, $f> {
121            type WithDenied = <Self as LaneProfile>::WithDenied;
122            type WithoutDenied = <Self as LaneProfile>::WithoutDenied;
123            type WithTransient = <Self as LaneProfile>::WithTransient;
124            type WithoutTransient = <Self as LaneProfile>::WithoutTransient;
125            type WithFatal = <Self as LaneProfile>::WithFatal;
126        }
127    };
128}
129profile!(false, false, false; Infallible, Infallible, Infallible);
130profile!(false, false, true; Infallible, Infallible, Fatal);
131profile!(false, true, false; Infallible, Transient, Infallible);
132profile!(false, true, true; Infallible, Transient, Fatal);
133profile!(true, false, false; Denied, Infallible, Infallible);
134profile!(true, false, true; Denied, Infallible, Fatal);
135profile!(true, true, false; Denied, Transient, Infallible);
136profile!(true, true, true; Denied, Transient, Fatal);
137
138pub type AllLanes = Profile<true, true, true>;
139
140/// No lanes at all — the profile of a pure [`crate::Rejection`]: everything
141/// about it is rejected, nothing is a fault.
142pub type NoLanes = Profile<false, false, false>;
143
144/// The lane-wise union of two profiles — `derive(Classify)` infers a
145/// `delegate` variant's contribution to the enclosing `Lanes` this way, and
146/// a `delegate` enum's full `Lanes` as the union of every variant's. Sealed,
147/// closed over the eight profiles (no `generic_const_exprs`): each pair is
148/// written out by [`union_impl`](self) below.
149#[doc(hidden)]
150pub trait Union<Other: LaneProfile>: LaneProfile {
151    type Out: LaneProfile;
152}
153macro_rules! union_impl {
154    ($d1:literal,$t1:literal,$f1:literal; $d2:literal,$t2:literal,$f2:literal) => {
155        impl Union<Profile<$d2, $t2, $f2>> for Profile<$d1, $t1, $f1> {
156            type Out = Profile<{ $d1 || $d2 }, { $t1 || $t2 }, { $f1 || $f2 }>;
157        }
158    };
159}
160union_impl!(false,false,false; false,false,false);
161union_impl!(false,false,false; false,false,true);
162union_impl!(false,false,false; false,true,false);
163union_impl!(false,false,false; false,true,true);
164union_impl!(false,false,false; true,false,false);
165union_impl!(false,false,false; true,false,true);
166union_impl!(false,false,false; true,true,false);
167union_impl!(false,false,false; true,true,true);
168union_impl!(false,false,true; false,false,false);
169union_impl!(false,false,true; false,false,true);
170union_impl!(false,false,true; false,true,false);
171union_impl!(false,false,true; false,true,true);
172union_impl!(false,false,true; true,false,false);
173union_impl!(false,false,true; true,false,true);
174union_impl!(false,false,true; true,true,false);
175union_impl!(false,false,true; true,true,true);
176union_impl!(false,true,false; false,false,false);
177union_impl!(false,true,false; false,false,true);
178union_impl!(false,true,false; false,true,false);
179union_impl!(false,true,false; false,true,true);
180union_impl!(false,true,false; true,false,false);
181union_impl!(false,true,false; true,false,true);
182union_impl!(false,true,false; true,true,false);
183union_impl!(false,true,false; true,true,true);
184union_impl!(false,true,true; false,false,false);
185union_impl!(false,true,true; false,false,true);
186union_impl!(false,true,true; false,true,false);
187union_impl!(false,true,true; false,true,true);
188union_impl!(false,true,true; true,false,false);
189union_impl!(false,true,true; true,false,true);
190union_impl!(false,true,true; true,true,false);
191union_impl!(false,true,true; true,true,true);
192union_impl!(true,false,false; false,false,false);
193union_impl!(true,false,false; false,false,true);
194union_impl!(true,false,false; false,true,false);
195union_impl!(true,false,false; false,true,true);
196union_impl!(true,false,false; true,false,false);
197union_impl!(true,false,false; true,false,true);
198union_impl!(true,false,false; true,true,false);
199union_impl!(true,false,false; true,true,true);
200union_impl!(true,false,true; false,false,false);
201union_impl!(true,false,true; false,false,true);
202union_impl!(true,false,true; false,true,false);
203union_impl!(true,false,true; false,true,true);
204union_impl!(true,false,true; true,false,false);
205union_impl!(true,false,true; true,false,true);
206union_impl!(true,false,true; true,true,false);
207union_impl!(true,false,true; true,true,true);
208union_impl!(true,true,false; false,false,false);
209union_impl!(true,true,false; false,false,true);
210union_impl!(true,true,false; false,true,false);
211union_impl!(true,true,false; false,true,true);
212union_impl!(true,true,false; true,false,false);
213union_impl!(true,true,false; true,false,true);
214union_impl!(true,true,false; true,true,false);
215union_impl!(true,true,false; true,true,true);
216union_impl!(true,true,true; false,false,false);
217union_impl!(true,true,true; false,false,true);
218union_impl!(true,true,true; false,true,false);
219union_impl!(true,true,true; false,true,true);
220union_impl!(true,true,true; true,false,false);
221union_impl!(true,true,true; true,false,true);
222union_impl!(true,true,true; true,true,false);
223union_impl!(true,true,true; true,true,true);
224
225/// `L` with its transient lane narrowed away — sugar for
226/// [`LaneProfile::WithoutTransient`], which is what `narrow_transient` and
227/// `retry` return through. Narrowing is a projection on the profile, not a
228/// second family of carriers: `Fault<WithoutTransient<L>>` is an ordinary
229/// `Fault` whose `Transient` slot is uninhabited, and an exhausted retry
230/// arrives as `Fatal(Exhausted)`.
231///
232/// Prefer naming the resulting profile directly wherever you can — narrowing
233/// `lanes!(Transient, Fatal)` yields `lanes!(Fatal)`, and writing that keeps a
234/// lane the caller can never see out of the signature. This alias is for code
235/// generic over `L`, where there is no concrete name to reach for.
236pub type WithoutTransient<L> = <L as LaneProfile>::WithoutTransient;
237
238/// `L` with its denied lane narrowed away — sugar for
239/// [`LaneProfile::WithoutDenied`], which is what `narrow_denied` returns
240/// through. Same caveat as [`WithoutTransient`]: prefer the concrete profile
241/// name wherever one is available.
242pub type WithoutDenied<L> = <L as LaneProfile>::WithoutDenied;
243
244/// Select a subset of `Denied`, `Transient`, and `Fatal`, in any order.
245#[macro_export]
246macro_rules! lanes {
247    ($($lane:ident),* $(,)?) => { $crate::lanes!(@acc [false, false, false] $($lane,)*) };
248
249    (@acc [$d:expr, $t:expr, $f:expr]) => { $crate::profile::Profile<{ $d }, { $t }, { $f }> };
250    (@acc [$d:expr, $t:expr, $f:expr] Denied, $($rest:ident,)*) => {
251        $crate::lanes!(@acc [true, $t, $f] $($rest,)*)
252    };
253    (@acc [$d:expr, $t:expr, $f:expr] Transient, $($rest:ident,)*) => {
254        $crate::lanes!(@acc [$d, true, $f] $($rest,)*)
255    };
256    (@acc [$d:expr, $t:expr, $f:expr] Fatal, $($rest:ident,)*) => {
257        $crate::lanes!(@acc [$d, $t, true] $($rest,)*)
258    };
259    // Both diagnostics expand to a well-formed `Profile`, so the only error
260    // reported is the message itself -- no follow-on `(): LaneProfile`.
261    (@acc [$d:expr, $t:expr, $f:expr] Rejected, $($rest:ident,)*) => {
262        $crate::profile::Profile<{{
263            compile_error!(
264                "`Rejected` is not a fault lane: it is selected by the carrier, not the \
265                 profile. Use `Fail<YourRejection, lanes!(..)>` instead of \
266                 `Fault<lanes!(Rejected, ..)>`."
267            );
268            $d
269        }}, { $t }, { $f }>
270    };
271    (@acc [$d:expr, $t:expr, $f:expr] $other:ident, $($rest:ident,)*) => {
272        $crate::profile::Profile<{{
273            compile_error!(concat!(
274                "unknown lane `",
275                stringify!($other),
276                "`: expected `Denied`, `Transient`, or `Fatal`"
277            ));
278            $d
279        }}, { $t }, { $f }>
280    };
281}