Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add must-install feature, so that a non-default handler can be the on… #52

Merged
merged 11 commits into from
Mar 25, 2022
Merged
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ jobs:
- --no-default-features
- --features track-caller
- --features pyo3
- --features auto-install
- --all-features
steps:
- uses: actions/checkout@v1
Expand All @@ -59,6 +60,7 @@ jobs:
- # default
- --no-default-features
- --features track-caller
- --features auto-install
# skip `--features pyo3` and `--all-features` because pyo3 doesn't support this msrv
steps:
- uses: actions/checkout@v1
Expand Down
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ readme = "README.md"
categories = ["rust-patterns"]

[features]
default = ["track-caller"]
default = ["auto-install", "track-caller"]
auto-install = []
track-caller = []

[dev-dependencies]
Expand Down
3 changes: 3 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,9 @@ impl Report {
/// #
/// # const REDACTED_CONTENT: () = ();
/// #
/// # #[cfg(not(feature = "auto-install"))]
/// # eyre::set_hook(Box::new(eyre::DefaultHandler::default_with)).unwrap();
/// #
/// # let error: Report = eyre!("...");
/// # let root_cause = &error;
/// #
Expand Down
48 changes: 46 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@
//! #
//! # const REDACTED_CONTENT: () = ();
//! #
//! # #[cfg(not(feature = "auto-install"))]
//! # eyre::set_hook(Box::new(eyre::DefaultHandler::default_with)).unwrap();
//! #
//! # let error: Report = eyre!("...");
//! # let root_cause = &error;
//! #
Expand Down Expand Up @@ -276,6 +279,9 @@
//! ```rust
//! use eyre::{eyre, Result};
//!
//! # #[cfg(not(feature = "auto-install"))]
//! # eyre::set_hook(Box::new(eyre::DefaultHandler::default_with)).unwrap();
//! #
//! let opt: Option<()> = None;
//! let result: Result<()> = opt.ok_or_else(|| eyre!("new error message"));
//! ```
Expand Down Expand Up @@ -445,7 +451,7 @@ type ErrorHook =
static HOOK: OnceCell<ErrorHook> = OnceCell::new();

/// Error indicating that `set_hook` was unable to install the provided ErrorHook
#[derive(Debug)]
#[derive(Debug, Clone, Copy)]
pub struct InstallError;

impl core::fmt::Display for InstallError {
Expand Down Expand Up @@ -559,6 +565,13 @@ pub fn set_hook(hook: ErrorHook) -> Result<(), InstallError> {
#[cfg_attr(track_caller, track_caller)]
#[cfg_attr(not(track_caller), allow(unused_mut))]
fn capture_handler(error: &(dyn StdError + 'static)) -> Box<dyn EyreHandler> {
#[cfg(not(feature = "auto-install"))]
let hook = HOOK
.get()
.expect("a handler must always be installed if the `auto-install` feature is disabled")
.as_ref();

#[cfg(feature = "auto-install")]
let hook = HOOK
.get_or_init(|| Box::new(DefaultHandler::default_with))
.as_ref();
Expand Down Expand Up @@ -699,8 +712,35 @@ pub struct DefaultHandler {
}

impl DefaultHandler {
/// Manual hook which constructs `DefaultHandler`s.
///
/// # Details
///
/// When supplied to the `set_hook` function, `default_with` will cause `eyre::Report` to use
/// `DefaultHandler` as the error report handler.
///
/// If the `auto-install` feature is enabled, and a user-provided hook for constructing
/// `EyreHandlers` was not installed using `set_hook`, `DefaultHandler::default_with`
/// is automatically installed as the hook.
///
/// # Example
///
/// ```rust,should_panic
/// use eyre::{DefaultHandler, eyre, InstallError, Result, set_hook};
///
/// fn main() -> Result<()> {
/// install_default().expect("default handler inexplicably already installed");
/// Err(eyre!("hello from default error city!"))
/// }
///
/// fn install_default() -> Result<(), InstallError> {
/// set_hook(Box::new(DefaultHandler::default_with))
/// }
///
/// ```
#[allow(unused_variables)]
fn default_with(error: &(dyn StdError + 'static)) -> Box<dyn EyreHandler> {
#[cfg_attr(not(feature = "auto-install"), allow(dead_code))]
yaahc marked this conversation as resolved.
Show resolved Hide resolved
pub fn default_with(error: &(dyn StdError + 'static)) -> Box<dyn EyreHandler> {
let backtrace = backtrace_if_absent!(error);

Box::new(Self {
Expand Down Expand Up @@ -973,6 +1013,8 @@ pub type Result<T, E = Report> = core::result::Result<T, E>;
/// }
///
/// fn main() {
/// # #[cfg(not(feature = "auto-install"))]
/// # eyre::set_hook(Box::new(eyre::DefaultHandler::default_with)).unwrap();
/// let err = do_it().unwrap_err();
/// if let Some(e) = err.downcast_ref::<SuspiciousError>() {
/// // If helper() returned SuspiciousError, this downcast will
Expand Down Expand Up @@ -1013,6 +1055,8 @@ pub type Result<T, E = Report> = core::result::Result<T, E>;
/// }
///
/// fn main() {
/// # #[cfg(not(feature = "auto-install"))]
/// # eyre::set_hook(Box::new(eyre::DefaultHandler::default_with)).unwrap();
/// let err = do_it().unwrap_err();
/// if let Some(e) = err.downcast_ref::<HelperFailed>() {
/// // If helper failed, this downcast will succeed because
Expand Down
17 changes: 16 additions & 1 deletion tests/common/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use eyre::{bail, Result};
#![allow(dead_code)]

use eyre::{bail, set_hook, DefaultHandler, InstallError, Result};
use once_cell::sync::OnceCell;
use std::io;

pub fn bail_literal() -> Result<()> {
Expand All @@ -12,3 +15,15 @@ pub fn bail_fmt() -> Result<()> {
pub fn bail_error() -> Result<()> {
bail!(io::Error::new(io::ErrorKind::Other, "oh no!"));
}

// Tests are multithreaded- use OnceCell to install hook once if auto-install
// feature is disabled.
pub fn maybe_install_handler() -> Result<(), InstallError> {
static INSTALLER: OnceCell<Result<(), InstallError>> = OnceCell::new();

if cfg!(not(feature = "auto-install")) {
*INSTALLER.get_or_init(|| set_hook(Box::new(DefaultHandler::default_with)))
} else {
Ok(())
}
}
11 changes: 11 additions & 0 deletions tests/test_boxed.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
mod common;

use self::common::maybe_install_handler;
use eyre::{eyre, Report};
use std::error::Error as StdError;
use std::io;
Expand All @@ -11,6 +14,8 @@ struct MyError {

#[test]
fn test_boxed_str() {
maybe_install_handler().unwrap();

let error = Box::<dyn StdError + Send + Sync>::from("oh no!");
let error: Report = eyre!(error);
assert_eq!("oh no!", error.to_string());
Expand All @@ -25,6 +30,8 @@ fn test_boxed_str() {

#[test]
fn test_boxed_thiserror() {
maybe_install_handler().unwrap();

let error = MyError {
source: io::Error::new(io::ErrorKind::Other, "oh no!"),
};
Expand All @@ -34,13 +41,17 @@ fn test_boxed_thiserror() {

#[test]
fn test_boxed_eyre() {
maybe_install_handler().unwrap();

let error: Report = eyre!("oh no!").wrap_err("it failed");
let error = eyre!(error);
assert_eq!("oh no!", error.source().unwrap().to_string());
}

#[test]
fn test_boxed_sources() {
maybe_install_handler().unwrap();

let error = MyError {
source: io::Error::new(io::ErrorKind::Other, "oh no!"),
};
Expand Down
9 changes: 9 additions & 0 deletions tests/test_chain.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
mod common;

use self::common::maybe_install_handler;
use eyre::{eyre, Report};

fn error() -> Report {
Expand All @@ -6,6 +9,8 @@ fn error() -> Report {

#[test]
fn test_iter() {
maybe_install_handler().unwrap();

let e = error();
let mut chain = e.chain();
assert_eq!("3", chain.next().unwrap().to_string());
Expand All @@ -18,6 +23,8 @@ fn test_iter() {

#[test]
fn test_rev() {
maybe_install_handler().unwrap();

let e = error();
let mut chain = e.chain().rev();
assert_eq!("0", chain.next().unwrap().to_string());
Expand All @@ -30,6 +37,8 @@ fn test_rev() {

#[test]
fn test_len() {
maybe_install_handler().unwrap();

let e = error();
let mut chain = e.chain();
assert_eq!(4, chain.len());
Expand Down
12 changes: 12 additions & 0 deletions tests/test_context.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
mod common;
mod drop;

use crate::common::maybe_install_handler;
use crate::drop::{DetectDrop, Flag};
use eyre::{Report, Result, WrapErr};
use std::fmt::{self, Display};
Expand Down Expand Up @@ -89,6 +91,8 @@ fn make_chain() -> (Report, Dropped) {

#[test]
fn test_downcast_ref() {
maybe_install_handler().unwrap();

let (err, dropped) = make_chain();

assert!(!err.is::<String>());
Expand All @@ -113,6 +117,8 @@ fn test_downcast_ref() {

#[test]
fn test_downcast_high() {
maybe_install_handler().unwrap();

let (err, dropped) = make_chain();

let err = err.downcast::<HighLevel>().unwrap();
Expand All @@ -125,6 +131,8 @@ fn test_downcast_high() {

#[test]
fn test_downcast_mid() {
maybe_install_handler().unwrap();

let (err, dropped) = make_chain();

let err = err.downcast::<MidLevel>().unwrap();
Expand All @@ -137,6 +145,8 @@ fn test_downcast_mid() {

#[test]
fn test_downcast_low() {
maybe_install_handler().unwrap();

let (err, dropped) = make_chain();

let err = err.downcast::<LowLevel>().unwrap();
Expand All @@ -149,6 +159,8 @@ fn test_downcast_low() {

#[test]
fn test_unsuccessful_downcast() {
maybe_install_handler().unwrap();

let (err, dropped) = make_chain();

let err = err.downcast::<String>().unwrap_err();
Expand Down
6 changes: 6 additions & 0 deletions tests/test_context_access.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
mod common;

use crate::common::maybe_install_handler;

#[test]
fn test_context() {
use eyre::{eyre, Report};

maybe_install_handler().unwrap();

let error: Report = eyre!("oh no!");
let _ = error.context();
}
4 changes: 4 additions & 0 deletions tests/test_convert.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
mod common;
mod drop;

use self::common::maybe_install_handler;
use self::drop::{DetectDrop, Flag};
use eyre::{Report, Result};
use std::error::Error as StdError;

#[test]
fn test_convert() {
maybe_install_handler().unwrap();

let has_dropped = Flag::new();
let error: Report = Report::new(DetectDrop::new(&has_dropped));
let box_dyn = Box::<dyn StdError + Send + Sync>::from(error);
Expand Down
12 changes: 12 additions & 0 deletions tests/test_downcast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ use std::io;

#[test]
fn test_downcast() {
maybe_install_handler().unwrap();

#[cfg(not(eyre_no_fmt_arguments_as_str))]
assert_eq!(
"oh no!",
Expand Down Expand Up @@ -38,6 +40,8 @@ fn test_downcast() {

#[test]
fn test_downcast_ref() {
maybe_install_handler().unwrap();

#[cfg(not(eyre_no_fmt_arguments_as_str))]
assert_eq!(
"oh no!",
Expand Down Expand Up @@ -69,6 +73,8 @@ fn test_downcast_ref() {

#[test]
fn test_downcast_mut() {
maybe_install_handler().unwrap();

#[cfg(not(eyre_no_fmt_arguments_as_str))]
assert_eq!(
"oh no!",
Expand Down Expand Up @@ -100,6 +106,8 @@ fn test_downcast_mut() {

#[test]
fn test_drop() {
maybe_install_handler().unwrap();

let has_dropped = Flag::new();
let error: Report = Report::new(DetectDrop::new(&has_dropped));
drop(error.downcast::<DetectDrop>().unwrap());
Expand All @@ -108,6 +116,8 @@ fn test_drop() {

#[test]
fn test_large_alignment() {
maybe_install_handler().unwrap();

#[repr(align(64))]
#[derive(Debug)]
struct LargeAlignedError(&'static str);
Expand All @@ -129,6 +139,8 @@ fn test_large_alignment() {

#[test]
fn test_unsuccessful_downcast() {
maybe_install_handler().unwrap();

let mut error = bail_error().unwrap_err();
assert!(error.downcast_ref::<&str>().is_none());
assert!(error.downcast_mut::<&str>().is_none());
Expand Down
Loading