1 | //! Helpers for adding custom sections to error reports |
2 | use crate::writers::WriterExt; |
3 | use std::fmt::{self, Display}; |
4 | |
5 | #[cfg (feature = "issue-url" )] |
6 | pub(crate) mod github; |
7 | pub(crate) mod help; |
8 | |
9 | /// An indented section with a header for an error report |
10 | /// |
11 | /// # Details |
12 | /// |
13 | /// This helper provides two functions to help with constructing nicely formatted |
14 | /// error reports. First, it handles indentation of every line of the body for |
15 | /// you, and makes sure it is consistent with the rest of color-eyre's output. |
16 | /// Second, it omits outputting the header if the body itself is empty, |
17 | /// preventing unnecessary pollution of the report for sections with dynamic |
18 | /// content. |
19 | /// |
20 | /// # Examples |
21 | /// |
22 | /// ```rust |
23 | /// use color_eyre::{eyre::eyre, SectionExt, Section, eyre::Report}; |
24 | /// use std::process::Command; |
25 | /// use tracing::instrument; |
26 | /// |
27 | /// trait Output { |
28 | /// fn output2(&mut self) -> Result<String, Report>; |
29 | /// } |
30 | /// |
31 | /// impl Output for Command { |
32 | /// #[instrument] |
33 | /// fn output2(&mut self) -> Result<String, Report> { |
34 | /// let output = self.output()?; |
35 | /// |
36 | /// let stdout = String::from_utf8_lossy(&output.stdout); |
37 | /// |
38 | /// if !output.status.success() { |
39 | /// let stderr = String::from_utf8_lossy(&output.stderr); |
40 | /// Err(eyre!("cmd exited with non-zero status code" )) |
41 | /// .with_section(move || stdout.trim().to_string().header("Stdout:" )) |
42 | /// .with_section(move || stderr.trim().to_string().header("Stderr:" )) |
43 | /// } else { |
44 | /// Ok(stdout.into()) |
45 | /// } |
46 | /// } |
47 | /// } |
48 | /// ``` |
49 | #[allow (missing_debug_implementations)] |
50 | pub struct IndentedSection<H, B> { |
51 | header: H, |
52 | body: B, |
53 | } |
54 | |
55 | impl<H, B> fmt::Display for IndentedSection<H, B> |
56 | where |
57 | H: Display + Send + Sync + 'static, |
58 | B: Display + Send + Sync + 'static, |
59 | { |
60 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
61 | use std::fmt::Write; |
62 | let mut headered: HeaderWriter<'_, H, &mut Formatter<'_>> = f.header(&self.header); |
63 | let headered: ReadyHeaderWriter<'_, '_, …, …> = headered.ready(); |
64 | let mut headered: HeaderWriter<'_, str, ReadyHeaderWriter<'_, '_, …, …>> = headered.header(" \n" ); |
65 | |
66 | let mut headered: ReadyHeaderWriter<'_, '_, …, …> = headered.ready(); |
67 | |
68 | let mut indented: Indented<'_, ReadyHeaderWriter<'_, '_, …, …>> = indenterIndented<'_, ReadyHeaderWriter<'_, '_, …, …>>::indented(&mut headered) |
69 | .with_format(indenter::Format::Uniform { indentation: " " }); |
70 | |
71 | write!(&mut indented, " {}" , self.body)?; |
72 | |
73 | Ok(()) |
74 | } |
75 | } |
76 | |
77 | /// Extension trait for constructing sections with commonly used formats |
78 | pub trait SectionExt: Sized { |
79 | /// Add a header to a `Section` and indent the body |
80 | /// |
81 | /// # Details |
82 | /// |
83 | /// Bodies are always indented to the same level as error messages and spans. |
84 | /// The header is not printed if the display impl of the body produces no |
85 | /// output. |
86 | /// |
87 | /// # Examples |
88 | /// |
89 | /// ```rust,no_run |
90 | /// use color_eyre::{eyre::eyre, Section, SectionExt, eyre::Report}; |
91 | /// |
92 | /// let all_in_header = "header \n body \n body" ; |
93 | /// let report = Err::<(), Report>(eyre!("an error occurred" )) |
94 | /// .section(all_in_header) |
95 | /// .unwrap_err(); |
96 | /// |
97 | /// let just_header = "header" ; |
98 | /// let just_body = "body \nbody" ; |
99 | /// let report2 = Err::<(), Report>(eyre!("an error occurred" )) |
100 | /// .section(just_body.header(just_header)) |
101 | /// .unwrap_err(); |
102 | /// |
103 | /// assert_eq!(format!("{:?}" , report), format!("{:?}" , report2)) |
104 | /// ``` |
105 | fn header<C>(self, header: C) -> IndentedSection<C, Self> |
106 | where |
107 | C: Display + Send + Sync + 'static; |
108 | } |
109 | |
110 | impl<T> SectionExt for T |
111 | where |
112 | T: Display + Send + Sync + 'static, |
113 | { |
114 | fn header<C>(self, header: C) -> IndentedSection<C, Self> |
115 | where |
116 | C: Display + Send + Sync + 'static, |
117 | { |
118 | IndentedSection { body: self, header } |
119 | } |
120 | } |
121 | |
122 | /// A helper trait for attaching informational sections to error reports to be |
123 | /// displayed after the chain of errors |
124 | /// |
125 | /// # Details |
126 | /// |
127 | /// `color_eyre` provides two types of help text that can be attached to error reports: custom |
128 | /// sections and pre-configured sections. Custom sections are added via the `section` and |
129 | /// `with_section` methods, and give maximum control over formatting. |
130 | /// |
131 | /// The pre-configured sections are provided via `suggestion`, `warning`, and `note`. These |
132 | /// sections are displayed after all other sections with no extra newlines between subsequent Help |
133 | /// sections. They consist only of a header portion and are prepended with a colored string |
134 | /// indicating the kind of section, e.g. `Note: This might have failed due to ..." |
135 | pub trait Section: crate::private::Sealed { |
136 | /// The return type of each method after adding context |
137 | type Return; |
138 | |
139 | /// Add a section to an error report, to be displayed after the chain of errors. |
140 | /// |
141 | /// # Details |
142 | /// |
143 | /// Sections are displayed in the order they are added to the error report. They are displayed |
144 | /// immediately after the `Error:` section and before the `SpanTrace` and `Backtrace` sections. |
145 | /// They consist of a header and an optional body. The body of the section is indented by |
146 | /// default. |
147 | /// |
148 | /// # Examples |
149 | /// |
150 | /// ```rust,should_panic |
151 | /// use color_eyre::{eyre::eyre, eyre::Report, Section}; |
152 | /// |
153 | /// Err(eyre!("command failed" )) |
154 | /// .section("Please report bugs to https://real.url/bugs" )?; |
155 | /// # Ok::<_, Report>(()) |
156 | /// ``` |
157 | fn section<D>(self, section: D) -> Self::Return |
158 | where |
159 | D: Display + Send + Sync + 'static; |
160 | |
161 | /// Add a Section to an error report, to be displayed after the chain of errors. The closure to |
162 | /// create the Section is lazily evaluated only in the case of an error. |
163 | /// |
164 | /// # Examples |
165 | /// |
166 | /// ```rust |
167 | /// use color_eyre::{eyre::eyre, eyre::Report, Section, SectionExt}; |
168 | /// |
169 | /// # #[cfg (not(miri))] |
170 | /// # { |
171 | /// let output = std::process::Command::new("ls" ) |
172 | /// .output()?; |
173 | /// |
174 | /// let output = if !output.status.success() { |
175 | /// let stderr = String::from_utf8_lossy(&output.stderr); |
176 | /// Err(eyre!("cmd exited with non-zero status code" )) |
177 | /// .with_section(move || stderr.trim().to_string().header("Stderr:" ))? |
178 | /// } else { |
179 | /// String::from_utf8_lossy(&output.stdout) |
180 | /// }; |
181 | /// |
182 | /// println!("{}" , output); |
183 | /// # } |
184 | /// # Ok::<_, Report>(()) |
185 | /// ``` |
186 | fn with_section<D, F>(self, section: F) -> Self::Return |
187 | where |
188 | D: Display + Send + Sync + 'static, |
189 | F: FnOnce() -> D; |
190 | |
191 | /// Add an error section to an error report, to be displayed after the primary error message |
192 | /// section. |
193 | /// |
194 | /// # Examples |
195 | /// |
196 | /// ```rust,should_panic |
197 | /// use color_eyre::{eyre::eyre, eyre::Report, Section}; |
198 | /// use thiserror::Error; |
199 | /// |
200 | /// #[derive(Debug, Error)] |
201 | /// #[error("{0}" )] |
202 | /// struct StrError(&'static str); |
203 | /// |
204 | /// Err(eyre!("command failed" )) |
205 | /// .error(StrError("got one error" )) |
206 | /// .error(StrError("got a second error" ))?; |
207 | /// # Ok::<_, Report>(()) |
208 | /// ``` |
209 | fn error<E>(self, error: E) -> Self::Return |
210 | where |
211 | E: std::error::Error + Send + Sync + 'static; |
212 | |
213 | /// Add an error section to an error report, to be displayed after the primary error message |
214 | /// section. The closure to create the Section is lazily evaluated only in the case of an error. |
215 | /// |
216 | /// # Examples |
217 | /// |
218 | /// ```rust,should_panic |
219 | /// use color_eyre::{eyre::eyre, eyre::Report, Section}; |
220 | /// use thiserror::Error; |
221 | /// |
222 | /// #[derive(Debug, Error)] |
223 | /// #[error("{0}" )] |
224 | /// struct StringError(String); |
225 | /// |
226 | /// Err(eyre!("command failed" )) |
227 | /// .with_error(|| StringError("got one error" .into())) |
228 | /// .with_error(|| StringError("got a second error" .into()))?; |
229 | /// # Ok::<_, Report>(()) |
230 | /// ``` |
231 | fn with_error<E, F>(self, error: F) -> Self::Return |
232 | where |
233 | F: FnOnce() -> E, |
234 | E: std::error::Error + Send + Sync + 'static; |
235 | |
236 | /// Add a Note to an error report, to be displayed after the chain of errors. |
237 | /// |
238 | /// # Examples |
239 | /// |
240 | /// ```rust |
241 | /// # use std::{error::Error, fmt::{self, Display}}; |
242 | /// # use color_eyre::eyre::Result; |
243 | /// # #[derive(Debug)] |
244 | /// # struct FakeErr; |
245 | /// # impl Display for FakeErr { |
246 | /// # fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
247 | /// # write!(f, "FakeErr" ) |
248 | /// # } |
249 | /// # } |
250 | /// # impl std::error::Error for FakeErr {} |
251 | /// # fn main() -> Result<()> { |
252 | /// # fn fallible_fn() -> Result<(), FakeErr> { |
253 | /// # Ok(()) |
254 | /// # } |
255 | /// use color_eyre::Section as _; |
256 | /// |
257 | /// fallible_fn().note("This might have failed due to ..." )?; |
258 | /// # Ok(()) |
259 | /// # } |
260 | /// ``` |
261 | fn note<D>(self, note: D) -> Self::Return |
262 | where |
263 | D: Display + Send + Sync + 'static; |
264 | |
265 | /// Add a Note to an error report, to be displayed after the chain of errors. The closure to |
266 | /// create the Note is lazily evaluated only in the case of an error. |
267 | /// |
268 | /// # Examples |
269 | /// |
270 | /// ```rust |
271 | /// # use std::{error::Error, fmt::{self, Display}}; |
272 | /// # use color_eyre::eyre::Result; |
273 | /// # #[derive(Debug)] |
274 | /// # struct FakeErr; |
275 | /// # impl Display for FakeErr { |
276 | /// # fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
277 | /// # write!(f, "FakeErr" ) |
278 | /// # } |
279 | /// # } |
280 | /// # impl std::error::Error for FakeErr {} |
281 | /// # fn main() -> Result<()> { |
282 | /// # fn fallible_fn() -> Result<(), FakeErr> { |
283 | /// # Ok(()) |
284 | /// # } |
285 | /// use color_eyre::Section as _; |
286 | /// |
287 | /// fallible_fn().with_note(|| { |
288 | /// format!("This might have failed due to ... It has failed {} times" , 100) |
289 | /// })?; |
290 | /// # Ok(()) |
291 | /// # } |
292 | /// ``` |
293 | fn with_note<D, F>(self, f: F) -> Self::Return |
294 | where |
295 | D: Display + Send + Sync + 'static, |
296 | F: FnOnce() -> D; |
297 | |
298 | /// Add a Warning to an error report, to be displayed after the chain of errors. |
299 | fn warning<D>(self, warning: D) -> Self::Return |
300 | where |
301 | D: Display + Send + Sync + 'static; |
302 | |
303 | /// Add a Warning to an error report, to be displayed after the chain of errors. The closure to |
304 | /// create the Warning is lazily evaluated only in the case of an error. |
305 | fn with_warning<D, F>(self, f: F) -> Self::Return |
306 | where |
307 | D: Display + Send + Sync + 'static, |
308 | F: FnOnce() -> D; |
309 | |
310 | /// Add a Suggestion to an error report, to be displayed after the chain of errors. |
311 | fn suggestion<D>(self, suggestion: D) -> Self::Return |
312 | where |
313 | D: Display + Send + Sync + 'static; |
314 | |
315 | /// Add a Suggestion to an error report, to be displayed after the chain of errors. The closure |
316 | /// to create the Suggestion is lazily evaluated only in the case of an error. |
317 | fn with_suggestion<D, F>(self, f: F) -> Self::Return |
318 | where |
319 | D: Display + Send + Sync + 'static, |
320 | F: FnOnce() -> D; |
321 | |
322 | /// Whether to suppress printing of collected backtrace (if any). |
323 | /// |
324 | /// Useful for reporting "unexceptional" errors for which a backtrace |
325 | /// isn't really necessary. |
326 | fn suppress_backtrace(self, suppress: bool) -> Self::Return; |
327 | } |
328 | |
329 | /// Trait for printing a panic error message for the given PanicInfo |
330 | pub trait PanicMessage: Send + Sync + 'static { |
331 | /// Display trait equivalent for implementing the display logic |
332 | fn display(&self, pi: &std::panic::PanicInfo<'_>, f: &mut fmt::Formatter<'_>) -> fmt::Result; |
333 | } |
334 | |