errormake/
lib.rs

1//! A macro for automatically generating structs which implement the
2//! `Error` trait from `std::error`.
3//!
4//! The `errormake!` macro generates a struct which implements an error
5//! and may optionally contain a description and/or a source error. The
6//! resulting struct may be either public or private to the module.
7//!
8//! Here is an example of using some of its functionality:
9//! ```
10//! use errormake::errormake;
11//!
12//! errormake!(pub ExampleError);
13//!
14//! // Create an error with no description or source
15//! let error1 = ExampleError::new();
16//! // Create an error with a description, but no source
17//! let error2 = ExampleError::with_description(String::from("Error description"));
18//! // Create an error with a source, but no description
19//! let error3 = ExampleError::with_source(Box::new(error1));
20//! // Create an error with a source and a description
21//! let error4 = ExampleError::with_source_and_description(Box::new(error3), String::from("Error description"));
22//! ```
23//!
24//! If making a public error struct, you can also add custom
25//! documentation through the `doc` attribute, as follows:
26//! ```
27//! use errormake::errormake;
28//!
29//! // The `DocumentedError` struct now has a documentation, which will
30//! // show up if `cargo doc` is run.
31//! errormake!(#[doc="Documentation comments"] pub DocumentedError);
32//! ```
33//!
34//! You can also convert the type of contained error into a dynamic
35//! Error object as follows:
36//! ```
37//! use std::error::Error;
38//! use errormake::errormake;
39//! errormake!(ExampleError);
40//!
41//! let error: ExampleError<dyn Error + 'static> = ExampleError::new().into_dynamic();
42//! ```
43//!
44
45#[macro_export]
46/// The macro used to generate basic Error structs.
47///
48/// See the [crate docs](../errormake/index.html) for the full
49/// documentation.
50macro_rules! errormake {
51    ($structname:ident) => {
52        #[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
53        struct $structname<T: ?Sized + 'static> {
54            source: Option<Box<T>>,
55            description: Option<String>,
56        }
57
58        errormake!(impl $structname);
59    };
60    ($(#[$meta:meta])* pub $structname:ident) => {
61        $(#[$meta])*
62        #[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
63        pub struct $structname<T: ?Sized + 'static> {
64            source: Option<Box<T>>,
65            description: Option<String>,
66        }
67
68        errormake!(impl $structname);
69    };
70    (impl $structname:ident) => {
71        #[allow(dead_code)]
72        impl $structname<std::convert::Infallible> {
73            // Using the never type would make more sense, one that type
74            // becomes stable. Until then, we have to continue using
75            // Infalible.
76
77            /// Instantiate with no source or description
78            pub fn new() -> $structname<std::convert::Infallible> {
79                $structname {
80                    source: None,
81                    description: None,
82                }
83            }
84
85            /// Instantiate with the given description and no source
86            pub fn with_description(description: String) -> $structname<std::convert::Infallible> {
87                $structname {
88                    source: None,
89                    description: Some(description),
90                }
91            }
92        }
93
94        #[allow(dead_code)]
95        impl<T: 'static> $structname<T> {
96            /// Instantiate with the given source and no description
97            pub fn with_source(source: T) -> $structname<T> {
98                $structname {
99                    source: Some(Box::new(source)),
100                    description: None,
101                }
102            }
103
104            /// Instantiate with the given source and description
105            pub fn with_source_and_description(source: T, description: String) -> $structname<T> {
106                $structname {
107                    source: Some(Box::new(source)),
108                    description: Some(description),
109                }
110            }
111        }
112
113        #[allow(dead_code)]
114        impl<T: ?Sized + 'static> $structname<T> {
115            /// Instantiate with optional source and description
116            /// determined by the arguments
117            pub fn with_optional_data(
118                source: Option<Box<T>>,
119                description: Option<String>,
120            ) -> $structname<T> {
121                $structname {
122                    source,
123                    description,
124                }
125            }
126        }
127
128        #[allow(dead_code)]
129        impl<T: std::error::Error + 'static> $structname<T> {
130            /// Convert the source error into a dynamic Error object, if
131            /// it exists
132            pub fn into_dynamic(self) -> $structname<dyn std::error::Error + 'static> {
133                $structname {
134                    source: self.source.map(|source| source as Box<dyn std::error::Error + 'static>),
135                    description: self.description,
136                }
137            }
138        }
139
140        impl<T: std::fmt::Display + ?Sized + 'static> std::fmt::Display for $structname<T> {
141            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
142                match &self.source {
143                    Some(source) => write!(
144                        f,
145                        "{}\n\nThe above error caused the following error:\n\n",
146                        source
147                    )?,
148                    None => {}
149                }
150                write!(
151                    f,
152                    concat!(stringify!($structname), ": {}"),
153                    match self.description.as_ref() {
154                        Some(description) => description,
155                        None => "No description provided",
156                    }
157                )?;
158                Ok(())
159            }
160        }
161
162        impl<T> std::error::Error for $structname<T>
163            where T: std::error::Error + 'static
164        {
165            fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
166                self.source
167                    .as_ref()
168                    .map(|err| err.as_ref() as &(dyn std::error::Error + 'static))
169            }
170        }
171
172        impl std::error::Error for $structname<dyn std::error::Error + 'static> {
173            fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
174                self.source
175                    .as_ref()
176                    .map(|err| err.as_ref())
177            }
178        }
179    };
180}
181
182errormake!(#[doc="An example of an error struct made by `errormake`"] pub ExampleErrorStruct);
183
184#[cfg(test)]
185mod tests {
186    use super::errormake;
187    use std::error::Error;
188
189    errormake!(TestingError);
190    errormake!(pub PublicTestingError);
191
192    #[test]
193    fn test_stable() {
194        let error1 = TestingError::new();
195        assert_eq!(
196            "TestingError: No description provided",
197            format!("{}", error1)
198        );
199        assert!(error1.source().is_none());
200        let error2 = TestingError::with_description(String::from("Custom error message"));
201        assert_eq!("TestingError: Custom error message", format!("{}", error2));
202        assert!(error2.source().is_none());
203        let error3 = TestingError::with_source(Box::new(error2));
204        assert_eq!("TestingError: Custom error message\n\nThe above error caused the following error:\n\nTestingError: No description provided", format!("{}", error3));
205        assert!(error3.source().is_some());
206        let error4 = TestingError::with_source_and_description(
207            Box::new(TestingError::with_description(String::from(
208                "Custom error message",
209            ))),
210            String::from("Another message"),
211        );
212        assert_eq!("TestingError: Custom error message\n\nThe above error caused the following error:\n\nTestingError: Another message", format!("{}", error4));
213        assert!(error4.source().is_some());
214    }
215
216    #[test]
217    fn test_derives() {
218        let error1 = TestingError::new();
219        assert_eq!(error1, error1.clone());
220        let error2 = TestingError::with_source(error1.clone());
221        assert_eq!(error2, error2.clone());
222        assert_eq!(error2, error2);
223        let error3 =
224            TestingError::with_source_and_description(error1.clone(), String::from("description"));
225        assert_ne!(error3, error2);
226        let error4 = TestingError::with_description(String::from("description"));
227        assert_ne!(error1, error4);
228    }
229
230    #[test]
231    fn test_dynamic() {
232        // Test two ways of making the type parameter dynamic
233        let error = TestingError::new();
234        let error = TestingError::with_source(error).into_dynamic();
235        assert!(error.source().is_some());
236        let box_error: Box<dyn Error + 'static> = Box::new(error);
237        let error = TestingError::with_optional_data(Some(box_error), None);
238        assert!(error.source().is_some());
239    }
240}