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}