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/// ```
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/// ```
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`]: std::error::Error
44#[macro_export]
45macro_rules! forward_error {
46    ($ty:ty) => {
47        impl ::std::error::Error for $ty {
48            fn source(&self) -> Option<&(dyn ::std::error::Error + 'static)> {
49                Some(::core::ops::Deref::deref(&self.0))
50            }
51        }
52    };
53
54    ($ty:ty => $field:ident) => {
55        impl ::std::error::Error for $ty {
56            fn source(&self) -> Option<&(dyn ::std::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 not compatible with `#[no_std]`.
66///
67/// # Examples
68///
69/// ```
70/// # extern crate alloc;
71/// use std::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`]: std::error::Error
88#[macro_export]
89macro_rules! impl_error_enum {
90    ($ty:ty, $($variant:ident ($($inner:ident),+) => $source:expr),+ ,) => {
91        impl ::std::error::Error for $ty {
92            fn source(&self) -> ::core::option::Option<&(dyn ::std::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 ::std::error::Error for $ty {
109            fn source(&self) -> ::core::option::Option<&(dyn ::std::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 ::std::error::Error for $ty {}
126    };
127
128    ($ty:ty) => {
129        $crate::impl_error_enum!($ty,);
130    };
131}
132
133#[cfg(test)]
134mod tests {
135    use alloc::string::String;
136    use std::error::Error as _;
137
138    #[test]
139    fn with_trailing_comma() {
140        #![allow(unused)]
141
142        #[derive(Debug)]
143        enum Foo {
144            Bar,
145        }
146
147        impl_display_enum!(Foo, Bar => "bar");
148        impl_error_enum!(Foo,);
149    }
150
151    #[test]
152    fn no_inner_data() {
153        #[derive(Debug)]
154        enum Foo {
155            Bar,
156            Baz,
157        }
158
159        impl_display_enum!(Foo, Bar => "bar", Baz => "qux");
160        impl_error_enum!(Foo);
161
162        assert!(Foo::Bar.source().is_none());
163        assert!(Foo::Baz.source().is_none());
164    }
165
166    #[test]
167    fn uniform_enum() {
168        #[derive(Debug)]
169        enum Foo {
170            Bar(String),
171            Baz(std::io::Error),
172            Qux(String, std::io::Error),
173        }
174
175        impl_display_enum!(
176            Foo,
177            Bar(desc) => "{desc}",
178            Baz(err) => "{err}",
179            Qux(desc, err) => "{desc}: {err}"
180        );
181        impl_error_enum!(Foo, Baz(err) => err, Qux(_desc, err) => err);
182
183        assert!(Foo::Bar(String::new()).source().is_none());
184
185        let io_err = std::io::Error::new(std::io::ErrorKind::Other, "test");
186        assert!(Foo::Baz(io_err).source().is_some());
187
188        let io_err = std::io::Error::new(std::io::ErrorKind::Other, "test");
189        assert!(Foo::Qux(String::new(), io_err).source().is_some());
190    }
191}