| #![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) |
| } |
| } |