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}