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}