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: 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
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 /// 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
321pub 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