miette/eyreish/
mod.rs

1#![cfg_attr(doc_cfg, feature(doc_cfg))]
2#![allow(
3    clippy::needless_doctest_main,
4    clippy::new_ret_no_self,
5    clippy::wrong_self_convention
6)]
7use core::fmt::Display;
8
9use std::error::Error as StdError;
10use std::sync::OnceLock;
11
12#[allow(unreachable_pub)]
13pub use into_diagnostic::*;
14#[doc(hidden)]
15#[allow(unreachable_pub)]
16pub use Report as ErrReport;
17/// Compatibility re-export of `Report` for interop with `anyhow`
18#[allow(unreachable_pub)]
19pub use Report as Error;
20#[doc(hidden)]
21#[allow(unreachable_pub)]
22pub use ReportHandler as EyreContext;
23/// Compatibility re-export of `WrapErr` for interop with `anyhow`
24#[allow(unreachable_pub)]
25pub use WrapErr as Context;
26
27#[cfg(not(feature = "fancy-base"))]
28use crate::DebugReportHandler;
29use crate::Diagnostic;
30#[cfg(feature = "fancy-base")]
31use crate::MietteHandler;
32
33use error::ErrorImpl;
34
35use self::ptr::Own;
36
37mod context;
38mod error;
39mod fmt;
40mod into_diagnostic;
41mod kind;
42mod macros;
43mod ptr;
44mod wrapper;
45
46/**
47Core Diagnostic wrapper type.
48
49## `eyre` Users
50
51You can just replace `use`s of `eyre::Report` with `miette::Report`.
52*/
53pub struct Report {
54    inner: Own<ErrorImpl<()>>,
55}
56
57unsafe impl Sync for Report {}
58unsafe impl Send for Report {}
59
60#[allow(missing_docs)]
61pub type ErrorHook =
62    Box<dyn Fn(&(dyn Diagnostic + 'static)) -> Box<dyn ReportHandler> + Sync + Send + 'static>;
63
64static HOOK: OnceLock<ErrorHook> = OnceLock::new();
65
66/// Error indicating that [`set_hook()`] was unable to install the provided
67/// [`ErrorHook`].
68#[derive(Debug)]
69pub struct InstallError;
70
71impl core::fmt::Display for InstallError {
72    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
73        f.write_str("cannot install provided ErrorHook, a hook has already been installed")
74    }
75}
76
77impl StdError for InstallError {}
78impl Diagnostic for InstallError {}
79
80/**
81Set the error hook.
82*/
83pub fn set_hook(hook: ErrorHook) -> Result<(), InstallError> {
84    HOOK.set(hook).map_err(|_| InstallError)
85}
86
87#[cfg_attr(track_caller, track_caller)]
88#[cfg_attr(not(track_caller), allow(unused_mut))]
89fn capture_handler(error: &(dyn Diagnostic + 'static)) -> Box<dyn ReportHandler> {
90    let hook = HOOK.get_or_init(|| Box::new(get_default_printer)).as_ref();
91
92    #[cfg(track_caller)]
93    {
94        let mut handler = hook(error);
95        handler.track_caller(std::panic::Location::caller());
96        handler
97    }
98    #[cfg(not(track_caller))]
99    {
100        hook(error)
101    }
102}
103
104fn get_default_printer(_err: &(dyn Diagnostic + 'static)) -> Box<dyn ReportHandler + 'static> {
105    #[cfg(feature = "fancy-base")]
106    return Box::new(MietteHandler::new());
107    #[cfg(not(feature = "fancy-base"))]
108    return Box::new(DebugReportHandler::new());
109}
110
111impl dyn ReportHandler {
112    #[allow(missing_docs)]
113    pub fn is<T: ReportHandler>(&self) -> bool {
114        // Get `TypeId` of the type this function is instantiated with.
115        let t = core::any::TypeId::of::<T>();
116
117        // Get `TypeId` of the type in the trait object (`self`).
118        let concrete = self.type_id();
119
120        // Compare both `TypeId`s on equality.
121        t == concrete
122    }
123
124    #[allow(missing_docs)]
125    pub fn downcast_ref<T: ReportHandler>(&self) -> Option<&T> {
126        if self.is::<T>() {
127            unsafe { Some(&*(self as *const dyn ReportHandler as *const T)) }
128        } else {
129            None
130        }
131    }
132
133    #[allow(missing_docs)]
134    pub fn downcast_mut<T: ReportHandler>(&mut self) -> Option<&mut T> {
135        if self.is::<T>() {
136            unsafe { Some(&mut *(self as *mut dyn ReportHandler as *mut T)) }
137        } else {
138            None
139        }
140    }
141}
142
143/// Error Report Handler trait for customizing `miette::Report`
144pub trait ReportHandler: core::any::Any + Send + Sync {
145    /// Define the report format
146    ///
147    /// Used to override the report format of `miette::Report`
148    ///
149    /// # Example
150    ///
151    /// ```rust
152    /// use indenter::indented;
153    /// use miette::{Diagnostic, ReportHandler};
154    ///
155    /// pub struct Handler;
156    ///
157    /// impl ReportHandler for Handler {
158    ///     fn debug(
159    ///         &self,
160    ///         error: &dyn Diagnostic,
161    ///         f: &mut core::fmt::Formatter<'_>,
162    ///     ) -> core::fmt::Result {
163    ///         use core::fmt::Write as _;
164    ///
165    ///         if f.alternate() {
166    ///             return core::fmt::Debug::fmt(error, f);
167    ///         }
168    ///
169    ///         write!(f, "{}", error)?;
170    ///
171    ///         Ok(())
172    ///     }
173    /// }
174    /// ```
175    fn debug(
176        &self,
177        error: &(dyn Diagnostic),
178        f: &mut core::fmt::Formatter<'_>,
179    ) -> core::fmt::Result;
180
181    /// Override for the `Display` format
182    fn display(
183        &self,
184        error: &(dyn StdError + 'static),
185        f: &mut core::fmt::Formatter<'_>,
186    ) -> core::fmt::Result {
187        write!(f, "{}", error)?;
188
189        if f.alternate() {
190            for cause in crate::chain::Chain::new(error).skip(1) {
191                write!(f, ": {}", cause)?;
192            }
193        }
194
195        Ok(())
196    }
197
198    /// Store the location of the caller who constructed this error report
199    #[allow(unused_variables)]
200    fn track_caller(&mut self, location: &'static std::panic::Location<'static>) {}
201}
202
203/// type alias for `Result<T, Report>`
204///
205/// This is a reasonable return type to use throughout your application but also
206/// for `main()`. If you do, failures will be printed along with a backtrace if
207/// one was captured.
208///
209/// `miette::Result` may be used with one *or* two type parameters.
210///
211/// ```rust
212/// use miette::Result;
213///
214/// # const IGNORE: &str = stringify! {
215/// fn demo1() -> Result<T> {...}
216///            // ^ equivalent to std::result::Result<T, miette::Error>
217///
218/// fn demo2() -> Result<T, OtherError> {...}
219///            // ^ equivalent to std::result::Result<T, OtherError>
220/// # };
221/// ```
222///
223/// # Example
224///
225/// ```
226/// # pub trait Deserialize {}
227/// #
228/// # mod serde_json {
229/// #     use super::Deserialize;
230/// #     use std::io;
231/// #
232/// #     pub fn from_str<T: Deserialize>(json: &str) -> io::Result<T> {
233/// #         unimplemented!()
234/// #     }
235/// # }
236/// #
237/// # #[derive(Debug)]
238/// # struct ClusterMap;
239/// #
240/// # impl Deserialize for ClusterMap {}
241/// #
242/// use miette::{IntoDiagnostic, Result};
243///
244/// fn main() -> Result<()> {
245///     # return Ok(());
246///     let config = std::fs::read_to_string("cluster.json").into_diagnostic()?;
247///     let map: ClusterMap = serde_json::from_str(&config).into_diagnostic()?;
248///     println!("cluster info: {:#?}", map);
249///     Ok(())
250/// }
251/// ```
252///
253/// ## `anyhow`/`eyre` Users
254///
255/// You can just replace `use`s of `anyhow::Result`/`eyre::Result` with
256/// `miette::Result`.
257pub type Result<T, E = Report> = core::result::Result<T, E>;
258
259/// Provides the [`wrap_err()`](WrapErr::wrap_err) method for [`Result`].
260///
261/// This trait is sealed and cannot be implemented for types outside of
262/// `miette`.
263///
264/// # Example
265///
266/// ```
267/// use miette::{WrapErr, IntoDiagnostic, Result};
268/// use std::{fs, path::PathBuf};
269///
270/// pub struct ImportantThing {
271///     path: PathBuf,
272/// }
273///
274/// impl ImportantThing {
275///     # const IGNORE: &'static str = stringify! {
276///     pub fn detach(&mut self) -> Result<()> {...}
277///     # };
278///     # fn detach(&mut self) -> Result<()> {
279///     #     unimplemented!()
280///     # }
281/// }
282///
283/// pub fn do_it(mut it: ImportantThing) -> Result<Vec<u8>> {
284///     it.detach().wrap_err("Failed to detach the important thing")?;
285///
286///     let path = &it.path;
287///     let content = fs::read(path)
288///         .into_diagnostic()
289///         .wrap_err_with(|| format!(
290///             "Failed to read instrs from {}",
291///             path.display())
292///         )?;
293///
294///     Ok(content)
295/// }
296/// ```
297///
298/// When printed, the outermost error would be printed first and the lower
299/// level underlying causes would be enumerated below.
300///
301/// ```console
302/// Error: Failed to read instrs from ./path/to/instrs.json
303///
304/// Caused by:
305///     No such file or directory (os error 2)
306/// ```
307///
308/// # Wrapping Types That Do Not Implement `Error`
309///
310/// For example `&str` and `Box<dyn Error>`.
311///
312/// Due to restrictions for coherence `Report` cannot implement `From` for types
313/// that don't implement `Error`. Attempts to do so will give `"this type might
314/// implement Error in the future"` as an error. As such, `wrap_err()`, which
315/// uses `From` under the hood, cannot be used to wrap these types. Instead we
316/// encourage you to use the combinators provided for `Result` in `std`/`core`.
317///
318/// For example, instead of this:
319///
320/// ```rust,compile_fail
321/// use std::error::Error;
322/// use miette::{WrapErr, Report};
323///
324/// fn wrap_example(err: Result<(), Box<dyn Error + Send + Sync + 'static>>)
325///     -> Result<(), Report>
326/// {
327///     err.wrap_err("saw a downstream error")
328/// }
329/// ```
330///
331/// We encourage you to write this:
332///
333/// ```rust
334/// use miette::{miette, Report, WrapErr};
335/// use std::error::Error;
336///
337/// fn wrap_example(err: Result<(), Box<dyn Error + Send + Sync + 'static>>) -> Result<(), Report> {
338///     err.map_err(|e| miette!(e))
339///         .wrap_err("saw a downstream error")
340/// }
341/// ```
342///
343/// # Effect on Downcasting
344///
345/// After attaching a message of type `D` onto an error of type `E`, the
346/// resulting `miette::Error` may be downcast to `D` **or** to `E`.
347///
348/// That is, in codebases that rely on downcasting, `miette`'s `wrap_err()`
349/// supports both of the following use cases:
350///
351///   - **Attaching messages whose type is insignificant onto errors whose type
352///     is used in downcasts.**
353///
354///     In other error libraries whose `wrap_err()` is not designed this way, it
355///     can be risky to introduce messages to existing code because new message
356///     might break existing working downcasts. In miette, any downcast that
357///     worked before adding the message will continue to work after you add a
358///     message, so you should freely wrap errors wherever it would be helpful.
359///
360///     ```
361///     # use miette::bail;
362///     # use thiserror::Error;
363///     #
364///     # #[derive(Error, Debug)]
365///     # #[error("???")]
366///     # struct SuspiciousError;
367///     #
368///     # fn helper() -> Result<()> {
369///     #     bail!(SuspiciousError);
370///     # }
371///     #
372///     use miette::{WrapErr, Result};
373///
374///     fn do_it() -> Result<()> {
375///         helper().wrap_err("Failed to complete the work")?;
376///         # const IGNORE: &str = stringify! {
377///         ...
378///         # };
379///         # unreachable!()
380///     }
381///
382///     fn main() {
383///         let err = do_it().unwrap_err();
384///         if let Some(e) = err.downcast_ref::<SuspiciousError>() {
385///             // If helper() returned SuspiciousError, this downcast will
386///             // correctly succeed even with the message in between.
387///             # return;
388///         }
389///         # panic!("expected downcast to succeed");
390///     }
391///     ```
392///
393///   - **Attaching message whose type is used in downcasts onto errors whose
394///     type is insignificant.**
395///
396///     Some codebases prefer to use machine-readable messages to categorize
397///     lower level errors in a way that will be actionable to higher levels of
398///     the application.
399///
400///     ```
401///     # use miette::bail;
402///     # use thiserror::Error;
403///     #
404///     # #[derive(Error, Debug)]
405///     # #[error("???")]
406///     # struct HelperFailed;
407///     #
408///     # fn helper() -> Result<()> {
409///     #     bail!("no such file or directory");
410///     # }
411///     #
412///     use miette::{WrapErr, Result};
413///
414///     fn do_it() -> Result<()> {
415///         helper().wrap_err(HelperFailed)?;
416///         # const IGNORE: &str = stringify! {
417///         ...
418///         # };
419///         # unreachable!()
420///     }
421///
422///     fn main() {
423///         let err = do_it().unwrap_err();
424///         if let Some(e) = err.downcast_ref::<HelperFailed>() {
425///             // If helper failed, this downcast will succeed because
426///             // HelperFailed is the message that has been attached to
427///             // that error.
428///             # return;
429///         }
430///         # panic!("expected downcast to succeed");
431///     }
432///     ```
433pub trait WrapErr<T, E>: context::private::Sealed {
434    /// Wrap the error value with a new adhoc error
435    #[cfg_attr(track_caller, track_caller)]
436    fn wrap_err<D>(self, msg: D) -> Result<T, Report>
437    where
438        D: Display + Send + Sync + 'static;
439
440    /// Wrap the error value with a new adhoc error that is evaluated lazily
441    /// only once an error does occur.
442    #[cfg_attr(track_caller, track_caller)]
443    fn wrap_err_with<D, F>(self, f: F) -> Result<T, Report>
444    where
445        D: Display + Send + Sync + 'static,
446        F: FnOnce() -> D;
447
448    /// Compatibility re-export of `wrap_err()` for interop with `anyhow`
449    #[cfg_attr(track_caller, track_caller)]
450    fn context<D>(self, msg: D) -> Result<T, Report>
451    where
452        D: Display + Send + Sync + 'static;
453
454    /// Compatibility re-export of `wrap_err_with()` for interop with `anyhow`
455    #[cfg_attr(track_caller, track_caller)]
456    fn with_context<D, F>(self, f: F) -> Result<T, Report>
457    where
458        D: Display + Send + Sync + 'static,
459        F: FnOnce() -> D;
460}
461
462// Private API. Referenced by macro-generated code.
463#[doc(hidden)]
464pub mod private {
465    use super::Report;
466    use core::fmt::{Debug, Display};
467
468    pub use core::result::Result::Err;
469
470    #[doc(hidden)]
471    pub mod kind {
472        pub use super::super::kind::{AdhocKind, TraitKind};
473
474        pub use super::super::kind::BoxedKind;
475    }
476
477    #[cfg_attr(track_caller, track_caller)]
478    #[cold]
479    pub fn new_adhoc<M>(message: M) -> Report
480    where
481        M: Display + Debug + Send + Sync + 'static,
482    {
483        Report::from_adhoc(message)
484    }
485}