human_errors/
extend.rs

1/// Create a shim error type between [`human_errors::Error`] and other error types.
2///
3/// # Examples
4/// ```
5/// human_errors::error_shim!(MyError);
6///
7/// impl From<std::num::ParseIntError> for MyError {
8///   fn from(err: std::num::ParseIntError) -> Self {
9///     user_with_internal(
10///       "We could not parse the number you provided.",
11///       "Make sure that you're providing a number in the form 12345 or -12345.",
12///       err,
13///     )
14///   }    
15/// }
16/// ```
17#[macro_export]
18macro_rules! error_shim {
19    ($type:ident) => {
20        /// A basic error triggered by something the user has done.
21        ///
22        /// Constructs a new [Error] describing a failure which was the result of an
23        /// action that the user has taken. This error includes a description of what
24        /// occurred, as well as some advice for the user to try to mitigate the problem.
25        ///
26        /// # Examples
27        /// ```
28        /// use human_errors;
29        ///
30        /// human_errors::user(
31        ///   "We could not open the config file you provided.",
32        ///   "Make sure that the file exists and is readable by the application.",
33        /// );
34        /// ```
35        #[allow(dead_code)]
36        pub fn user(description: &str, advice: &str) -> $type {
37            $crate::user(description, advice).into()
38        }
39
40        /// An error triggered by something the user has done, with a deeper cause.
41        ///
42        /// Constructs a new [Error] describing a failure which was the result of an
43        /// action that the user has taken. This error includes a description of what
44        /// occurred, as well as some advice for the user to try to mitigate the problem.
45        /// It also includes the details of another error which resulted in this failure,
46        /// as well as any advice that error may provide.
47        ///
48        /// # Examples
49        /// ```
50        /// use human_errors;
51        ///
52        /// human_errors::user_with_cause(
53        ///   "We could not open the config file you provided.",
54        ///   "Make sure that you've specified a valid config file with the --config option.",
55        ///   human_errors::user(
56        ///     "We could not find a file at /home/user/.config/demo.yml",
57        ///     "Make sure that the file exists and is readable by the application."
58        ///   )
59        /// );
60        /// ```
61        #[allow(dead_code)]
62        pub fn user_with_cause(description: &str, advice: &str, cause: $type) -> $type {
63            $crate::user_with_cause(description, advice, cause.into()).into()
64        }
65
66        /// An error triggered by something the user has done, with a deeper cause.
67        ///
68        /// Constructs a new [Error] describing a failure which was the result of an
69        /// action that the user has taken. This error includes a description of what
70        /// occurred, as well as some advice for the user to try to mitigate the problem.
71        /// It also includes the details of another error which resulted in this failure.
72        ///
73        /// **NOTE**: The internal error may be any type which may be converted into a [Box<std::error::Error>].
74        ///
75        /// # Examples
76        /// ```
77        /// use human_errors;
78        ///
79        /// human_errors::user_with_internal(
80        ///   "We could not open the config file you provided.",
81        ///   "Make sure that the file exists and is readable by the application.",
82        ///   human_errors::detailed_message("ENOENT 2: No such file or directory")
83        /// );
84        /// ```
85        #[allow(dead_code)]
86        pub fn user_with_internal<T>(description: &str, advice: &str, internal: T) -> $type
87        where
88            T: Into<Box<dyn std::error::Error + Send + Sync>>,
89        {
90            $crate::user_with_internal(description, advice, internal).into()
91        }
92
93        /// An error triggered by the system rather than the user.
94        ///
95        /// Constructs a new [Error] describing a failure which was the result of a failure
96        /// in the system, rather than a user's action. This error includes a description of what
97        /// occurred, as well as some advice for the user to try to mitigate the problem.
98        ///
99        /// # Examples
100        /// ```
101        /// use human_errors;
102        ///
103        /// human_errors::system(
104        ///   "We could not open the config file you provided.",
105        ///   "Make sure that the file exists and is readable by the application."
106        /// );
107        /// ```
108        #[allow(dead_code)]
109        pub fn system(description: &str, advice: &str) -> $type {
110            $crate::system(description, advice).into()
111        }
112
113        /// An error triggered by the system rather than the user, with a deeper cause.
114        ///
115        /// Constructs a new [Error] describing a failure which was the result of a failure
116        /// in the system, rather than a user's action. This error includes a description of what
117        /// occurred, as well as some advice for the user to try to mitigate the problem.
118        /// It also includes the details of another error which resulted in this failure,
119        /// as well as any advice that error may provide.
120        ///
121        /// # Examples
122        /// ```
123        /// use human_errors;
124        ///
125        /// human_errors::system_with_cause(
126        ///   "We could not open the config file you provided.",
127        ///   "Make sure that you've specified a valid config file with the --config option.",
128        ///   human_errors::system(
129        ///     "We could not find a file at /home/user/.config/demo.yml",
130        ///     "Make sure that the file exists and is readable by the application."
131        ///   )
132        /// );
133        /// ```
134        #[allow(dead_code)]
135        pub fn system_with_cause(description: &str, advice: &str, cause: $type) -> $type {
136            $crate::system_with_cause(description, advice, cause.into()).into()
137        }
138
139        /// An error triggered by the system rather than the user, with a deeper cause.
140        ///
141        /// Constructs a new [Error] describing a failure which was the result of a failure
142        /// in the system, rather than a user's action. This error includes a description of what
143        /// occurred, as well as some advice for the user to try to mitigate the problem.
144        /// It also includes the details of another error which resulted in this failure.
145        ///
146        /// **NOTE**: The internal error may be any type which may be converted into a [Box<std::error::Error>].
147        ///
148        /// # Examples
149        /// ```
150        /// use human_errors;
151        ///
152        /// human_errors::system_with_internal(
153        ///   "We could not open the config file you provided.",
154        ///   "Make sure that the file exists and is readable by the application.",
155        ///   human_errors::detailed_message("ENOENT 2: No such file or directory")
156        /// );
157        /// ```
158        #[allow(dead_code)]
159        pub fn system_with_internal<T>(description: &str, advice: &str, internal: T) -> $type
160        where
161            T: Into<Box<dyn std::error::Error + Send + Sync>>,
162        {
163            $crate::system_with_internal(description, advice, internal).into()
164        }
165
166        /// The fundamental error type used by this library.
167        ///
168        /// An error type which encapsulates information about whether an error
169        /// is the result of something the user did, or a system failure outside
170        /// of their control. These errors include a description of what occurred,
171        /// advice on how to proceed and references to the causal chain which led
172        /// to this failure.
173        ///
174        /// # Examples
175        /// ```
176        /// let err = human_errors::user(
177        ///   "We could not open the config file you provided.",
178        ///   "Make sure that the file exists and is readable by the application.",
179        /// );
180        ///
181        /// // Prints the error and any advice for the user.
182        /// println!("{}", err)
183        /// ```
184        #[derive(Debug)]
185        pub struct $type($crate::Error);
186
187        impl From<$crate::Error> for $type {
188            fn from(err: $crate::Error) -> Self {
189                Self(err)
190            }
191        }
192
193        #[allow(clippy::from_over_into)]
194        impl Into<$crate::Error> for $type {
195            fn into(self) -> $crate::Error {
196                self.0
197            }
198        }
199
200        #[allow(dead_code)]
201        impl $type {
202            /// Gets the description message from this error.
203            ///
204            /// Gets the description which was provided as the first argument when constructing
205            /// this error.
206            ///
207            /// # Examples
208            /// ```
209            /// use human_errors;
210            ///
211            /// let err = human_errors::user(
212            ///   "We could not open the config file you provided.",
213            ///   "Make sure that the file exists and is readable by the application.",
214            /// );
215            ///
216            /// // Prints: "We could not open the config file you provided."
217            /// println!("{}", err.description())
218            /// ```
219            pub fn description(&self) -> String {
220                self.0.description()
221            }
222
223            /// Gets the formatted error and its advice.
224            ///
225            /// Generates a string containing the description of the error and any causes,
226            /// as well as a list of suggestions for how a user should
227            /// deal with this error. The "deepest" error's advice is presented first, with
228            /// successively higher errors appearing lower in the list. This is done because
229            /// the most specific error is the one most likely to have the best advice on how
230            /// to resolve the problem.
231            ///
232            /// # Examples
233            /// ```
234            /// use human_errors;
235            ///
236            /// let err = human_errors::user_with_cause(
237            ///   "We could not open the config file you provided.",
238            ///   "Make sure that you've specified a valid config file with the --config option.",
239            ///   human_errors::user(
240            ///     "We could not find a file at /home/user/.config/demo.yml",
241            ///     "Make sure that the file exists and is readable by the application."
242            ///   )
243            /// );
244            ///
245            /// // Prints a message like the following:
246            /// // Oh no! We could not open the config file you provided.
247            /// //
248            /// // This was caused by:
249            /// // We could not find a file at /home/user/.config/demo.yml
250            /// //
251            /// // To try and fix this, you can:
252            /// //  - Make sure that the file exists and is readable by the application.
253            /// //  - Make sure that you've specified a valid config file with the --config option.
254            /// println!("{}", err.message());
255            /// ```
256            pub fn message(&self) -> String {
257                self.0.message()
258            }
259
260            /// Checks if this error is a user error.
261            ///
262            /// Returns `true` if this error is a [Error::UserError],
263            /// otherwise `false`.
264            ///
265            /// # Examples
266            /// ```
267            /// use human_errors;
268            ///
269            /// let err = human_errors::user(
270            ///   "We could not open the config file you provided.",
271            ///   "Make sure that the file exists and is readable by the application.",
272            /// );
273            ///
274            /// // Prints "is_user?: true"
275            /// println!("is_user?: {}", err.is_user());
276            /// ```
277            pub fn is_user(&self) -> bool {
278                self.0.is_user()
279            }
280
281            /// Checks if this error is a system error.
282            ///
283            /// Returns `true` if this error is a [Error::SystemError],
284            /// otherwise `false`.
285            ///
286            /// # Examples
287            /// ```
288            /// use human_errors;
289            ///
290            /// let err = human_errors::system(
291            ///   "Failed to generate config file.",
292            ///   "Please file an error report on GitHub."
293            /// );
294            ///
295            /// // Prints "is_system?: true"
296            /// println!("is_system?: {}", err.is_system());
297            /// ```
298            pub fn is_system(&self) -> bool {
299                self.0.is_system()
300            }
301        }
302
303        impl std::error::Error for $type {
304            fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
305                self.0.source()
306            }
307        }
308
309        impl std::fmt::Display for $type {
310            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
311                self.0.fmt(f)
312            }
313        }
314    };
315}
316
317#[cfg(test)]
318mod tests {
319    error_shim!(MyError);
320
321    impl From<std::num::ParseIntError> for MyError {
322        fn from(err: std::num::ParseIntError) -> Self {
323            user_with_internal(
324                "We could not parse the number you provided.",
325                "Make sure that you're providing a number in the form 12345 or -12345.",
326                err,
327            )
328        }
329    }
330
331    #[test]
332    fn test_error_conversion() {
333        let err = user("Something exploded.", "Don't blow it up in future.");
334
335        assert_eq!(err.description(), "Something exploded.");
336    }
337}