/rust/registry/src/index.crates.io-6f17d22bba15001f/snafu-0.7.5/src/report.rs
Line | Count | Source (jump to first uncovered line) |
1 | | use crate::ChainCompat; |
2 | | use core::fmt; |
3 | | |
4 | | #[cfg(all(feature = "std", feature = "rust_1_61"))] |
5 | | use std::process::{ExitCode, Termination}; |
6 | | |
7 | | /// Opinionated solution to format an error in a user-friendly |
8 | | /// way. Useful as the return type from `main` and test functions. |
9 | | /// |
10 | | /// Most users will use the [`snafu::report`][] procedural macro |
11 | | /// instead of directly using this type, but you can if you do not |
12 | | /// wish to use the macro. |
13 | | /// |
14 | | /// [`snafu::report`]: macro@crate::report |
15 | | /// |
16 | | /// ## Rust 1.61 and up |
17 | | /// |
18 | | /// Change the return type of the function to [`Report`][] and wrap |
19 | | /// the body of your function with [`Report::capture`][]. |
20 | | /// |
21 | | /// ## Rust before 1.61 |
22 | | /// |
23 | | /// Use [`Report`][] as the error type inside of [`Result`][] and then |
24 | | /// call either [`Report::capture_into_result`][] or |
25 | | /// [`Report::from_error`][]. |
26 | | /// |
27 | | /// ## Nightly Rust |
28 | | /// |
29 | | /// Enabling the [`unstable-try-trait` feature flag][try-ff] will |
30 | | /// allow you to use the `?` operator directly: |
31 | | /// |
32 | | /// ```rust |
33 | | /// use snafu::{prelude::*, Report}; |
34 | | /// |
35 | | /// # #[cfg(all(feature = "unstable-try-trait", feature = "rust_1_61"))] |
36 | | /// fn main() -> Report<PlaceholderError> { |
37 | | /// let _v = may_fail_with_placeholder_error()?; |
38 | | /// |
39 | | /// Report::ok() |
40 | | /// } |
41 | | /// # #[cfg(not(all(feature = "unstable-try-trait", feature = "rust_1_61")))] fn main() {} |
42 | | /// # #[derive(Debug, Snafu)] |
43 | | /// # struct PlaceholderError; |
44 | | /// # fn may_fail_with_placeholder_error() -> Result<u8, PlaceholderError> { Ok(42) } |
45 | | /// ``` |
46 | | /// |
47 | | /// [try-ff]: crate::guide::feature_flags#unstable-try-trait |
48 | | /// |
49 | | /// ## Interaction with the Provider API |
50 | | /// |
51 | | /// If you return a [`Report`][] from your function and enable the |
52 | | /// [`unstable-provider-api` feature flag][provider-ff], additional |
53 | | /// capabilities will be added: |
54 | | /// |
55 | | /// 1. If provided, a [`Backtrace`][] will be included in the output. |
56 | | /// 1. If provided, a [`ExitCode`][] will be used as the return value. |
57 | | /// |
58 | | /// [provider-ff]: crate::guide::feature_flags#unstable-provider-api |
59 | | /// [`Backtrace`]: crate::Backtrace |
60 | | /// [`ExitCode`]: std::process::ExitCode |
61 | | /// |
62 | | /// ## Stability of the output |
63 | | /// |
64 | | /// The exact content and format of a displayed `Report` are not |
65 | | /// stable, but this type strives to print the error and as much |
66 | | /// user-relevant information in an easily-consumable manner |
67 | | pub struct Report<E>(Result<(), E>); |
68 | | |
69 | | impl<E> Report<E> { |
70 | | /// Convert an error into a [`Report`][]. |
71 | | /// |
72 | | /// Recommended if you support versions of Rust before 1.61. |
73 | | /// |
74 | | /// ```rust |
75 | | /// use snafu::{prelude::*, Report}; |
76 | | /// |
77 | | /// #[derive(Debug, Snafu)] |
78 | | /// struct PlaceholderError; |
79 | | /// |
80 | | /// fn main() -> Result<(), Report<PlaceholderError>> { |
81 | | /// let _v = may_fail_with_placeholder_error().map_err(Report::from_error)?; |
82 | | /// Ok(()) |
83 | | /// } |
84 | | /// |
85 | | /// fn may_fail_with_placeholder_error() -> Result<u8, PlaceholderError> { |
86 | | /// Ok(42) |
87 | | /// } |
88 | | /// ``` |
89 | 0 | pub fn from_error(error: E) -> Self { |
90 | 0 | Self(Err(error)) |
91 | 0 | } |
92 | | |
93 | | /// Executes a closure that returns a [`Result`][], converting the |
94 | | /// error variant into a [`Report`][]. |
95 | | /// |
96 | | /// Recommended if you support versions of Rust before 1.61. |
97 | | /// |
98 | | /// ```rust |
99 | | /// use snafu::{prelude::*, Report}; |
100 | | /// |
101 | | /// #[derive(Debug, Snafu)] |
102 | | /// struct PlaceholderError; |
103 | | /// |
104 | | /// fn main() -> Result<(), Report<PlaceholderError>> { |
105 | | /// Report::capture_into_result(|| { |
106 | | /// let _v = may_fail_with_placeholder_error()?; |
107 | | /// |
108 | | /// Ok(()) |
109 | | /// }) |
110 | | /// } |
111 | | /// |
112 | | /// fn may_fail_with_placeholder_error() -> Result<u8, PlaceholderError> { |
113 | | /// Ok(42) |
114 | | /// } |
115 | | /// ``` |
116 | 0 | pub fn capture_into_result<T>(body: impl FnOnce() -> Result<T, E>) -> Result<T, Self> { |
117 | 0 | body().map_err(Self::from_error) |
118 | 0 | } |
119 | | |
120 | | /// Executes a closure that returns a [`Result`][], converting any |
121 | | /// error to a [`Report`][]. |
122 | | /// |
123 | | /// Recommended if you only support Rust version 1.61 or above. |
124 | | /// |
125 | | /// ```rust |
126 | | /// use snafu::{prelude::*, Report}; |
127 | | /// |
128 | | /// #[derive(Debug, Snafu)] |
129 | | /// struct PlaceholderError; |
130 | | /// |
131 | | /// # #[cfg(feature = "rust_1_61")] |
132 | | /// fn main() -> Report<PlaceholderError> { |
133 | | /// Report::capture(|| { |
134 | | /// let _v = may_fail_with_placeholder_error()?; |
135 | | /// |
136 | | /// Ok(()) |
137 | | /// }) |
138 | | /// } |
139 | | /// # #[cfg(not(feature = "rust_1_61"))] fn main() {} |
140 | | /// |
141 | | /// fn may_fail_with_placeholder_error() -> Result<u8, PlaceholderError> { |
142 | | /// Ok(42) |
143 | | /// } |
144 | | /// ``` |
145 | 0 | pub fn capture(body: impl FnOnce() -> Result<(), E>) -> Self { |
146 | 0 | Self(body()) |
147 | 0 | } |
148 | | |
149 | | /// A [`Report`][] that indicates no error occurred. |
150 | 0 | pub const fn ok() -> Self { |
151 | 0 | Self(Ok(())) |
152 | 0 | } |
153 | | } |
154 | | |
155 | | impl<E> From<Result<(), E>> for Report<E> { |
156 | 0 | fn from(other: Result<(), E>) -> Self { |
157 | 0 | Self(other) |
158 | 0 | } |
159 | | } |
160 | | |
161 | | impl<E> fmt::Debug for Report<E> |
162 | | where |
163 | | E: crate::Error, |
164 | | { |
165 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
166 | 0 | fmt::Display::fmt(self, f) |
167 | 0 | } |
168 | | } |
169 | | |
170 | | impl<E> fmt::Display for Report<E> |
171 | | where |
172 | | E: crate::Error, |
173 | | { |
174 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
175 | 0 | match &self.0 { |
176 | 0 | Err(e) => fmt::Display::fmt(&ReportFormatter(e), f), |
177 | 0 | _ => Ok(()), |
178 | | } |
179 | 0 | } |
180 | | } |
181 | | |
182 | | #[cfg(all(feature = "std", feature = "rust_1_61"))] |
183 | | impl<E> Termination for Report<E> |
184 | | where |
185 | | E: crate::Error, |
186 | | { |
187 | | fn report(self) -> ExitCode { |
188 | | match self.0 { |
189 | | Ok(()) => ExitCode::SUCCESS, |
190 | | Err(e) => { |
191 | | eprintln!("{}", ReportFormatter(&e)); |
192 | | |
193 | | #[cfg(feature = "unstable-provider-api")] |
194 | | { |
195 | | use core::any; |
196 | | |
197 | | any::request_value::<ExitCode>(&e) |
198 | | .or_else(|| any::request_ref::<ExitCode>(&e).copied()) |
199 | | .unwrap_or(ExitCode::FAILURE) |
200 | | } |
201 | | |
202 | | #[cfg(not(feature = "unstable-provider-api"))] |
203 | | { |
204 | | ExitCode::FAILURE |
205 | | } |
206 | | } |
207 | | } |
208 | | } |
209 | | } |
210 | | |
211 | | #[cfg(feature = "unstable-try-trait")] |
212 | | impl<T, E> core::ops::FromResidual<Result<T, E>> for Report<E> { |
213 | | fn from_residual(residual: Result<T, E>) -> Self { |
214 | | Self(residual.map(drop)) |
215 | | } |
216 | | } |
217 | | |
218 | | struct ReportFormatter<'a>(&'a dyn crate::Error); |
219 | | |
220 | | impl<'a> fmt::Display for ReportFormatter<'a> { |
221 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
222 | 0 | #[cfg(feature = "std")] |
223 | 0 | { |
224 | 0 | if trace_cleaning_enabled() { |
225 | 0 | self.cleaned_error_trace(f)?; |
226 | | } else { |
227 | 0 | self.error_trace(f)?; |
228 | | } |
229 | | } |
230 | | |
231 | | #[cfg(not(feature = "std"))] |
232 | | { |
233 | | self.error_trace(f)?; |
234 | | } |
235 | | |
236 | | #[cfg(feature = "unstable-provider-api")] |
237 | | { |
238 | | use core::any; |
239 | | |
240 | | if let Some(bt) = any::request_ref::<crate::Backtrace>(self.0) { |
241 | | writeln!(f, "\nBacktrace:\n{}", bt)?; |
242 | | } |
243 | | } |
244 | | |
245 | 0 | Ok(()) |
246 | 0 | } |
247 | | } |
248 | | |
249 | | impl<'a> ReportFormatter<'a> { |
250 | 0 | fn error_trace(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { |
251 | 0 | writeln!(f, "{}", self.0)?; |
252 | | |
253 | 0 | let sources = ChainCompat::new(self.0).skip(1); |
254 | 0 | let plurality = sources.clone().take(2).count(); |
255 | 0 |
|
256 | 0 | match plurality { |
257 | 0 | 0 => {} |
258 | 0 | 1 => writeln!(f, "\nCaused by this error:")?, |
259 | 0 | _ => writeln!(f, "\nCaused by these errors (recent errors listed first):")?, |
260 | | } |
261 | | |
262 | 0 | for (i, source) in sources.enumerate() { |
263 | | // Let's use 1-based indexing for presentation |
264 | 0 | let i = i + 1; |
265 | 0 | writeln!(f, "{:3}: {}", i, source)?; |
266 | | } |
267 | | |
268 | 0 | Ok(()) |
269 | 0 | } |
270 | | |
271 | | #[cfg(feature = "std")] |
272 | 0 | fn cleaned_error_trace(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { |
273 | | const NOTE: char = '*'; |
274 | | |
275 | 0 | let mut any_cleaned = false; |
276 | 0 | let mut any_removed = false; |
277 | 0 | let cleaned_messages: Vec<_> = CleanedErrorText::new(self.0) |
278 | 0 | .flat_map(|(_, mut msg, cleaned)| { |
279 | 0 | if msg.is_empty() { |
280 | 0 | any_removed = true; |
281 | 0 | None |
282 | | } else { |
283 | 0 | if cleaned { |
284 | 0 | any_cleaned = true; |
285 | 0 | msg.push(' '); |
286 | 0 | msg.push(NOTE); |
287 | 0 | } |
288 | 0 | Some(msg) |
289 | | } |
290 | 0 | }) |
291 | 0 | .collect(); |
292 | 0 |
|
293 | 0 | let mut visible_messages = cleaned_messages.iter(); |
294 | | |
295 | 0 | let head = match visible_messages.next() { |
296 | 0 | Some(v) => v, |
297 | 0 | None => return Ok(()), |
298 | | }; |
299 | | |
300 | 0 | writeln!(f, "{}", head)?; |
301 | | |
302 | 0 | match cleaned_messages.len() { |
303 | 0 | 0 | 1 => {} |
304 | 0 | 2 => writeln!(f, "\nCaused by this error:")?, |
305 | 0 | _ => writeln!(f, "\nCaused by these errors (recent errors listed first):")?, |
306 | | } |
307 | | |
308 | 0 | for (i, msg) in visible_messages.enumerate() { |
309 | | // Let's use 1-based indexing for presentation |
310 | 0 | let i = i + 1; |
311 | 0 | writeln!(f, "{:3}: {}", i, msg)?; |
312 | | } |
313 | | |
314 | 0 | if any_cleaned || any_removed { |
315 | 0 | write!(f, "\nNOTE: ")?; |
316 | | |
317 | 0 | if any_cleaned { |
318 | 0 | write!( |
319 | 0 | f, |
320 | 0 | "Some redundant information has been removed from the lines marked with {}. ", |
321 | 0 | NOTE, |
322 | 0 | )?; |
323 | | } else { |
324 | 0 | write!(f, "Some redundant information has been removed. ")?; |
325 | | } |
326 | | |
327 | 0 | writeln!( |
328 | 0 | f, |
329 | 0 | "Set {}=1 to disable this behavior.", |
330 | 0 | SNAFU_RAW_ERROR_MESSAGES, |
331 | 0 | )?; |
332 | 0 | } |
333 | | |
334 | 0 | Ok(()) |
335 | 0 | } |
336 | | } |
337 | | |
338 | | #[cfg(feature = "std")] |
339 | | const SNAFU_RAW_ERROR_MESSAGES: &str = "SNAFU_RAW_ERROR_MESSAGES"; |
340 | | |
341 | | #[cfg(feature = "std")] |
342 | 0 | fn trace_cleaning_enabled() -> bool { |
343 | | use crate::once_bool::OnceBool; |
344 | | use std::env; |
345 | | |
346 | | static DISABLED: OnceBool = OnceBool::new(); |
347 | 0 | !DISABLED.get(|| env::var_os(SNAFU_RAW_ERROR_MESSAGES).map_or(false, |v| v == "1")) |
348 | 0 | } |
349 | | |
350 | | /// An iterator over an Error and its sources that removes duplicated |
351 | | /// text from the error display strings. |
352 | | /// |
353 | | /// It's common for errors with a `source` to have a `Display` |
354 | | /// implementation that includes their source text as well: |
355 | | /// |
356 | | /// ```text |
357 | | /// Outer error text: Middle error text: Inner error text |
358 | | /// ``` |
359 | | /// |
360 | | /// This works for smaller errors without much detail, but can be |
361 | | /// annoying when trying to format the error in a more structured way, |
362 | | /// such as line-by-line: |
363 | | /// |
364 | | /// ```text |
365 | | /// 1. Outer error text: Middle error text: Inner error text |
366 | | /// 2. Middle error text: Inner error text |
367 | | /// 3. Inner error text |
368 | | /// ``` |
369 | | /// |
370 | | /// This iterator compares each pair of errors in the source chain, |
371 | | /// removing the source error's text from the containing error's text: |
372 | | /// |
373 | | /// ```text |
374 | | /// 1. Outer error text |
375 | | /// 2. Middle error text |
376 | | /// 3. Inner error text |
377 | | /// ``` |
378 | | #[cfg(feature = "std")] |
379 | | pub struct CleanedErrorText<'a>(Option<CleanedErrorTextStep<'a>>); |
380 | | |
381 | | #[cfg(feature = "std")] |
382 | | impl<'a> CleanedErrorText<'a> { |
383 | | /// Constructs the iterator. |
384 | 0 | pub fn new(error: &'a dyn crate::Error) -> Self { |
385 | 0 | Self(Some(CleanedErrorTextStep::new(error))) |
386 | 0 | } |
387 | | } |
388 | | |
389 | | #[cfg(feature = "std")] |
390 | | impl<'a> Iterator for CleanedErrorText<'a> { |
391 | | /// The original error, the display string and if it has been cleaned |
392 | | type Item = (&'a dyn crate::Error, String, bool); |
393 | | |
394 | 0 | fn next(&mut self) -> Option<Self::Item> { |
395 | | use std::mem; |
396 | | |
397 | 0 | let mut step = self.0.take()?; |
398 | 0 | let mut error_text = mem::replace(&mut step.error_text, Default::default()); |
399 | 0 |
|
400 | 0 | match step.error.source() { |
401 | 0 | Some(next_error) => { |
402 | 0 | let next_error_text = next_error.to_string(); |
403 | 0 |
|
404 | 0 | let cleaned_text = error_text |
405 | 0 | .trim_end_matches(&next_error_text) |
406 | 0 | .trim_end() |
407 | 0 | .trim_end_matches(':'); |
408 | 0 | let cleaned = cleaned_text.len() != error_text.len(); |
409 | 0 | let cleaned_len = cleaned_text.len(); |
410 | 0 | error_text.truncate(cleaned_len); |
411 | 0 |
|
412 | 0 | self.0 = Some(CleanedErrorTextStep { |
413 | 0 | error: next_error, |
414 | 0 | error_text: next_error_text, |
415 | 0 | }); |
416 | 0 |
|
417 | 0 | Some((step.error, error_text, cleaned)) |
418 | | } |
419 | 0 | None => Some((step.error, error_text, false)), |
420 | | } |
421 | 0 | } |
422 | | } |
423 | | |
424 | | #[cfg(feature = "std")] |
425 | | struct CleanedErrorTextStep<'a> { |
426 | | error: &'a dyn crate::Error, |
427 | | error_text: String, |
428 | | } |
429 | | |
430 | | #[cfg(feature = "std")] |
431 | | impl<'a> CleanedErrorTextStep<'a> { |
432 | 0 | fn new(error: &'a dyn crate::Error) -> Self { |
433 | 0 | let error_text = error.to_string(); |
434 | 0 | Self { error, error_text } |
435 | 0 | } |
436 | | } |
437 | | |
438 | | #[doc(hidden)] |
439 | | pub trait __InternalExtractErrorType { |
440 | | type Err; |
441 | | } |
442 | | |
443 | | impl<T, E> __InternalExtractErrorType for core::result::Result<T, E> { |
444 | | type Err = E; |
445 | | } |