mirror of https://github.com/zkat/miette.git
497 lines
14 KiB
Rust
497 lines
14 KiB
Rust
#![cfg_attr(doc_cfg, feature(doc_cfg))]
|
|
#![allow(
|
|
clippy::needless_doctest_main,
|
|
clippy::new_ret_no_self,
|
|
clippy::wrong_self_convention
|
|
)]
|
|
extern crate alloc;
|
|
|
|
use core::fmt::Display;
|
|
use alloc::boxed::Box;
|
|
|
|
#[cfg(feature = "std")]
|
|
use std::error::Error as StdError;
|
|
#[cfg(not(feature = "std"))]
|
|
use crate::StdError as StdError;
|
|
use spin::Once;
|
|
|
|
#[allow(unreachable_pub)]
|
|
pub use into_diagnostic::*;
|
|
#[doc(hidden)]
|
|
#[allow(unreachable_pub)]
|
|
pub use Report as ErrReport;
|
|
/// Compatibility re-export of `Report` for interop with `anyhow`
|
|
#[allow(unreachable_pub)]
|
|
pub use Report as Error;
|
|
#[doc(hidden)]
|
|
#[allow(unreachable_pub)]
|
|
pub use ReportHandler as EyreContext;
|
|
/// Compatibility re-export of `WrapErr` for interop with `anyhow`
|
|
#[allow(unreachable_pub)]
|
|
pub use WrapErr as Context;
|
|
|
|
#[cfg(not(feature = "fancy-base"))]
|
|
use crate::DebugReportHandler;
|
|
use crate::Diagnostic;
|
|
#[cfg(feature = "fancy-base")]
|
|
use crate::MietteHandler;
|
|
|
|
use error::ErrorImpl;
|
|
|
|
use self::ptr::Own;
|
|
|
|
mod context;
|
|
mod error;
|
|
mod fmt;
|
|
mod into_diagnostic;
|
|
mod kind;
|
|
mod macros;
|
|
mod ptr;
|
|
mod wrapper;
|
|
|
|
/**
|
|
Core Diagnostic wrapper type.
|
|
|
|
## `eyre` Users
|
|
|
|
You can just replace `use`s of `eyre::Report` with `miette::Report`.
|
|
*/
|
|
pub struct Report {
|
|
inner: Own<ErrorImpl<()>>,
|
|
}
|
|
|
|
unsafe impl Sync for Report {}
|
|
unsafe impl Send for Report {}
|
|
|
|
#[allow(missing_docs)]
|
|
pub type ErrorHook =
|
|
Box<dyn Fn(&(dyn Diagnostic + 'static)) -> Box<dyn ReportHandler> + Sync + Send + 'static>;
|
|
|
|
static HOOK: Once<ErrorHook> = Once::new();
|
|
|
|
fn default_hook() -> ErrorHook {
|
|
Box::new(get_default_printer)
|
|
}
|
|
|
|
/// Error indicating that [`set_hook()`] was unable to install the provided
|
|
/// [`ErrorHook`].
|
|
#[derive(Debug)]
|
|
pub struct InstallError;
|
|
|
|
impl core::fmt::Display for InstallError {
|
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
f.write_str("cannot install provided ErrorHook, a hook has already been installed")
|
|
}
|
|
}
|
|
|
|
impl StdError for InstallError {}
|
|
impl Diagnostic for InstallError {}
|
|
|
|
/**
|
|
Set the error hook.
|
|
*/
|
|
pub fn set_hook(hook: ErrorHook) -> Result<(), InstallError> {
|
|
HOOK.call_once(|| hook);
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg_attr(track_caller, track_caller)]
|
|
#[cfg_attr(not(track_caller), allow(unused_mut))]
|
|
fn capture_handler(error: &(dyn Diagnostic + 'static)) -> Box<dyn ReportHandler> {
|
|
static DEFAULT: Once<ErrorHook> = Once::new();
|
|
let hook = HOOK.get().unwrap_or_else(|| {
|
|
DEFAULT.call_once(|| default_hook());
|
|
DEFAULT.get().unwrap()
|
|
});
|
|
|
|
#[cfg(track_caller)]
|
|
{
|
|
let mut handler = hook(error);
|
|
handler.track_caller(core::panic::Location::caller());
|
|
handler
|
|
}
|
|
#[cfg(not(track_caller))]
|
|
{
|
|
hook(error)
|
|
}
|
|
}
|
|
|
|
fn get_default_printer(_err: &(dyn Diagnostic + 'static)) -> Box<dyn ReportHandler + 'static> {
|
|
#[cfg(feature = "fancy-base")]
|
|
return Box::new(MietteHandler::new());
|
|
#[cfg(not(feature = "fancy-base"))]
|
|
return Box::new(DebugReportHandler::new());
|
|
}
|
|
|
|
impl dyn ReportHandler {
|
|
#[allow(missing_docs)]
|
|
pub fn is<T: ReportHandler>(&self) -> bool {
|
|
// Get `TypeId` of the type this function is instantiated with.
|
|
let t = core::any::TypeId::of::<T>();
|
|
|
|
// Get `TypeId` of the type in the trait object (`self`).
|
|
let concrete = self.type_id();
|
|
|
|
// Compare both `TypeId`s on equality.
|
|
t == concrete
|
|
}
|
|
|
|
#[allow(missing_docs)]
|
|
pub fn downcast_ref<T: ReportHandler>(&self) -> Option<&T> {
|
|
if self.is::<T>() {
|
|
unsafe { Some(&*(self as *const dyn ReportHandler as *const T)) }
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
#[allow(missing_docs)]
|
|
pub fn downcast_mut<T: ReportHandler>(&mut self) -> Option<&mut T> {
|
|
if self.is::<T>() {
|
|
unsafe { Some(&mut *(self as *mut dyn ReportHandler as *mut T)) }
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Error Report Handler trait for customizing `miette::Report`
|
|
pub trait ReportHandler: core::any::Any + Send + Sync {
|
|
/// Define the report format
|
|
///
|
|
/// Used to override the report format of `miette::Report`
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```rust
|
|
/// use indenter::indented;
|
|
/// use miette::{Diagnostic, ReportHandler};
|
|
///
|
|
/// pub struct Handler;
|
|
///
|
|
/// impl ReportHandler for Handler {
|
|
/// fn debug(
|
|
/// &self,
|
|
/// error: &dyn Diagnostic,
|
|
/// f: &mut core::fmt::Formatter<'_>,
|
|
/// ) -> core::fmt::Result {
|
|
/// use core::fmt::Write as _;
|
|
///
|
|
/// if f.alternate() {
|
|
/// return core::fmt::Debug::fmt(error, f);
|
|
/// }
|
|
///
|
|
/// write!(f, "{}", error)?;
|
|
///
|
|
/// Ok(())
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
fn debug(&self, error: &dyn Diagnostic, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result;
|
|
|
|
/// Override for the `Display` format
|
|
fn display(
|
|
&self,
|
|
error: &(dyn StdError + 'static),
|
|
f: &mut core::fmt::Formatter<'_>,
|
|
) -> core::fmt::Result {
|
|
write!(f, "{}", error)?;
|
|
|
|
if f.alternate() {
|
|
for cause in crate::chain::Chain::new(error).skip(1) {
|
|
write!(f, ": {}", cause)?;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Store the location of the caller who constructed this error report
|
|
#[allow(unused_variables)]
|
|
fn track_caller(&mut self, location: &'static core::panic::Location<'static>) {}
|
|
}
|
|
|
|
/// type alias for `Result<T, Report>`
|
|
///
|
|
/// This is a reasonable return type to use throughout your application but also
|
|
/// for `main()`. If you do, failures will be printed along with a backtrace if
|
|
/// one was captured.
|
|
///
|
|
/// `miette::Result` may be used with one *or* two type parameters.
|
|
///
|
|
/// ```rust
|
|
/// use miette::Result;
|
|
///
|
|
/// # const IGNORE: &str = stringify! {
|
|
/// fn demo1() -> Result<T> {...}
|
|
/// // ^ equivalent to std::result::Result<T, miette::Error>
|
|
///
|
|
/// fn demo2() -> Result<T, OtherError> {...}
|
|
/// // ^ equivalent to std::result::Result<T, OtherError>
|
|
/// # };
|
|
/// ```
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```
|
|
/// # pub trait Deserialize {}
|
|
/// #
|
|
/// # mod serde_json {
|
|
/// # use super::Deserialize;
|
|
/// # use std::io;
|
|
/// #
|
|
/// # pub fn from_str<T: Deserialize>(json: &str) -> io::Result<T> {
|
|
/// # unimplemented!()
|
|
/// # }
|
|
/// # }
|
|
/// #
|
|
/// # #[derive(Debug)]
|
|
/// # struct ClusterMap;
|
|
/// #
|
|
/// # impl Deserialize for ClusterMap {}
|
|
/// #
|
|
/// use miette::{IntoDiagnostic, Result};
|
|
///
|
|
/// fn main() -> Result<()> {
|
|
/// # return Ok(());
|
|
/// let config = std::fs::read_to_string("cluster.json").into_diagnostic()?;
|
|
/// let map: ClusterMap = serde_json::from_str(&config).into_diagnostic()?;
|
|
/// println!("cluster info: {:#?}", map);
|
|
/// Ok(())
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// ## `anyhow`/`eyre` Users
|
|
///
|
|
/// You can just replace `use`s of `anyhow::Result`/`eyre::Result` with
|
|
/// `miette::Result`.
|
|
pub type Result<T, E = Report> = core::result::Result<T, E>;
|
|
|
|
/// Provides the [`wrap_err()`](WrapErr::wrap_err) method for [`Result`].
|
|
///
|
|
/// This trait is sealed and cannot be implemented for types outside of
|
|
/// `miette`.
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```
|
|
/// use miette::{WrapErr, IntoDiagnostic, Result};
|
|
/// use std::{fs, path::PathBuf};
|
|
///
|
|
/// pub struct ImportantThing {
|
|
/// path: PathBuf,
|
|
/// }
|
|
///
|
|
/// impl ImportantThing {
|
|
/// # const IGNORE: &'static str = stringify! {
|
|
/// pub fn detach(&mut self) -> Result<()> {...}
|
|
/// # };
|
|
/// # fn detach(&mut self) -> Result<()> {
|
|
/// # unimplemented!()
|
|
/// # }
|
|
/// }
|
|
///
|
|
/// pub fn do_it(mut it: ImportantThing) -> Result<Vec<u8>> {
|
|
/// it.detach().wrap_err("Failed to detach the important thing")?;
|
|
///
|
|
/// let path = &it.path;
|
|
/// let content = fs::read(path)
|
|
/// .into_diagnostic()
|
|
/// .wrap_err_with(|| format!(
|
|
/// "Failed to read instrs from {}",
|
|
/// path.display())
|
|
/// )?;
|
|
///
|
|
/// Ok(content)
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// When printed, the outermost error would be printed first and the lower
|
|
/// level underlying causes would be enumerated below.
|
|
///
|
|
/// ```console
|
|
/// Error: Failed to read instrs from ./path/to/instrs.json
|
|
///
|
|
/// Caused by:
|
|
/// No such file or directory (os error 2)
|
|
/// ```
|
|
///
|
|
/// # Wrapping Types That Do Not Implement `Error`
|
|
///
|
|
/// For example `&str` and `Box<dyn Error>`.
|
|
///
|
|
/// Due to restrictions for coherence `Report` cannot implement `From` for types
|
|
/// that don't implement `Error`. Attempts to do so will give `"this type might
|
|
/// implement Error in the future"` as an error. As such, `wrap_err()`, which
|
|
/// uses `From` under the hood, cannot be used to wrap these types. Instead we
|
|
/// encourage you to use the combinators provided for `Result` in `std`/`core`.
|
|
///
|
|
/// For example, instead of this:
|
|
///
|
|
/// ```rust,compile_fail
|
|
/// use std::error::Error;
|
|
/// use miette::{WrapErr, Report};
|
|
///
|
|
/// fn wrap_example(err: Result<(), Box<dyn Error + Send + Sync + 'static>>)
|
|
/// -> Result<(), Report>
|
|
/// {
|
|
/// err.wrap_err("saw a downstream error")
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// We encourage you to write this:
|
|
///
|
|
/// ```rust
|
|
/// use miette::{miette, Report, WrapErr};
|
|
/// use std::error::Error;
|
|
///
|
|
/// fn wrap_example(err: Result<(), Box<dyn Error + Send + Sync + 'static>>) -> Result<(), Report> {
|
|
/// err.map_err(|e| miette!(e))
|
|
/// .wrap_err("saw a downstream error")
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// # Effect on Downcasting
|
|
///
|
|
/// After attaching a message of type `D` onto an error of type `E`, the
|
|
/// resulting `miette::Error` may be downcast to `D` **or** to `E`.
|
|
///
|
|
/// That is, in codebases that rely on downcasting, `miette`'s `wrap_err()`
|
|
/// supports both of the following use cases:
|
|
///
|
|
/// - **Attaching messages whose type is insignificant onto errors whose type
|
|
/// is used in downcasts.**
|
|
///
|
|
/// In other error libraries whose `wrap_err()` is not designed this way, it
|
|
/// can be risky to introduce messages to existing code because new message
|
|
/// might break existing working downcasts. In miette, any downcast that
|
|
/// worked before adding the message will continue to work after you add a
|
|
/// message, so you should freely wrap errors wherever it would be helpful.
|
|
///
|
|
/// ```
|
|
/// # use miette::bail;
|
|
/// # use thiserror::Error;
|
|
/// #
|
|
/// # #[derive(Error, Debug)]
|
|
/// # #[error("???")]
|
|
/// # struct SuspiciousError;
|
|
/// #
|
|
/// # fn helper() -> Result<()> {
|
|
/// # bail!(SuspiciousError);
|
|
/// # }
|
|
/// #
|
|
/// use miette::{WrapErr, Result};
|
|
///
|
|
/// fn do_it() -> Result<()> {
|
|
/// helper().wrap_err("Failed to complete the work")?;
|
|
/// # const IGNORE: &str = stringify! {
|
|
/// ...
|
|
/// # };
|
|
/// # unreachable!()
|
|
/// }
|
|
///
|
|
/// fn main() {
|
|
/// let err = do_it().unwrap_err();
|
|
/// if let Some(e) = err.downcast_ref::<SuspiciousError>() {
|
|
/// // If helper() returned SuspiciousError, this downcast will
|
|
/// // correctly succeed even with the message in between.
|
|
/// # return;
|
|
/// }
|
|
/// # panic!("expected downcast to succeed");
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// - **Attaching message whose type is used in downcasts onto errors whose
|
|
/// type is insignificant.**
|
|
///
|
|
/// Some codebases prefer to use machine-readable messages to categorize
|
|
/// lower level errors in a way that will be actionable to higher levels of
|
|
/// the application.
|
|
///
|
|
/// ```
|
|
/// # use miette::bail;
|
|
/// # use thiserror::Error;
|
|
/// #
|
|
/// # #[derive(Error, Debug)]
|
|
/// # #[error("???")]
|
|
/// # struct HelperFailed;
|
|
/// #
|
|
/// # fn helper() -> Result<()> {
|
|
/// # bail!("no such file or directory");
|
|
/// # }
|
|
/// #
|
|
/// use miette::{WrapErr, Result};
|
|
///
|
|
/// fn do_it() -> Result<()> {
|
|
/// helper().wrap_err(HelperFailed)?;
|
|
/// # const IGNORE: &str = stringify! {
|
|
/// ...
|
|
/// # };
|
|
/// # unreachable!()
|
|
/// }
|
|
///
|
|
/// fn main() {
|
|
/// let err = do_it().unwrap_err();
|
|
/// if let Some(e) = err.downcast_ref::<HelperFailed>() {
|
|
/// // If helper failed, this downcast will succeed because
|
|
/// // HelperFailed is the message that has been attached to
|
|
/// // that error.
|
|
/// # return;
|
|
/// }
|
|
/// # panic!("expected downcast to succeed");
|
|
/// }
|
|
/// ```
|
|
pub trait WrapErr<T, E>: context::private::Sealed {
|
|
/// Wrap the error value with a new adhoc error
|
|
#[cfg_attr(track_caller, track_caller)]
|
|
fn wrap_err<D>(self, msg: D) -> Result<T, Report>
|
|
where
|
|
D: Display + Send + Sync + 'static;
|
|
|
|
/// Wrap the error value with a new adhoc error that is evaluated lazily
|
|
/// only once an error does occur.
|
|
#[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;
|
|
|
|
/// Compatibility re-export of `wrap_err()` for interop with `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_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;
|
|
}
|
|
|
|
// Private API. Referenced by macro-generated code.
|
|
#[doc(hidden)]
|
|
pub mod private {
|
|
use super::Report;
|
|
use core::fmt::{Debug, Display};
|
|
|
|
pub use core::result::Result::Err;
|
|
|
|
#[doc(hidden)]
|
|
pub mod kind {
|
|
pub use super::super::kind::{AdhocKind, TraitKind};
|
|
|
|
pub use super::super::kind::BoxedKind;
|
|
}
|
|
|
|
#[cfg_attr(track_caller, track_caller)]
|
|
#[cold]
|
|
pub fn new_adhoc<M>(message: M) -> Report
|
|
where
|
|
M: Display + Debug + Send + Sync + 'static,
|
|
{
|
|
Report::from_adhoc(message)
|
|
}
|
|
}
|