mirror of
https://github.com/eyre-rs/eyre.git
synced 2025-09-27 13:01:29 +00:00
Merge branch 'master' into bump-backtrace-rs
This commit is contained in:
commit
a6c8a61628
@ -17,6 +17,7 @@ rust-version = "1.65.0"
|
|||||||
indenter = "0.3.0"
|
indenter = "0.3.0"
|
||||||
once_cell = "1.18.0"
|
once_cell = "1.18.0"
|
||||||
owo-colors = "4.0"
|
owo-colors = "4.0"
|
||||||
|
autocfg = "1.0"
|
||||||
|
|
||||||
[profile.dev.package.backtrace]
|
[profile.dev.package.backtrace]
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
|
22
README.md
22
README.md
@ -122,9 +122,12 @@ avoid using `eyre::Report` as your public error type.
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- If using the nightly channel, a backtrace is captured and printed with the
|
- If using rust >1.65, a backtrace is captured and printed with the
|
||||||
error if the underlying error type does not already provide its own. In order
|
error.
|
||||||
to see backtraces, they must be enabled through the environment variables
|
|
||||||
|
On nightly eyre will use the underlying error's backtrace if it has one.
|
||||||
|
|
||||||
|
In order to see backtraces, they must be enabled through the environment variables
|
||||||
described in [`std::backtrace`]:
|
described in [`std::backtrace`]:
|
||||||
|
|
||||||
- If you want panics and errors to both have backtraces, set
|
- If you want panics and errors to both have backtraces, set
|
||||||
@ -141,7 +144,7 @@ avoid using `eyre::Report` as your public error type.
|
|||||||
- Eyre works with any error type that has an impl of `std::error::Error`,
|
- Eyre works with any error type that has an impl of `std::error::Error`,
|
||||||
including ones defined in your crate. We do not bundle a `derive(Error)` macro
|
including ones defined in your crate. We do not bundle a `derive(Error)` macro
|
||||||
but you can write the impls yourself or use a standalone macro like
|
but you can write the impls yourself or use a standalone macro like
|
||||||
[thiserror].
|
[thiserror](https://github.com/dtolnay/thiserror).
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
@ -178,6 +181,15 @@ No-std support was removed in 2020 in [commit 608a16a] due to unaddressed upstre
|
|||||||
[commit 608a16a]:
|
[commit 608a16a]:
|
||||||
https://github.com/eyre-rs/eyre/pull/29/commits/608a16aa2c2c27eca6c88001cc94c6973c18f1d5
|
https://github.com/eyre-rs/eyre/pull/29/commits/608a16aa2c2c27eca6c88001cc94c6973c18f1d5
|
||||||
|
|
||||||
|
|
||||||
|
## Backtrace support
|
||||||
|
|
||||||
|
The built in default handler has support for capturing backtrace using `rustc-1.65` or later.
|
||||||
|
|
||||||
|
Backtraces are captured when an error is converted to an `eyre::Report` (such as using `?` or `eyre!`).
|
||||||
|
|
||||||
|
If using the nightly toolchain, backtraces will also be captured and accessed from other errors using [error_generic_member_access](https://github.com/rust-lang/rfcs/pull/2895) if available.
|
||||||
|
|
||||||
## Comparison to failure
|
## Comparison to failure
|
||||||
|
|
||||||
The `eyre::Report` type works something like `failure::Error`, but unlike
|
The `eyre::Report` type works something like `failure::Error`, but unlike
|
||||||
@ -195,8 +207,6 @@ you need an error type that can be handled via match or reported. This is
|
|||||||
common in library crates where you don't know how your users will handle
|
common in library crates where you don't know how your users will handle
|
||||||
your errors.
|
your errors.
|
||||||
|
|
||||||
[thiserror]: https://github.com/dtolnay/thiserror
|
|
||||||
|
|
||||||
## Compatibility with `anyhow`
|
## Compatibility with `anyhow`
|
||||||
|
|
||||||
This crate does its best to be usable as a drop in replacement of `anyhow` and
|
This crate does its best to be usable as a drop in replacement of `anyhow` and
|
||||||
|
@ -11,39 +11,6 @@ use std::env;
|
|||||||
use std::fmt::Write as _;
|
use std::fmt::Write as _;
|
||||||
use std::{fmt, path::PathBuf, sync::Arc};
|
use std::{fmt, path::PathBuf, sync::Arc};
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct InstallError;
|
|
||||||
|
|
||||||
impl fmt::Display for InstallError {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.write_str("could not install the BacktracePrinter as another was already installed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for InstallError {}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct InstallThemeError;
|
|
||||||
|
|
||||||
impl fmt::Display for InstallThemeError {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.write_str("could not set the provided `Theme` globally as another was already set")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for InstallThemeError {}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct InstallColorSpantraceThemeError;
|
|
||||||
|
|
||||||
impl fmt::Display for InstallColorSpantraceThemeError {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.write_str("could not set the provided `Theme` via `color_spantrace::set_theme` globally as another was already set")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for InstallColorSpantraceThemeError {}
|
|
||||||
|
|
||||||
/// A struct that represents a theme that is used by `color_eyre`
|
/// A struct that represents a theme that is used by `color_eyre`
|
||||||
#[derive(Debug, Copy, Clone, Default)]
|
#[derive(Debug, Copy, Clone, Default)]
|
||||||
pub struct Theme {
|
pub struct Theme {
|
||||||
|
@ -28,11 +28,13 @@ impl<W> WriterExt for W {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "issue-url")]
|
||||||
pub(crate) trait DisplayExt: Sized + Display {
|
pub(crate) trait DisplayExt: Sized + Display {
|
||||||
fn with_header<H: Display>(self, header: H) -> Header<Self, H>;
|
fn with_header<H: Display>(self, header: H) -> Header<Self, H>;
|
||||||
fn with_footer<F: Display>(self, footer: F) -> Footer<Self, F>;
|
fn with_footer<F: Display>(self, footer: F) -> Footer<Self, F>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "issue-url")]
|
||||||
impl<T> DisplayExt for T
|
impl<T> DisplayExt for T
|
||||||
where
|
where
|
||||||
T: Display,
|
T: Display,
|
||||||
@ -80,11 +82,13 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "issue-url")]
|
||||||
pub(crate) struct FooterWriter<W> {
|
pub(crate) struct FooterWriter<W> {
|
||||||
inner: W,
|
inner: W,
|
||||||
had_output: bool,
|
had_output: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "issue-url")]
|
||||||
impl<W> fmt::Write for FooterWriter<W>
|
impl<W> fmt::Write for FooterWriter<W>
|
||||||
where
|
where
|
||||||
W: fmt::Write,
|
W: fmt::Write,
|
||||||
@ -98,6 +102,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "issue-url")]
|
||||||
#[allow(explicit_outlives_requirements)]
|
#[allow(explicit_outlives_requirements)]
|
||||||
pub(crate) struct Footer<B, H>
|
pub(crate) struct Footer<B, H>
|
||||||
where
|
where
|
||||||
@ -108,6 +113,7 @@ where
|
|||||||
footer: H,
|
footer: H,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "issue-url")]
|
||||||
impl<B, H> fmt::Display for Footer<B, H>
|
impl<B, H> fmt::Display for Footer<B, H>
|
||||||
where
|
where
|
||||||
B: Display,
|
B: Display,
|
||||||
@ -129,6 +135,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "issue-url")]
|
||||||
#[allow(explicit_outlives_requirements)]
|
#[allow(explicit_outlives_requirements)]
|
||||||
pub(crate) struct Header<B, H>
|
pub(crate) struct Header<B, H>
|
||||||
where
|
where
|
||||||
@ -139,6 +146,7 @@ where
|
|||||||
h: H,
|
h: H,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "issue-url")]
|
||||||
impl<B, H> fmt::Display for Header<B, H>
|
impl<B, H> fmt::Display for Header<B, H>
|
||||||
where
|
where
|
||||||
B: Display,
|
B: Display,
|
||||||
|
@ -170,7 +170,12 @@ fn test_backwards_compatibility(target: String, file_name: &str) {
|
|||||||
fn normalize_backtrace(input: &str) -> String {
|
fn normalize_backtrace(input: &str) -> String {
|
||||||
input
|
input
|
||||||
.lines()
|
.lines()
|
||||||
.take_while(|v| !v.contains("core::panic") && !v.contains("theme_test_helper::main"))
|
.take_while(|v| {
|
||||||
|
!v.contains("core::panic")
|
||||||
|
&& !v.contains("theme_test_helper::main")
|
||||||
|
&& !v.contains("theme::test_error_backwards_compatibility::closure")
|
||||||
|
&& !v.contains("theme::test_error_backwards_compatibility::{{closure}}")
|
||||||
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join("\n")
|
.join("\n")
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,9 @@ indenter = { workspace = true }
|
|||||||
once_cell = { workspace = true }
|
once_cell = { workspace = true }
|
||||||
pyo3 = { version = "0.20", optional = true, default-features = false }
|
pyo3 = { version = "0.20", optional = true, default-features = false }
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
autocfg = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
futures = { version = "0.3", default-features = false }
|
futures = { version = "0.3", default-features = false }
|
||||||
rustversion = "1.0"
|
rustversion = "1.0"
|
||||||
|
173
eyre/build.rs
173
eyre/build.rs
@ -1,23 +1,53 @@
|
|||||||
use std::env;
|
use std::{
|
||||||
use std::ffi::OsString;
|
env, fs,
|
||||||
use std::fs;
|
path::Path,
|
||||||
use std::path::Path;
|
process::{Command, ExitStatus},
|
||||||
use std::process::{Command, ExitStatus};
|
};
|
||||||
use std::str;
|
|
||||||
|
|
||||||
// This code exercises the surface area that we expect of the std Backtrace
|
fn main() {
|
||||||
// type. If the current toolchain is able to compile it, we go ahead and use
|
let ac = autocfg::new();
|
||||||
// backtrace in eyre.
|
|
||||||
const BACKTRACE_PROBE: &str = r#"
|
// https://github.com/rust-lang/rust/issues/99301 [nightly]
|
||||||
#![feature(backtrace)]
|
//
|
||||||
|
// Autocfg does currently not support custom probes, or `nightly` only features
|
||||||
|
match compile_probe(GENERIC_MEMBER_ACCESS_PROBE) {
|
||||||
|
Some(status) if status.success() => autocfg::emit("generic_member_access"),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/rust-lang/rust/issues/47809 [rustc-1.46]
|
||||||
|
ac.emit_expression_cfg("std::panic::Location::caller", "track_caller");
|
||||||
|
|
||||||
|
if ac.probe_rustc_version(1, 52) {
|
||||||
|
autocfg::emit("eyre_no_fmt_arguments_as_str");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ac.probe_rustc_version(1, 58) {
|
||||||
|
autocfg::emit("eyre_no_fmt_args_capture");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ac.probe_rustc_version(1, 65) {
|
||||||
|
autocfg::emit("backtrace")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This code exercises the surface area or the generic member access feature for the `std::error::Error` trait.
|
||||||
|
//
|
||||||
|
// This is use to detect and supply backtrace information through different errors types.
|
||||||
|
const GENERIC_MEMBER_ACCESS_PROBE: &str = r#"
|
||||||
|
#![feature(error_generic_member_access)]
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use std::backtrace::{Backtrace, BacktraceStatus};
|
use std::error::{Error, Request};
|
||||||
use std::error::Error;
|
|
||||||
use std::fmt::{self, Display};
|
use std::fmt::{self, Display};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct E;
|
struct E {
|
||||||
|
backtrace: MyBacktrace,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct MyBacktrace;
|
||||||
|
|
||||||
impl Display for E {
|
impl Display for E {
|
||||||
fn fmt(&self, _formatter: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, _formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
@ -26,59 +56,44 @@ const BACKTRACE_PROBE: &str = r#"
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Error for E {
|
impl Error for E {
|
||||||
fn backtrace(&self) -> Option<&Backtrace> {
|
fn provide<'a>(&'a self, request: &mut Request<'a>) {
|
||||||
let backtrace = Backtrace::capture();
|
request
|
||||||
match backtrace.status() {
|
.provide_ref::<MyBacktrace>(&self.backtrace);
|
||||||
BacktraceStatus::Captured | BacktraceStatus::Disabled | _ => {}
|
|
||||||
}
|
|
||||||
unimplemented!()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
const TRACK_CALLER_PROBE: &str = r#"
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
#[track_caller]
|
|
||||||
fn foo() {
|
|
||||||
let _location = std::panic::Location::caller();
|
|
||||||
}
|
|
||||||
"#;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
match compile_probe(BACKTRACE_PROBE) {
|
|
||||||
Some(status) if status.success() => println!("cargo:rustc-cfg=backtrace"),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
match compile_probe(TRACK_CALLER_PROBE) {
|
|
||||||
Some(status) if status.success() => println!("cargo:rustc-cfg=track_caller"),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
let version = match rustc_version_info() {
|
|
||||||
Some(version) => version,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
version.toolchain.set_feature();
|
|
||||||
|
|
||||||
if version.minor < 52 {
|
|
||||||
println!("cargo:rustc-cfg=eyre_no_fmt_arguments_as_str");
|
|
||||||
}
|
|
||||||
|
|
||||||
if version.minor < 58 {
|
|
||||||
println!("cargo:rustc-cfg=eyre_no_fmt_args_capture");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compile_probe(probe: &str) -> Option<ExitStatus> {
|
fn compile_probe(probe: &str) -> Option<ExitStatus> {
|
||||||
let rustc = env::var_os("RUSTC")?;
|
let rustc = env::var_os("RUSTC")?;
|
||||||
let out_dir = env::var_os("OUT_DIR")?;
|
let out_dir = env::var_os("OUT_DIR")?;
|
||||||
let probefile = Path::new(&out_dir).join("probe.rs");
|
let probefile = Path::new(&out_dir).join("probe.rs");
|
||||||
fs::write(&probefile, probe).ok()?;
|
fs::write(&probefile, probe).ok()?;
|
||||||
Command::new(rustc)
|
|
||||||
.arg("--edition=2018")
|
let rustc_wrapper = env::var_os("RUSTC_WRAPPER").filter(|wrapper| !wrapper.is_empty());
|
||||||
|
let rustc_workspace_wrapper =
|
||||||
|
env::var_os("RUSTC_WORKSPACE_WRAPPER").filter(|wrapper| !wrapper.is_empty());
|
||||||
|
let mut rustc = rustc_wrapper
|
||||||
|
.into_iter()
|
||||||
|
.chain(rustc_workspace_wrapper)
|
||||||
|
.chain(std::iter::once(rustc));
|
||||||
|
|
||||||
|
let mut cmd = Command::new(rustc.next().unwrap());
|
||||||
|
cmd.args(rustc);
|
||||||
|
|
||||||
|
if let Some(target) = env::var_os("TARGET") {
|
||||||
|
cmd.arg("--target").arg(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If Cargo wants to set RUSTFLAGS, use that.
|
||||||
|
if let Ok(rustflags) = env::var("CARGO_ENCODED_RUSTFLAGS") {
|
||||||
|
if !rustflags.is_empty() {
|
||||||
|
for arg in rustflags.split('\x1f') {
|
||||||
|
cmd.arg(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.arg("--edition=2018")
|
||||||
.arg("--crate-name=eyre_build")
|
.arg("--crate-name=eyre_build")
|
||||||
.arg("--crate-type=lib")
|
.arg("--crate-type=lib")
|
||||||
.arg("--emit=metadata")
|
.arg("--emit=metadata")
|
||||||
@ -88,45 +103,3 @@ fn compile_probe(probe: &str) -> Option<ExitStatus> {
|
|||||||
.status()
|
.status()
|
||||||
.ok()
|
.ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO factor this toolchain parsing and related tests into its own file
|
|
||||||
#[derive(PartialEq)]
|
|
||||||
enum Toolchain {
|
|
||||||
Stable,
|
|
||||||
Beta,
|
|
||||||
Nightly,
|
|
||||||
}
|
|
||||||
impl Toolchain {
|
|
||||||
fn set_feature(self) {
|
|
||||||
match self {
|
|
||||||
Toolchain::Nightly => println!("cargo:rustc-cfg=nightly"),
|
|
||||||
Toolchain::Beta => println!("cargo:rustc-cfg=beta"),
|
|
||||||
Toolchain::Stable => println!("cargo:rustc-cfg=stable"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct VersionInfo {
|
|
||||||
minor: u32,
|
|
||||||
toolchain: Toolchain,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rustc_version_info() -> Option<VersionInfo> {
|
|
||||||
let rustc = env::var_os("RUSTC").unwrap_or_else(|| OsString::from("rustc"));
|
|
||||||
let output = Command::new(rustc).arg("--version").output().ok()?;
|
|
||||||
let version = str::from_utf8(&output.stdout).ok()?;
|
|
||||||
let mut pieces = version.split(['.', ' ', '-']);
|
|
||||||
if pieces.next() != Some("rustc") {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let _major: u32 = pieces.next()?.parse().ok()?;
|
|
||||||
let minor = pieces.next()?.parse().ok()?;
|
|
||||||
let _patch: u32 = pieces.next()?.parse().ok()?;
|
|
||||||
let toolchain = match pieces.next() {
|
|
||||||
Some("beta") => Toolchain::Beta,
|
|
||||||
Some("nightly") => Toolchain::Nightly,
|
|
||||||
_ => Toolchain::Stable,
|
|
||||||
};
|
|
||||||
let version = VersionInfo { minor, toolchain };
|
|
||||||
Some(version)
|
|
||||||
}
|
|
||||||
|
@ -5,18 +5,32 @@ pub(crate) use std::backtrace::Backtrace;
|
|||||||
pub(crate) enum Backtrace {}
|
pub(crate) enum Backtrace {}
|
||||||
|
|
||||||
#[cfg(backtrace)]
|
#[cfg(backtrace)]
|
||||||
macro_rules! backtrace_if_absent {
|
macro_rules! capture_backtrace {
|
||||||
($err:expr) => {
|
() => {
|
||||||
match $err.backtrace() {
|
Some(Backtrace::capture())
|
||||||
Some(_) => None,
|
|
||||||
None => Some(Backtrace::capture()),
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(backtrace))]
|
#[cfg(not(backtrace))]
|
||||||
macro_rules! backtrace_if_absent {
|
macro_rules! capture_backtrace {
|
||||||
($err:expr) => {
|
() => {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
/// Capture a backtrace iff there is not already a backtrace in the error chain
|
||||||
|
#[cfg(generic_member_access)]
|
||||||
|
macro_rules! backtrace_if_absent {
|
||||||
|
($err:expr) => {
|
||||||
|
match std::error::request_ref::<std::backtrace::Backtrace>($err as &dyn std::error::Error) {
|
||||||
|
Some(_) => None,
|
||||||
|
None => capture_backtrace!(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(generic_member_access))]
|
||||||
|
macro_rules! backtrace_if_absent {
|
||||||
|
($err:expr) => {
|
||||||
|
capture_backtrace!()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -2,9 +2,6 @@ use crate::error::{ContextError, ErrorImpl};
|
|||||||
use crate::{Report, StdError, WrapErr};
|
use crate::{Report, StdError, WrapErr};
|
||||||
use core::fmt::{self, Debug, Display, Write};
|
use core::fmt::{self, Debug, Display, Write};
|
||||||
|
|
||||||
#[cfg(backtrace)]
|
|
||||||
use std::backtrace::Backtrace;
|
|
||||||
|
|
||||||
mod ext {
|
mod ext {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
@ -61,42 +58,34 @@ where
|
|||||||
Err(e) => Err(e.ext_report(msg())),
|
Err(e) => Err(e.ext_report(msg())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "anyhow")]
|
#[cfg(feature = "anyhow")]
|
||||||
fn context<D>(self, msg: D) -> Result<T, Report>
|
impl<T, E> crate::ContextCompat<T> for Result<T, E>
|
||||||
|
where
|
||||||
|
Self: WrapErr<T, E>,
|
||||||
|
{
|
||||||
|
#[track_caller]
|
||||||
|
fn context<D>(self, msg: D) -> crate::Result<T, Report>
|
||||||
where
|
where
|
||||||
D: Display + Send + Sync + 'static,
|
D: Display + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
self.wrap_err(msg)
|
self.wrap_err(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "anyhow")]
|
#[track_caller]
|
||||||
fn with_context<D, F>(self, msg: F) -> Result<T, Report>
|
fn with_context<D, F>(self, f: F) -> crate::Result<T, Report>
|
||||||
where
|
where
|
||||||
D: Display + Send + Sync + 'static,
|
D: Display + Send + Sync + 'static,
|
||||||
F: FnOnce() -> D,
|
F: FnOnce() -> D,
|
||||||
{
|
{
|
||||||
self.wrap_err_with(msg)
|
self.wrap_err_with(f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "anyhow")]
|
#[cfg(feature = "anyhow")]
|
||||||
impl<T> crate::ContextCompat<T> for Option<T> {
|
impl<T> crate::ContextCompat<T> for Option<T> {
|
||||||
fn wrap_err<D>(self, msg: D) -> Result<T, Report>
|
#[track_caller]
|
||||||
where
|
|
||||||
D: Display + Send + Sync + 'static,
|
|
||||||
{
|
|
||||||
self.context(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn wrap_err_with<D, F>(self, msg: F) -> Result<T, Report>
|
|
||||||
where
|
|
||||||
D: Display + Send + Sync + 'static,
|
|
||||||
F: FnOnce() -> D,
|
|
||||||
{
|
|
||||||
self.with_context(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn context<D>(self, msg: D) -> Result<T, Report>
|
fn context<D>(self, msg: D) -> Result<T, Report>
|
||||||
where
|
where
|
||||||
D: Display + Send + Sync + 'static,
|
D: Display + Send + Sync + 'static,
|
||||||
@ -107,6 +96,7 @@ impl<T> crate::ContextCompat<T> for Option<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
fn with_context<D, F>(self, msg: F) -> Result<T, Report>
|
fn with_context<D, F>(self, msg: F) -> Result<T, Report>
|
||||||
where
|
where
|
||||||
D: Display + Send + Sync + 'static,
|
D: Display + Send + Sync + 'static,
|
||||||
@ -146,9 +136,9 @@ where
|
|||||||
D: Display,
|
D: Display,
|
||||||
E: StdError + 'static,
|
E: StdError + 'static,
|
||||||
{
|
{
|
||||||
#[cfg(backtrace)]
|
#[cfg(generic_member_access)]
|
||||||
fn backtrace(&self) -> Option<&Backtrace> {
|
fn provide<'a>(&'a self, request: &mut std::error::Request<'a>) {
|
||||||
self.error.backtrace()
|
self.error.provide(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
||||||
|
@ -855,6 +855,11 @@ impl<E> StdError for ErrorImpl<E>
|
|||||||
where
|
where
|
||||||
E: StdError,
|
E: StdError,
|
||||||
{
|
{
|
||||||
|
#[cfg(generic_member_access)]
|
||||||
|
fn provide<'a>(&'a self, request: &mut std::error::Request<'a>) {
|
||||||
|
self._object.provide(request)
|
||||||
|
}
|
||||||
|
|
||||||
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
||||||
ErrorImpl::<()>::error(self.erase()).source()
|
ErrorImpl::<()>::error(self.erase()).source()
|
||||||
}
|
}
|
||||||
|
@ -355,7 +355,7 @@
|
|||||||
unused_parens,
|
unused_parens,
|
||||||
while_true
|
while_true
|
||||||
)]
|
)]
|
||||||
#![cfg_attr(backtrace, feature(backtrace))]
|
#![cfg_attr(generic_member_access, feature(error_generic_member_access))]
|
||||||
#![cfg_attr(doc_cfg, feature(doc_cfg))]
|
#![cfg_attr(doc_cfg, feature(doc_cfg))]
|
||||||
#![allow(
|
#![allow(
|
||||||
clippy::needless_doctest_main,
|
clippy::needless_doctest_main,
|
||||||
@ -624,6 +624,7 @@ fn capture_handler(error: &(dyn StdError + 'static)) -> Box<dyn EyreHandler> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl dyn EyreHandler {
|
impl dyn EyreHandler {
|
||||||
|
/// Check if the handler is of type `T`
|
||||||
pub fn is<T: EyreHandler>(&self) -> bool {
|
pub fn is<T: EyreHandler>(&self) -> bool {
|
||||||
// Get `TypeId` of the type this function is instantiated with.
|
// Get `TypeId` of the type this function is instantiated with.
|
||||||
let t = core::any::TypeId::of::<T>();
|
let t = core::any::TypeId::of::<T>();
|
||||||
@ -635,6 +636,7 @@ impl dyn EyreHandler {
|
|||||||
t == concrete
|
t == concrete
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Downcast the handler to a concrete type
|
||||||
pub fn downcast_ref<T: EyreHandler>(&self) -> Option<&T> {
|
pub fn downcast_ref<T: EyreHandler>(&self) -> Option<&T> {
|
||||||
if self.is::<T>() {
|
if self.is::<T>() {
|
||||||
unsafe { Some(&*(self as *const dyn EyreHandler as *const T)) }
|
unsafe { Some(&*(self as *const dyn EyreHandler as *const T)) }
|
||||||
@ -643,6 +645,7 @@ impl dyn EyreHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Downcast the handler to a concrete type
|
||||||
pub fn downcast_mut<T: EyreHandler>(&mut self) -> Option<&mut T> {
|
pub fn downcast_mut<T: EyreHandler>(&mut self) -> Option<&mut T> {
|
||||||
if self.is::<T>() {
|
if self.is::<T>() {
|
||||||
unsafe { Some(&mut *(self as *mut dyn EyreHandler as *mut T)) }
|
unsafe { Some(&mut *(self as *mut dyn EyreHandler as *mut T)) }
|
||||||
@ -775,6 +778,7 @@ impl DefaultHandler {
|
|||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
#[cfg_attr(not(feature = "auto-install"), allow(dead_code))]
|
#[cfg_attr(not(feature = "auto-install"), allow(dead_code))]
|
||||||
pub fn default_with(error: &(dyn StdError + 'static)) -> Box<dyn EyreHandler> {
|
pub fn default_with(error: &(dyn StdError + 'static)) -> Box<dyn EyreHandler> {
|
||||||
|
// Capture the backtrace if the source error did not already capture one
|
||||||
let backtrace = backtrace_if_absent!(error);
|
let backtrace = backtrace_if_absent!(error);
|
||||||
|
|
||||||
Box::new(Self {
|
Box::new(Self {
|
||||||
@ -834,15 +838,19 @@ impl EyreHandler for DefaultHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(backtrace)]
|
#[cfg(generic_member_access)]
|
||||||
{
|
{
|
||||||
use std::backtrace::BacktraceStatus;
|
use std::backtrace::BacktraceStatus;
|
||||||
|
|
||||||
|
// The backtrace can be stored either in the handler instance, or the error itself.
|
||||||
|
//
|
||||||
|
// If the source error has a backtrace, the handler should not capture one
|
||||||
let backtrace = self
|
let backtrace = self
|
||||||
.backtrace
|
.backtrace
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.or_else(|| error.backtrace())
|
.or_else(|| std::error::request_ref::<Backtrace>(error))
|
||||||
.expect("backtrace capture failed");
|
.expect("backtrace capture failed");
|
||||||
|
|
||||||
if let BacktraceStatus::Captured = backtrace.status() {
|
if let BacktraceStatus::Captured = backtrace.status() {
|
||||||
write!(f, "\n\nStack backtrace:\n{}", backtrace)?;
|
write!(f, "\n\nStack backtrace:\n{}", backtrace)?;
|
||||||
}
|
}
|
||||||
@ -1122,21 +1130,6 @@ pub trait WrapErr<T, E>: context::private::Sealed {
|
|||||||
where
|
where
|
||||||
D: Display + Send + Sync + 'static,
|
D: Display + Send + Sync + 'static,
|
||||||
F: FnOnce() -> D;
|
F: FnOnce() -> D;
|
||||||
|
|
||||||
/// Compatibility re-export of wrap_err for interop with `anyhow`
|
|
||||||
#[cfg(feature = "anyhow")]
|
|
||||||
#[cfg_attr(track_caller, track_caller)]
|
|
||||||
fn context<D>(self, msg: D) -> Result<T, Report>
|
|
||||||
where
|
|
||||||
D: Display + Send + Sync + 'static;
|
|
||||||
|
|
||||||
/// Compatibility re-export of wrap_err_with for interop with `anyhow`
|
|
||||||
#[cfg(feature = "anyhow")]
|
|
||||||
#[cfg_attr(track_caller, track_caller)]
|
|
||||||
fn with_context<D, F>(self, f: F) -> Result<T, Report>
|
|
||||||
where
|
|
||||||
D: Display + Send + Sync + 'static,
|
|
||||||
F: FnOnce() -> D;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Provides the [`ok_or_eyre`][OptionExt::ok_or_eyre] method for [`Option`].
|
/// Provides the [`ok_or_eyre`][OptionExt::ok_or_eyre] method for [`Option`].
|
||||||
@ -1194,7 +1187,8 @@ pub trait OptionExt<T>: context::private::Sealed {
|
|||||||
M: Debug + Display + Send + Sync + 'static;
|
M: Debug + Display + Send + Sync + 'static;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Provides the `context` method for `Option` when porting from `anyhow`
|
/// Provides the `context` and `with_context` methods for `Result` and `Option` to enhance
|
||||||
|
/// compatibility when porting from anyhow.
|
||||||
///
|
///
|
||||||
/// This trait is sealed and cannot be implemented for types outside of
|
/// This trait is sealed and cannot be implemented for types outside of
|
||||||
/// `eyre`.
|
/// `eyre`.
|
||||||
@ -1253,19 +1247,6 @@ pub trait ContextCompat<T>: context::private::Sealed {
|
|||||||
where
|
where
|
||||||
D: Display + Send + Sync + 'static,
|
D: Display + Send + Sync + 'static,
|
||||||
F: FnOnce() -> D;
|
F: FnOnce() -> D;
|
||||||
|
|
||||||
/// Compatibility re-export of `context` for porting from `anyhow` to `eyre`
|
|
||||||
#[cfg_attr(track_caller, track_caller)]
|
|
||||||
fn wrap_err<D>(self, msg: D) -> Result<T, Report>
|
|
||||||
where
|
|
||||||
D: Display + Send + Sync + 'static;
|
|
||||||
|
|
||||||
/// Compatibility re-export of `with_context` for porting from `anyhow` to `eyre`
|
|
||||||
#[cfg_attr(track_caller, track_caller)]
|
|
||||||
fn wrap_err_with<D, F>(self, f: F) -> Result<T, Report>
|
|
||||||
where
|
|
||||||
D: Display + Send + Sync + 'static,
|
|
||||||
F: FnOnce() -> D;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Equivalent to `Ok::<_, eyre::Error>(value)`.
|
/// Equivalent to `Ok::<_, eyre::Error>(value)`.
|
||||||
|
@ -82,9 +82,9 @@ impl Display for BoxedError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl StdError for BoxedError {
|
impl StdError for BoxedError {
|
||||||
#[cfg(backtrace)]
|
#[cfg(generic_member_access)]
|
||||||
fn backtrace(&self) -> Option<&crate::backtrace::Backtrace> {
|
fn provide<'a>(&'a self, request: &mut std::error::Request<'a>) {
|
||||||
self.0.backtrace()
|
self.0.provide(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
||||||
|
73
eyre/tests/generic_member_access.rs
Normal file
73
eyre/tests/generic_member_access.rs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
#![cfg_attr(generic_member_access, feature(error_generic_member_access))]
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
|
||||||
|
#[cfg(all(generic_member_access, not(miri)))]
|
||||||
|
#[test]
|
||||||
|
/// Tests that generic member access works through an `eyre::Report`
|
||||||
|
fn generic_member_access() {
|
||||||
|
use crate::common::maybe_install_handler;
|
||||||
|
|
||||||
|
use eyre::WrapErr;
|
||||||
|
use std::backtrace::Backtrace;
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
fn fail() -> Result<(), MyError> {
|
||||||
|
Err(MyError {
|
||||||
|
cupcake: MyCupcake("Blueberry".into()),
|
||||||
|
backtrace: std::backtrace::Backtrace::capture(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
maybe_install_handler().unwrap();
|
||||||
|
|
||||||
|
std::env::set_var("RUST_BACKTRACE", "1");
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
struct MyCupcake(String);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct MyError {
|
||||||
|
cupcake: MyCupcake,
|
||||||
|
backtrace: std::backtrace::Backtrace,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for MyError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "Error: {}", self.cupcake.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for MyError {
|
||||||
|
fn provide<'a>(&'a self, request: &mut std::error::Request<'a>) {
|
||||||
|
request
|
||||||
|
.provide_ref(&self.cupcake)
|
||||||
|
.provide_ref(&self.backtrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let err = fail()
|
||||||
|
.wrap_err("Failed to bake my favorite cupcake")
|
||||||
|
.unwrap_err();
|
||||||
|
|
||||||
|
let err: Box<dyn std::error::Error> = err.into();
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
format!("{:?}", err).contains("generic_member_access::generic_member_access::fail"),
|
||||||
|
"should contain the source error backtrace"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
std::error::request_ref::<MyCupcake>(&*err),
|
||||||
|
Some(&MyCupcake("Blueberry".into()))
|
||||||
|
);
|
||||||
|
|
||||||
|
let bt = std::error::request_ref::<Backtrace>(&*err).unwrap();
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
bt.to_string()
|
||||||
|
.contains("generic_member_access::generic_member_access::fail"),
|
||||||
|
"should contain the fail method as it was captured by the original error\n\n{}",
|
||||||
|
bt
|
||||||
|
);
|
||||||
|
}
|
@ -105,7 +105,7 @@ fn test_context() {
|
|||||||
Box::new(LocationHandler::new(expected_location))
|
Box::new(LocationHandler::new(expected_location))
|
||||||
}));
|
}));
|
||||||
|
|
||||||
use eyre::WrapErr;
|
use eyre::ContextCompat;
|
||||||
let err = read_path("totally_fake_path")
|
let err = read_path("totally_fake_path")
|
||||||
.context("oopsie")
|
.context("oopsie")
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
@ -122,7 +122,7 @@ fn test_with_context() {
|
|||||||
Box::new(LocationHandler::new(expected_location))
|
Box::new(LocationHandler::new(expected_location))
|
||||||
}));
|
}));
|
||||||
|
|
||||||
use eyre::WrapErr;
|
use eyre::ContextCompat;
|
||||||
let err = read_path("totally_fake_path")
|
let err = read_path("totally_fake_path")
|
||||||
.with_context(|| "oopsie")
|
.with_context(|| "oopsie")
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
@ -131,36 +131,6 @@ fn test_with_context() {
|
|||||||
println!("{:?}", err);
|
println!("{:?}", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "anyhow")]
|
|
||||||
#[test]
|
|
||||||
fn test_option_compat_wrap_err() {
|
|
||||||
let _ = eyre::set_hook(Box::new(|_e| {
|
|
||||||
let expected_location = file!();
|
|
||||||
Box::new(LocationHandler::new(expected_location))
|
|
||||||
}));
|
|
||||||
|
|
||||||
use eyre::ContextCompat;
|
|
||||||
let err = None::<()>.wrap_err("oopsie").unwrap_err();
|
|
||||||
|
|
||||||
// should panic if the location isn't in our crate
|
|
||||||
println!("{:?}", err);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "anyhow")]
|
|
||||||
#[test]
|
|
||||||
fn test_option_compat_wrap_err_with() {
|
|
||||||
let _ = eyre::set_hook(Box::new(|_e| {
|
|
||||||
let expected_location = file!();
|
|
||||||
Box::new(LocationHandler::new(expected_location))
|
|
||||||
}));
|
|
||||||
|
|
||||||
use eyre::ContextCompat;
|
|
||||||
let err = None::<()>.wrap_err_with(|| "oopsie").unwrap_err();
|
|
||||||
|
|
||||||
// should panic if the location isn't in our crate
|
|
||||||
println!("{:?}", err);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "anyhow")]
|
#[cfg(feature = "anyhow")]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_option_compat_context() {
|
fn test_option_compat_context() {
|
||||||
|
@ -4,7 +4,6 @@ mod drop;
|
|||||||
use self::common::maybe_install_handler;
|
use self::common::maybe_install_handler;
|
||||||
use self::drop::{DetectDrop, Flag};
|
use self::drop::{DetectDrop, Flag};
|
||||||
use eyre::Report;
|
use eyre::Report;
|
||||||
use std::marker::Unpin;
|
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
// These tests check our build script against rustversion.
|
|
||||||
|
|
||||||
#[rustversion::attr(not(nightly), ignore)]
|
|
||||||
#[test]
|
|
||||||
fn nightlytest() {
|
|
||||||
if !cfg!(nightly) {
|
|
||||||
panic!("nightly feature isn't set when the toolchain is nightly.");
|
|
||||||
}
|
|
||||||
if cfg!(any(beta, stable)) {
|
|
||||||
panic!("beta, stable, and nightly are mutually exclusive features.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rustversion::attr(not(beta), ignore)]
|
|
||||||
#[test]
|
|
||||||
fn betatest() {
|
|
||||||
if !cfg!(beta) {
|
|
||||||
panic!("beta feature is not set when the toolchain is beta.");
|
|
||||||
}
|
|
||||||
if cfg!(any(nightly, stable)) {
|
|
||||||
panic!("beta, stable, and nightly are mutually exclusive features.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rustversion::attr(not(stable), ignore)]
|
|
||||||
#[test]
|
|
||||||
fn stabletest() {
|
|
||||||
if !cfg!(stable) {
|
|
||||||
panic!("stable feature is not set when the toolchain is stable.");
|
|
||||||
}
|
|
||||||
if cfg!(any(nightly, beta)) {
|
|
||||||
panic!("beta, stable, and nightly are mutually exclusive features.")
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user