color_eyre/section/mod.rs
1//! Helpers for adding custom sections to error reports
2use crate::writers::WriterExt;
3use std::fmt::{self, Display};
4
5#[cfg(feature = "issue-url")]
6pub(crate) mod github;
7pub(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)]
50pub struct IndentedSection<H, B> {
51 header: H,
52 body: B,
53}
54
55impl<H, B> fmt::Display for IndentedSection<H, B>
56where
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 = f.header(&self.header);
63 let headered = headered.ready();
64 let mut headered = headered.header("\n");
65
66 let mut headered = headered.ready();
67
68 let mut indented = indenter::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
78pub 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
110impl<T> SectionExt for T
111where
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 ..."
135pub 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
330pub 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}