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 …> = 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 | /// let output = std::process::Command::new("ls" ) |
170 | /// .output()?; |
171 | /// |
172 | /// let output = if !output.status.success() { |
173 | /// let stderr = String::from_utf8_lossy(&output.stderr); |
174 | /// Err(eyre!("cmd exited with non-zero status code" )) |
175 | /// .with_section(move || stderr.trim().to_string().header("Stderr:" ))? |
176 | /// } else { |
177 | /// String::from_utf8_lossy(&output.stdout) |
178 | /// }; |
179 | /// |
180 | /// println!("{}" , output); |
181 | /// # Ok::<_, Report>(()) |
182 | /// ``` |
183 | fn with_section<D, F>(self, section: F) -> Self::Return |
184 | where |
185 | D: Display + Send + Sync + 'static, |
186 | F: FnOnce() -> D; |
187 | |
188 | /// Add an error section to an error report, to be displayed after the primary error message |
189 | /// section. |
190 | /// |
191 | /// # Examples |
192 | /// |
193 | /// ```rust,should_panic |
194 | /// use color_eyre::{eyre::eyre, eyre::Report, Section}; |
195 | /// use thiserror::Error; |
196 | /// |
197 | /// #[derive(Debug, Error)] |
198 | /// #[error("{0}" )] |
199 | /// struct StrError(&'static str); |
200 | /// |
201 | /// Err(eyre!("command failed" )) |
202 | /// .error(StrError("got one error" )) |
203 | /// .error(StrError("got a second error" ))?; |
204 | /// # Ok::<_, Report>(()) |
205 | /// ``` |
206 | fn error<E>(self, error: E) -> Self::Return |
207 | where |
208 | E: std::error::Error + Send + Sync + 'static; |
209 | |
210 | /// Add an error section to an error report, to be displayed after the primary error message |
211 | /// section. The closure to create the Section is lazily evaluated only in the case of an error. |
212 | /// |
213 | /// # Examples |
214 | /// |
215 | /// ```rust,should_panic |
216 | /// use color_eyre::{eyre::eyre, eyre::Report, Section}; |
217 | /// use thiserror::Error; |
218 | /// |
219 | /// #[derive(Debug, Error)] |
220 | /// #[error("{0}" )] |
221 | /// struct StringError(String); |
222 | /// |
223 | /// Err(eyre!("command failed" )) |
224 | /// .with_error(|| StringError("got one error" .into())) |
225 | /// .with_error(|| StringError("got a second error" .into()))?; |
226 | /// # Ok::<_, Report>(()) |
227 | /// ``` |
228 | fn with_error<E, F>(self, error: F) -> Self::Return |
229 | where |
230 | F: FnOnce() -> E, |
231 | E: std::error::Error + Send + Sync + 'static; |
232 | |
233 | /// Add a Note to an error report, to be displayed after the chain of errors. |
234 | /// |
235 | /// # Examples |
236 | /// |
237 | /// ```rust |
238 | /// # use std::{error::Error, fmt::{self, Display}}; |
239 | /// # use color_eyre::eyre::Result; |
240 | /// # #[derive(Debug)] |
241 | /// # struct FakeErr; |
242 | /// # impl Display for FakeErr { |
243 | /// # fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
244 | /// # write!(f, "FakeErr" ) |
245 | /// # } |
246 | /// # } |
247 | /// # impl std::error::Error for FakeErr {} |
248 | /// # fn main() -> Result<()> { |
249 | /// # fn fallible_fn() -> Result<(), FakeErr> { |
250 | /// # Ok(()) |
251 | /// # } |
252 | /// use color_eyre::Section as _; |
253 | /// |
254 | /// fallible_fn().note("This might have failed due to ..." )?; |
255 | /// # Ok(()) |
256 | /// # } |
257 | /// ``` |
258 | fn note<D>(self, note: D) -> Self::Return |
259 | where |
260 | D: Display + Send + Sync + 'static; |
261 | |
262 | /// Add a Note to an error report, to be displayed after the chain of errors. The closure to |
263 | /// create the Note is lazily evaluated only in the case of an error. |
264 | /// |
265 | /// # Examples |
266 | /// |
267 | /// ```rust |
268 | /// # use std::{error::Error, fmt::{self, Display}}; |
269 | /// # use color_eyre::eyre::Result; |
270 | /// # #[derive(Debug)] |
271 | /// # struct FakeErr; |
272 | /// # impl Display for FakeErr { |
273 | /// # fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
274 | /// # write!(f, "FakeErr" ) |
275 | /// # } |
276 | /// # } |
277 | /// # impl std::error::Error for FakeErr {} |
278 | /// # fn main() -> Result<()> { |
279 | /// # fn fallible_fn() -> Result<(), FakeErr> { |
280 | /// # Ok(()) |
281 | /// # } |
282 | /// use color_eyre::Section as _; |
283 | /// |
284 | /// fallible_fn().with_note(|| { |
285 | /// format!("This might have failed due to ... It has failed {} times" , 100) |
286 | /// })?; |
287 | /// # Ok(()) |
288 | /// # } |
289 | /// ``` |
290 | fn with_note<D, F>(self, f: F) -> Self::Return |
291 | where |
292 | D: Display + Send + Sync + 'static, |
293 | F: FnOnce() -> D; |
294 | |
295 | /// Add a Warning to an error report, to be displayed after the chain of errors. |
296 | fn warning<D>(self, warning: D) -> Self::Return |
297 | where |
298 | D: Display + Send + Sync + 'static; |
299 | |
300 | /// Add a Warning to an error report, to be displayed after the chain of errors. The closure to |
301 | /// create the Warning is lazily evaluated only in the case of an error. |
302 | fn with_warning<D, F>(self, f: F) -> Self::Return |
303 | where |
304 | D: Display + Send + Sync + 'static, |
305 | F: FnOnce() -> D; |
306 | |
307 | /// Add a Suggestion to an error report, to be displayed after the chain of errors. |
308 | fn suggestion<D>(self, suggestion: D) -> Self::Return |
309 | where |
310 | D: Display + Send + Sync + 'static; |
311 | |
312 | /// Add a Suggestion to an error report, to be displayed after the chain of errors. The closure |
313 | /// to create the Suggestion is lazily evaluated only in the case of an error. |
314 | fn with_suggestion<D, F>(self, f: F) -> Self::Return |
315 | where |
316 | D: Display + Send + Sync + 'static, |
317 | F: FnOnce() -> D; |
318 | } |
319 | |
320 | /// Trait for printing a panic error message for the given PanicInfo |
321 | pub trait PanicMessage: Send + Sync + 'static { |
322 | /// Display trait equivalent for implementing the display logic |
323 | fn display(&self, pi: &std::panic::PanicInfo<'_>, f: &mut fmt::Formatter<'_>) -> fmt::Result; |
324 | } |
325 | |