blob: 0efceedfbe75c2a4d09d44c6ad74290a484f9319 [file] [log] [blame] [edit]
#![cfg_attr(doc_cfg, feature(doc_cfg))]
#![allow(
clippy::needless_doctest_main,
clippy::new_ret_no_self,
clippy::wrong_self_convention
)]
use core::fmt::Display;
use std::error::Error as StdError;
use once_cell::sync::OnceCell;
#[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-no-backtrace"))]
use crate::DebugReportHandler;
use crate::Diagnostic;
#[cfg(feature = "fancy-no-backtrace")]
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 {}
///
pub type ErrorHook =
Box<dyn Fn(&(dyn Diagnostic + 'static)) -> Box<dyn ReportHandler> + Sync + Send + 'static>;
static HOOK: OnceCell<ErrorHook> = OnceCell::new();
/// 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.set(hook).map_err(|_| InstallError)
}
#[cfg_attr(track_caller, track_caller)]
#[cfg_attr(not(track_caller), allow(unused_mut))]
fn capture_handler(error: &(dyn Diagnostic + 'static)) -> Box<dyn ReportHandler> {
let hook = HOOK.get_or_init(|| Box::new(get_default_printer)).as_ref();
#[cfg(track_caller)]
{
let mut handler = hook(error);
handler.track_caller(std::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-no-backtrace")]
return Box::new(MietteHandler::new());
#[cfg(not(feature = "fancy-no-backtrace"))]
return Box::new(DebugReportHandler::new());
}
impl dyn ReportHandler {
///
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
}
///
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
}
}
///
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 std::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)]
pub fn new_adhoc<M>(message: M) -> Report
where
M: Display + Debug + Send + Sync + 'static,
{
Report::from_adhoc(message)
}
}