tiered_result/
lib.rs

1//! This crate is an alternate strategy for error handling for libraries.
2//!
3//! Public functions that might want to keep trying in case of error, or report
4//! multiple errors can take an additional parameter `&mut dyn ErrorHandler`. When
5//! they encounter an error that they can sort-of recover from, they can invoke the
6//! error handler (which calls back into the library user's code) and the error
7//! handler gets to decide if the function should keep going or stop early.
8//!
9//! When the error handler says to return early, this is considered a fatal error
10//! and all further processing and recovery should stop, and the library should
11//! return control to the caller as quickly as possibly.
12//!
13//! If the error handler says to continue but the error does not allow the
14//! calculation to finish, it should return null. This leads to the four variants in
15//! a [`TieredResult`]: [`Ok`], [`Err`], [`Null`], and [`Fatal`].
16//!
17//! Your library code might look something like this:
18//! ```rust
19//! use tiered_result::TieredResult;
20//! use std::fmt::{Display, Formatter};
21//! pub use tiered_result::{ErrorHandler, ClientResponse};
22//! use nullable_result::NullableResult;
23//!
24//! pub fn public_fn(handler: &mut dyn ErrorHandler<Error, Fatality>) -> Option<i64> {
25//!     match private_fn(handler) {
26//!         TieredResult::Ok(n) => Some(i64::from(n)),
27//!         TieredResult::Err(_) => Some(-1),
28//!         TieredResult::Null => None,
29//!         TieredResult::Fatal(_) => Some(-2)
30//!     }
31//! }
32//!
33//! fn private_fn(handler: &mut dyn ErrorHandler<Error, Fatality>) -> TieredResult<u32, Error, Fatality> {//!
34//!     let n = another_private_fn(handler)?; // <-- this `?` bubbles up the fatal error
35//!                                           //     leaving a nullable result behind
36//!     let n = n?; // <-- this `?` bubbles up the null/err.
37//!     // the previous two lines could be written as let n = another_private_fn(handler)??;
38//!
39//!     if n == 42 {
40//!         handler.handle_error(Error("we don't like 42".to_owned()))?;
41//!     }
42//!     TieredResult::Ok(n + 5)
43//! }
44//!
45//! fn another_private_fn(handler: &mut dyn ErrorHandler<Error, Fatality>) -> TieredResult<u32, Error, Fatality> {
46//!     // --snip--
47//!     # TieredResult::Ok(2)
48//! }
49//!
50//! // a struct to represent fatal errors, can carry additional info if you need it to
51//! struct Fatality;
52//!
53//! #[derive(Clone, Debug)]
54//! struct Error(String); // any old error type
55//!
56//! impl std::error::Error for Error {}
57//! impl Display for Error {
58//!     fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
59//!        write!(f, "There was an error: {}", self.0)
60//!     }
61//! }
62//! ```
63//!
64//! The calling application might look like this:
65//! ```rust
66//! # mod the_lib_from_before {
67//! #     pub use tiered_result::{ErrorHandler, ClientResponse};
68//! #     pub struct Error;
69//! #     pub struct Fatality;
70//! #     pub fn public_fn(handler: &mut dyn ErrorHandler<Error, Fatality>) -> Option<i64> {
71//! #         Some(5)
72//! #     }
73//! # }
74//! use the_lib_from_before::*;
75//!
76//! #[derive(Default)]
77//! struct Handler(u8);
78//!
79//! impl ErrorHandler<Error, Fatality> for Handler {
80//!     fn handle_error(&mut self, error: Error) -> ClientResponse<Fatality, ()> {
81//!         if self.0 > 2 { // allow two errors before giving up
82//!             ClientResponse::Throw(Fatality)
83//!         } else {
84//!             ClientResponse::Continue(())
85//!         }
86//!     }
87//! }
88//!
89//! let mut handler = Handler::default();
90//! println!("{:?}", public_fn(&mut handler));
91//! ```
92#![cfg_attr(not(feature = "std"), no_std)]
93#![feature(try_trait_v2)]
94#![warn(missing_docs)]
95
96mod try_trait;
97
98use self::TieredResult::*;
99use core::{convert::Infallible, fmt::Debug, ops::Deref};
100use nullable_result::NullableResult;
101
102/// This trait should be part of your public API so that your users can implement
103/// it for their own types.
104pub trait ErrorHandler<E, F, C = ()> {
105    /// Take an error and decide if the library should continue without the result
106    /// that was supposed to have been produced.
107    fn handle_error(&mut self, error: E) -> ClientResponse<F, C>;
108}
109
110/// A result type to be used internally by your library.
111#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
112#[must_use]
113pub enum TieredResult<T, E, F> {
114    /// Equivalent to [`Result::Ok`] or [`NullableResult::Ok`]
115    Ok(T),
116
117    /// Equivalent to [`Result::Err`] or [`NullableResult::Err`]
118    Err(E),
119
120    /// Equivalent to [`NullableResult::Null`]
121    Null,
122
123    /// This variant will be produced by the `?` operator a a [`ClientResponse`].
124    /// It can be used to easily bubble up to your API boundary, then converted so
125    /// something your users can handle.
126    Fatal(F),
127}
128
129/// A result type to be used internally by your library.
130///
131/// This can be used when one of your functions doesn't produce any errors of its
132/// own, but might need to bubble up a fatal error.
133#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
134#[must_use]
135pub enum FatalResult<T, F> {
136    /// See [`TieredResult::Ok`]
137    Ok(T),
138
139    /// See [`TieredResult::Null`]
140    Null,
141
142    /// See [`TieredResult::Fatal`]
143    Fatal(F),
144}
145
146/// A result type to be used internally by your library.
147///
148/// This can be used when one of your functions doesn't do any fallible operations
149/// directly, but still needs to bubble a fatal error up from its callees.
150#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
151#[must_use]
152pub enum FatalOrOk<T, F> {
153    /// See [`TieredResult::Ok`]
154    Ok(T),
155
156    /// See [`TieredResult::Fatal`]
157    Fatal(F),
158}
159
160/// The return type of [`ErrorHandler::handle_error`]. This type (or a suitable alias)
161/// should be part of your library's public interface. Additionally you may want to
162/// provide methods or constants for your users' convenience.
163#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
164#[must_use]
165pub enum ClientResponse<F, C = ()> {
166    /// Stop processing
167    Throw(F),
168
169    /// Try to continue
170    Continue(C),
171}
172
173impl<T, E, F> Default for TieredResult<T, E, F> {
174    fn default() -> Self {
175        Null
176    }
177}
178
179impl<T, E: Debug, F> TieredResult<T, E, F> {
180    /// Panics if it's not `Ok`, otherwise returns the contained value.
181    #[inline]
182    #[track_caller]
183    pub fn unwrap(self) -> T {
184        match self {
185            Ok(item) => item,
186            Err(err) => panic!(
187                "tried to unwrap a tiered result containing Err: {:?}",
188                err
189            ),
190            Null => {
191                panic!("tried to unwrap a tiered result containing `Null`")
192            }
193            Fatal(_) => {
194                panic!("tried to unwrap a tiered result containing `Fatal`")
195            }
196        }
197    }
198}
199
200impl<T: Default, E, F> TieredResult<T, E, F> {
201    /// Returns the contained value if it's `Ok`, otherwise returns the default value
202    /// for that type.
203    #[inline]
204    pub fn unwrap_or_default(self) -> T {
205        match self {
206            Ok(item) => item,
207            _ => T::default(),
208        }
209    }
210}
211
212impl<T: Copy, E, F> TieredResult<&'_ T, E, F> {
213    /// Returns a [`TieredResult`] with the [`Ok`] part copied.
214    #[inline]
215    pub fn copied(self) -> TieredResult<T, E, F> {
216        self.map(|&item| item)
217    }
218}
219
220impl<T: Copy, E, F> TieredResult<&'_ mut T, E, F> {
221    /// Returns a [`TieredResult`] with the [`Ok`] part copied.
222    #[inline]
223    pub fn copied(self) -> TieredResult<T, E, F> {
224        self.map(|&mut item| item)
225    }
226}
227
228impl<T: Clone, E, F> TieredResult<&'_ T, E, F> {
229    /// Returns a [`TieredResult`] with the [`Ok`] part cloned.
230    #[inline]
231    pub fn cloned(self) -> TieredResult<T, E, F> {
232        self.map(|item| item.clone())
233    }
234}
235
236impl<T: Clone, E, F> TieredResult<&'_ mut T, E, F> {
237    /// Returns a [`NullableResult`] with the [`Ok`] part cloned.
238    #[inline]
239    pub fn cloned(self) -> TieredResult<T, E, F> {
240        self.map(|item| item.clone())
241    }
242}
243
244impl<T: Deref, E, F: Clone> TieredResult<T, E, F> {
245    /// Coerce the [`Ok`] variant of the original result with [`Deref`] and returns the
246    /// new [`TieredResult`]
247    #[inline]
248    pub fn as_deref(&self) -> TieredResult<&T::Target, &E, F> {
249        match self {
250            Ok(item) => Ok(item.deref()),
251            Err(err) => Err(err),
252            Null => Null,
253            Fatal(fatality) => Fatal(fatality.clone()),
254        }
255    }
256}
257
258impl<T, E, F: Clone> TieredResult<T, E, F> {
259    /// Convert from a `TieredResult<T, E, F>` or `&TieredResult<T, E, F>` to a
260    /// `TieredResult<&T, &E, F>`.
261    #[inline]
262    pub fn as_ref(&self) -> TieredResult<&T, &E, F> {
263        match self {
264            Ok(item) => Ok(item),
265            Err(err) => Err(err),
266            Null => Null,
267            Fatal(fatality) => Fatal(fatality.clone()),
268        }
269    }
270
271    /// Convert from a `mut TieredResult<T, E, F>` or `&mut NullableResult<T, E, F>` to a
272    /// `TieredResult<&mut T, &mut E, F>`.
273    #[inline]
274    pub fn as_mut(&mut self) -> TieredResult<&mut T, &mut E, F> {
275        match self {
276            Ok(item) => Ok(item),
277            Err(err) => Err(err),
278            Null => Null,
279            Fatal(fatality) => Fatal(fatality.clone()),
280        }
281    }
282}
283
284impl<T, E, F> TieredResult<T, E, F> {
285    /// Returns `true` if this result is an [`Ok`] value
286    #[inline]
287    #[must_use]
288    pub fn is_ok(&self) -> bool {
289        matches!(self, Ok(_))
290    }
291
292    /// Returns `true` if this result is an [`Err`] value
293    #[inline]
294    #[must_use]
295    pub fn is_err(&self) -> bool {
296        matches!(self, Err(_))
297    }
298
299    /// Returns `true` if this result is a [`Null`] value
300    #[inline]
301    #[must_use]
302    pub fn is_null(&self) -> bool {
303        matches!(self, Null)
304    }
305
306    /// Returns `true` if this result is [`Fatal`] value
307    #[inline]
308    #[must_use]
309    pub fn is_fatal(&self) -> bool {
310        matches!(&self, Fatal(_))
311    }
312
313    /// Returns the contained [`Ok`] value, consuming `self`.
314    ///
315    /// # Panics
316    /// Panics if the value is not [`Ok`] with the provided message.
317    #[inline]
318    #[track_caller]
319    pub fn expect(self, msg: &str) -> T {
320        match self {
321            Ok(item) => item,
322            _ => panic!("{}", msg),
323        }
324    }
325
326    /// Returns the contained value if it's [`Ok`], returns `item` otherwise.
327    #[inline]
328    pub fn unwrap_or(self, item: T) -> T {
329        match self {
330            Ok(item) => item,
331            _ => item,
332        }
333    }
334
335    /// Returns the contained value if it's [`Ok`], otherwise, it calls `f` and forwards
336    /// its return value.
337    #[inline]
338    pub fn unwrap_or_else<Func: FnOnce() -> T>(self, f: Func) -> T {
339        match self {
340            Ok(item) => item,
341            _ => f(),
342        }
343    }
344
345    /// Replace a fatal error with [`NullableResult::Null`]
346    #[inline]
347    pub fn recover_as_null(self) -> NullableResult<T, E> {
348        self.optional_nullable_result()
349            .unwrap_or(NullableResult::Null)
350    }
351
352    /// Replace a fatal error with the provided error
353    #[inline]
354    pub fn recover_as_err(self, err: E) -> NullableResult<T, E> {
355        self.optional_nullable_result()
356            .unwrap_or(NullableResult::Err(err))
357    }
358
359    /// Replace a fatal error with the error returned by the provided function.
360    #[inline]
361    pub fn recover_as_err_with<Func: FnOnce() -> E>(
362        self,
363        f: Func,
364    ) -> NullableResult<T, E> {
365        self.optional_nullable_result()
366            .unwrap_or_else(|| NullableResult::Err(f()))
367    }
368
369    #[inline]
370    fn optional_nullable_result(self) -> Option<NullableResult<T, E>> {
371        match self {
372            Ok(item) => Some(NullableResult::Ok(item)),
373            Err(err) => Some(NullableResult::Err(err)),
374            Null => Some(NullableResult::Null),
375            Fatal(_) => None,
376        }
377    }
378
379    /// Maps to a [`TieredResult`] with a different ok type.
380    #[inline]
381    pub fn map<U, Func: FnOnce(T) -> U>(
382        self,
383        f: Func,
384    ) -> TieredResult<U, E, F> {
385        match self {
386            Ok(item) => Ok(f(item)),
387            Err(err) => Err(err),
388            Null => Null,
389            Fatal(fatality) => Fatal(fatality),
390        }
391    }
392
393    /// Maps to a [`TieredResult`] with a different err type.
394    #[inline]
395    pub fn map_err<U, Func: FnOnce(E) -> U>(
396        self,
397        f: Func,
398    ) -> TieredResult<T, U, F> {
399        match self {
400            Ok(item) => Ok(item),
401            Err(err) => Err(f(err)),
402            Null => Null,
403            Fatal(fatality) => Fatal(fatality),
404        }
405    }
406
407    /// Maps to a [`TieredResult`] with a different fatality type.
408    #[inline]
409    pub fn map_fatal<U, Func: FnOnce(F) -> U>(
410        self,
411        f: Func,
412    ) -> TieredResult<T, E, U> {
413        match self {
414            Ok(item) => Ok(item),
415            Err(err) => Err(err),
416            Null => Null,
417            Fatal(fatality) => Fatal(f(fatality)),
418        }
419    }
420
421    /// If `self` is [`Ok`], returns `res`, keeps the value of `self` otherwise.
422    #[inline]
423    pub fn and<U>(self, res: TieredResult<U, E, F>) -> TieredResult<U, E, F> {
424        match self {
425            Ok(_) => res,
426            Err(err) => Err(err),
427            Null => Null,
428            Fatal(fatality) => Fatal(fatality),
429        }
430    }
431
432    /// Calls `op` if the result is [`Ok`], otherwise returns the value of `self`.
433    #[inline]
434    pub fn and_then<U, Func>(self, op: Func) -> TieredResult<U, E, F>
435    where
436        Func: FnOnce(T) -> TieredResult<U, E, F>,
437    {
438        match self {
439            Ok(item) => op(item),
440            Err(err) => Err(err),
441            Null => Null,
442            Fatal(fatality) => Fatal(fatality),
443        }
444    }
445}
446
447impl<T, E, F> TieredResult<TieredResult<T, E, F>, E, F> {
448    /// Convert from `TieredResult<TieredResult<T, E>, E>` to
449    /// `TieredResult<T, E>`.
450    #[inline]
451    pub fn flatten(self) -> TieredResult<T, E, F> {
452        match self {
453            Ok(Ok(item)) => Ok(item),
454            Ok(Err(err)) | Err(err) => Err(err),
455            Ok(Null) | Null => Null,
456            Ok(Fatal(fatality)) | Fatal(fatality) => Fatal(fatality),
457        }
458    }
459}
460
461/// Analogue of [TryFrom] that returns a [`TieredResult`]
462pub trait TryFromFatal<T>: Sized {
463    /// The type that is returned if conversion fails
464    type Error;
465
466    /// The type that is returned if conversion fails fatally
467    type Fatality;
468
469    /// Convert a `T` to [`TieredResult<Self, Self::Error, Self::Fatality>`]
470    fn try_from_fatal(
471        item: T,
472    ) -> TieredResult<Self, Self::Error, Self::Fatality>;
473}
474
475/// Analogue of [TryInto] that returns a [`TieredResult`]
476pub trait TryIntoFatal<T>: Sized {
477    /// The type that is returned if conversion fails
478    type Error;
479
480    /// The type that is returned if conversion fails fatally
481    type Fatality;
482
483    /// Convert a `Self` to [`TieredResult<T, Self::Error, Self::Fatality>`]
484    fn try_into_fatal(self) -> TieredResult<T, Self::Error, Self::Fatality>;
485}
486
487impl<T, U: nullable_result::MaybeTryFrom<T>> TryFromFatal<T> for U {
488    type Error = U::Error;
489    type Fatality = Infallible;
490
491    #[inline]
492    fn try_from_fatal(
493        item: T,
494    ) -> TieredResult<Self, Self::Error, Self::Fatality> {
495        Ok(U::maybe_try_from(item)?)
496    }
497}
498
499impl<T, U: TryFromFatal<T>> TryIntoFatal<U> for T {
500    type Error = U::Error;
501    type Fatality = U::Fatality;
502
503    #[inline]
504    fn try_into_fatal(self) -> TieredResult<U, Self::Error, Self::Fatality> {
505        U::try_from_fatal(self)
506    }
507}
508
509impl<T, F> FatalOrOk<T, F> {
510    /// Return the contained item
511    ///
512    /// # Panics
513    /// Panics if the value contains a fatal error.
514    #[inline]
515    #[track_caller]
516    pub fn unwrap(self) -> T {
517        use FatalOrOk::*;
518
519        match self {
520            Ok(item) => item,
521            Fatal(_) => {
522                panic!("tried to unwrap a tiered result containing `Fatal`")
523            }
524        }
525    }
526}