human_errors/error.rs
1use std::{error, fmt};
2
3/// The fundamental error type used by this library.
4///
5/// An error type which encapsulates information about whether an error
6/// is the result of something the user did, or a system failure outside
7/// of their control. These errors include a description of what occurred,
8/// advice on how to proceed and references to the causal chain which led
9/// to this failure.
10///
11/// # Examples
12/// ```
13/// use human_errors;
14///
15/// let err = human_errors::user(
16/// "We could not open the config file you provided.",
17/// "Make sure that the file exists and is readable by the application.",
18/// );
19///
20/// // Prints the error and any advice for the user.
21/// println!("{}", err)
22/// ```
23#[derive(Debug)]
24pub enum Error {
25 /// An error which was the result of actions that the user took.
26 ///
27 /// These errors are usually things which a user can easily resolve by
28 /// changing how they interact with the system. Advice should be used
29 /// to guide the user to the correct interaction paths and help them
30 /// self-mitigate without needing to open support tickets.
31 ///
32 /// These errors are usually generated with [`crate::user`], [`crate::user_with_cause`]
33 /// and [`crate::user_with_internal`].
34 UserError(
35 String,
36 String,
37 Option<Box<Error>>,
38 Option<Box<dyn error::Error + Send + Sync>>,
39 ),
40
41 /// An error which was the result of the system failing rather than the user's actions.
42 ///
43 /// These kinds of issues are usually the result of the system entering
44 /// an unexpected state and/or violating an assumption on behalf of the
45 /// developer. Often these issues cannot be resolved by the user directly,
46 /// so the advice should guide them to the best way to raise a bug with you
47 /// and provide you with information to help them fix the issue.
48 ///
49 /// These errors are usually generated with [`crate::system`], [`crate::system_with_cause`]
50 /// and [`crate::system_with_internal`].
51 SystemError(
52 String,
53 String,
54 Option<Box<Error>>,
55 Option<Box<dyn error::Error + Send + Sync>>,
56 ),
57}
58
59impl Error {
60 /// Gets the description message from this error.
61 ///
62 /// Gets the description which was provided as the first argument when constructing
63 /// this error.
64 ///
65 /// # Examples
66 /// ```
67 /// use human_errors;
68 ///
69 /// let err = human_errors::user(
70 /// "We could not open the config file you provided.",
71 /// "Make sure that the file exists and is readable by the application.",
72 /// );
73 ///
74 /// // Prints: "We could not open the config file you provided."
75 /// println!("{}", err.description())
76 /// ```
77 pub fn description(&self) -> String {
78 match self {
79 Error::UserError(description, ..) | Error::SystemError(description, ..) => {
80 description.clone()
81 }
82 }
83 }
84
85 /// Gets the formatted error and its advice.
86 ///
87 /// Generates a string containing the description of the error and any causes,
88 /// as well as a list of suggestions for how a user should
89 /// deal with this error. The "deepest" error's advice is presented first, with
90 /// successively higher errors appearing lower in the list. This is done because
91 /// the most specific error is the one most likely to have the best advice on how
92 /// to resolve the problem.
93 ///
94 /// # Examples
95 /// ```
96 /// use human_errors;
97 ///
98 /// let err = human_errors::user_with_cause(
99 /// "We could not open the config file you provided.",
100 /// "Make sure that you've specified a valid config file with the --config option.",
101 /// human_errors::user(
102 /// "We could not find a file at /home/user/.config/demo.yml",
103 /// "Make sure that the file exists and is readable by the application."
104 /// )
105 /// );
106 ///
107 /// // Prints a message like the following:
108 /// // Oh no! We could not open the config file you provided.
109 /// //
110 /// // This was caused by:
111 /// // We could not find a file at /home/user/.config/demo.yml
112 /// //
113 /// // To try and fix this, you can:
114 /// // - Make sure that the file exists and is readable by the application.
115 /// // - Make sure that you've specified a valid config file with the --config option.
116 /// println!("{}", err.message());
117 /// ```
118 pub fn message(&self) -> String {
119 let description = match self {
120 Error::UserError(description, ..) | Error::SystemError(description, ..) => description,
121 };
122
123 let hero_message = match self {
124 Error::UserError(_, _, _, _) => {
125 format!("Oh no! {}", description)
126 }
127 Error::SystemError(_, _, _, _) => {
128 format!("Whoops! {} (This isn't your fault)", description)
129 }
130 };
131
132 match (self.caused_by(), self.advice()) {
133 (Some(cause), Some(advice)) if !advice.is_empty() => {
134 format!(
135 "{}\n\nThis was caused by:\n{}\n\nTo try and fix this, you can:\n{}",
136 hero_message, cause, advice
137 )
138 }
139 (Some(cause), _) => {
140 format!("{}\n\nThis was caused by:\n{}", hero_message, cause)
141 }
142 (None, Some(advice)) if !advice.is_empty() => {
143 format!(
144 "{}\n\nTo try and fix this, you can:\n{}",
145 hero_message, advice
146 )
147 }
148 _ => hero_message,
149 }
150 }
151
152 fn caused_by(&self) -> Option<String> {
153 match self {
154 Error::UserError(.., Some(cause), _) | Error::SystemError(.., Some(cause), _) => {
155 match cause.caused_by() {
156 Some(child_cause) => {
157 Some(format!(" - {}\n{}", cause.description(), child_cause))
158 }
159 None => Some(format!(" - {}", cause.description())),
160 }
161 }
162 Error::UserError(.., Some(internal)) | Error::SystemError(.., Some(internal)) => {
163 Some(self.internal_caused_by(internal.as_ref()))
164 }
165 _ => None,
166 }
167 }
168
169 fn internal_caused_by(&self, error: &dyn error::Error) -> String {
170 match error.source() {
171 Some(source) => format!(" - {}\n{}", error, self.internal_caused_by(source)),
172 None => format!(" - {}", error),
173 }
174 }
175
176 fn advice(&self) -> Option<String> {
177 let (advice, cause) = match self {
178 Error::UserError(_, advice, cause, _) | Error::SystemError(_, advice, cause, _) => {
179 (advice, cause)
180 }
181 };
182
183 match cause {
184 // We bias towards the most specific advice first (i.e. the lowest-level error) because that's most likely to be correct.
185 Some(cause) => match (advice, cause.advice()) {
186 (advice, Some(cause_advice)) if !advice.is_empty() && !cause_advice.is_empty() => {
187 Some(format!("{}\n - {}", cause_advice, advice))
188 }
189 (advice, _) if !advice.is_empty() => Some(format!(" - {}", advice)),
190 (_, Some(cause_advice)) if !cause_advice.is_empty() => Some(cause_advice),
191 _ => None,
192 },
193 None if !advice.is_empty() => Some(format!(" - {}", advice)),
194 _ => None,
195 }
196 }
197
198 /// Checks if this error is a user error.
199 ///
200 /// Returns `true` if this error is a [Error::UserError],
201 /// otherwise `false`.
202 ///
203 /// # Examples
204 /// ```
205 /// use human_errors;
206 ///
207 /// let err = human_errors::user(
208 /// "We could not open the config file you provided.",
209 /// "Make sure that the file exists and is readable by the application.",
210 /// );
211 ///
212 /// // Prints "is_user?: true"
213 /// println!("is_user?: {}", err.is_user());
214 /// ```
215 pub fn is_user(&self) -> bool {
216 matches!(self, Error::UserError(..))
217 }
218
219 /// Checks if this error is a system error.
220 ///
221 /// Returns `true` if this error is a [Error::SystemError],
222 /// otherwise `false`.
223 ///
224 /// # Examples
225 /// ```
226 /// use human_errors;
227 ///
228 /// let err = human_errors::system(
229 /// "Failed to generate config file.",
230 /// "Please file an error report on GitHub."
231 /// );
232 ///
233 /// // Prints "is_system?: true"
234 /// println!("is_system?: {}", err.is_system());
235 /// ```
236 pub fn is_system(&self) -> bool {
237 matches!(self, Error::SystemError(..))
238 }
239}
240
241impl std::error::Error for Error {
242 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
243 match self {
244 Error::UserError(.., Some(ref err)) | Error::SystemError(.., Some(ref err)) => {
245 err.source()
246 }
247 _ => None,
248 }
249 }
250}
251
252impl fmt::Display for Error {
253 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
254 write!(f, "{}", self.message())
255 }
256}