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}