Skip to main content

ResultExt

Trait ResultExt 

Source
pub trait ResultExt<T, E>: Sized {
    // Required methods
    fn lift<P>(
        self,
    ) -> Result<T, Fail<P, <<E as IntoLanes>::Lanes as LaneProfile>::WithFatal>>
       where E: IntoLanes,
             <E as IntoLanes>::Rejected: Rejection,
             P: Rejection + Lift<<E as IntoLanes>::Rejected>,
             <<E as IntoLanes>::Lanes as LaneProfile>::Fatal: Into<Fatal>,
             <P as Lift<<E as IntoLanes>::Rejected>>::Unmapped: UnmappedInto<Fatal>;
    fn narrow_rejected(self) -> Result<T, <E as NarrowRejectedLane>::Narrowed>
       where E: NarrowRejectedLane;
    fn narrow_transient(
        self,
        attempts: u32,
    ) -> Result<T, <E as Laned>::WithoutTransient>
       where E: Laned;
    fn narrow_denied(self) -> Result<T, <E as NarrowDeniedLane>::Narrowed>
       where E: NarrowDeniedLane;
    fn rejected(
        self,
    ) -> Result<Result<T, <E as IntoLanes>::Rejected>, Fault<<E as IntoLanes>::Lanes>>
       where E: IntoLanes,
             <E as IntoLanes>::Rejected: Rejection;
    fn narrow_rejected_with(
        self,
        on_rejected: impl FnOnce(<E as IntoLanes>::Rejected) -> Result<T, <E as IntoLanes>::Rejected>,
    ) -> Result<T, Fault<<E as IntoLanes>::Lanes>>
       where E: IntoLanes,
             <E as IntoLanes>::Rejected: Rejection,
             <E as IntoLanes>::Lanes: LaneProfile<Fatal = Fatal>;
    fn map_rejected<D2>(
        self,
        f: impl FnOnce(<E as IntoLanes>::Rejected) -> D2,
    ) -> Result<T, Fail<D2, <E as IntoLanes>::Lanes>>
       where E: IntoLanes,
             <E as IntoLanes>::Rejected: Rejection;
    fn into_fault(self) -> Result<T, Fault<<E as IntoLanes>::Lanes>>
       where E: FaultSource;
    fn into_fail(
        self,
    ) -> Result<T, Fail<<E as IntoLanes>::Rejected, <E as IntoLanes>::Lanes>>
       where E: FailSource;
    fn classify<W>(self) -> Result<T, W>
       where W: Classify + From<E>;
}
Expand description

The one trait a consumer imports to move a Result between error signatures: lift its rejection, narrow away a lane that is no longer live at this boundary, hand the rejection to the caller as a value, classify a foreign error into a local wrapper, or (under tracing) record it onto the current span.

Required Methods§

Source

fn lift<P>( self, ) -> Result<T, Fail<P, <<E as IntoLanes>::Lanes as LaneProfile>::WithFatal>>
where E: IntoLanes, <E as IntoLanes>::Rejected: Rejection, P: Rejection + Lift<<E as IntoLanes>::Rejected>, <<E as IntoLanes>::Lanes as LaneProfile>::Fatal: Into<Fatal>, <P as Lift<<E as IntoLanes>::Rejected>>::Unmapped: UnmappedInto<Fatal>,

Lift a rejection into a new vocabulary and enable Fatal for any unmapped case. ? subsequently expands the resulting lanes.

Source

fn narrow_rejected(self) -> Result<T, <E as NarrowRejectedLane>::Narrowed>
where E: NarrowRejectedLane,

Narrows away the Rejected lane: a rejection with no caller left to correct it becomes Fatal(Invariant), carrying the rejection as its source. On a Fail<D, L> the result is Fault<L>. On a bare Rejection — a public method that returns just R because its caller can act on it, consumed by an internal frame that already proved the precondition — the result is the bare Fatal, and ? carries it into whatever Fault/Fail the function returns, so the destination profile is never named at the call site:

use errlanes::{Fault, ResultExt, lanes};

#[derive(Debug, errlanes::Rejection)]
#[rejection(code = "LANE_DISABLED")]
struct LaneDisabled;

fn listen() -> Result<(), LaneDisabled> {
    Err(LaneDisabled)
}

// The lane was required at registration: by now, off is an invariant.
fn run() -> Result<(), Fault<lanes!(Transient, Fatal)>> {
    listen().narrow_rejected()?;
    Ok(())
}

assert!(matches!(run(), Err(Fault::Fatal(_))));

A Fault has no rejected lane to narrow, and that is a compile error, not an identity:

ⓘ
use errlanes::{Fault, ResultExt, lanes};
fn narrow(value: Result<(), Fault<lanes!(Fatal)>>)
    -> Result<(), Fault<lanes!(Fatal)>>
{
    value.narrow_rejected()
}
Source

fn narrow_transient( self, attempts: u32, ) -> Result<T, <E as Laned>::WithoutTransient>
where E: Laned,

Narrows away the Transient lane: a caller-owned retry loop hands back the narrowed profile once it stops retrying, so it stops offering Transient to its own callers. attempts is what the loop counted; an exhausted transient becomes Fatal(Exhausted) with the last transient as its source.

Source

fn narrow_denied(self) -> Result<T, <E as NarrowDeniedLane>::Narrowed>
where E: NarrowDeniedLane,

Narrows away the Denied lane: a denial at a boundary with no subject (code running as the system) becomes Fatal(Denied). Only available where E’s profile enables Denied:

ⓘ
use errlanes::{Fault, ResultExt, lanes};
fn narrow(value: Result<(), Fault<lanes!(Transient, Fatal)>>)
    -> Result<(), Fault<lanes!(Transient, Fatal)>>
{
    value.narrow_denied()
}
Source

fn rejected( self, ) -> Result<Result<T, <E as IntoLanes>::Rejected>, Fault<<E as IntoLanes>::Lanes>>
where E: IntoLanes, <E as IntoLanes>::Rejected: Rejection,

Hands the rejection to the caller as a value and keeps the faults propagating: Result<T, Fail<D, L>> becomes Result<Result<T, D>, Fault<L>>, the Result-level form of Fail::rejected. The outer ? carries the faults on into any enclosing carrier; the inner Result is the domain outcome, with the rejection as its Err, matched right where it occurred:

use errlanes::{Fail, Fault, ResultExt, lanes};

#[derive(Debug, errlanes::Rejection)]
#[rejection(code = "TIMED_OUT")]
struct TimedOut;

fn await_completion() -> Result<u64, Fail<TimedOut, lanes!(Transient, Fatal)>> {
    Err(Fail::Rejected(TimedOut))
}

fn poll_once() -> Result<Option<u64>, Fault<lanes!(Transient, Fatal)>> {
    match await_completion().rejected()? {
        Ok(outcome) => Ok(Some(outcome)),
        Err(TimedOut) => Ok(None),
    }
}

assert!(poll_once().unwrap().is_none());

This is the dual of narrow_rejected: that one is for a boundary with no caller left to correct the rejection, this one for the call site that is going to. There is deliberately no Option-returning accessor on a Result: an as_rejected() that answered None for both Ok and a fault would be the one place a lane could be dropped without naming it. Only available where E carries a rejected lane; a Fault has none:

ⓘ
use errlanes::{Fault, ResultExt, lanes};
fn split(value: Result<(), Fault<lanes!(Fatal)>>) {
    let _ = value.rejected();
}
Source

fn narrow_rejected_with( self, on_rejected: impl FnOnce(<E as IntoLanes>::Rejected) -> Result<T, <E as IntoLanes>::Rejected>, ) -> Result<T, Fault<<E as IntoLanes>::Lanes>>
where E: IntoLanes, <E as IntoLanes>::Rejected: Rejection, <E as IntoLanes>::Lanes: LaneProfile<Fatal = Fatal>,

Narrows away the Rejected lane after the closure has had first refusal: on_rejected receives the rejection by value and either recovers with the success value (Ok) or hands back the member it does not handle (Err), which is then narrowed exactly as narrow_rejected would — Fatal(Invariant) with the rejection as an opaque source. Faults already on the result pass through untouched.

The closure’s match is the exhaustiveness proof: when every member is recovered there is nothing left to narrow, and a member added later is a compile error in the closure instead of a silent Fatal(Invariant):

use errlanes::{Fail, Fault, ResultExt, lanes};

#[derive(Debug, errlanes::Rejection)]
#[rejection(code = "ALREADY_ADDED")]
struct AlreadyAdded;

fn add() -> Result<u32, Fail<AlreadyAdded, lanes!(Transient, Fatal)>> {
    Err(Fail::Rejected(AlreadyAdded))
}

fn ensure_added() -> Result<(), Fault<lanes!(Transient, Fatal)>> {
    add().map(|_| ()).narrow_rejected_with(|AlreadyAdded| Ok(()))
}

assert!(ensure_added().is_ok());

Where some members are invariants, the closure hands them back:

use errlanes::{Fail, Fault, ResultExt, lanes};

#[derive(Debug, errlanes::Rejection)]
enum Outcome {
    Missing,
    Corrupt,
}

fn read(value: Result<u32, Fail<Outcome, lanes!(Fatal)>>) -> Result<u32, Fault<lanes!(Fatal)>> {
    value.narrow_rejected_with(|r| match r {
        Outcome::Missing => Ok(0),
        Outcome::Corrupt => Err(r),
    })
}

assert_eq!(read(Err(Fail::Rejected(Outcome::Missing))).unwrap(), 0);
assert!(matches!(read(Err(Fail::Rejected(Outcome::Corrupt))), Err(Fault::Fatal(_))));
Source

fn map_rejected<D2>( self, f: impl FnOnce(<E as IntoLanes>::Rejected) -> D2, ) -> Result<T, Fail<D2, <E as IntoLanes>::Lanes>>
where E: IntoLanes, <E as IntoLanes>::Rejected: Rejection,

Maps the rejection with a closure and leaves every other lane as it is: the Result-level form of Fail::map_rejected. For a type-level remap use lift; this is for enriching a rejection with data only the call site has, such as the input that was attempted: repo.create(new).await.map_rejected(|r| r.with_attempted(id))?.

Source

fn into_fault(self) -> Result<T, Fault<<E as IntoLanes>::Lanes>>
where E: FaultSource,

Convert a never-rejecting source to its own Fault at a box or carrier boundary.

Source

fn into_fail( self, ) -> Result<T, Fail<<E as IntoLanes>::Rejected, <E as IntoLanes>::Lanes>>
where E: FailSource,

Convert a rejecting source to its own Fail at a foreign carrier boundary.

Source

fn classify<W>(self) -> Result<T, W>
where W: Classify + From<E>,

.classify::<W>() — the verb that turns a foreign error into a local Classify wrapper at a one-off call site, so a function that does not itself return W can still enter the lanes through it: conn.query(..).await.classify::<DbWrite>()?.

Dyn Compatibility§

This trait is not dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementations on Foreign Types§

Source§

impl<T, E> ResultExt<T, E> for Result<T, E>

Source§

fn lift<P>( self, ) -> Result<T, Fail<P, <<E as IntoLanes>::Lanes as LaneProfile>::WithFatal>>
where E: IntoLanes, <E as IntoLanes>::Rejected: Rejection, P: Rejection + Lift<<E as IntoLanes>::Rejected>, <<E as IntoLanes>::Lanes as LaneProfile>::Fatal: Into<Fatal>, <P as Lift<<E as IntoLanes>::Rejected>>::Unmapped: UnmappedInto<Fatal>,

Source§

fn narrow_rejected(self) -> Result<T, <E as NarrowRejectedLane>::Narrowed>
where E: NarrowRejectedLane,

Source§

fn narrow_transient( self, attempts: u32, ) -> Result<T, <E as Laned>::WithoutTransient>
where E: Laned,

Source§

fn narrow_denied(self) -> Result<T, <E as NarrowDeniedLane>::Narrowed>
where E: NarrowDeniedLane,

Source§

fn rejected( self, ) -> Result<Result<T, <E as IntoLanes>::Rejected>, Fault<<E as IntoLanes>::Lanes>>
where E: IntoLanes, <E as IntoLanes>::Rejected: Rejection,

Source§

fn narrow_rejected_with( self, on_rejected: impl FnOnce(<E as IntoLanes>::Rejected) -> Result<T, <E as IntoLanes>::Rejected>, ) -> Result<T, Fault<<E as IntoLanes>::Lanes>>
where E: IntoLanes, <E as IntoLanes>::Rejected: Rejection, <E as IntoLanes>::Lanes: LaneProfile<Fatal = Fatal>,

Source§

fn map_rejected<D2>( self, f: impl FnOnce(<E as IntoLanes>::Rejected) -> D2, ) -> Result<T, Fail<D2, <E as IntoLanes>::Lanes>>
where E: IntoLanes, <E as IntoLanes>::Rejected: Rejection,

Source§

fn into_fault(self) -> Result<T, Fault<<E as IntoLanes>::Lanes>>
where E: FaultSource,

Source§

fn into_fail( self, ) -> Result<T, Fail<<E as IntoLanes>::Rejected, <E as IntoLanes>::Lanes>>
where E: FailSource,

Source§

fn classify<W>(self) -> Result<T, W>
where W: Classify + From<E>,

Implementors§