Skip to main content

errlanes/
carrier.rs

1//! How values enter the two carriers, [`Fault<L>`] and [`Fail<R, L>`]: the
2//! [`IntoLanes`] trait, the blanket `?` conversions keyed on its `Shape` tag,
3//! and the borrowed [`LaneRef`] view. Everything in this module except
4//! [`LaneRef`] is hidden plumbing for errlanes' own blanket impls.
5
6use std::{
7    convert::Infallible,
8    fmt::{self, Debug},
9};
10
11use crate::{
12    classify::Classify,
13    fail::{Fail, Fault, Level, Lift, Rejection},
14    lane::{Denied, Exhausted, Fatal, FatalKind, Lane, Transient},
15    profile::{LaneProfile, NoLanes, Profile},
16};
17
18/// Type-level tags carried by [`IntoLanes`]. Hidden: only the macros and
19/// errlanes' own blankets name them.
20#[doc(hidden)]
21pub mod kind {
22    /// `Shape`: what errlanes' own blankets key on, through the sealed marker
23    /// traits below. Anything [`Classify`](crate::Classify).
24    pub struct Source;
25    /// `Transient`, `Fatal`, `Denied`, `Exhausted`, `Infallible`.
26    pub struct Payload;
27    /// [`Fault<L>`](crate::Fault).
28    pub struct Fault;
29    /// [`Fail<D, L>`](crate::Fail).
30    pub struct Fail;
31
32    /// Shapes `?` may carry into a `Fault<M>`.
33    pub trait IntoFault {}
34    impl IntoFault for Source {}
35    impl IntoFault for Payload {}
36
37    /// Shapes `?` may carry into a `Fail<D, M>`.
38    pub trait IntoFail {}
39    impl IntoFail for Source {}
40    impl IntoFail for Payload {}
41    impl IntoFail for Fault {}
42
43    /// Shapes `?` may carry into the bare `Fatal` / `Transient` payloads.
44    pub trait IntoBareLane {}
45    impl IntoBareLane for Source {}
46}
47
48/// Every value that can enter the lanes. Hidden: written by errlanes, never
49/// by hand.
50///
51/// [`Classify`] itself is unchanged; this is the one trait errlanes' blankets
52/// key on.
53#[doc(hidden)]
54#[diagnostic::on_unimplemented(
55    message = "`{Self}` does not enter the lanes",
56    note = "implement `errlanes::Classify` (or derive `Rejection` / `Classify`)"
57)]
58pub trait IntoLanes: Sized {
59    type Shape;
60    type Rejected;
61    type Lanes: LaneProfile;
62    fn into_lanes(self) -> Fail<Self::Rejected, Self::Lanes>;
63}
64
65impl<W: Classify> IntoLanes for W {
66    type Shape = kind::Source;
67    type Rejected = W::Rejected;
68    type Lanes = W::Lanes;
69    fn into_lanes(self) -> Fail<Self::Rejected, Self::Lanes> {
70        self.classify()
71    }
72}
73
74impl<L: LaneProfile> IntoLanes for Fault<L> {
75    type Shape = kind::Fault;
76    type Rejected = Infallible;
77    type Lanes = L;
78    fn into_lanes(self) -> Fail<Infallible, L> {
79        match self {
80            Fault::Denied(d) => Fail::Denied(d),
81            Fault::Transient(t) => Fail::Transient(t),
82            Fault::Fatal(f) => Fail::Fatal(f),
83        }
84    }
85}
86
87impl<D, L: LaneProfile> IntoLanes for Fail<D, L> {
88    type Shape = kind::Fail;
89    type Rejected = D;
90    type Lanes = L;
91    fn into_lanes(self) -> Fail<D, L> {
92        self
93    }
94}
95
96macro_rules! payload {
97    ($ty:ty, $d:literal, $t:literal, $f:literal, $variant:ident) => {
98        impl IntoLanes for $ty {
99            type Shape = kind::Payload;
100            type Rejected = Infallible;
101            type Lanes = Profile<$d, $t, $f>;
102            fn into_lanes(self) -> Fail<Infallible, Self::Lanes> {
103                Fail::$variant(self)
104            }
105        }
106    };
107}
108payload!(Transient, false, true, false, Transient);
109payload!(Fatal, false, false, true, Fatal);
110payload!(Denied, true, false, false, Denied);
111
112impl IntoLanes for Exhausted {
113    type Shape = kind::Payload;
114    type Rejected = Infallible;
115    type Lanes = Profile<false, false, true>;
116    fn into_lanes(self) -> Fail<Infallible, Self::Lanes> {
117        Fail::Fatal(Fatal::from_error(FatalKind::Exhausted, self))
118    }
119}
120
121impl IntoLanes for Infallible {
122    type Shape = kind::Payload;
123    type Rejected = Infallible;
124    type Lanes = NoLanes;
125    fn into_lanes(self) -> Fail<Infallible, NoLanes> {
126        match self {}
127    }
128}
129
130/// A fault-only value, as a `Fault<M>`. The shared body of every `?` into a
131/// `Fault`; the lane-subset bounds are what make it total.
132pub(crate) fn fail_into_fault<L: LaneProfile, M: LaneProfile>(f: Fail<Infallible, L>) -> Fault<M>
133where
134    L::Denied: Into<M::Denied>,
135    L::Transient: Into<M::Transient>,
136    L::Fatal: Into<M::Fatal>,
137{
138    match f {
139        Fail::Rejected(never) => match never {},
140        Fail::Denied(d) => Fault::Denied(d.into()),
141        Fail::Transient(t) => Fault::Transient(t.into()),
142        Fail::Fatal(x) => Fault::Fatal(x.into()),
143    }
144}
145
146/// `?` into a [`Fault`]: only for a source that never rejects, and only into a
147/// profile that holds all of the source's lanes. A rejection, or a mixed
148/// wrapper, entering a `Fault` function stays an explicit narrowing
149/// (`narrow_rejected`), never a silent `?`.
150///
151/// Why this is coherent with the reflexive `From<T> for T`: `Fault<M>`'s own
152/// `Shape` is `kind::Fault`, which is not [`kind::IntoFault`], and every type
153/// and marker involved is local to errlanes. Do not replace the marker bound
154/// with associated-type *equality* across several impls on a generic `W`: two
155/// such impls overlap (rust-lang/rust#20400).
156impl<W: IntoLanes<Rejected = Infallible>, M: LaneProfile> From<W> for Fault<M>
157where
158    W::Shape: kind::IntoFault,
159    <W::Lanes as LaneProfile>::Denied: Into<M::Denied>,
160    <W::Lanes as LaneProfile>::Transient: Into<M::Transient>,
161    <W::Lanes as LaneProfile>::Fatal: Into<M::Fatal>,
162{
163    fn from(w: W) -> Self {
164        fail_into_fault(w.into_lanes())
165    }
166}
167
168/// `?` into a [`Fail`]: the rejected part must be absorbed totally by `D`;
169/// a partial conversion needs `.lift()?`. The source's
170/// lanes must fit the destination's profile.
171impl<W: IntoLanes, D: Lift<W::Rejected, Unmapped = Infallible>, M: LaneProfile> From<W>
172    for Fail<D, M>
173where
174    W::Shape: kind::IntoFail,
175    <W::Lanes as LaneProfile>::Denied: Into<M::Denied>,
176    <W::Lanes as LaneProfile>::Transient: Into<M::Transient>,
177    <W::Lanes as LaneProfile>::Fatal: Into<M::Fatal>,
178{
179    fn from(w: W) -> Self {
180        fail_absorb(w.into_lanes())
181    }
182}
183
184fn fail_absorb<R, L: LaneProfile, D: Lift<R, Unmapped = Infallible>, M: LaneProfile>(
185    f: Fail<R, L>,
186) -> Fail<D, M>
187where
188    L::Denied: Into<M::Denied>,
189    L::Transient: Into<M::Transient>,
190    L::Fatal: Into<M::Fatal>,
191{
192    match f {
193        Fail::Rejected(r) => match D::lift(r) {
194            Ok(d) => Fail::Rejected(d),
195            Err(never) => match never {},
196        },
197        Fail::Denied(d) => Fail::Denied(d.into()),
198        Fail::Transient(t) => Fail::Transient(t.into()),
199        Fail::Fatal(x) => Fail::Fatal(x.into()),
200    }
201}
202
203/// Strict profile inclusions are explicit: a generic `From<Fault<L>> for
204/// Fault<M>` overlaps the standard reflexive `From<T> for T` when L = M.
205/// Rust cannot prove that a generic inclusion excludes equality.
206macro_rules! profile_inclusion {
207    (($sd:literal,$st:literal,$sf:literal) => ($dd:literal,$dt:literal,$df:literal)) => {
208        impl From<Fault<Profile<$sd, $st, $sf>>> for Fault<Profile<$dd, $dt, $df>> {
209            #[allow(unreachable_code)]
210            fn from(value: Fault<Profile<$sd, $st, $sf>>) -> Self {
211                match value {
212                    Fault::Denied(v) => Fault::Denied(v.into()),
213                    Fault::Transient(v) => Fault::Transient(v.into()),
214                    Fault::Fatal(v) => Fault::Fatal(v.into()),
215                }
216            }
217        }
218        impl<R> From<Fail<R, Profile<$sd, $st, $sf>>> for Fail<R, Profile<$dd, $dt, $df>> {
219            #[allow(unreachable_code)]
220            fn from(value: Fail<R, Profile<$sd, $st, $sf>>) -> Self {
221                match value {
222                    Fail::Rejected(v) => Fail::Rejected(v),
223                    Fail::Denied(v) => Fail::Denied(v.into()),
224                    Fail::Transient(v) => Fail::Transient(v.into()),
225                    Fail::Fatal(v) => Fail::Fatal(v.into()),
226                }
227            }
228        }
229    };
230}
231profile_inclusion!((false,false,false) => (true,false,false));
232profile_inclusion!((false,false,false) => (false,true,false));
233profile_inclusion!((false,false,false) => (false,false,true));
234profile_inclusion!((false,false,false) => (true,true,false));
235profile_inclusion!((false,false,false) => (true,false,true));
236profile_inclusion!((false,false,false) => (false,true,true));
237profile_inclusion!((false,false,false) => (true,true,true));
238profile_inclusion!((true,false,false) => (true,true,false));
239profile_inclusion!((true,false,false) => (true,false,true));
240profile_inclusion!((true,false,false) => (true,true,true));
241profile_inclusion!((false,true,false) => (true,true,false));
242profile_inclusion!((false,true,false) => (false,true,true));
243profile_inclusion!((false,true,false) => (true,true,true));
244profile_inclusion!((false,false,true) => (true,false,true));
245profile_inclusion!((false,false,true) => (false,true,true));
246profile_inclusion!((false,false,true) => (true,true,true));
247profile_inclusion!((true,true,false) => (true,true,true));
248profile_inclusion!((true,false,true) => (true,true,true));
249profile_inclusion!((false,true,true) => (true,true,true));
250
251/// A source that only ever ends in the fatal lane converts into that bare
252/// payload directly, so `-> Result<T, Fatal>` is a legal, narrowest signature
253/// for a fault-only wrapper.
254impl<W: IntoLanes<Rejected = Infallible, Lanes = Profile<false, false, true>>> From<W> for Fatal
255where
256    W::Shape: kind::IntoBareLane,
257{
258    fn from(w: W) -> Self {
259        match w.into_lanes() {
260            Fail::Rejected(never) => match never {},
261            Fail::Denied(never) => match never {},
262            Fail::Transient(never) => match never {},
263            Fail::Fatal(x) => x,
264        }
265    }
266}
267
268/// As above, for a source whose only lane is transient.
269impl<W: IntoLanes<Rejected = Infallible, Lanes = Profile<false, true, false>>> From<W> for Transient
270where
271    W::Shape: kind::IntoBareLane,
272{
273    fn from(w: W) -> Self {
274        match w.into_lanes() {
275            Fail::Rejected(never) => match never {},
276            Fail::Denied(never) => match never {},
277            Fail::Transient(t) => t,
278            Fail::Fatal(never) => match never {},
279        }
280    }
281}
282
283/// A borrowed view of a carrier's current lane. `Fault::lanes` and
284/// `Fail::lanes` both return one, so `record`, `message` and `lane` have
285/// a single implementation over it.
286///
287/// `R` is the rejection type; it is [`Infallible`] for a fault-only value,
288/// which makes the `Rejected` arm unreachable.
289pub enum LaneRef<'a, R> {
290    Rejected(&'a R),
291    Denied(&'a Denied),
292    Transient(&'a Transient),
293    Fatal(&'a Fatal),
294}
295
296impl<R> Clone for LaneRef<'_, R> {
297    fn clone(&self) -> Self {
298        *self
299    }
300}
301
302impl<R> Copy for LaneRef<'_, R> {}
303
304impl<R: Debug> Debug for LaneRef<'_, R> {
305    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
306        match self {
307            LaneRef::Rejected(r) => f.debug_tuple("Rejected").field(r).finish(),
308            LaneRef::Denied(d) => f.debug_tuple("Denied").field(d).finish(),
309            LaneRef::Transient(t) => f.debug_tuple("Transient").field(t).finish(),
310            LaneRef::Fatal(x) => f.debug_tuple("Fatal").field(x).finish(),
311        }
312    }
313}
314
315impl<R> LaneRef<'_, R> {
316    /// Which lane this is.
317    pub fn lane(&self) -> Lane {
318        match self {
319            LaneRef::Rejected(_) => Lane::Rejected,
320            LaneRef::Denied(_) => Lane::Denied,
321            LaneRef::Transient(_) => Lane::Transient,
322            LaneRef::Fatal(_) => Lane::Fatal,
323        }
324    }
325}
326
327impl<R: LaneRejected> LaneRef<'_, R> {
328    /// The operator-safe one-line text for this failure: exactly what `record`
329    /// writes to `exception.message`. `Transient` / `Fatal`: the whole
330    /// `source()` chain joined with `": "`. `Denied`: its `Display`.
331    /// `Rejected`: the rejection's `code`, never its message (display
332    /// discipline — a rejection's message may embed caller-supplied input).
333    pub fn message(&self) -> String {
334        match self {
335            LaneRef::Rejected(d) => d.code_string(),
336            LaneRef::Denied(d) => d.to_string(),
337            LaneRef::Transient(t) => crate::dynamic::message_chain(*t),
338            LaneRef::Fatal(x) => crate::dynamic::message_chain(*x),
339        }
340    }
341
342    /// The operator level of this failure: the lane default
343    /// ([`Lane::level`]), except a `Rejected` outcome reports what its
344    /// rejection declares through [`Rejection::level`].
345    pub fn level(&self) -> Level {
346        match self {
347            LaneRef::Rejected(d) => d.level(),
348            other => other.lane().level(),
349        }
350    }
351
352    /// Records this lane onto `span`. See [`crate::FIELDS`].
353    #[cfg(feature = "tracing")]
354    pub(crate) fn record(&self, span: &tracing::Span) {
355        crate::record::record_lanes(span, *self);
356    }
357
358    /// Emits this lane as an event at its own level. See [`crate::emit`].
359    #[cfg(feature = "tracing")]
360    pub(crate) fn emit(&self) {
361        crate::record::emit_lanes(*self);
362    }
363}
364
365/// What the `Rejected` arm of a [`LaneRef`] needs from its `R`: a code, and a
366/// level. A [`Rejection`], or `Infallible` (no arm). Hidden: it exists so that
367/// one `message` / `record` body serves `Fault` and `Fail` alike.
368#[doc(hidden)]
369pub trait LaneRejected {
370    fn code_string(&self) -> String;
371    fn code_str(&self) -> &'static str;
372    fn level(&self) -> Level;
373}
374
375impl<R: Rejection> LaneRejected for R {
376    fn code_string(&self) -> String {
377        self.code().to_string()
378    }
379    fn code_str(&self) -> &'static str {
380        Into::<&'static str>::into(self.code())
381    }
382    fn level(&self) -> Level {
383        Rejection::level(self)
384    }
385}
386
387impl LaneRejected for Infallible {
388    fn code_string(&self) -> String {
389        match *self {}
390    }
391    fn code_str(&self) -> &'static str {
392        match *self {}
393    }
394    fn level(&self) -> Level {
395        match *self {}
396    }
397}
398
399impl<L: LaneProfile> Fault<L> {
400    /// A borrowed view of the current lane.
401    pub fn lanes(&self) -> LaneRef<'_, Infallible> {
402        use crate::profile::Slot;
403        match self {
404            Fault::Denied(d) => LaneRef::Denied(d.marker()),
405            Fault::Transient(t) => LaneRef::Transient(t.marker()),
406            Fault::Fatal(x) => LaneRef::Fatal(x.marker()),
407        }
408    }
409}
410
411impl<D, L: LaneProfile> Fail<D, L> {
412    /// A borrowed view of the current lane.
413    pub fn lanes(&self) -> LaneRef<'_, D> {
414        use crate::profile::Slot;
415        match self {
416            Fail::Rejected(d) => LaneRef::Rejected(d),
417            Fail::Denied(d) => LaneRef::Denied(d.marker()),
418            Fail::Transient(t) => LaneRef::Transient(t.marker()),
419            Fail::Fatal(x) => LaneRef::Fatal(x.marker()),
420        }
421    }
422}