infinite_errors/
lib.rs

1//! Generic error handling framework with static backtraces.
2
3use std::panic::Location;
4
5pub use derive_more::Error;
6pub use infinite_errors_macros::err_context;
7
8/// Generate a rich error type using a given error kind.
9///
10/// The type will be called `Error`. Also generates an `ErrorContext` trait
11/// similar to [ErrorContext] but specialized for this new error type.
12///
13/// The reason why we cannot define an error type in this crate and export it
14/// is because orphan rules would make the `?` operator more awkward to use.
15///
16/// # Usage
17///
18/// Define your error kind and then call this macro with that error kind as
19/// argument:
20///
21/// ```ignore
22/// declare_error_type!(ErrorKind);
23/// ```
24///
25/// Both the error type and the generated `ErrorContext` trait will be `pub`.
26#[macro_export]
27macro_rules! declare_error_type {
28    ($error_kind:ident) => {
29        /// Generic eror type with backtrace.
30        #[derive(::std::fmt::Debug, ::infinite_errors::Error)]
31        pub struct Error {
32            kind: $error_kind,
33            cause: ::std::option::Option<::std::boxed::Box<Error>>,
34            location: &'static ::std::panic::Location<'static>,
35        }
36
37        impl Error {
38            /// Create a new [Error] from an error kind and an error [Location].
39            pub fn new(
40                kind: $error_kind,
41                location: &'static ::std::panic::Location<'static>,
42            ) -> Self {
43                Self {
44                    kind,
45                    cause: ::std::option::Option::None,
46                    location,
47                }
48            }
49
50            /// Get the internal error kind.
51            pub fn kind(&self) -> &$error_kind {
52                &self.kind
53            }
54
55            /// Get the cause for this error, if one exists.
56            pub fn cause(&self) -> ::std::option::Option<&Self> {
57                self.cause.as_deref()
58            }
59
60            /// Get the location where this [Error] was constructed.
61            pub fn location(&self) -> &'static ::std::panic::Location<'static> {
62                self.location
63            }
64        }
65
66        impl ::infinite_errors::ErrorType for Error {
67            type ErrorKind = $error_kind;
68
69            fn new(
70                kind: Self::ErrorKind,
71                cause: ::std::option::Option<::std::boxed::Box<Self>>,
72                location: &'static ::std::panic::Location<'static>,
73            ) -> Self {
74                Self {
75                    kind,
76                    cause,
77                    location,
78                }
79            }
80        }
81
82        impl ::std::fmt::Display for Error {
83            fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
84                write!(f, "{}", self.kind)?;
85                if let Some(cause) = &self.cause {
86                    write!(f, ": {cause}")?;
87                }
88
89                Ok(())
90            }
91        }
92
93        impl<T> ::std::convert::From<T> for Error
94        where
95            T: ::std::convert::Into<$error_kind>,
96        {
97            #[track_caller]
98            fn from(kind: T) -> Self {
99                Self::new(kind.into(), ::std::panic::Location::caller())
100            }
101        }
102
103        /// Helper trait to add context to errors.
104        pub trait ErrorContext<T> {
105            /// Add an error kind to the top of the error backtrace.
106            #[track_caller]
107            fn err_context(self, kind: $error_kind) -> ::std::result::Result<T, Error>;
108
109            /// Add an error kind returned by a function to the top of the error
110            /// backtrace. The function should only be called if `self` is indeed an
111            /// error.
112            #[track_caller]
113            fn err_context_with(
114                self,
115                kind: impl FnOnce() -> $error_kind,
116            ) -> ::std::result::Result<T, Error>;
117        }
118
119        impl<T, OE> ErrorContext<T> for ::std::result::Result<T, OE>
120        where
121            OE: Into<Error>,
122        {
123            fn err_context(self, kind: $error_kind) -> ::std::result::Result<T, Error> {
124                self.map_err(|x| Error {
125                    kind,
126                    cause: ::std::option::Option::Some(::std::boxed::Box::new(x.into())),
127                    location: ::std::panic::Location::caller(),
128                })
129            }
130
131            fn err_context_with(
132                self,
133                f: impl FnOnce() -> $error_kind,
134            ) -> ::std::result::Result<T, Error> {
135                self.map_err(|x| Error {
136                    kind: f(),
137                    cause: ::std::option::Option::Some(::std::boxed::Box::new(x.into())),
138                    location: ::std::panic::Location::caller(),
139                })
140            }
141        }
142    };
143}
144
145/// Trait for error types created by [declare_error_type].
146pub trait ErrorType {
147    /// The `ErrorKind` type.
148    type ErrorKind;
149
150    /// Create a new [ErrorType] with the given inner kind, cause and error
151    /// location.
152    fn new(
153        kind: Self::ErrorKind,
154        cause: Option<Box<Self>>,
155        location: &'static Location<'static>,
156    ) -> Self;
157}
158
159/// Helper trait to add context to errors.
160///
161/// Most likely you want to use the trait of the same name and API generated
162/// by [declare_error_type].
163pub trait ErrorContext<T, K, E> {
164    /// Add an error kind to the top of the error backtrace.
165    #[track_caller]
166    fn err_context(self, kind: K) -> Result<T, E>;
167
168    /// Add an error kind returned by a function to the top of the error
169    /// backtrace. The function should only be called if `self` is indeed an
170    /// error.
171    #[track_caller]
172    fn err_context_with(self, kind: impl FnOnce() -> K) -> Result<T, E>;
173}
174
175impl<T, K, E, OE> ErrorContext<T, K, E> for Result<T, OE>
176where
177    OE: Into<E>,
178    E: ErrorType<ErrorKind = K>,
179{
180    fn err_context(self, kind: K) -> Result<T, E> {
181        self.map_err(|x| E::new(kind, Some(Box::new(x.into())), Location::caller()))
182    }
183
184    fn err_context_with(self, f: impl FnOnce() -> K) -> Result<T, E> {
185        self.map_err(|x| E::new(f(), Some(Box::new(x.into())), Location::caller()))
186    }
187}