mirror of
https://github.com/eyre-rs/eyre.git
synced 2025-09-27 13:01:29 +00:00
Add custom section support for reports (#18)
This commit is contained in:
parent
9f5defedb7
commit
7b2d9f4218
@ -29,6 +29,7 @@ once_cell = "1.4.0"
|
|||||||
tracing-subscriber = "0.2.5"
|
tracing-subscriber = "0.2.5"
|
||||||
tracing = "0.1.13"
|
tracing = "0.1.13"
|
||||||
pretty_assertions = "0.6.1"
|
pretty_assertions = "0.6.1"
|
||||||
|
thiserror = "1.0.19"
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
all-features = true
|
all-features = true
|
||||||
|
162
README.md
162
README.md
@ -11,8 +11,14 @@
|
|||||||
[docs-badge]: https://img.shields.io/badge/docs-latest-blue.svg
|
[docs-badge]: https://img.shields.io/badge/docs-latest-blue.svg
|
||||||
[docs-url]: https://docs.rs/color-eyre
|
[docs-url]: https://docs.rs/color-eyre
|
||||||
|
|
||||||
A custom context for the [`eyre`] crate for colorful error reports, suggestions,
|
A custom context for the [`eyre`] crate for colorful error reports with suggestions, custom
|
||||||
and [`tracing-error`] support.
|
sections, [`tracing-error`] support, and backtraces on stable.
|
||||||
|
|
||||||
|
## TLDR
|
||||||
|
|
||||||
|
`color_eyre` helps you build error reports that look like this:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
@ -48,17 +54,33 @@ eyre = "0.4"
|
|||||||
color-eyre = { version = "0.3", default-features = false }
|
color-eyre = { version = "0.3", default-features = false }
|
||||||
```
|
```
|
||||||
|
|
||||||
## Example
|
## Features
|
||||||
|
|
||||||
|
### Multiple report format verbosity levels
|
||||||
|
|
||||||
|
`color-eyre` provides 3 different report formats for how it formats the captured `SpanTrace`
|
||||||
|
and `Backtrace`, minimal, short, and full. Take the following example, taken from
|
||||||
|
[`examples/usage.rs`]:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use color_eyre::{Help, Report};
|
use color_eyre::{Help, Report};
|
||||||
use eyre::WrapErr;
|
use eyre::WrapErr;
|
||||||
use tracing::{info, instrument};
|
use tracing::{info, instrument};
|
||||||
use tracing_error::ErrorLayer;
|
|
||||||
use tracing_subscriber::prelude::*;
|
|
||||||
use tracing_subscriber::{fmt, EnvFilter};
|
|
||||||
|
|
||||||
|
#[instrument]
|
||||||
fn main() -> Result<(), Report> {
|
fn main() -> Result<(), Report> {
|
||||||
|
#[cfg(feature = "capture-spantrace")]
|
||||||
|
install_tracing();
|
||||||
|
|
||||||
|
Ok(read_config()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "capture-spantrace")]
|
||||||
|
fn install_tracing() {
|
||||||
|
use tracing_error::ErrorLayer;
|
||||||
|
use tracing_subscriber::prelude::*;
|
||||||
|
use tracing_subscriber::{fmt, EnvFilter};
|
||||||
|
|
||||||
let fmt_layer = fmt::layer().with_target(false);
|
let fmt_layer = fmt::layer().with_target(false);
|
||||||
let filter_layer = EnvFilter::try_from_default_env()
|
let filter_layer = EnvFilter::try_from_default_env()
|
||||||
.or_else(|_| EnvFilter::try_new("info"))
|
.or_else(|_| EnvFilter::try_new("info"))
|
||||||
@ -69,8 +91,6 @@ fn main() -> Result<(), Report> {
|
|||||||
.with(fmt_layer)
|
.with(fmt_layer)
|
||||||
.with(ErrorLayer::default())
|
.with(ErrorLayer::default())
|
||||||
.init();
|
.init();
|
||||||
|
|
||||||
Ok(read_config()?)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument]
|
#[instrument]
|
||||||
@ -87,17 +107,112 @@ fn read_config() -> Result<(), Report> {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Minimal Report Format
|
---
|
||||||
|
|
||||||

|
Running `cargo run --example usage` without `RUST_LIB_BACKTRACE` set will produce a minimal
|
||||||
|
report like this:
|
||||||
|
|
||||||
## Short Report Format (with `RUST_LIB_BACKTRACE=1`)
|

|
||||||
|
|
||||||

|
<br>
|
||||||
|
|
||||||
## Full Report Format (with `RUST_LIB_BACKTRACE=full`)
|
Running `RUST_LIB_BACKTRACE=1 cargo run --example usage` tells `color-eyre` to use the short
|
||||||
|
format, which additionally capture a [`backtrace::Backtrace`]:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
Finally, running `RUST_LIB_BACKTRACE=full cargo run --example usage` tells `color-eyre` to use
|
||||||
|
the full format, which in addition to the above will attempt to include source lines where the
|
||||||
|
error originated from, assuming it can find them on the disk.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Custom `Section`s for error reports via [`Help`] trait
|
||||||
|
|
||||||
|
The `section` module provides helpers for adding extra sections to error reports. Sections are
|
||||||
|
disinct from error messages and are displayed independently from the chain of errors. Take this
|
||||||
|
example of adding sections to contain `stderr` and `stdout` from a failed command, taken from
|
||||||
|
[`examples/custom_section.rs`]:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use color_eyre::{SectionExt, Help, Report};
|
||||||
|
use eyre::eyre;
|
||||||
|
use std::process::Command;
|
||||||
|
use tracing::instrument;
|
||||||
|
|
||||||
|
trait Output {
|
||||||
|
fn output2(&mut self) -> Result<String, Report>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Output for Command {
|
||||||
|
#[instrument]
|
||||||
|
fn output2(&mut self) -> Result<String, Report> {
|
||||||
|
let output = self.output()?;
|
||||||
|
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
Err(eyre!("cmd exited with non-zero status code"))
|
||||||
|
.with_section(move || {
|
||||||
|
"Stdout:"
|
||||||
|
.skip_if(|| stdout.is_empty())
|
||||||
|
.body(stdout.trim().to_string())
|
||||||
|
})
|
||||||
|
.with_section(move || {
|
||||||
|
"Stderr:"
|
||||||
|
.skip_if(|| stderr.is_empty())
|
||||||
|
.body(stderr.trim().to_string())
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(stdout.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Here we have an function that, if the command exits unsuccessfully, creates a report indicating
|
||||||
|
the failure and attaches two sections, one for `stdout` and one for `stderr`. Each section
|
||||||
|
includes a short header and a body that contains the actual output. Additionally these sections
|
||||||
|
use `skip_if` to tell the report not to include them if there was no output, preventing empty
|
||||||
|
sections from polluting the end report.
|
||||||
|
|
||||||
|
Running `cargo run --example custom_section` shows us how these sections are included in the
|
||||||
|
output:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Only the `Stderr:` section actually gets included. The `cat` command fails, so stdout ends up
|
||||||
|
being empty and is skipped in the final report. This gives us a short and concise error report
|
||||||
|
indicating exactly what was attempted and how it failed.
|
||||||
|
|
||||||
|
### Aggregating multiple errors into one report
|
||||||
|
|
||||||
|
It's not uncommon for programs like batched task runners or parsers to want to
|
||||||
|
return an error with multiple sources. The current version of the error trait
|
||||||
|
does not support this use case very well, though there is [work being
|
||||||
|
done](https://github.com/rust-lang/rfcs/pull/2895) to improve this.
|
||||||
|
|
||||||
|
For now however one way to work around this is to compose errors outside the
|
||||||
|
error trait. `color-eyre` supports such composition in its error reports via
|
||||||
|
the `Help` trait.
|
||||||
|
|
||||||
|
For an example of how to aggregate errors check out [`examples/multiple_errors.rs`].
|
||||||
|
|
||||||
|
### Custom configuration for `color-backtrace` for setting custom filters and more
|
||||||
|
|
||||||
|
The pretty printing for backtraces and span traces isn't actually provided by `color-eyre`, but
|
||||||
|
instead comes from its dependencies [`color-backtrace`] and [`color-spantrace`].
|
||||||
|
`color-backtrace` in particular has many more features than are exported by `color-eyre`, such
|
||||||
|
as customized color schemes, panic hooks, and custom frame filters. The custom frame filters
|
||||||
|
are particularly useful when combined with `color-eyre`, so to enable their usage we provide
|
||||||
|
the `install` fn for setting up a custom `BacktracePrinter` with custom filters installed.
|
||||||
|
|
||||||
|
For an example of how to setup custom filters, check out [`examples/custom_filter.rs`].
|
||||||
|
|
||||||
## Explanation
|
## Explanation
|
||||||
|
|
||||||
@ -112,17 +227,7 @@ pub type Report = eyre::Report<Context>;
|
|||||||
pub type Result<T, E = Report> = core::result::Result<T, E>;
|
pub type Result<T, E = Report> = core::result::Result<T, E>;
|
||||||
```
|
```
|
||||||
|
|
||||||
## Features
|
Please refer to the [`Context`] type's docs for more details about its feature set.
|
||||||
|
|
||||||
- captures a [`backtrace::Backtrace`] and prints using [`color-backtrace`]
|
|
||||||
- captures a [`tracing_error::SpanTrace`] and prints using
|
|
||||||
[`color-spantrace`]
|
|
||||||
- Only capture SpanTrace by default for better performance.
|
|
||||||
- display source lines when `RUST_LIB_BACKTRACE=full` is set
|
|
||||||
- store help text via [`Help`] trait and display after final report
|
|
||||||
- custom `color-backtrace` configuration via `color_eyre::install`, such as
|
|
||||||
adding custom frame filters
|
|
||||||
|
|
||||||
|
|
||||||
[`eyre`]: https://docs.rs/eyre
|
[`eyre`]: https://docs.rs/eyre
|
||||||
[`tracing-error`]: https://docs.rs/tracing-error
|
[`tracing-error`]: https://docs.rs/tracing-error
|
||||||
@ -131,9 +236,14 @@ pub type Result<T, E = Report> = core::result::Result<T, E>;
|
|||||||
[`backtrace::Backtrace`]: https://docs.rs/backtrace/*/backtrace/struct.Backtrace.html
|
[`backtrace::Backtrace`]: https://docs.rs/backtrace/*/backtrace/struct.Backtrace.html
|
||||||
[`tracing_error::SpanTrace`]: https://docs.rs/tracing-error/*/tracing_error/struct.SpanTrace.html
|
[`tracing_error::SpanTrace`]: https://docs.rs/tracing-error/*/tracing_error/struct.SpanTrace.html
|
||||||
[`color-spantrace`]: https://github.com/yaahc/color-spantrace
|
[`color-spantrace`]: https://github.com/yaahc/color-spantrace
|
||||||
[`Help`]: trait.Help.html
|
[`Help`]: https://docs.rs/color-eyre/*/color_eyre/trait.Help.html
|
||||||
[`eyre::Report`]: https://docs.rs/eyre/*/eyre/struct.Report.html
|
[`eyre::Report`]: https://docs.rs/eyre/*/eyre/struct.Report.html
|
||||||
[`eyre::Result`]: https://docs.rs/eyre/*/eyre/type.Result.html
|
[`eyre::Result`]: https://docs.rs/eyre/*/eyre/type.Result.html
|
||||||
|
[`Context`]: https://docs.rs/color-eyre/*/color_eyre/struct.Context.html
|
||||||
|
[`examples/usage.rs`]: https://github.com/yaahc/color-eyre/blob/master/examples/usage.rs
|
||||||
|
[`examples/custom_filter.rs`]: https://github.com/yaahc/color-eyre/blob/master/examples/custom_filter.rs
|
||||||
|
[`examples/custom_section.rs`]: https://github.com/yaahc/color-eyre/blob/master/examples/custom_section.rs
|
||||||
|
[`examples/multiple_errors.rs`]: https://github.com/yaahc/color-eyre/blob/master/examples/multiple_errors.rs
|
||||||
|
|
||||||
#### License
|
#### License
|
||||||
|
|
||||||
|
72
examples/custom_section.rs
Normal file
72
examples/custom_section.rs
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
use color_eyre::{Help, Report, SectionExt};
|
||||||
|
use eyre::{eyre, WrapErr};
|
||||||
|
use std::process::Command;
|
||||||
|
use tracing::instrument;
|
||||||
|
|
||||||
|
trait Output {
|
||||||
|
fn output2(&mut self) -> Result<String, Report>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Output for Command {
|
||||||
|
#[instrument]
|
||||||
|
fn output2(&mut self) -> Result<String, Report> {
|
||||||
|
let output = self.output()?;
|
||||||
|
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
Err(eyre!("cmd exited with non-zero status code"))
|
||||||
|
.with_section(move || {
|
||||||
|
"Stdout:"
|
||||||
|
.skip_if(|| stdout.is_empty())
|
||||||
|
.body(stdout.trim().to_string())
|
||||||
|
})
|
||||||
|
.with_section(move || {
|
||||||
|
"Stderr:"
|
||||||
|
.skip_if(|| stderr.is_empty())
|
||||||
|
.body(stderr.trim().to_string())
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(stdout.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument]
|
||||||
|
fn main() -> Result<(), Report> {
|
||||||
|
#[cfg(feature = "capture-spantrace")]
|
||||||
|
install_tracing();
|
||||||
|
|
||||||
|
Ok(read_config().map(drop)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "capture-spantrace")]
|
||||||
|
fn install_tracing() {
|
||||||
|
use tracing_error::ErrorLayer;
|
||||||
|
use tracing_subscriber::prelude::*;
|
||||||
|
use tracing_subscriber::{fmt, EnvFilter};
|
||||||
|
|
||||||
|
let fmt_layer = fmt::layer().with_target(false);
|
||||||
|
let filter_layer = EnvFilter::try_from_default_env()
|
||||||
|
.or_else(|_| EnvFilter::try_new("info"))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
tracing_subscriber::registry()
|
||||||
|
.with(filter_layer)
|
||||||
|
.with(fmt_layer)
|
||||||
|
.with(ErrorLayer::default())
|
||||||
|
.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument]
|
||||||
|
fn read_file(path: &str) -> Result<String, Report> {
|
||||||
|
Command::new("cat").arg("fake_file").output2()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument]
|
||||||
|
fn read_config() -> Result<String, Report> {
|
||||||
|
read_file("fake_file")
|
||||||
|
.wrap_err("Unable to read config")
|
||||||
|
.suggestion("try using a file that exists next time")
|
||||||
|
}
|
53
examples/multiple_errors.rs
Normal file
53
examples/multiple_errors.rs
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
use color_eyre::{Help, Report};
|
||||||
|
use eyre::eyre;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
fn main() -> Result<(), Report> {
|
||||||
|
let errors = get_errors();
|
||||||
|
join_errors(errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn join_errors(results: Vec<Result<(), SourceError>>) -> Result<(), Report> {
|
||||||
|
if results.iter().all(|r| r.is_ok()) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
results
|
||||||
|
.into_iter()
|
||||||
|
.filter(Result::is_err)
|
||||||
|
.map(Result::unwrap_err)
|
||||||
|
.fold(Err(eyre!("encountered multiple errors")), |report, e| {
|
||||||
|
report.error(e)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function to generate errors
|
||||||
|
fn get_errors() -> Vec<Result<(), SourceError>> {
|
||||||
|
vec![
|
||||||
|
Err(SourceError {
|
||||||
|
source: StrError("The task you ran encountered an error"),
|
||||||
|
msg: "The task could not be completed",
|
||||||
|
}),
|
||||||
|
Err(SourceError {
|
||||||
|
source: StrError("The machine you're connecting to is actively on fire"),
|
||||||
|
msg: "The machine is unreachable",
|
||||||
|
}),
|
||||||
|
Err(SourceError {
|
||||||
|
source: StrError("The file you're parsing is literally written in c++ instead of rust, what the hell"),
|
||||||
|
msg: "The file could not be parsed",
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Arbitrary error type for demonstration purposes
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
#[error("{0}")]
|
||||||
|
struct StrError(&'static str);
|
||||||
|
|
||||||
|
/// Arbitrary error type for demonstration purposes with a source error
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
#[error("{msg}")]
|
||||||
|
struct SourceError {
|
||||||
|
msg: &'static str,
|
||||||
|
source: StrError,
|
||||||
|
}
|
BIN
pictures/custom_section.png
Normal file
BIN
pictures/custom_section.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 131 KiB |
218
src/help.rs
218
src/help.rs
@ -1,218 +0,0 @@
|
|||||||
use crate::{Report, Result};
|
|
||||||
use ansi_term::Color::*;
|
|
||||||
use std::fmt::{self, Display};
|
|
||||||
|
|
||||||
/// A helper trait for attaching help text to errors to be displayed after the chain of errors
|
|
||||||
pub trait Help<T>: private::Sealed {
|
|
||||||
/// Add a note to an error, to be displayed after the chain of errors.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # use std::{error::Error, fmt::{self, Display}};
|
|
||||||
/// # use color_eyre::Result;
|
|
||||||
/// # #[derive(Debug)]
|
|
||||||
/// # struct FakeErr;
|
|
||||||
/// # impl Display for FakeErr {
|
|
||||||
/// # fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
/// # write!(f, "FakeErr")
|
|
||||||
/// # }
|
|
||||||
/// # }
|
|
||||||
/// # impl std::error::Error for FakeErr {}
|
|
||||||
/// # fn main() -> Result<()> {
|
|
||||||
/// # fn fallible_fn() -> Result<(), FakeErr> {
|
|
||||||
/// # Ok(())
|
|
||||||
/// # }
|
|
||||||
/// use color_eyre::Help as _;
|
|
||||||
///
|
|
||||||
/// fallible_fn().note("This might have failed due to ...")?;
|
|
||||||
/// # Ok(())
|
|
||||||
/// # }
|
|
||||||
/// ```
|
|
||||||
fn note<C>(self, context: C) -> Result<T>
|
|
||||||
where
|
|
||||||
C: Display + Send + Sync + 'static;
|
|
||||||
|
|
||||||
/// Add a Note to an error, to be displayed after the chain of errors, which is lazily
|
|
||||||
/// evaluated only in the case of an error.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # use std::{error::Error, fmt::{self, Display}};
|
|
||||||
/// # use color_eyre::Result;
|
|
||||||
/// # #[derive(Debug)]
|
|
||||||
/// # struct FakeErr;
|
|
||||||
/// # impl Display for FakeErr {
|
|
||||||
/// # fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
/// # write!(f, "FakeErr")
|
|
||||||
/// # }
|
|
||||||
/// # }
|
|
||||||
/// # impl std::error::Error for FakeErr {}
|
|
||||||
/// # fn main() -> Result<()> {
|
|
||||||
/// # fn fallible_fn() -> Result<(), FakeErr> {
|
|
||||||
/// # Ok(())
|
|
||||||
/// # }
|
|
||||||
/// use color_eyre::Help as _;
|
|
||||||
///
|
|
||||||
/// fallible_fn().with_note(|| {
|
|
||||||
/// format!("This might have failed due to ... It has failed {} times", 100)
|
|
||||||
/// })?;
|
|
||||||
/// # Ok(())
|
|
||||||
/// # }
|
|
||||||
/// ```
|
|
||||||
fn with_note<C, F>(self, f: F) -> Result<T>
|
|
||||||
where
|
|
||||||
C: Display + Send + Sync + 'static,
|
|
||||||
F: FnOnce() -> C;
|
|
||||||
|
|
||||||
/// Add a Warning to an error, to be displayed after the chain of errors.
|
|
||||||
fn warning<C>(self, context: C) -> Result<T>
|
|
||||||
where
|
|
||||||
C: Display + Send + Sync + 'static;
|
|
||||||
|
|
||||||
/// Add a Warning to an error, to be displayed after the chain of errors, which is lazily
|
|
||||||
/// evaluated only in the case of an error.
|
|
||||||
fn with_warning<C, F>(self, f: F) -> Result<T>
|
|
||||||
where
|
|
||||||
C: Display + Send + Sync + 'static,
|
|
||||||
F: FnOnce() -> C;
|
|
||||||
|
|
||||||
/// Add a Suggestion to an error, to be displayed after the chain of errors.
|
|
||||||
fn suggestion<C>(self, context: C) -> Result<T>
|
|
||||||
where
|
|
||||||
C: Display + Send + Sync + 'static;
|
|
||||||
|
|
||||||
/// Add a Suggestion to an error, to be displayed after the chain of errors, which is lazily
|
|
||||||
/// evaluated only in the case of an error.
|
|
||||||
fn with_suggestion<C, F>(self, f: F) -> Result<T>
|
|
||||||
where
|
|
||||||
C: Display + Send + Sync + 'static,
|
|
||||||
F: FnOnce() -> C;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, E> Help<T> for std::result::Result<T, E>
|
|
||||||
where
|
|
||||||
E: Into<Report>,
|
|
||||||
{
|
|
||||||
fn note<C>(self, context: C) -> Result<T>
|
|
||||||
where
|
|
||||||
C: Display + Send + Sync + 'static,
|
|
||||||
{
|
|
||||||
self.map_err(|e| {
|
|
||||||
let mut e = e.into();
|
|
||||||
e.context_mut().help.push(HelpInfo::Note(Box::new(context)));
|
|
||||||
e
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn with_note<C, F>(self, context: F) -> Result<T>
|
|
||||||
where
|
|
||||||
C: Display + Send + Sync + 'static,
|
|
||||||
F: FnOnce() -> C,
|
|
||||||
{
|
|
||||||
self.map_err(|e| {
|
|
||||||
let mut e = e.into();
|
|
||||||
e.context_mut()
|
|
||||||
.help
|
|
||||||
.push(HelpInfo::Note(Box::new(context())));
|
|
||||||
e
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn warning<C>(self, context: C) -> Result<T>
|
|
||||||
where
|
|
||||||
C: Display + Send + Sync + 'static,
|
|
||||||
{
|
|
||||||
self.map_err(|e| {
|
|
||||||
let mut e = e.into();
|
|
||||||
e.context_mut()
|
|
||||||
.help
|
|
||||||
.push(HelpInfo::Warning(Box::new(context)));
|
|
||||||
e
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn with_warning<C, F>(self, context: F) -> Result<T>
|
|
||||||
where
|
|
||||||
C: Display + Send + Sync + 'static,
|
|
||||||
F: FnOnce() -> C,
|
|
||||||
{
|
|
||||||
self.map_err(|e| {
|
|
||||||
let mut e = e.into();
|
|
||||||
e.context_mut()
|
|
||||||
.help
|
|
||||||
.push(HelpInfo::Warning(Box::new(context())));
|
|
||||||
e
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn suggestion<C>(self, context: C) -> Result<T>
|
|
||||||
where
|
|
||||||
C: Display + Send + Sync + 'static,
|
|
||||||
{
|
|
||||||
self.map_err(|e| {
|
|
||||||
let mut e = e.into();
|
|
||||||
e.context_mut()
|
|
||||||
.help
|
|
||||||
.push(HelpInfo::Suggestion(Box::new(context)));
|
|
||||||
e
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn with_suggestion<C, F>(self, context: F) -> Result<T>
|
|
||||||
where
|
|
||||||
C: Display + Send + Sync + 'static,
|
|
||||||
F: FnOnce() -> C,
|
|
||||||
{
|
|
||||||
self.map_err(|e| {
|
|
||||||
let mut e = e.into();
|
|
||||||
e.context_mut()
|
|
||||||
.help
|
|
||||||
.push(HelpInfo::Suggestion(Box::new(context())));
|
|
||||||
e
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) enum HelpInfo {
|
|
||||||
Note(Box<dyn Display + Send + Sync + 'static>),
|
|
||||||
Warning(Box<dyn Display + Send + Sync + 'static>),
|
|
||||||
Suggestion(Box<dyn Display + Send + Sync + 'static>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for HelpInfo {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::Note(context) => write!(f, "{}: {}", Cyan.paint("Note"), context),
|
|
||||||
Self::Warning(context) => write!(f, "{}: {}", Yellow.paint("Warning"), context),
|
|
||||||
Self::Suggestion(context) => write!(f, "{}: {}", Cyan.paint("Suggestion"), context),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for HelpInfo {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::Note(context) => f
|
|
||||||
.debug_tuple("Note")
|
|
||||||
.field(&format_args!("{}", context))
|
|
||||||
.finish(),
|
|
||||||
Self::Warning(context) => f
|
|
||||||
.debug_tuple("Warning")
|
|
||||||
.field(&format_args!("{}", context))
|
|
||||||
.finish(),
|
|
||||||
Self::Suggestion(context) => f
|
|
||||||
.debug_tuple("Suggestion")
|
|
||||||
.field(&format_args!("{}", context))
|
|
||||||
.finish(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) mod private {
|
|
||||||
use crate::Report;
|
|
||||||
pub trait Sealed {}
|
|
||||||
|
|
||||||
impl<T, E> Sealed for std::result::Result<T, E> where E: Into<Report> {}
|
|
||||||
}
|
|
179
src/lib.rs
179
src/lib.rs
@ -1,5 +1,11 @@
|
|||||||
//! A custom context for the [`eyre`] crate for colorful error reports, suggestions,
|
//! A custom context for the [`eyre`] crate for colorful error reports with suggestions, custom
|
||||||
//! and [`tracing-error`] support.
|
//! sections, [`tracing-error`] support, and backtraces on stable.
|
||||||
|
//!
|
||||||
|
//! ## TLDR
|
||||||
|
//!
|
||||||
|
//! `color_eyre` helps you build error reports that look like this:
|
||||||
|
//!
|
||||||
|
//! 
|
||||||
//!
|
//!
|
||||||
//! ## Setup
|
//! ## Setup
|
||||||
//!
|
//!
|
||||||
@ -35,7 +41,13 @@
|
|||||||
//! color-eyre = { version = "0.3", default-features = false }
|
//! color-eyre = { version = "0.3", default-features = false }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! ## Example
|
//! ## Features
|
||||||
|
//!
|
||||||
|
//! ### Multiple report format verbosity levels
|
||||||
|
//!
|
||||||
|
//! `color-eyre` provides 3 different report formats for how it formats the captured `SpanTrace`
|
||||||
|
//! and `Backtrace`, minimal, short, and full. Take the following example, taken from
|
||||||
|
//! [`examples/usage.rs`]:
|
||||||
//!
|
//!
|
||||||
//! ```rust,should_panic
|
//! ```rust,should_panic
|
||||||
//! use color_eyre::{Help, Report};
|
//! use color_eyre::{Help, Report};
|
||||||
@ -82,18 +94,113 @@
|
|||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! ## Minimal Report Format
|
//! ---
|
||||||
|
//!
|
||||||
|
//! Running `cargo run --example usage` without `RUST_LIB_BACKTRACE` set will produce a minimal
|
||||||
|
//! report like this:
|
||||||
//!
|
//!
|
||||||
//! 
|
//! 
|
||||||
//!
|
//!
|
||||||
//! ## Short Report Format (with `RUST_LIB_BACKTRACE=1`)
|
//! <br>
|
||||||
|
//!
|
||||||
|
//! Running `RUST_LIB_BACKTRACE=1 cargo run --example usage` tells `color-eyre` to use the short
|
||||||
|
//! format, which additionally capture a [`backtrace::Backtrace`]:
|
||||||
//!
|
//!
|
||||||
//! 
|
//! 
|
||||||
//!
|
//!
|
||||||
//! ## Full Report Format (with `RUST_LIB_BACKTRACE=full`)
|
//! <br>
|
||||||
|
//!
|
||||||
|
//! Finally, running `RUST_LIB_BACKTRACE=full cargo run --example usage` tells `color-eyre` to use
|
||||||
|
//! the full format, which in addition to the above will attempt to include source lines where the
|
||||||
|
//! error originated from, assuming it can find them on the disk.
|
||||||
//!
|
//!
|
||||||
//! 
|
//! 
|
||||||
//!
|
//!
|
||||||
|
//! ### Custom `Section`s for error reports via [`Help`] trait
|
||||||
|
//!
|
||||||
|
//! The `section` module provides helpers for adding extra sections to error reports. Sections are
|
||||||
|
//! disinct from error messages and are displayed independently from the chain of errors. Take this
|
||||||
|
//! example of adding sections to contain `stderr` and `stdout` from a failed command, taken from
|
||||||
|
//! [`examples/custom_section.rs`]:
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use color_eyre::{SectionExt, Help, Report};
|
||||||
|
//! use eyre::eyre;
|
||||||
|
//! use std::process::Command;
|
||||||
|
//! use tracing::instrument;
|
||||||
|
//!
|
||||||
|
//! trait Output {
|
||||||
|
//! fn output2(&mut self) -> Result<String, Report>;
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! impl Output for Command {
|
||||||
|
//! #[instrument]
|
||||||
|
//! fn output2(&mut self) -> Result<String, Report> {
|
||||||
|
//! let output = self.output()?;
|
||||||
|
//!
|
||||||
|
//! let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
|
//!
|
||||||
|
//! if !output.status.success() {
|
||||||
|
//! let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
//! Err(eyre!("cmd exited with non-zero status code"))
|
||||||
|
//! .with_section(move || {
|
||||||
|
//! "Stdout:"
|
||||||
|
//! .skip_if(|| stdout.is_empty())
|
||||||
|
//! .body(stdout.trim().to_string())
|
||||||
|
//! })
|
||||||
|
//! .with_section(move || {
|
||||||
|
//! "Stderr:"
|
||||||
|
//! .skip_if(|| stderr.is_empty())
|
||||||
|
//! .body(stderr.trim().to_string())
|
||||||
|
//! })
|
||||||
|
//! } else {
|
||||||
|
//! Ok(stdout.into())
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ---
|
||||||
|
//!
|
||||||
|
//! Here we have an function that, if the command exits unsuccessfully, creates a report indicating
|
||||||
|
//! the failure and attaches two sections, one for `stdout` and one for `stderr`. Each section
|
||||||
|
//! includes a short header and a body that contains the actual output. Additionally these sections
|
||||||
|
//! use `skip_if` to tell the report not to include them if there was no output, preventing empty
|
||||||
|
//! sections from polluting the end report.
|
||||||
|
//!
|
||||||
|
//! Running `cargo run --example custom_section` shows us how these sections are included in the
|
||||||
|
//! output:
|
||||||
|
//!
|
||||||
|
//! 
|
||||||
|
//!
|
||||||
|
//! Only the `Stderr:` section actually gets included. The `cat` command fails, so stdout ends up
|
||||||
|
//! being empty and is skipped in the final report. This gives us a short and concise error report
|
||||||
|
//! indicating exactly what was attempted and how it failed.
|
||||||
|
//!
|
||||||
|
//! ### Aggregating multiple errors into one report
|
||||||
|
//!
|
||||||
|
//! It's not uncommon for programs like batched task runners or parsers to want to
|
||||||
|
//! return an error with multiple sources. The current version of the error trait
|
||||||
|
//! does not support this use case very well, though there is [work being
|
||||||
|
//! done](https://github.com/rust-lang/rfcs/pull/2895) to improve this.
|
||||||
|
//!
|
||||||
|
//! For now however one way to work around this is to compose errors outside the
|
||||||
|
//! error trait. `color-eyre` supports such composition in its error reports via
|
||||||
|
//! the `Help` trait.
|
||||||
|
//!
|
||||||
|
//! For an example of how to aggregate errors check out [`examples/multiple_errors.rs`].
|
||||||
|
//!
|
||||||
|
//! ### Custom configuration for `color-backtrace` for setting custom filters and more
|
||||||
|
//!
|
||||||
|
//! The pretty printing for backtraces and span traces isn't actually provided by `color-eyre`, but
|
||||||
|
//! instead comes from its dependencies [`color-backtrace`] and [`color-spantrace`].
|
||||||
|
//! `color-backtrace` in particular has many more features than are exported by `color-eyre`, such
|
||||||
|
//! as customized color schemes, panic hooks, and custom frame filters. The custom frame filters
|
||||||
|
//! are particularly useful when combined with `color-eyre`, so to enable their usage we provide
|
||||||
|
//! the `install` fn for setting up a custom `BacktracePrinter` with custom filters installed.
|
||||||
|
//!
|
||||||
|
//! For an example of how to setup custom filters, check out [`examples/custom_filter.rs`].
|
||||||
|
//!
|
||||||
//! ## Explanation
|
//! ## Explanation
|
||||||
//!
|
//!
|
||||||
//! This crate works by defining a `Context` type which implements [`eyre::EyreContext`]
|
//! This crate works by defining a `Context` type which implements [`eyre::EyreContext`]
|
||||||
@ -109,18 +216,6 @@
|
|||||||
//!
|
//!
|
||||||
//! Please refer to the [`Context`] type's docs for more details about its feature set.
|
//! Please refer to the [`Context`] type's docs for more details about its feature set.
|
||||||
//!
|
//!
|
||||||
//! ## Features
|
|
||||||
//!
|
|
||||||
//! - captures a [`backtrace::Backtrace`] and prints using [`color-backtrace`]
|
|
||||||
//! - captures a [`tracing_error::SpanTrace`] and prints using
|
|
||||||
//! [`color-spantrace`]
|
|
||||||
//! - Only capture SpanTrace by default for better performance.
|
|
||||||
//! - display source lines when `RUST_LIB_BACKTRACE=full` is set
|
|
||||||
//! - store help text via [`Help`] trait and display after final report
|
|
||||||
//! - custom `color-backtrace` configuration via `color_eyre::install`,
|
|
||||||
//! such as adding custom filters
|
|
||||||
//!
|
|
||||||
//!
|
|
||||||
//! [`eyre`]: https://docs.rs/eyre
|
//! [`eyre`]: https://docs.rs/eyre
|
||||||
//! [`tracing-error`]: https://docs.rs/tracing-error
|
//! [`tracing-error`]: https://docs.rs/tracing-error
|
||||||
//! [`color-backtrace`]: https://docs.rs/color-backtrace
|
//! [`color-backtrace`]: https://docs.rs/color-backtrace
|
||||||
@ -128,10 +223,14 @@
|
|||||||
//! [`backtrace::Backtrace`]: https://docs.rs/backtrace/*/backtrace/struct.Backtrace.html
|
//! [`backtrace::Backtrace`]: https://docs.rs/backtrace/*/backtrace/struct.Backtrace.html
|
||||||
//! [`tracing_error::SpanTrace`]: https://docs.rs/tracing-error/*/tracing_error/struct.SpanTrace.html
|
//! [`tracing_error::SpanTrace`]: https://docs.rs/tracing-error/*/tracing_error/struct.SpanTrace.html
|
||||||
//! [`color-spantrace`]: https://github.com/yaahc/color-spantrace
|
//! [`color-spantrace`]: https://github.com/yaahc/color-spantrace
|
||||||
//! [`Help`]: trait.Help.html
|
//! [`Help`]: https://docs.rs/color-eyre/*/color_eyre/trait.Help.html
|
||||||
//! [`eyre::Report`]: https://docs.rs/eyre/*/eyre/struct.Report.html
|
//! [`eyre::Report`]: https://docs.rs/eyre/*/eyre/struct.Report.html
|
||||||
//! [`eyre::Result`]: https://docs.rs/eyre/*/eyre/type.Result.html
|
//! [`eyre::Result`]: https://docs.rs/eyre/*/eyre/type.Result.html
|
||||||
//! [`Context`]: struct.Context.html
|
//! [`Context`]: https://docs.rs/color-eyre/*/color_eyre/struct.Context.html
|
||||||
|
//! [`examples/usage.rs`]: https://github.com/yaahc/color-eyre/blob/master/examples/usage.rs
|
||||||
|
//! [`examples/custom_filter.rs`]: https://github.com/yaahc/color-eyre/blob/master/examples/custom_filter.rs
|
||||||
|
//! [`examples/custom_section.rs`]: https://github.com/yaahc/color-eyre/blob/master/examples/custom_section.rs
|
||||||
|
//! [`examples/multiple_errors.rs`]: https://github.com/yaahc/color-eyre/blob/master/examples/multiple_errors.rs
|
||||||
#![doc(html_root_url = "https://docs.rs/color-eyre/0.3.2")]
|
#![doc(html_root_url = "https://docs.rs/color-eyre/0.3.2")]
|
||||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||||
#![warn(
|
#![warn(
|
||||||
@ -161,10 +260,10 @@ use ansi_term::Color::*;
|
|||||||
use backtrace::Backtrace;
|
use backtrace::Backtrace;
|
||||||
pub use color_backtrace::BacktracePrinter;
|
pub use color_backtrace::BacktracePrinter;
|
||||||
use eyre::*;
|
use eyre::*;
|
||||||
pub use help::Help;
|
|
||||||
use help::HelpInfo;
|
|
||||||
use indenter::{indented, Format};
|
use indenter::{indented, Format};
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
|
use section::Order;
|
||||||
|
pub use section::{help::Help, Section, SectionExt};
|
||||||
#[cfg(feature = "capture-spantrace")]
|
#[cfg(feature = "capture-spantrace")]
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::{
|
use std::{
|
||||||
@ -175,7 +274,7 @@ use std::{
|
|||||||
#[cfg(feature = "capture-spantrace")]
|
#[cfg(feature = "capture-spantrace")]
|
||||||
use tracing_error::{ExtractSpanTrace, SpanTrace, SpanTraceStatus};
|
use tracing_error::{ExtractSpanTrace, SpanTrace, SpanTraceStatus};
|
||||||
|
|
||||||
mod help;
|
pub mod section;
|
||||||
|
|
||||||
static CONFIG: OnceCell<BacktracePrinter> = OnceCell::new();
|
static CONFIG: OnceCell<BacktracePrinter> = OnceCell::new();
|
||||||
|
|
||||||
@ -194,7 +293,7 @@ pub struct Context {
|
|||||||
backtrace: Option<Backtrace>,
|
backtrace: Option<Backtrace>,
|
||||||
#[cfg(feature = "capture-spantrace")]
|
#[cfg(feature = "capture-spantrace")]
|
||||||
span_trace: Option<SpanTrace>,
|
span_trace: Option<SpanTrace>,
|
||||||
help: Vec<HelpInfo>,
|
sections: Vec<Section>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -318,7 +417,7 @@ impl EyreContext for Context {
|
|||||||
backtrace,
|
backtrace,
|
||||||
#[cfg(feature = "capture-spantrace")]
|
#[cfg(feature = "capture-spantrace")]
|
||||||
span_trace,
|
span_trace,
|
||||||
help: Vec::new(),
|
sections: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -347,6 +446,22 @@ impl EyreContext for Context {
|
|||||||
write!(indented(f).ind(n), "{}", Red.paint(&buf))?;
|
write!(indented(f).ind(n), "{}", Red.paint(&buf))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for section in self
|
||||||
|
.sections
|
||||||
|
.iter()
|
||||||
|
.filter(|s| matches!(s.order, Order::AfterErrMsgs))
|
||||||
|
{
|
||||||
|
write!(f, "\n\n{:?}", section)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for section in self
|
||||||
|
.sections
|
||||||
|
.iter()
|
||||||
|
.filter(|s| matches!(s.order, Order::BeforeSpanTrace))
|
||||||
|
{
|
||||||
|
write!(f, "\n\n{:?}", section)?;
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "capture-spantrace")]
|
#[cfg(feature = "capture-spantrace")]
|
||||||
{
|
{
|
||||||
let span_trace = self
|
let span_trace = self
|
||||||
@ -378,12 +493,20 @@ impl EyreContext for Context {
|
|||||||
"{}",
|
"{}",
|
||||||
bt_str
|
bt_str
|
||||||
)?;
|
)?;
|
||||||
} else if !self.help.is_empty() {
|
} else if self
|
||||||
|
.sections
|
||||||
|
.iter()
|
||||||
|
.any(|s| matches!(s.order, Order::AfterBackTrace))
|
||||||
|
{
|
||||||
writeln!(f)?;
|
writeln!(f)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
for help in &self.help {
|
for section in self
|
||||||
write!(f, "\n{}", help)?;
|
.sections
|
||||||
|
.iter()
|
||||||
|
.filter(|s| matches!(s.order, Order::AfterBackTrace))
|
||||||
|
{
|
||||||
|
write!(f, "\n{:?}", section)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
405
src/section/help.rs
Normal file
405
src/section/help.rs
Normal file
@ -0,0 +1,405 @@
|
|||||||
|
//! Provides an extension trait for attaching `Section`s to error reports.
|
||||||
|
use crate::{section, Report, Result, Section};
|
||||||
|
use ansi_term::Color::*;
|
||||||
|
use std::fmt::{self, Display};
|
||||||
|
|
||||||
|
/// A helper trait for attaching help text to errors to be displayed after the chain of errors
|
||||||
|
///
|
||||||
|
/// `color_eyre` provides two types of help text that can be attached to error reports: custom
|
||||||
|
/// sections and pre-configured sections. Custom sections are added via the `section` and
|
||||||
|
/// `with_section` methods, and give maximum control over formatting. For more details check out
|
||||||
|
/// the docs for [`Section`].
|
||||||
|
///
|
||||||
|
/// The pre-configured sections are provided via `suggestion`, `warning`, and `note`. These
|
||||||
|
/// sections are displayed after all other sections with no extra newlines between subsequent Help
|
||||||
|
/// sections. They consist only of a header portion and are prepended with a colored string
|
||||||
|
/// indicating the kind of section, e.g. `Note: This might have failed due to ..."
|
||||||
|
///
|
||||||
|
/// [`Section`]: struct.Section.html
|
||||||
|
pub trait Help<T>: private::Sealed {
|
||||||
|
/// Add a section to an error report, to be displayed after the chain of errors.
|
||||||
|
///
|
||||||
|
/// Sections are displayed in the order they are added to the error report. They are displayed
|
||||||
|
/// immediately after the `Error:` section and before the `SpanTrace` and `Backtrace` sections.
|
||||||
|
/// They consist of a header and an optional body. The body of the section is indented by
|
||||||
|
/// default.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust,should_panic
|
||||||
|
/// use color_eyre::{Report, Help};
|
||||||
|
/// use eyre::eyre;
|
||||||
|
///
|
||||||
|
/// Err(eyre!("command failed"))
|
||||||
|
/// .section("Please report bugs to https://real.url/bugs")?;
|
||||||
|
/// # Ok::<_, Report>(())
|
||||||
|
/// ```
|
||||||
|
fn section<C>(self, section: C) -> Result<T>
|
||||||
|
where
|
||||||
|
C: Into<Section>;
|
||||||
|
|
||||||
|
/// Add a Section to an error report, to be displayed after the chain of errors. The closure to
|
||||||
|
/// create the Section is lazily evaluated only in the case of an error.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use color_eyre::{Report, Help, SectionExt};
|
||||||
|
/// use eyre::eyre;
|
||||||
|
///
|
||||||
|
/// let output = std::process::Command::new("ls")
|
||||||
|
/// .output()?;
|
||||||
|
///
|
||||||
|
/// let output = if !output.status.success() {
|
||||||
|
/// let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
/// Err(eyre!("cmd exited with non-zero status code"))
|
||||||
|
/// .with_section(move || {
|
||||||
|
/// "Stderr:"
|
||||||
|
/// .skip_if(|| stderr.is_empty())
|
||||||
|
/// .body(stderr.trim().to_string())
|
||||||
|
/// })?
|
||||||
|
/// } else {
|
||||||
|
/// String::from_utf8_lossy(&output.stdout)
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// println!("{}", output);
|
||||||
|
/// # Ok::<_, Report>(())
|
||||||
|
/// ```
|
||||||
|
fn with_section<C, F>(self, section: F) -> Result<T>
|
||||||
|
where
|
||||||
|
C: Into<Section>,
|
||||||
|
F: FnOnce() -> C;
|
||||||
|
|
||||||
|
/// Add an error section to an error report, to be displayed after the primary error message
|
||||||
|
/// section.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust,should_panic
|
||||||
|
/// use color_eyre::{Report, Help};
|
||||||
|
/// use eyre::eyre;
|
||||||
|
/// use thiserror::Error;
|
||||||
|
///
|
||||||
|
/// #[derive(Debug, Error)]
|
||||||
|
/// #[error("{0}")]
|
||||||
|
/// struct StrError(&'static str);
|
||||||
|
///
|
||||||
|
/// Err(eyre!("command failed"))
|
||||||
|
/// .error(StrError("got one error"))
|
||||||
|
/// .error(StrError("got a second error"))?;
|
||||||
|
/// # Ok::<_, Report>(())
|
||||||
|
/// ```
|
||||||
|
fn error<E>(self, error: E) -> Result<T>
|
||||||
|
where
|
||||||
|
E: std::error::Error + Send + Sync + 'static;
|
||||||
|
|
||||||
|
/// Add an error section to an error report, to be displayed after the primary error message
|
||||||
|
/// section. The closure to create the Section is lazily evaluated only in the case of an error.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust,should_panic
|
||||||
|
/// use color_eyre::{Report, Help};
|
||||||
|
/// use eyre::eyre;
|
||||||
|
/// use thiserror::Error;
|
||||||
|
///
|
||||||
|
/// #[derive(Debug, Error)]
|
||||||
|
/// #[error("{0}")]
|
||||||
|
/// struct StringError(String);
|
||||||
|
///
|
||||||
|
/// Err(eyre!("command failed"))
|
||||||
|
/// .with_error(|| StringError("got one error".into()))
|
||||||
|
/// .with_error(|| StringError("got a second error".into()))?;
|
||||||
|
/// # Ok::<_, Report>(())
|
||||||
|
/// ```
|
||||||
|
fn with_error<E, F>(self, error: F) -> Result<T>
|
||||||
|
where
|
||||||
|
F: FnOnce() -> E,
|
||||||
|
E: std::error::Error + Send + Sync + 'static;
|
||||||
|
|
||||||
|
/// Add a Note to an error report, to be displayed after the chain of errors.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use std::{error::Error, fmt::{self, Display}};
|
||||||
|
/// # use color_eyre::Result;
|
||||||
|
/// # #[derive(Debug)]
|
||||||
|
/// # struct FakeErr;
|
||||||
|
/// # impl Display for FakeErr {
|
||||||
|
/// # fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
/// # write!(f, "FakeErr")
|
||||||
|
/// # }
|
||||||
|
/// # }
|
||||||
|
/// # impl std::error::Error for FakeErr {}
|
||||||
|
/// # fn main() -> Result<()> {
|
||||||
|
/// # fn fallible_fn() -> Result<(), FakeErr> {
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// use color_eyre::Help as _;
|
||||||
|
///
|
||||||
|
/// fallible_fn().note("This might have failed due to ...")?;
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
fn note<C>(self, context: C) -> Result<T>
|
||||||
|
where
|
||||||
|
C: Display + Send + Sync + 'static;
|
||||||
|
|
||||||
|
/// Add a Note to an error report, to be displayed after the chain of errors. The closure to
|
||||||
|
/// create the Note is lazily evaluated only in the case of an error.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use std::{error::Error, fmt::{self, Display}};
|
||||||
|
/// # use color_eyre::Result;
|
||||||
|
/// # #[derive(Debug)]
|
||||||
|
/// # struct FakeErr;
|
||||||
|
/// # impl Display for FakeErr {
|
||||||
|
/// # fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
/// # write!(f, "FakeErr")
|
||||||
|
/// # }
|
||||||
|
/// # }
|
||||||
|
/// # impl std::error::Error for FakeErr {}
|
||||||
|
/// # fn main() -> Result<()> {
|
||||||
|
/// # fn fallible_fn() -> Result<(), FakeErr> {
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// use color_eyre::Help as _;
|
||||||
|
///
|
||||||
|
/// fallible_fn().with_note(|| {
|
||||||
|
/// format!("This might have failed due to ... It has failed {} times", 100)
|
||||||
|
/// })?;
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
fn with_note<C, F>(self, f: F) -> Result<T>
|
||||||
|
where
|
||||||
|
C: Display + Send + Sync + 'static,
|
||||||
|
F: FnOnce() -> C;
|
||||||
|
|
||||||
|
/// Add a Warning to an error report, to be displayed after the chain of errors.
|
||||||
|
fn warning<C>(self, context: C) -> Result<T>
|
||||||
|
where
|
||||||
|
C: Display + Send + Sync + 'static;
|
||||||
|
|
||||||
|
/// Add a Warning to an error report, to be displayed after the chain of errors. The closure to
|
||||||
|
/// create the Warning is lazily evaluated only in the case of an error.
|
||||||
|
fn with_warning<C, F>(self, f: F) -> Result<T>
|
||||||
|
where
|
||||||
|
C: Display + Send + Sync + 'static,
|
||||||
|
F: FnOnce() -> C;
|
||||||
|
|
||||||
|
/// Add a Suggestion to an error report, to be displayed after the chain of errors.
|
||||||
|
fn suggestion<C>(self, context: C) -> Result<T>
|
||||||
|
where
|
||||||
|
C: Display + Send + Sync + 'static;
|
||||||
|
|
||||||
|
/// Add a Suggestion to an error report, to be displayed after the chain of errors. The closure
|
||||||
|
/// to create the Suggestion is lazily evaluated only in the case of an error.
|
||||||
|
fn with_suggestion<C, F>(self, f: F) -> Result<T>
|
||||||
|
where
|
||||||
|
C: Display + Send + Sync + 'static,
|
||||||
|
F: FnOnce() -> C;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, E> Help<T> for std::result::Result<T, E>
|
||||||
|
where
|
||||||
|
E: Into<Report>,
|
||||||
|
{
|
||||||
|
fn note<C>(self, context: C) -> Result<T>
|
||||||
|
where
|
||||||
|
C: Display + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
self.map_err(|e| {
|
||||||
|
let mut e = e.into();
|
||||||
|
e.context_mut().sections.push(
|
||||||
|
Section::from(HelpInfo::Note(Box::new(context)))
|
||||||
|
.order(section::Order::AfterBackTrace),
|
||||||
|
);
|
||||||
|
e
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_note<C, F>(self, context: F) -> Result<T>
|
||||||
|
where
|
||||||
|
C: Display + Send + Sync + 'static,
|
||||||
|
F: FnOnce() -> C,
|
||||||
|
{
|
||||||
|
self.map_err(|e| {
|
||||||
|
let mut e = e.into();
|
||||||
|
e.context_mut().sections.push(
|
||||||
|
Section::from(HelpInfo::Note(Box::new(context())))
|
||||||
|
.order(section::Order::AfterBackTrace),
|
||||||
|
);
|
||||||
|
e
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn warning<C>(self, context: C) -> Result<T>
|
||||||
|
where
|
||||||
|
C: Display + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
self.map_err(|e| {
|
||||||
|
let mut e = e.into();
|
||||||
|
e.context_mut().sections.push(
|
||||||
|
Section::from(HelpInfo::Warning(Box::new(context)))
|
||||||
|
.order(section::Order::AfterBackTrace),
|
||||||
|
);
|
||||||
|
e
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_warning<C, F>(self, context: F) -> Result<T>
|
||||||
|
where
|
||||||
|
C: Display + Send + Sync + 'static,
|
||||||
|
F: FnOnce() -> C,
|
||||||
|
{
|
||||||
|
self.map_err(|e| {
|
||||||
|
let mut e = e.into();
|
||||||
|
e.context_mut().sections.push(
|
||||||
|
Section::from(HelpInfo::Warning(Box::new(context())))
|
||||||
|
.order(section::Order::AfterBackTrace),
|
||||||
|
);
|
||||||
|
e
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn suggestion<C>(self, context: C) -> Result<T>
|
||||||
|
where
|
||||||
|
C: Display + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
self.map_err(|e| {
|
||||||
|
let mut e = e.into();
|
||||||
|
e.context_mut().sections.push(
|
||||||
|
Section::from(HelpInfo::Suggestion(Box::new(context)))
|
||||||
|
.order(section::Order::AfterBackTrace),
|
||||||
|
);
|
||||||
|
e
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_suggestion<C, F>(self, context: F) -> Result<T>
|
||||||
|
where
|
||||||
|
C: Display + Send + Sync + 'static,
|
||||||
|
F: FnOnce() -> C,
|
||||||
|
{
|
||||||
|
self.map_err(|e| {
|
||||||
|
let mut e = e.into();
|
||||||
|
e.context_mut().sections.push(
|
||||||
|
Section::from(HelpInfo::Suggestion(Box::new(context())))
|
||||||
|
.order(section::Order::AfterBackTrace),
|
||||||
|
);
|
||||||
|
e
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_section<C, F>(self, section: F) -> Result<T>
|
||||||
|
where
|
||||||
|
C: Into<Section>,
|
||||||
|
F: FnOnce() -> C,
|
||||||
|
{
|
||||||
|
self.map_err(|e| {
|
||||||
|
let mut e = e.into();
|
||||||
|
let section = section().into();
|
||||||
|
|
||||||
|
if !matches!(section.order, section::Order::SkipEntirely) {
|
||||||
|
e.context_mut().sections.push(section);
|
||||||
|
}
|
||||||
|
|
||||||
|
e
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn section<C>(self, section: C) -> Result<T>
|
||||||
|
where
|
||||||
|
C: Into<Section>,
|
||||||
|
{
|
||||||
|
self.map_err(|e| {
|
||||||
|
let mut e = e.into();
|
||||||
|
let section = section.into();
|
||||||
|
|
||||||
|
if !matches!(section.order, section::Order::SkipEntirely) {
|
||||||
|
e.context_mut().sections.push(section);
|
||||||
|
}
|
||||||
|
|
||||||
|
e
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn error<E2>(self, error: E2) -> Result<T>
|
||||||
|
where
|
||||||
|
E2: std::error::Error + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
self.map_err(|e| {
|
||||||
|
let mut e = e.into();
|
||||||
|
let section = Section {
|
||||||
|
inner: section::SectionKind::Error(Box::new(error)),
|
||||||
|
order: section::Order::AfterErrMsgs,
|
||||||
|
};
|
||||||
|
|
||||||
|
e.context_mut().sections.push(section);
|
||||||
|
e
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_error<E2, F>(self, error: F) -> Result<T>
|
||||||
|
where
|
||||||
|
F: FnOnce() -> E2,
|
||||||
|
E2: std::error::Error + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
self.map_err(|e| {
|
||||||
|
let mut e = e.into();
|
||||||
|
let section = Section {
|
||||||
|
inner: section::SectionKind::Error(Box::new(error())),
|
||||||
|
order: section::Order::AfterErrMsgs,
|
||||||
|
};
|
||||||
|
|
||||||
|
e.context_mut().sections.push(section);
|
||||||
|
e
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) enum HelpInfo {
|
||||||
|
Note(Box<dyn Display + Send + Sync + 'static>),
|
||||||
|
Warning(Box<dyn Display + Send + Sync + 'static>),
|
||||||
|
Suggestion(Box<dyn Display + Send + Sync + 'static>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for HelpInfo {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Note(context) => write!(f, "{}: {}", Cyan.paint("Note"), context),
|
||||||
|
Self::Warning(context) => write!(f, "{}: {}", Yellow.paint("Warning"), context),
|
||||||
|
Self::Suggestion(context) => write!(f, "{}: {}", Cyan.paint("Suggestion"), context),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for HelpInfo {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Note(context) => f
|
||||||
|
.debug_tuple("Note")
|
||||||
|
.field(&format_args!("{}", context))
|
||||||
|
.finish(),
|
||||||
|
Self::Warning(context) => f
|
||||||
|
.debug_tuple("Warning")
|
||||||
|
.field(&format_args!("{}", context))
|
||||||
|
.finish(),
|
||||||
|
Self::Suggestion(context) => f
|
||||||
|
.debug_tuple("Suggestion")
|
||||||
|
.field(&format_args!("{}", context))
|
||||||
|
.finish(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) mod private {
|
||||||
|
use crate::Report;
|
||||||
|
pub trait Sealed {}
|
||||||
|
|
||||||
|
impl<T, E> Sealed for std::result::Result<T, E> where E: Into<Report> {}
|
||||||
|
}
|
248
src/section/mod.rs
Normal file
248
src/section/mod.rs
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
//! Helpers for adding custom sections to error reports
|
||||||
|
use ansi_term::Color::*;
|
||||||
|
use indenter::indented;
|
||||||
|
use std::fmt::{self, Display, Write};
|
||||||
|
|
||||||
|
pub mod help;
|
||||||
|
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) enum Order {
|
||||||
|
AfterErrMsgs,
|
||||||
|
BeforeSpanTrace,
|
||||||
|
AfterBackTrace,
|
||||||
|
SkipEntirely,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A custom section for an error report.
|
||||||
|
///
|
||||||
|
/// # Details
|
||||||
|
///
|
||||||
|
/// Sections consist of two parts, a header: and an optional body. The header can contain any
|
||||||
|
/// number of lines and has no indentation applied to it by default. The body can contain any
|
||||||
|
/// number of lines and is always written after the header with indentation inserted before
|
||||||
|
/// every line.
|
||||||
|
///
|
||||||
|
/// # Construction
|
||||||
|
///
|
||||||
|
/// Sections are meant to be constructed via `Into<Section>`, which is implemented for all types
|
||||||
|
/// that implement `Display`. The constructed `Section` then takes ownership of the `Display` type
|
||||||
|
/// and boxes it internally for use later when printing the report.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use color_eyre::{SectionExt, Help, Report};
|
||||||
|
/// use eyre::eyre;
|
||||||
|
/// use std::process::Command;
|
||||||
|
/// use tracing::instrument;
|
||||||
|
///
|
||||||
|
/// trait Output {
|
||||||
|
/// fn output2(&mut self) -> Result<String, Report>;
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// impl Output for Command {
|
||||||
|
/// #[instrument]
|
||||||
|
/// fn output2(&mut self) -> Result<String, Report> {
|
||||||
|
/// let output = self.output()?;
|
||||||
|
///
|
||||||
|
/// let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
|
///
|
||||||
|
/// if !output.status.success() {
|
||||||
|
/// let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
/// Err(eyre!("cmd exited with non-zero status code"))
|
||||||
|
/// .with_section(move || {
|
||||||
|
/// "Stdout:"
|
||||||
|
/// .skip_if(|| stdout.is_empty())
|
||||||
|
/// .body(stdout.trim().to_string())
|
||||||
|
/// })
|
||||||
|
/// .with_section(move || {
|
||||||
|
/// "Stderr:"
|
||||||
|
/// .skip_if(|| stderr.is_empty())
|
||||||
|
/// .body(stderr.trim().to_string())
|
||||||
|
/// })
|
||||||
|
/// } else {
|
||||||
|
/// Ok(stdout.into())
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub struct Section {
|
||||||
|
pub(crate) inner: SectionKind,
|
||||||
|
pub(crate) order: Order,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) enum SectionKind {
|
||||||
|
Header(Box<dyn Display + Send + Sync + 'static>),
|
||||||
|
WithBody(
|
||||||
|
Box<dyn Display + Send + Sync + 'static>,
|
||||||
|
Box<dyn Display + Send + Sync + 'static>,
|
||||||
|
),
|
||||||
|
Error(Box<dyn std::error::Error + Send + Sync + 'static>),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extension trait for customizing the content of a `Section`
|
||||||
|
pub trait SectionExt {
|
||||||
|
/// Add a body to a `Section`
|
||||||
|
///
|
||||||
|
/// Bodies are always indented to the same level as error messages and spans.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use color_eyre::{Help, SectionExt, Report};
|
||||||
|
/// use eyre::eyre;
|
||||||
|
///
|
||||||
|
/// let all_in_header = "header\n body\n body";
|
||||||
|
/// let report = Err::<(), Report>(eyre!("an error occurred"))
|
||||||
|
/// .section(all_in_header)
|
||||||
|
/// .unwrap_err();
|
||||||
|
///
|
||||||
|
/// let just_header = "header";
|
||||||
|
/// let just_body = "body\nbody";
|
||||||
|
/// let report2 = Err::<(), Report>(eyre!("an error occurred"))
|
||||||
|
/// .section(just_header.body(just_body))
|
||||||
|
/// .unwrap_err();
|
||||||
|
///
|
||||||
|
/// assert_eq!(format!("{:?}", report), format!("{:?}", report2))
|
||||||
|
/// ```
|
||||||
|
fn body<C>(self, body: C) -> Section
|
||||||
|
where
|
||||||
|
C: Display + Send + Sync + 'static;
|
||||||
|
|
||||||
|
/// Skip a section based on some condition. For example, skip a section if the body is empty.
|
||||||
|
///
|
||||||
|
/// The skipped section is not stored in the report and is instead immediately dropped.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use eyre::eyre;
|
||||||
|
/// use color_eyre::{SectionExt, Report, Help};
|
||||||
|
///
|
||||||
|
/// fn add_body(report: Report, body: String) -> Result<(), Report> {
|
||||||
|
/// Err(report)
|
||||||
|
/// .with_section(|| "ExtraInfo:".skip_if(|| body.is_empty()).body(body))
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// let report = eyre!("an error occurred");
|
||||||
|
/// let before = format!("{:?}", report);
|
||||||
|
/// let body = String::new();
|
||||||
|
/// let report = add_body(report, body).unwrap_err();
|
||||||
|
/// let after = format!("{:?}", report);
|
||||||
|
/// assert_eq!(before, after);
|
||||||
|
///
|
||||||
|
/// let report = eyre!("an error occurred");
|
||||||
|
/// let before = format!("{:?}", report);
|
||||||
|
/// let body = String::from("Some actual text here");
|
||||||
|
/// let report = add_body(report, body).unwrap_err();
|
||||||
|
/// let after = format!("{:?}", report);
|
||||||
|
/// assert_ne!(before, after);
|
||||||
|
/// ```
|
||||||
|
fn skip_if<F>(self, condition: F) -> Section
|
||||||
|
where
|
||||||
|
F: FnOnce() -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Section {
|
||||||
|
pub(crate) fn order(mut self, order: Order) -> Self {
|
||||||
|
self.order = order;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> SectionExt for T
|
||||||
|
where
|
||||||
|
Section: From<T>,
|
||||||
|
{
|
||||||
|
fn body<C>(self, body: C) -> Section
|
||||||
|
where
|
||||||
|
C: Display + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
let section = Section::from(self);
|
||||||
|
|
||||||
|
let header = match section.inner {
|
||||||
|
SectionKind::Header(header) => header,
|
||||||
|
SectionKind::WithBody(header, _body) => header,
|
||||||
|
SectionKind::Error(_) => unreachable!("bodies cannot be added to Error sections"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let inner = SectionKind::WithBody(header, Box::new(body));
|
||||||
|
|
||||||
|
Section {
|
||||||
|
inner,
|
||||||
|
order: section.order,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn skip_if<F>(self, condition: F) -> Section
|
||||||
|
where
|
||||||
|
F: FnOnce() -> bool,
|
||||||
|
{
|
||||||
|
let mut section = Section::from(self);
|
||||||
|
|
||||||
|
section.order = if condition() {
|
||||||
|
Order::SkipEntirely
|
||||||
|
} else {
|
||||||
|
section.order
|
||||||
|
};
|
||||||
|
|
||||||
|
section
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> From<T> for Section
|
||||||
|
where
|
||||||
|
T: Display + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
fn from(header: T) -> Self {
|
||||||
|
let inner = SectionKind::Header(Box::new(header));
|
||||||
|
|
||||||
|
Self {
|
||||||
|
inner,
|
||||||
|
order: Order::BeforeSpanTrace,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Section {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.inner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for SectionKind {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
SectionKind::Header(header) => write!(f, "{}", header)?,
|
||||||
|
SectionKind::WithBody(header, body) => {
|
||||||
|
write!(f, "{}", header)?;
|
||||||
|
writeln!(f)?;
|
||||||
|
write!(
|
||||||
|
indenter::indented(f)
|
||||||
|
.with_format(indenter::Format::Uniform { indentation: " " }),
|
||||||
|
"{}",
|
||||||
|
body
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
SectionKind::Error(error) => {
|
||||||
|
// a lot here
|
||||||
|
let errors = std::iter::successors(
|
||||||
|
Some(error.as_ref() as &(dyn std::error::Error + 'static)),
|
||||||
|
|e| e.source(),
|
||||||
|
);
|
||||||
|
|
||||||
|
write!(f, "Error:")?;
|
||||||
|
let mut buf = String::new();
|
||||||
|
for (n, error) in errors.enumerate() {
|
||||||
|
writeln!(f)?;
|
||||||
|
buf.clear();
|
||||||
|
write!(&mut buf, "{}", error).unwrap();
|
||||||
|
write!(indented(f).ind(n), "{}", Red.paint(&buf))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user