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}