impl_more/
error.rs

1/// Implements [`Error`] for structs and forwards the `source` implementation to one of its fields.
2///
3/// Emitted code is not compatible with `#[no_std]`.
4///
5/// Newtype structs can omit the field identifier.
6///
7/// # Examples
8///
9/// For newtype struct:
10///
11/// ```ignore
12/// use std::error::Error as _;
13///
14/// #[derive(Debug)]
15/// struct MyError(eyre::Report);
16///
17/// impl_more::forward_display!(MyError);
18/// impl_more::forward_error!(MyError);
19///
20/// let err = MyError(eyre::eyre!("something went wrong"));
21/// assert_eq!(err.source().unwrap().to_string(), "something went wrong");
22/// ```
23///
24/// For struct with named field:
25///
26/// ```ignore
27/// use std::error::Error as _;
28///
29/// #[derive(Debug)]
30/// struct MyError {
31///     cause: eyre::Report,
32/// }
33///
34/// impl_more::forward_display!(MyError => cause);
35/// impl_more::forward_error!(MyError => cause);
36///
37/// let err = MyError { cause: eyre::eyre!("something went wrong") };
38/// assert_eq!(err.source().unwrap().to_string(), "something went wrong");
39/// ```
40///
41/// This macro does not yet support use with generic error wrappers.
42///
43/// [`Error`]: core::error::Error
44#[macro_export]
45macro_rules! forward_error {
46    ($ty:ty) => {
47        impl ::core::error::Error for $ty {
48            fn source(&self) -> Option<&(dyn ::core::error::Error + 'static)> {
49                Some(::core::ops::Deref::deref(&self.0))
50            }
51        }
52    };
53
54    ($ty:ty => $field:ident) => {
55        impl ::core::error::Error for $ty {
56            fn source(&self) -> Option<&(dyn ::core::error::Error + 'static)> {
57                Some(::core::ops::Deref::deref(&self.$field))
58            }
59        }
60    };
61}
62
63/// Implements [`Error`] for enums.
64///
65/// Emitted code is compatible with `#[no_std]` after Rust v1.81.
66///
67/// # Examples
68///
69/// ```ignore
70/// # extern crate alloc;
71/// use core::error::Error as _;
72///
73/// #[derive(Debug)]
74/// enum Err {
75///     Io(std::io::Error),
76///     Generic(String),
77/// }
78///
79/// impl_more::impl_display_enum!(Err, Io(err) => "{err}", Generic(msg) => "{msg}");
80/// impl_more::impl_error_enum!(Err, Io(err) => err);
81///
82/// # let io_err = std::io::Error::new(std::io::ErrorKind::Other, "test");
83/// assert!(Err::Io(io_err).source().is_some());
84/// assert!(Err::Generic("oops".to_owned()).source().is_none());
85/// ```
86///
87/// [`Error`]: core::error::Error
88#[macro_export]
89macro_rules! impl_error_enum {
90    ($ty:ty: $($variant:ident ($($inner:ident),+) => $source:expr),+ ,) => {
91        impl ::core::error::Error for $ty {
92            fn source(&self) -> ::core::option::Option<&(dyn ::core::error::Error + 'static)> {
93                match self {
94                    $(
95                        Self::$variant($($inner),+) => ::core::option::Option::Some($source),
96                    )*
97                    _ => ::core::option::Option::None,
98                }
99            }
100        }
101    };
102
103    ($ty:ty: $($variant:ident ($($inner:ident),+) => $source:expr),+) => {
104        $crate::impl_error_enum!($ty: $($variant ($($inner),+) => $source),+ ,);
105    };
106
107    ($ty:ty: $($variant:ident { $($inner:ident),+ } => $source:expr),+ ,) => {
108        impl ::core::error::Error for $ty {
109            fn source(&self) -> ::core::option::Option<&(dyn ::core::error::Error + 'static)> {
110                match self {
111                    $(
112                        Self::$variant($($inner),+) => ::core::option::Option::Some($source),
113                    )*
114                    _ => ::core::option::Option::None,
115                }
116            }
117        }
118    };
119
120    ($ty:ty: $($variant:ident { $($inner:ident),+ } => $source:expr),+) => {
121        $crate::impl_error_enum!($ty, $($variant { $($inner),+ } => $source),+ ,);
122    };
123
124    ($ty:ty) => {
125        impl ::core::error::Error for $ty {}
126    };
127}
128
129/// Implements leaf [`Error`]s.
130///
131/// Emitted code is compatible with `#[no_std]` after Rust v1.81.
132///
133/// # Examples
134///
135/// ```ignore
136/// #[derive(Debug)]
137/// struct LeafError;
138///
139/// impl_more::impl_display!(LeafError; "leaf");
140/// impl_more::impl_error_enum!(LeafError);
141/// ```
142///
143/// [`Error`]: core::error::Error
144#[macro_export]
145macro_rules! impl_leaf_error {
146    ($ty:ty) => {
147        impl ::core::error::Error for $ty {}
148    };
149}
150
151#[cfg(test)]
152#[rustversion::since(1.81)]
153mod tests {
154    use alloc::string::String;
155    use core::error::Error as _;
156
157    #[test]
158    fn with_trailing_comma() {
159        #![allow(unused)]
160
161        #[derive(Debug)]
162        enum Foo {
163            Bar,
164        }
165
166        impl_display_enum!(Foo: Bar => "bar");
167        impl_leaf_error!(Foo);
168    }
169
170    #[test]
171    fn no_inner_data() {
172        #[derive(Debug)]
173        enum Foo {
174            Bar,
175            Baz,
176        }
177
178        impl_display_enum!(Foo: Bar => "bar", Baz => "qux");
179        impl_error_enum!(Foo);
180
181        assert!(Foo::Bar.source().is_none());
182        assert!(Foo::Baz.source().is_none());
183    }
184
185    #[test]
186    fn uniform_enum() {
187        #[derive(Debug)]
188        enum Foo {
189            Bar(String),
190            Baz(std::io::Error),
191            Qux(String, std::io::Error),
192        }
193
194        impl_display_enum! {
195            Foo:
196            Bar(desc) => "{desc}",
197            Baz(err) => "{err}",
198            Qux(desc, err) => "{desc}: {err}"
199        };
200        impl_error_enum! {
201            Foo:
202            Baz(err) => err,
203            Qux(_desc, err) => err
204        };
205
206        assert!(Foo::Bar(String::new()).source().is_none());
207
208        let io_err = std::io::Error::new(std::io::ErrorKind::Other, "test");
209        assert!(Foo::Baz(io_err).source().is_some());
210
211        let io_err = std::io::Error::new(std::io::ErrorKind::Other, "test");
212        assert!(Foo::Qux(String::new(), io_err).source().is_some());
213    }
214}