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}