miette/eyreish/
mod.rs

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