saluki_error/lib.rs
1//! A trait object-based error type based on `anyhow` for ergonomic error handling.
2//!
3//! This crate is a thin wrapper around `anyhow` that provides a trait object-based error type, `GenericError`, along
4//! with helper extension traits and macros for ergonomically converting errors into `GenericError`.
5//!
6//! This crate is still interoperable with `anyhow`, but is meant to be used as part of the comprehensive set of
7//! Saluki-specific crates so that engineers do not need to necessarily know or care deeply about which specific
8//! third-party crate to use for error handling.
9#![deny(warnings)]
10#![deny(missing_docs)]
11
12/// A wrapper around a dynamic error type.
13///
14/// `GenericError` works a lot like `Box<dyn std::error::Error>`, but with these differences:
15///
16/// - `GenericError` requires that the error is `Send`, `Sync`, and `'static`.
17/// - `GenericError` guarantees that a backtrace is available, even if the underlying error type does not provide one.
18/// - `GenericError` is represented as a narrow pointer — exactly one word in size instead of two.
19pub type GenericError = anyhow::Error;
20
21/// Macro for constructing a generic error.
22///
23/// The resulting value evaluates to [`GenericError`], and can be construct from a string literal, a format string (with
24/// arguments accepted, in the same order as `std::format!`), or a value which implements `Debug` and `Display`, such as
25/// an existing error that implements `std::error::Error`.
26///
27/// When the value given implements `std::error::Error`, the source of the existing error value will be used as the
28/// source of the error created by this macro.
29#[macro_export]
30macro_rules! generic_error {
31 // This macro forwards to the [`anyhow::anyhow`] macro, and is intended to be used in place of that macro. We simply
32 // use our own macro, instead of re-exporting it, so that we can provide better documentation that isn't
33 // `anyhow`-specific.
34 ($msg:literal $(,)?) => { $crate::_anyhow!($msg) };
35 ($err:expr $(,)?) => { $crate::_anyhow!($err) };
36 ($fmt:expr, $($arg:tt)*) => { $crate::_anyhow!($fmt, $($arg)*) };
37}
38
39use std::fmt::Display;
40
41#[doc(hidden)]
42pub use anyhow::anyhow as _anyhow;
43
44mod private {
45 pub trait Sealed {}
46
47 impl<T, E> Sealed for Result<T, E> {}
48}
49
50/// Helper methods for providing context on errors.
51///
52/// Slightly different than `anyhow::Context` as the methods on this trait are named to avoid conflicting with
53/// similarly-named methods provide by `snafu`.
54pub trait ErrorContext<T, E>: private::Sealed {
55 /// Wrap the error value with additional context.
56 fn error_context<C>(self, context: C) -> Result<T, GenericError>
57 where
58 C: Display + Send + Sync + 'static;
59
60 /// Wrap the error value with additional context that is evaluated lazily only once an error does occur.
61 fn with_error_context<C, F>(self, f: F) -> Result<T, GenericError>
62 where
63 C: Display + Send + Sync + 'static,
64 F: FnOnce() -> C;
65}
66
67impl<T, E> ErrorContext<T, E> for Result<T, E>
68where
69 Result<T, E>: anyhow::Context<T, E>,
70{
71 fn error_context<C>(self, context: C) -> Result<T, GenericError>
72 where
73 C: Display + Send + Sync + 'static,
74 {
75 <Self as anyhow::Context<T, E>>::context(self, context)
76 }
77
78 fn with_error_context<C, F>(self, context: F) -> Result<T, GenericError>
79 where
80 C: Display + Send + Sync + 'static,
81 F: FnOnce() -> C,
82 {
83 <Self as anyhow::Context<T, E>>::with_context(self, context)
84 }
85}