| 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 | |