Repository: zkat/miette
Pull request #35 (2021-08-25 03:30:13)

anyhow-ify DiagnosticReport
*sigh*

I guess I have to do this and turn DiagnosticReport into an anyhow::Error type, and try and make it easy to move from anyhow to miette. I think it'll help a lot with adoption, but also it's just a good idea for apps to have this facility and I haven't spent enough time focusing on that use case.


Patch:
diff --git a/Cargo.toml b/Cargo.toml
index ff7dc29..1d70acd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,7 +13,7 @@ exclude = ["images/", "tests/", "miette-derive/"]
 
 [dependencies]
 thiserror = "1.0.26"
-miette-derive = { version = "=1.1.0", path = "miette-derive" }
+miette-derive = { path = "miette-derive", version = "=1.1.0" }
 once_cell = "1.8.0"
 owo-colors = "2.0.0"
 atty = "0.2.14"
@@ -22,5 +22,12 @@ ci_info = "0.14.2"
 [dev-dependencies]
 semver = "1.0.4"
 
+# Eyre devdeps
+futures = { version = "0.3", default-features = false }
+indenter = "0.3.0"
+rustversion = "1.0"
+trybuild = { version = "1.0.19", features = ["diff"] }
+syn = { version = "1.0", features = ["full"] }
+
 [workspace]
 members = ["miette-derive"]
diff --git a/Makefile.toml b/Makefile.toml
index 55a0bb0..1b83b43 100644
--- a/Makefile.toml
+++ b/Makefile.toml
@@ -8,4 +8,4 @@ args = ["--prepend", "CHANGELOG.md", "-u", "--tag", "${@}"]
 workspace=false
 install_crate="cargo-release"
 command = "cargo"
-args = ["release", "--tag-prefix", "", "--workspace", "${@}"]
+args = ["release", "--workspace", "${@}"]
diff --git a/README.md b/README.md
index 14181ec..dd30b5d 100644
--- a/README.md
+++ b/README.md
@@ -45,11 +45,11 @@ diagnostic error code: ruget::api::bad_json
 - Unique error codes on every [Diagnostic].
 - Custom links to get more details on error codes.
 - Super handy derive macro for defining diagnostic metadata.
-- Lightweight [`anyhow`](https://docs.rs/anyhow)/[`eyre`](https://docs.rs/eyre)-style error wrapper type, [DiagnosticReport],
+- [`anyhow`](https://docs.rs/anyhow)/[`eyre`](https://docs.rs/eyre)-compatible error wrapper type, [Report],
   which can be returned from `main`.
 - Generic support for arbitrary [Source]s for snippet data, with default support for `String`s included.
 
-The `miette` crate also comes bundled with a default [DiagnosticReportPrinter] with the following features:
+The `miette` crate also comes bundled with a default [ReportHandler] with the following features:
 
 - Fancy graphical [diagnostic output](#about), using ANSI/Unicode text
 - single- and multi-line highlighting support
@@ -97,12 +97,12 @@ struct MyBad {
 /*
 Now let's define a function!
 
-Use this DiagnosticResult type (or its expanded version) as the return type
+Use this Result type (or its expanded version) as the return type
 throughout your app (but NOT your libraries! Those should always return concrete
 types!).
 */
-use miette::{DiagnosticResult, NamedSource};
-fn this_fails() -> DiagnosticResult<()> {
+use miette::{Result, NamedSource};
+fn this_fails() -> Result<()> {
     // You can use plain strings as a `Source`, or anything that implements
     // the one-method `Source` trait.
     let src = "source\n  text\n    here".to_string();
@@ -118,12 +118,12 @@ fn this_fails() -> DiagnosticResult<()> {
 }
 
 /*
-Now to get everything printed nicely, just return a DiagnosticResult<()>
+Now to get everything printed nicely, just return a Result<()>
 and you're all set!
 
 Note: You can swap out the default reporter for a custom one using `miette::set_reporter()`
 */
-fn pretend_this_is_main() -> DiagnosticResult<()> {
+fn pretend_this_is_main() -> Result<()> {
     // kaboom~
     this_fails()?;
 
@@ -183,7 +183,7 @@ pub enum MyLibError {
 
 Then, return this error type from all your fallible public APIs. It's a best
 practice to wrap any "external" error types in your error `enum` instead of
-using something like [eyre](https://docs.rs/eyre) in a library.
+using something like [Report] in a library.
 
 ### ... in application code
 
@@ -191,33 +191,50 @@ Application code tends to work a little differently than libraries. You don't
 always need or care to define dedicated error wrappers for errors coming from
 external libraries and tools.
 
-For this situation, `miette` includes two tools: [DiagnosticReport] and
+For this situation, `miette` includes two tools: [Report] and
 [IntoDiagnostic]. They work in tandem to make it easy to convert regular
 `std::error::Error`s into [Diagnostic]s. Additionally, there's a
-[DiagnosticResult] type alias that you can use to be more terse:
+[Result] type alias that you can use to be more terse.
+
+When dealing with non-`Diagnostic` types, you'll want to `.into_diagnostic()`
+them:
+
+```rust
+// my_app/lib/my_internal_file.rs
+use miette::{IntoDiagnostic, Result};
+use semver::Version;
+
+pub fn some_tool() -> Result<Version> {
+    Ok("1.2.x".parse().into_diagnostic()?)
+}
+```
+
+`miette` also includes an `anyhow`/`eyre`-style `Context`/`WrapErr` traits that
+you can import to add ad-hoc context messages to your `Diagnostic`s, as well,
+though you'll still need to use `.into_diagnostic()` to make use of it:
 
 ```rust
 // my_app/lib/my_internal_file.rs
-use miette::{IntoDiagnostic, DiagnosticResult};
+use miette::{IntoDiagnostic, Result, WrapErr};
 use semver::Version;
 
-pub fn some_tool() -> DiagnosticResult<Version> {
-    Ok("1.2.x".parse().into_diagnostic("my_app::semver::parse_error")?)
+pub fn some_tool() -> Result<Version> {
+    Ok("1.2.x".parse().into_diagnostic().wrap_err("Parsing this tool's semver version failed.")?)
 }
 ```
 
 ### ... in `main()`
 
 `main()` is just like any other part of your application-internal code. Use
-`DiagnosticResult` as your return value, and it will pretty-print your
+`Result` as your return value, and it will pretty-print your
 diagnostics automatically.
 
 ```rust
-use miette::{DiagnosticResult, IntoDiagnostic};
+use miette::{Result, IntoDiagnostic};
 use semver::Version;
 
-fn pretend_this_is_main() -> DiagnosticResult<()> {
-    let version: Version = "1.2.x".parse().into_diagnostic("my_app::semver::parse_error")?;
+fn pretend_this_is_main() -> Result<()> {
+    let version: Version = "1.2.x".parse().into_diagnostic()?;
     println!("{}", version);
     Ok(())
 }
@@ -249,7 +266,7 @@ use thiserror::Error;
 #[diagnostic(
     code(my_app::my_error),
     // You can do formatting!
-    url("https://my_website.com/error_codes#{}", self.code())
+    url("https://my_website.com/error_codes#{}", self.code().unwrap())
 )]
 struct MyErr;
 ```
@@ -330,7 +347,7 @@ pub struct MyErrorType {
   [`color-eyre`](https://crates.io/crates/color-eyre): these two enormously
   influential error handling libraries have pushed forward the experience of
   application-level error handling and error reporting. `miette`'s
-  `DiagnosticReport` type is an attempt at a very very rough version of their
+  `Report` type is an attempt at a very very rough version of their
   `Report` types.
 - [`thiserror`](https://crates.io/crates/thiserror) for setting the standard
   for library-level error definitions, and for being the inspiration behind
@@ -344,7 +361,7 @@ pub struct MyErrorType {
 
 `miette` is released to the Rust community under the [Apache license 2.0](./LICENSE).
 
-It also includes some code taken from [`eyre`](https://github.com/yaahc/eyre),
+It also includes code taken from [`eyre`](https://github.com/yaahc/eyre),
 and some from [`thiserror`](https://github.com/dtolnay/thiserror), also under
 the Apache License. Some code is taken from
 [`ariadne`](https://github.com/zesterer/ariadne), which is MIT licensed.
diff --git a/miette-derive/src/code.rs b/miette-derive/src/code.rs
index acbacc7..35bc189 100644
--- a/miette-derive/src/code.rs
+++ b/miette-derive/src/code.rs
@@ -56,29 +56,30 @@ impl Code {
              }| {
                 match args {
                     DiagnosticDefArgs::Transparent => {
-                        forward_to_single_field_variant(ident, fields, quote! { code() })
+                        Some(forward_to_single_field_variant(ident, fields, quote! { code() }))
                     }
                     DiagnosticDefArgs::Concrete(DiagnosticConcreteArgs { code, .. }) => {
-                        let code = &code.0;
-                        match fields {
+                        let code = &code.as_ref()?.0;
+                        Some(match fields {
                             syn::Fields::Named(_) => {
-                                quote! { Self::#ident { .. } => std::boxed::Box::new(#code), }
+                                quote! { Self::#ident { .. } => std::option::Option::Some(std::boxed::Box::new(#code)), }
                             }
                             syn::Fields::Unnamed(_) => {
-                                quote! { Self::#ident(..) => std::boxed::Box::new(#code), }
+                                quote! { Self::#ident(..) => std::option::Option::Some(std::boxed::Box::new(#code)), }
                             }
                             syn::Fields::Unit => {
-                                quote! { Self::#ident => std::boxed::Box::new(#code), }
+                                quote! { Self::#ident => std::option::Option::Some(std::boxed::Box::new(#code)), }
                             }
-                        }
+                        })
                     }
                 }
             },
         );
         Some(quote! {
-            fn code<'a>(&'a self) -> std::boxed::Box<dyn std::fmt::Display + 'a> {
+            fn code<'a>(&'a self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + 'a>> {
                 match self {
                     #(#code_pairs)*
+                    _ => std::option::Option::None,
                 }
             }
         })
@@ -87,8 +88,8 @@ impl Code {
     pub(crate) fn gen_struct(&self) -> Option<TokenStream> {
         let code = &self.0;
         Some(quote! {
-            fn code<'a>(&'a self) -> std::boxed::Box<dyn std::fmt::Display + 'a> {
-                std::boxed::Box::new(#code)
+            fn code<'a>(&'a self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + 'a>> {
+                std::option::Option::Some(std::boxed::Box::new(#code))
             }
         })
     }
diff --git a/miette-derive/src/diagnostic.rs b/miette-derive/src/diagnostic.rs
index f84b861..d5309ab 100644
--- a/miette-derive/src/diagnostic.rs
+++ b/miette-derive/src/diagnostic.rs
@@ -34,8 +34,9 @@ pub enum DiagnosticDefArgs {
     Concrete(DiagnosticConcreteArgs),
 }
 
+#[derive(Default)]
 pub struct DiagnosticConcreteArgs {
-    pub code: Code,
+    pub code: Option<Code>,
     pub severity: Option<Severity>,
     pub help: Option<Help>,
     pub snippets: Option<Snippets>,
@@ -44,7 +45,7 @@ pub struct DiagnosticConcreteArgs {
 
 impl DiagnosticConcreteArgs {
     fn parse(
-        ident: &syn::Ident,
+        _ident: &syn::Ident,
         fields: &syn::Fields,
         attr: &syn::Attribute,
         args: impl Iterator<Item = DiagnosticArg>,
@@ -75,8 +76,7 @@ impl DiagnosticConcreteArgs {
         }
         let snippets = Snippets::from_fields(fields)?;
         let concrete = DiagnosticConcreteArgs {
-            code: code
-                .ok_or_else(|| syn::Error::new(ident.span(), "Diagnostic code is required."))?,
+            code,
             help,
             severity,
             snippets,
@@ -132,11 +132,12 @@ impl Diagnostic {
                         args,
                     }
                 } else {
-                    // Also handle when there's multiple `#[diagnostic]` attrs?
-                    return Err(syn::Error::new(
-                        input.ident.span(),
-                        "#[diagnostic] attribute is required when deriving Diagnostic.",
-                    ));
+                    Diagnostic::Struct {
+                        fields: data_struct.fields,
+                        ident: input.ident,
+                        generics: input.generics,
+                        args: DiagnosticDefArgs::Concrete(Default::default()),
+                    }
                 }
             }
             syn::Data::Enum(syn::DataEnum { variants, .. }) => {
@@ -206,7 +207,7 @@ impl Diagnostic {
 
                         quote! {
                             impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause {
-                                fn code<'a>(&'a self) -> std::boxed::Box<dyn std::fmt::Display + 'a> {
+                                fn code<'a>(&'a self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + 'a>> {
                                     #matcher
                                     #field_name.code()
                                 }
@@ -230,7 +231,7 @@ impl Diagnostic {
                         }
                     }
                     DiagnosticDefArgs::Concrete(concrete) => {
-                        let code_body = concrete.code.gen_struct();
+                        let code_body = concrete.code.as_ref().and_then(|x| x.gen_struct());
                         let help_body = concrete.help.as_ref().and_then(|x| x.gen_struct(fields));
                         let sev_body = concrete.severity.as_ref().and_then(|x| x.gen_struct());
                         let snip_body = concrete
diff --git a/miette-derive/src/severity.rs b/miette-derive/src/severity.rs
index 633e8f7..d828335 100644
--- a/miette-derive/src/severity.rs
+++ b/miette-derive/src/severity.rs
@@ -91,7 +91,7 @@ impl Severity {
                fn severity(&self) -> std::option::Option<miette::Severity> {
                    match self {
                         #(#sev_pairs)*
-                        _ => None,
+                        _ => std::option::Option::None,
                    }
                }
             })
diff --git a/src/chain.rs b/src/chain.rs
index a71c476..fe4d336 100644
--- a/src/chain.rs
+++ b/src/chain.rs
@@ -10,7 +10,7 @@ use ChainState::*;
 
 #[derive(Clone)]
 #[allow(missing_debug_implementations)]
-pub(crate) struct Chain<'a> {
+pub struct Chain<'a> {
     state: crate::chain::ChainState<'a>,
 }
 
@@ -25,7 +25,7 @@ pub(crate) enum ChainState<'a> {
 }
 
 impl<'a> Chain<'a> {
-    pub(crate) fn new(head: &'a (dyn StdError + 'static)) -> Self {
+    pub fn new(head: &'a (dyn StdError + 'static)) -> Self {
         Chain {
             state: ChainState::Linked { next: Some(head) },
         }
diff --git a/src/error.rs b/src/error.rs
index bf02d12..76a50a9 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -24,9 +24,9 @@ pub enum MietteError {
     )]
     OutOfBounds,
 
-    /// Returned when installing a [crate::DiagnosticReportPrinter] failed.
+    /// Returned when installing a [crate::ReportHandler] failed.
     /// Typically, this will be because [crate::set_printer] was called twice.
-    #[error("Failed to install DiagnosticReportPrinter")]
+    #[error("Failed to install ReportHandler")]
     #[diagnostic(code(miette::set_printer_failed), url(docsrs))]
     SetPrinterFailure,
 }
diff --git a/src/eyreish/context.rs b/src/eyreish/context.rs
new file mode 100644
index 0000000..a4ae4e9
--- /dev/null
+++ b/src/eyreish/context.rs
@@ -0,0 +1,159 @@
+use super::error::ContextError;
+use super::{Report, WrapErr};
+use core::fmt::{self, Debug, Display, Write};
+
+use std::error::Error as StdError;
+
+use crate::Diagnostic;
+
+mod ext {
+    use super::*;
+
+    pub trait Diag {
+        #[cfg_attr(track_caller, track_caller)]
+        fn ext_report<D>(self, msg: D) -> Report
+        where
+            D: Display + Send + Sync + 'static;
+    }
+
+    impl<E> Diag for E
+    where
+        E: Diagnostic + Send + Sync + 'static,
+    {
+        fn ext_report<D>(self, msg: D) -> Report
+        where
+            D: Display + Send + Sync + 'static,
+        {
+            Report::from_msg(msg, self)
+        }
+    }
+
+    impl Diag for Report {
+        fn ext_report<D>(self, msg: D) -> Report
+        where
+            D: Display + Send + Sync + 'static,
+        {
+            self.wrap_err(msg)
+        }
+    }
+}
+
+impl<T, E> WrapErr<T, E> for Result<T, E>
+where
+    E: ext::Diag + Send + Sync + 'static,
+{
+    fn wrap_err<D>(self, msg: D) -> Result<T, Report>
+    where
+        D: Display + Send + Sync + 'static,
+    {
+        match self {
+            Ok(t) => Ok(t),
+            Err(e) => Err(e.ext_report(msg)),
+        }
+    }
+
+    fn wrap_err_with<D, F>(self, msg: F) -> Result<T, Report>
+    where
+        D: Display + Send + Sync + 'static,
+        F: FnOnce() -> D,
+    {
+        match self {
+            Ok(t) => Ok(t),
+            Err(e) => Err(e.ext_report(msg())),
+        }
+    }
+
+    fn context<D>(self, msg: D) -> Result<T, Report>
+    where
+        D: Display + Send + Sync + 'static,
+    {
+        self.wrap_err(msg)
+    }
+
+    fn with_context<D, F>(self, msg: F) -> Result<T, Report>
+    where
+        D: Display + Send + Sync + 'static,
+        F: FnOnce() -> D,
+    {
+        self.wrap_err_with(msg)
+    }
+}
+
+impl<D, E> Debug for ContextError<D, E>
+where
+    D: Display,
+    E: Debug,
+{
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.debug_struct("Error")
+            .field("msg", &Quoted(&self.msg))
+            .field("source", &self.error)
+            .finish()
+    }
+}
+
+impl<D, E> Display for ContextError<D, E>
+where
+    D: Display,
+{
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        Display::fmt(&self.msg, f)
+    }
+}
+
+impl<D, E> StdError for ContextError<D, E>
+where
+    D: Display,
+    E: StdError + 'static,
+{
+    fn source(&self) -> Option<&(dyn StdError + 'static)> {
+        Some(&self.error)
+    }
+}
+
+impl<D> StdError for ContextError<D, Report>
+where
+    D: Display,
+{
+    fn source(&self) -> Option<&(dyn StdError + 'static)> {
+        Some(self.error.inner.error())
+    }
+}
+
+impl<D, E> Diagnostic for ContextError<D, E>
+where
+    D: Display,
+    E: Diagnostic + 'static,
+{
+}
+
+impl<D> Diagnostic for ContextError<D, Report> where D: Display {}
+
+struct Quoted<D>(D);
+
+impl<D> Debug for Quoted<D>
+where
+    D: Display,
+{
+    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+        formatter.write_char('"')?;
+        Quoted(&mut *formatter).write_fmt(format_args!("{}", self.0))?;
+        formatter.write_char('"')?;
+        Ok(())
+    }
+}
+
+impl Write for Quoted<&mut fmt::Formatter<'_>> {
+    fn write_str(&mut self, s: &str) -> fmt::Result {
+        Display::fmt(&s.escape_debug(), self.0)
+    }
+}
+
+pub(crate) mod private {
+    use super::*;
+
+    pub trait Sealed {}
+
+    impl<T, E> Sealed for Result<T, E> where E: ext::Diag {}
+    impl<T> Sealed for Option<T> {}
+}
diff --git a/src/eyreish/error.rs b/src/eyreish/error.rs
new file mode 100644
index 0000000..f9dc525
--- /dev/null
+++ b/src/eyreish/error.rs
@@ -0,0 +1,723 @@
+use core::any::TypeId;
+use core::fmt::{self, Debug, Display};
+use core::mem::{self, ManuallyDrop};
+use core::ptr::{self, NonNull};
+use std::error::Error as StdError;
+
+use super::Report;
+use super::ReportHandler;
+use crate::chain::Chain;
+use crate::Diagnostic;
+use core::ops::{Deref, DerefMut};
+
+impl Report {
+    /// Create a new error object from any error type.
+    ///
+    /// The error type must be thread safe and `'static`, so that the `Report`
+    /// will be as well.
+    ///
+    /// If the error type does not provide a backtrace, a backtrace will be
+    /// created here to ensure that a backtrace exists.
+    #[cfg_attr(track_caller, track_caller)]
+    pub fn new<E>(error: E) -> Self
+    where
+        E: Diagnostic + Send + Sync + 'static,
+    {
+        Report::from_std(error)
+    }
+
+    /// Create a new error object from a printable error message.
+    ///
+    /// If the argument implements std::error::Error, prefer `Report::new`
+    /// instead which preserves the underlying error's cause chain and
+    /// backtrace. If the argument may or may not implement std::error::Error
+    /// now or in the future, use `miette!(err)` which handles either way
+    /// correctly.
+    ///
+    /// `Report::msg("...")` is equivalent to `miette!("...")` but occasionally
+    /// convenient in places where a function is preferable over a macro, such
+    /// as iterator or stream combinators:
+    ///
+    /// ```
+    /// # mod ffi {
+    /// #     pub struct Input;
+    /// #     pub struct Output;
+    /// #     pub async fn do_some_work(_: Input) -> Result<Output, &'static str> {
+    /// #         unimplemented!()
+    /// #     }
+    /// # }
+    /// #
+    /// # use ffi::{Input, Output};
+    /// #
+    /// use miette::{Report, Result};
+    /// use futures::stream::{Stream, StreamExt, TryStreamExt};
+    ///
+    /// async fn demo<S>(stream: S) -> Result<Vec<Output>>
+    /// where
+    ///     S: Stream<Item = Input>,
+    /// {
+    ///     stream
+    ///         .then(ffi::do_some_work) // returns Result<Output, &str>
+    ///         .map_err(Report::msg)
+    ///         .try_collect()
+    ///         .await
+    /// }
+    /// ```
+    #[cfg_attr(track_caller, track_caller)]
+    pub fn msg<M>(message: M) -> Self
+    where
+        M: Display + Debug + Send + Sync + 'static,
+    {
+        Report::from_adhoc(message)
+    }
+
+    #[cfg_attr(track_caller, track_caller)]
+    pub(crate) fn from_std<E>(error: E) -> Self
+    where
+        E: Diagnostic + Send + Sync + 'static,
+    {
+        let vtable = &ErrorVTable {
+            object_drop: object_drop::<E>,
+            object_ref: object_ref::<E>,
+            object_mut: object_mut::<E>,
+            object_ref_stderr: object_ref_stderr::<E>,
+            object_boxed: object_boxed::<E>,
+            object_downcast: object_downcast::<E>,
+            object_drop_rest: object_drop_front::<E>,
+        };
+
+        // Safety: passing vtable that operates on the right type E.
+        let handler = Some(super::capture_handler(&error));
+
+        unsafe { Report::construct(error, vtable, handler) }
+    }
+
+    #[cfg_attr(track_caller, track_caller)]
+    pub(crate) fn from_adhoc<M>(message: M) -> Self
+    where
+        M: Display + Debug + Send + Sync + 'static,
+    {
+        use super::wrapper::MessageError;
+        let error: MessageError<M> = MessageError(message);
+        let vtable = &ErrorVTable {
+            object_drop: object_drop::<MessageError<M>>,
+            object_ref: object_ref::<MessageError<M>>,
+            object_mut: object_mut::<MessageError<M>>,
+            object_ref_stderr: object_ref_stderr::<MessageError<M>>,
+            object_boxed: object_boxed::<MessageError<M>>,
+            object_downcast: object_downcast::<M>,
+            object_drop_rest: object_drop_front::<M>,
+        };
+
+        // Safety: MessageError is repr(transparent) so it is okay for the
+        // vtable to allow casting the MessageError<M> to M.
+        let handler = Some(super::capture_handler(&error));
+
+        unsafe { Report::construct(error, vtable, handler) }
+    }
+
+    #[cfg_attr(track_caller, track_caller)]
+    pub(crate) fn from_msg<D, E>(msg: D, error: E) -> Self
+    where
+        D: Display + Send + Sync + 'static,
+        E: Diagnostic + Send + Sync + 'static,
+    {
+        let error: ContextError<D, E> = ContextError { msg, error };
+
+        let vtable = &ErrorVTable {
+            object_drop: object_drop::<ContextError<D, E>>,
+            object_ref: object_ref::<ContextError<D, E>>,
+            object_mut: object_mut::<ContextError<D, E>>,
+            object_ref_stderr: object_ref_stderr::<ContextError<D, E>>,
+            object_boxed: object_boxed::<ContextError<D, E>>,
+            object_downcast: context_downcast::<D, E>,
+            object_drop_rest: context_drop_rest::<D, E>,
+        };
+
+        // Safety: passing vtable that operates on the right type.
+        let handler = Some(super::capture_handler(&error));
+
+        unsafe { Report::construct(error, vtable, handler) }
+    }
+
+    #[cfg_attr(track_caller, track_caller)]
+    pub(crate) fn from_boxed(error: Box<dyn Diagnostic + Send + Sync>) -> Self {
+        use super::wrapper::BoxedError;
+        let error = BoxedError(error);
+        let handler = Some(super::capture_handler(&error));
+
+        let vtable = &ErrorVTable {
+            object_drop: object_drop::<BoxedError>,
+            object_ref: object_ref::<BoxedError>,
+            object_mut: object_mut::<BoxedError>,
+            object_ref_stderr: object_ref_stderr::<BoxedError>,
+            object_boxed: object_boxed::<BoxedError>,
+            object_downcast: object_downcast::<Box<dyn Diagnostic + Send + Sync>>,
+            object_drop_rest: object_drop_front::<Box<dyn Diagnostic + Send + Sync>>,
+        };
+
+        // Safety: BoxedError is repr(transparent) so it is okay for the vtable
+        // to allow casting to Box<dyn StdError + Send + Sync>.
+        unsafe { Report::construct(error, vtable, handler) }
+    }
+
+    // Takes backtrace as argument rather than capturing it here so that the
+    // user sees one fewer layer of wrapping noise in the backtrace.
+    //
+    // Unsafe because the given vtable must have sensible behavior on the error
+    // value of type E.
+    unsafe fn construct<E>(
+        error: E,
+        vtable: &'static ErrorVTable,
+        handler: Option<Box<dyn ReportHandler>>,
+    ) -> Self
+    where
+        E: Diagnostic + Send + Sync + 'static,
+    {
+        let inner = Box::new(ErrorImpl {
+            vtable,
+            handler,
+            _object: error,
+        });
+        // Erase the concrete type of E from the compile-time type system. This
+        // is equivalent to the safe unsize coercion from Box<ErrorImpl<E>> to
+        // Box<ErrorImpl<dyn StdError + Send + Sync + 'static>> except that the
+        // result is a thin pointer. The necessary behavior for manipulating the
+        // underlying ErrorImpl<E> is preserved in the vtable provided by the
+        // caller rather than a builtin fat pointer vtable.
+        let erased = mem::transmute::<Box<ErrorImpl<E>>, Box<ErrorImpl<()>>>(inner);
+        let inner = ManuallyDrop::new(erased);
+        Report { inner }
+    }
+
+    /// Create a new error from an error message to wrap the existing error.
+    ///
+    /// For attaching a higher level error message to a `Result` as it is propagated, the
+    /// [crate::WrapErr] extension trait may be more convenient than this function.
+    ///
+    /// The primary reason to use `error.wrap_err(...)` instead of `result.wrap_err(...)` via the
+    /// `WrapErr` trait would be if the message needs to depend on some data held by the underlying
+    /// error:
+    ///
+    pub fn wrap_err<D>(mut self, msg: D) -> Self
+    where
+        D: Display + Send + Sync + 'static,
+    {
+        let handler = self.inner.handler.take();
+        let error: ContextError<D, Report> = ContextError { msg, error: self };
+
+        let vtable = &ErrorVTable {
+            object_drop: object_drop::<ContextError<D, Report>>,
+            object_ref: object_ref::<ContextError<D, Report>>,
+            object_mut: object_mut::<ContextError<D, Report>>,
+            object_ref_stderr: object_ref_stderr::<ContextError<D, Report>>,
+            object_boxed: object_boxed::<ContextError<D, Report>>,
+            object_downcast: context_chain_downcast::<D>,
+            object_drop_rest: context_chain_drop_rest::<D>,
+        };
+
+        // Safety: passing vtable that operates on the right type.
+        unsafe { Report::construct(error, vtable, handler) }
+    }
+
+    /// An iterator of the chain of source errors contained by this Report.
+    ///
+    /// This iterator will visit every error in the cause chain of this error
+    /// object, beginning with the error that this error object was created
+    /// from.
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// use miette::Report;
+    /// use std::io;
+    ///
+    /// pub fn underlying_io_error_kind(error: &Report) -> Option<io::ErrorKind> {
+    ///     for cause in error.chain() {
+    ///         if let Some(io_error) = cause.downcast_ref::<io::Error>() {
+    ///             return Some(io_error.kind());
+    ///         }
+    ///     }
+    ///     None
+    /// }
+    /// ```
+    pub fn chain(&self) -> Chain<'_> {
+        self.inner.chain()
+    }
+
+    /// The lowest level cause of this error &mdash; this error's cause's
+    /// cause's cause etc.
+    ///
+    /// The root cause is the last error in the iterator produced by
+    /// [`chain()`][Report::chain].
+    pub fn root_cause(&self) -> &(dyn StdError + 'static) {
+        let mut chain = self.chain();
+        let mut root_cause = chain.next().unwrap();
+        for cause in chain {
+            root_cause = cause;
+        }
+        root_cause
+    }
+
+    /// Returns true if `E` is the type held by this error object.
+    ///
+    /// For errors constructed from messages, this method returns true if `E` matches the type of
+    /// the message `D` **or** the type of the error on which the message has been attached. For
+    /// details about the interaction between message and downcasting, [see here].
+    ///
+    /// [see here]: trait.WrapErr.html#effect-on-downcasting
+    pub fn is<E>(&self) -> bool
+    where
+        E: Display + Debug + Send + Sync + 'static,
+    {
+        self.downcast_ref::<E>().is_some()
+    }
+
+    /// Attempt to downcast the error object to a concrete type.
+    pub fn downcast<E>(self) -> Result<E, Self>
+    where
+        E: Display + Debug + Send + Sync + 'static,
+    {
+        let target = TypeId::of::<E>();
+        unsafe {
+            // Use vtable to find NonNull<()> which points to a value of type E
+            // somewhere inside the data structure.
+            let addr = match (self.inner.vtable.object_downcast)(&self.inner, target) {
+                Some(addr) => addr,
+                None => return Err(self),
+            };
+
+            // Prepare to read E out of the data structure. We'll drop the rest
+            // of the data structure separately so that E is not dropped.
+            let outer = ManuallyDrop::new(self);
+
+            // Read E from where the vtable found it.
+            let error = ptr::read(addr.cast::<E>().as_ptr());
+
+            // Read Box<ErrorImpl<()>> from self. Can't move it out because
+            // Report has a Drop impl which we want to not run.
+            let inner = ptr::read(&outer.inner);
+            let erased = ManuallyDrop::into_inner(inner);
+
+            // Drop rest of the data structure outside of E.
+            (erased.vtable.object_drop_rest)(erased, target);
+
+            Ok(error)
+        }
+    }
+
+    /// Downcast this error object by reference.
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// # use miette::{Report, miette};
+    /// # use std::fmt::{self, Display};
+    /// # use std::task::Poll;
+    /// #
+    /// # #[derive(Debug)]
+    /// # enum DataStoreError {
+    /// #     Censored(()),
+    /// # }
+    /// #
+    /// # impl Display for DataStoreError {
+    /// #     fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+    /// #         unimplemented!()
+    /// #     }
+    /// # }
+    /// #
+    /// # impl std::error::Error for DataStoreError {}
+    /// #
+    /// # const REDACTED_CONTENT: () = ();
+    /// #
+    /// # let error: Report = miette!("...");
+    /// # let root_cause = &error;
+    /// #
+    /// # let ret =
+    /// // If the error was caused by redaction, then return a tombstone instead
+    /// // of the content.
+    /// match root_cause.downcast_ref::<DataStoreError>() {
+    ///     Some(DataStoreError::Censored(_)) => Ok(Poll::Ready(REDACTED_CONTENT)),
+    ///     None => Err(error),
+    /// }
+    /// # ;
+    /// ```
+    pub fn downcast_ref<E>(&self) -> Option<&E>
+    where
+        E: Display + Debug + Send + Sync + 'static,
+    {
+        let target = TypeId::of::<E>();
+        unsafe {
+            // Use vtable to find NonNull<()> which points to a value of type E
+            // somewhere inside the data structure.
+            let addr = (self.inner.vtable.object_downcast)(&self.inner, target)?;
+            Some(&*addr.cast::<E>().as_ptr())
+        }
+    }
+
+    /// Downcast this error object by mutable reference.
+    pub fn downcast_mut<E>(&mut self) -> Option<&mut E>
+    where
+        E: Display + Debug + Send + Sync + 'static,
+    {
+        let target = TypeId::of::<E>();
+        unsafe {
+            // Use vtable to find NonNull<()> which points to a value of type E
+            // somewhere inside the data structure.
+            let addr = (self.inner.vtable.object_downcast)(&self.inner, target)?;
+            Some(&mut *addr.cast::<E>().as_ptr())
+        }
+    }
+
+    /// Get a reference to the Handler for this Report.
+    pub fn handler(&self) -> &dyn ReportHandler {
+        self.inner.handler.as_ref().unwrap().as_ref()
+    }
+
+    /// Get a mutable reference to the Handler for this Report.
+    pub fn handler_mut(&mut self) -> &mut dyn ReportHandler {
+        self.inner.handler.as_mut().unwrap().as_mut()
+    }
+
+    /// Get a reference to the Handler for this Report.
+    #[doc(hidden)]
+    pub fn context(&self) -> &dyn ReportHandler {
+        self.inner.handler.as_ref().unwrap().as_ref()
+    }
+
+    /// Get a mutable reference to the Handler for this Report.
+    #[doc(hidden)]
+    pub fn context_mut(&mut self) -> &mut dyn ReportHandler {
+        self.inner.handler.as_mut().unwrap().as_mut()
+    }
+}
+
+impl<E> From<E> for Report
+where
+    E: Diagnostic + Send + Sync + 'static,
+{
+    #[cfg_attr(track_caller, track_caller)]
+    fn from(error: E) -> Self {
+        Report::from_std(error)
+    }
+}
+
+impl Deref for Report {
+    type Target = dyn Diagnostic + Send + Sync + 'static;
+
+    fn deref(&self) -> &Self::Target {
+        self.inner.diagnostic()
+    }
+}
+
+impl DerefMut for Report {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        self.inner.diagnostic_mut()
+    }
+}
+
+impl Display for Report {
+    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+        self.inner.display(formatter)
+    }
+}
+
+impl Debug for Report {
+    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+        self.inner.debug(formatter)
+    }
+}
+
+impl Drop for Report {
+    fn drop(&mut self) {
+        unsafe {
+            // Read Box<ErrorImpl<()>> from self.
+            let inner = ptr::read(&self.inner);
+            let erased = ManuallyDrop::into_inner(inner);
+
+            // Invoke the vtable's drop behavior.
+            (erased.vtable.object_drop)(erased);
+        }
+    }
+}
+
+struct ErrorVTable {
+    object_drop: unsafe fn(Box<ErrorImpl<()>>),
+    object_ref: unsafe fn(&ErrorImpl<()>) -> &(dyn Diagnostic + Send + Sync + 'static),
+    object_mut: unsafe fn(&mut ErrorImpl<()>) -> &mut (dyn Diagnostic + Send + Sync + 'static),
+    object_ref_stderr: unsafe fn(&ErrorImpl<()>) -> &(dyn StdError + Send + Sync + 'static),
+    #[allow(clippy::type_complexity)]
+    object_boxed: unsafe fn(Box<ErrorImpl<()>>) -> Box<dyn Diagnostic + Send + Sync + 'static>,
+    object_downcast: unsafe fn(&ErrorImpl<()>, TypeId) -> Option<NonNull<()>>,
+    object_drop_rest: unsafe fn(Box<ErrorImpl<()>>, TypeId),
+}
+
+// Safety: requires layout of *e to match ErrorImpl<E>.
+unsafe fn object_drop<E>(e: Box<ErrorImpl<()>>) {
+    // Cast back to ErrorImpl<E> so that the allocator receives the correct
+    // Layout to deallocate the Box's memory.
+    let unerased = mem::transmute::<Box<ErrorImpl<()>>, Box<ErrorImpl<E>>>(e);
+    drop(unerased);
+}
+
+// Safety: requires layout of *e to match ErrorImpl<E>.
+unsafe fn object_drop_front<E>(e: Box<ErrorImpl<()>>, target: TypeId) {
+    // Drop the fields of ErrorImpl other than E as well as the Box allocation,
+    // without dropping E itself. This is used by downcast after doing a
+    // ptr::read to take ownership of the E.
+    let _ = target;
+    let unerased = mem::transmute::<Box<ErrorImpl<()>>, Box<ErrorImpl<ManuallyDrop<E>>>>(e);
+    drop(unerased);
+}
+
+// Safety: requires layout of *e to match ErrorImpl<E>.
+unsafe fn object_ref<E>(e: &ErrorImpl<()>) -> &(dyn Diagnostic + Send + Sync + 'static)
+where
+    E: Diagnostic + Send + Sync + 'static,
+{
+    // Attach E's native StdError vtable onto a pointer to self._object.
+    &(*(e as *const ErrorImpl<()> as *const ErrorImpl<E>))._object
+}
+
+// Safety: requires layout of *e to match ErrorImpl<E>.
+unsafe fn object_mut<E>(e: &mut ErrorImpl<()>) -> &mut (dyn Diagnostic + Send + Sync + 'static)
+where
+    E: Diagnostic + Send + Sync + 'static,
+{
+    // Attach E's native StdError vtable onto a pointer to self._object.
+    &mut (*(e as *mut ErrorImpl<()> as *mut ErrorImpl<E>))._object
+}
+
+// Safety: requires layout of *e to match ErrorImpl<E>.
+unsafe fn object_ref_stderr<E>(e: &ErrorImpl<()>) -> &(dyn StdError + Send + Sync + 'static)
+where
+    E: StdError + Send + Sync + 'static,
+{
+    // Attach E's native StdError vtable onto a pointer to self._object.
+    &(*(e as *const ErrorImpl<()> as *const ErrorImpl<E>))._object
+}
+
+// Safety: requires layout of *e to match ErrorImpl<E>.
+unsafe fn object_boxed<E>(e: Box<ErrorImpl<()>>) -> Box<dyn Diagnostic + Send + Sync + 'static>
+where
+    E: Diagnostic + Send + Sync + 'static,
+{
+    // Attach ErrorImpl<E>'s native StdError vtable. The StdError impl is below.
+    mem::transmute::<Box<ErrorImpl<()>>, Box<ErrorImpl<E>>>(e)
+}
+
+// Safety: requires layout of *e to match ErrorImpl<E>.
+unsafe fn object_downcast<E>(e: &ErrorImpl<()>, target: TypeId) -> Option<NonNull<()>>
+where
+    E: 'static,
+{
+    if TypeId::of::<E>() == target {
+        // Caller is looking for an E pointer and e is ErrorImpl<E>, take a
+        // pointer to its E field.
+        let unerased = e as *const ErrorImpl<()> as *const ErrorImpl<E>;
+        let addr = &(*unerased)._object as *const E as *mut ();
+        Some(NonNull::new_unchecked(addr))
+    } else {
+        None
+    }
+}
+
+// Safety: requires layout of *e to match ErrorImpl<ContextError<D, E>>.
+unsafe fn context_downcast<D, E>(e: &ErrorImpl<()>, target: TypeId) -> Option<NonNull<()>>
+where
+    D: 'static,
+    E: 'static,
+{
+    if TypeId::of::<D>() == target {
+        let unerased = e as *const ErrorImpl<()> as *const ErrorImpl<ContextError<D, E>>;
+        let addr = &(*unerased)._object.msg as *const D as *mut ();
+        Some(NonNull::new_unchecked(addr))
+    } else if TypeId::of::<E>() == target {
+        let unerased = e as *const ErrorImpl<()> as *const ErrorImpl<ContextError<D, E>>;
+        let addr = &(*unerased)._object.error as *const E as *mut ();
+        Some(NonNull::new_unchecked(addr))
+    } else {
+        None
+    }
+}
+
+// Safety: requires layout of *e to match ErrorImpl<ContextError<D, E>>.
+unsafe fn context_drop_rest<D, E>(e: Box<ErrorImpl<()>>, target: TypeId)
+where
+    D: 'static,
+    E: 'static,
+{
+    // Called after downcasting by value to either the D or the E and doing a
+    // ptr::read to take ownership of that value.
+    if TypeId::of::<D>() == target {
+        let unerased = mem::transmute::<
+            Box<ErrorImpl<()>>,
+            Box<ErrorImpl<ContextError<ManuallyDrop<D>, E>>>,
+        >(e);
+        drop(unerased);
+    } else {
+        let unerased = mem::transmute::<
+            Box<ErrorImpl<()>>,
+            Box<ErrorImpl<ContextError<D, ManuallyDrop<E>>>>,
+        >(e);
+        drop(unerased);
+    }
+}
+
+// Safety: requires layout of *e to match ErrorImpl<ContextError<D, Report>>.
+unsafe fn context_chain_downcast<D>(e: &ErrorImpl<()>, target: TypeId) -> Option<NonNull<()>>
+where
+    D: 'static,
+{
+    let unerased = e as *const ErrorImpl<()> as *const ErrorImpl<ContextError<D, Report>>;
+    if TypeId::of::<D>() == target {
+        let addr = &(*unerased)._object.msg as *const D as *mut ();
+        Some(NonNull::new_unchecked(addr))
+    } else {
+        // Recurse down the context chain per the inner error's vtable.
+        let source = &(*unerased)._object.error;
+        (source.inner.vtable.object_downcast)(&source.inner, target)
+    }
+}
+
+// Safety: requires layout of *e to match ErrorImpl<ContextError<D, Report>>.
+unsafe fn context_chain_drop_rest<D>(e: Box<ErrorImpl<()>>, target: TypeId)
+where
+    D: 'static,
+{
+    // Called after downcasting by value to either the D or one of the causes
+    // and doing a ptr::read to take ownership of that value.
+    if TypeId::of::<D>() == target {
+        let unerased = mem::transmute::<
+            Box<ErrorImpl<()>>,
+            Box<ErrorImpl<ContextError<ManuallyDrop<D>, Report>>>,
+        >(e);
+        // Drop the entire rest of the data structure rooted in the next Report.
+        drop(unerased);
+    } else {
+        let unerased = mem::transmute::<
+            Box<ErrorImpl<()>>,
+            Box<ErrorImpl<ContextError<D, ManuallyDrop<Report>>>>,
+        >(e);
+        // Read out a ManuallyDrop<Box<ErrorImpl<()>>> from the next error.
+        let inner = ptr::read(&unerased._object.error.inner);
+        drop(unerased);
+        let erased = ManuallyDrop::into_inner(inner);
+        // Recursively drop the next error using the same target typeid.
+        (erased.vtable.object_drop_rest)(erased, target);
+    }
+}
+
+// repr C to ensure that E remains in the final position.
+#[repr(C)]
+pub(crate) struct ErrorImpl<E> {
+    vtable: &'static ErrorVTable,
+    pub(crate) handler: Option<Box<dyn ReportHandler>>,
+    // NOTE: Don't use directly. Use only through vtable. Erased type may have
+    // different alignment.
+    _object: E,
+}
+
+// repr C to ensure that ContextError<D, E> has the same layout as
+// ContextError<ManuallyDrop<D>, E> and ContextError<D, ManuallyDrop<E>>.
+#[repr(C)]
+pub(crate) struct ContextError<D, E> {
+    pub(crate) msg: D,
+    pub(crate) error: E,
+}
+
+impl<E> ErrorImpl<E> {
+    fn erase(&self) -> &ErrorImpl<()> {
+        // Erase the concrete type of E but preserve the vtable in self.vtable
+        // for manipulating the resulting thin pointer. This is analogous to an
+        // unsize coercion.
+        unsafe { &*(self as *const ErrorImpl<E> as *const ErrorImpl<()>) }
+    }
+}
+
+impl ErrorImpl<()> {
+    pub(crate) fn error(&self) -> &(dyn StdError + Send + Sync + 'static) {
+        // Use vtable to attach E's native StdError vtable for the right
+        // original type E.
+        unsafe { &*(self.vtable.object_ref_stderr)(self) }
+    }
+
+    pub(crate) fn diagnostic(&self) -> &(dyn Diagnostic + Send + Sync + 'static) {
+        // Use vtable to attach E's native StdError vtable for the right
+        // original type E.
+        unsafe { &*(self.vtable.object_ref)(self) }
+    }
+
+    pub(crate) fn diagnostic_mut(&mut self) -> &mut (dyn Diagnostic + Send + Sync + 'static) {
+        // Use vtable to attach E's native StdError vtable for the right
+        // original type E.
+        unsafe { &mut *(self.vtable.object_mut)(self) }
+    }
+
+    pub(crate) fn chain(&self) -> Chain<'_> {
+        Chain::new(self.error())
+    }
+}
+
+impl<E> StdError for ErrorImpl<E>
+where
+    E: StdError,
+{
+    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+        self.erase().diagnostic().source()
+    }
+}
+
+impl<E> Diagnostic for ErrorImpl<E> where E: Diagnostic {}
+
+impl<E> Debug for ErrorImpl<E>
+where
+    E: Debug,
+{
+    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+        self.erase().debug(formatter)
+    }
+}
+
+impl<E> Display for ErrorImpl<E>
+where
+    E: Display,
+{
+    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+        Display::fmt(&self.erase().diagnostic(), formatter)
+    }
+}
+
+impl From<Report> for Box<dyn Diagnostic + Send + Sync + 'static> {
+    fn from(error: Report) -> Self {
+        let outer = ManuallyDrop::new(error);
+        unsafe {
+            // Read Box<ErrorImpl<()>> from error. Can't move it out because
+            // Report has a Drop impl which we want to not run.
+            let inner = ptr::read(&outer.inner);
+            let erased = ManuallyDrop::into_inner(inner);
+
+            // Use vtable to attach ErrorImpl<E>'s native StdError vtable for
+            // the right original type E.
+            (erased.vtable.object_boxed)(erased)
+        }
+    }
+}
+
+impl From<Report> for Box<dyn Diagnostic + 'static> {
+    fn from(error: Report) -> Self {
+        Box::<dyn Diagnostic + Send + Sync>::from(error)
+    }
+}
+
+impl AsRef<dyn Diagnostic + Send + Sync> for Report {
+    fn as_ref(&self) -> &(dyn Diagnostic + Send + Sync + 'static) {
+        &**self
+    }
+}
+
+impl AsRef<dyn Diagnostic> for Report {
+    fn as_ref(&self) -> &(dyn Diagnostic + 'static) {
+        &**self
+    }
+}
diff --git a/src/eyreish/fmt.rs b/src/eyreish/fmt.rs
new file mode 100644
index 0000000..bb094ca
--- /dev/null
+++ b/src/eyreish/fmt.rs
@@ -0,0 +1,18 @@
+use super::error::ErrorImpl;
+use core::fmt;
+
+impl ErrorImpl<()> {
+    pub(crate) fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        self.handler
+            .as_ref()
+            .map(|handler| handler.display(self.error(), f))
+            .unwrap_or_else(|| core::fmt::Display::fmt(self.diagnostic(), f))
+    }
+
+    pub(crate) fn debug(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        self.handler
+            .as_ref()
+            .map(|handler| handler.debug(self.diagnostic(), f))
+            .unwrap_or_else(|| core::fmt::Debug::fmt(self.diagnostic(), f))
+    }
+}
diff --git a/src/eyreish/into_diagnostic.rs b/src/eyreish/into_diagnostic.rs
new file mode 100644
index 0000000..3e03694
--- /dev/null
+++ b/src/eyreish/into_diagnostic.rs
@@ -0,0 +1,25 @@
+use thiserror::Error;
+
+use crate::{Diagnostic, Report};
+
+/// Convenience [Diagnostic] that can be used as an "anonymous" wrapper for
+/// Errors. This is intended to be paired with [IntoDiagnostic].
+#[derive(Debug, Error)]
+#[error(transparent)]
+struct DiagnosticError(Box<dyn std::error::Error + Send + Sync + 'static>);
+impl Diagnostic for DiagnosticError {}
+
+/**
+Convenience trait that adds a `.into_diagnostic()` method that converts a type to a `Result<T, DiagnosticError>`.
+*/
+pub trait IntoDiagnostic<T, E> {
+    /// Converts [Result]-like types that return regular errors into a
+    /// `Result` that returns a [Diagnostic].
+    fn into_diagnostic(self) -> Result<T, Report>;
+}
+
+impl<T, E: std::error::Error + Send + Sync + 'static> IntoDiagnostic<T, E> for Result<T, E> {
+    fn into_diagnostic(self) -> Result<T, Report> {
+        self.map_err(|e| DiagnosticError(Box::new(e)).into())
+    }
+}
diff --git a/src/eyreish/kind.rs b/src/eyreish/kind.rs
new file mode 100644
index 0000000..ce60b50
--- /dev/null
+++ b/src/eyreish/kind.rs
@@ -0,0 +1,111 @@
+#![allow(missing_debug_implementations, missing_docs)]
+// Tagged dispatch mechanism for resolving the behavior of `miette!($expr)`.
+//
+// When miette! is given a single expr argument to turn into miette::Report, we
+// want the resulting Report to pick up the input's implementation of source()
+// and backtrace() if it has a std::error::Error impl, otherwise require nothing
+// more than Display and Debug.
+//
+// Expressed in terms of specialization, we want something like:
+//
+//     trait EyreNew {
+//         fn new(self) -> Report;
+//     }
+//
+//     impl<T> EyreNew for T
+//     where
+//         T: Display + Debug + Send + Sync + 'static,
+//     {
+//         default fn new(self) -> Report {
+//             /* no std error impl */
+//         }
+//     }
+//
+//     impl<T> EyreNew for T
+//     where
+//         T: std::error::Error + Send + Sync + 'static,
+//     {
+//         fn new(self) -> Report {
+//             /* use std error's source() and backtrace() */
+//         }
+//     }
+//
+// Since specialization is not stable yet, instead we rely on autoref behavior
+// of method resolution to perform tagged dispatch. Here we have two traits
+// AdhocKind and TraitKind that both have an miette_kind() method. AdhocKind is
+// implemented whether or not the caller's type has a std error impl, while
+// TraitKind is implemented only when a std error impl does exist. The ambiguity
+// is resolved by AdhocKind requiring an extra autoref so that it has lower
+// precedence.
+//
+// The miette! macro will set up the call in this form:
+//
+//     #[allow(unused_imports)]
+//     use $crate::private::{AdhocKind, TraitKind};
+//     let error = $msg;
+//     (&error).miette_kind().new(error)
+
+use super::Report;
+use core::fmt::{Debug, Display};
+
+use crate::Diagnostic;
+
+pub struct Adhoc;
+
+pub trait AdhocKind: Sized {
+    #[inline]
+    fn miette_kind(&self) -> Adhoc {
+        Adhoc
+    }
+}
+
+impl<T> AdhocKind for &T where T: ?Sized + Display + Debug + Send + Sync + 'static {}
+
+impl Adhoc {
+    #[cfg_attr(track_caller, track_caller)]
+    pub fn new<M>(self, message: M) -> Report
+    where
+        M: Display + Debug + Send + Sync + 'static,
+    {
+        Report::from_adhoc(message)
+    }
+}
+
+pub struct Trait;
+
+pub trait TraitKind: Sized {
+    #[inline]
+    fn miette_kind(&self) -> Trait {
+        Trait
+    }
+}
+
+impl<E> TraitKind for E where E: Into<Report> {}
+
+impl Trait {
+    #[cfg_attr(track_caller, track_caller)]
+    pub fn new<E>(self, error: E) -> Report
+    where
+        E: Into<Report>,
+    {
+        error.into()
+    }
+}
+
+pub struct Boxed;
+
+pub trait BoxedKind: Sized {
+    #[inline]
+    fn miette_kind(&self) -> Boxed {
+        Boxed
+    }
+}
+
+impl BoxedKind for Box<dyn Diagnostic + Send + Sync> {}
+
+impl Boxed {
+    #[cfg_attr(track_caller, track_caller)]
+    pub fn new(self, error: Box<dyn Diagnostic + Send + Sync>) -> Report {
+        Report::from_boxed(error)
+    }
+}
diff --git a/src/eyreish/macros.rs b/src/eyreish/macros.rs
new file mode 100644
index 0000000..ff8c1fe
--- /dev/null
+++ b/src/eyreish/macros.rs
@@ -0,0 +1,164 @@
+/// Return early with an error.
+///
+/// This macro is equivalent to `return Err(From::from($err))`.
+///
+/// # Example
+///
+/// ```
+/// # use miette::{bail, Result};
+/// #
+/// # fn has_permission(user: usize, resource: usize) -> bool {
+/// #     true
+/// # }
+/// #
+/// # fn main() -> Result<()> {
+/// #     let user = 0;
+/// #     let resource = 0;
+/// #
+/// if !has_permission(user, resource) {
+///     bail!("permission denied for accessing {}", resource);
+/// }
+/// #     Ok(())
+/// # }
+/// ```
+///
+/// ```
+/// # use miette::{bail, Result};
+/// # use thiserror::Error;
+/// #
+/// # const MAX_DEPTH: usize = 1;
+/// #
+/// #[derive(Error, Debug)]
+/// enum ScienceError {
+///     #[error("recursion limit exceeded")]
+///     RecursionLimitExceeded,
+///     # #[error("...")]
+///     # More = (stringify! {
+///     ...
+///     # }, 1).1,
+/// }
+///
+/// # fn main() -> Result<()> {
+/// #     let depth = 0;
+/// #     let err: &'static dyn std::error::Error = &ScienceError::RecursionLimitExceeded;
+/// #
+/// if depth > MAX_DEPTH {
+///     bail!(ScienceError::RecursionLimitExceeded);
+/// }
+/// #     Ok(())
+/// # }
+/// ```
+#[macro_export]
+macro_rules! bail {
+    ($msg:literal $(,)?) => {
+        return $crate::private::Err($crate::miette!($msg));
+    };
+    ($err:expr $(,)?) => {
+        return $crate::private::Err($crate::miette!($err));
+    };
+    ($fmt:expr, $($arg:tt)*) => {
+        return $crate::private::Err($crate::miette!($fmt, $($arg)*));
+    };
+}
+
+/// Return early with an error if a condition is not satisfied.
+///
+/// This macro is equivalent to `if !$cond { return Err(From::from($err)); }`.
+///
+/// Analogously to `assert!`, `ensure!` takes a condition and exits the function
+/// if the condition fails. Unlike `assert!`, `ensure!` returns an `Error`
+/// rather than panicking.
+///
+/// # Example
+///
+/// ```
+/// # use miette::{ensure, Result};
+/// #
+/// # fn main() -> Result<()> {
+/// #     let user = 0;
+/// #
+/// ensure!(user == 0, "only user 0 is allowed");
+/// #     Ok(())
+/// # }
+/// ```
+///
+/// ```
+/// # use miette::{ensure, Result};
+/// # use thiserror::Error;
+/// #
+/// # const MAX_DEPTH: usize = 1;
+/// #
+/// #[derive(Error, Debug)]
+/// enum ScienceError {
+///     #[error("recursion limit exceeded")]
+///     RecursionLimitExceeded,
+///     # #[error("...")]
+///     # More = (stringify! {
+///     ...
+///     # }, 1).1,
+/// }
+///
+/// # fn main() -> Result<()> {
+/// #     let depth = 0;
+/// #
+/// ensure!(depth <= MAX_DEPTH, ScienceError::RecursionLimitExceeded);
+/// #     Ok(())
+/// # }
+/// ```
+#[macro_export]
+macro_rules! ensure {
+    ($cond:expr, $msg:literal $(,)?) => {
+        if !$cond {
+            return $crate::private::Err($crate::miette!($msg));
+        }
+    };
+    ($cond:expr, $err:expr $(,)?) => {
+        if !$cond {
+            return $crate::private::Err($crate::miette!($err));
+        }
+    };
+    ($cond:expr, $fmt:expr, $($arg:tt)*) => {
+        if !$cond {
+            return $crate::private::Err($crate::miette!($fmt, $($arg)*));
+        }
+    };
+}
+
+/// Construct an ad-hoc error from a string.
+///
+/// This evaluates to an `Error`. It can take either just a string, or a format
+/// string with arguments. It also can take any custom type which implements
+/// `Debug` and `Display`.
+///
+/// # Example
+///
+/// ```
+/// # type V = ();
+/// #
+/// use miette::{miette, Result};
+///
+/// fn lookup(key: &str) -> Result<V> {
+///     if key.len() != 16 {
+///         return Err(miette!("key length must be 16 characters, got {:?}", key));
+///     }
+///
+///     // ...
+///     # Ok(())
+/// }
+/// ```
+#[macro_export]
+macro_rules! miette {
+    ($msg:literal $(,)?) => {
+        // Handle $:literal as a special case to make cargo-expanded code more
+        // concise in the common case.
+        $crate::private::new_adhoc($msg)
+    };
+    ($err:expr $(,)?) => ({
+        use $crate::private::kind::*;
+        let error = $err;
+        (&error).miette_kind().new(error)
+    });
+    ($fmt:expr, $($arg:tt)*) => {
+        $crate::private::new_adhoc(format!($fmt, $($arg)*))
+    };
+}
diff --git a/src/eyreish/mod.rs b/src/eyreish/mod.rs
new file mode 100644
index 0000000..8a45e27
--- /dev/null
+++ b/src/eyreish/mod.rs
@@ -0,0 +1,490 @@
+#![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 core::mem::ManuallyDrop;
+
+use std::error::Error as StdError;
+
+use atty::Stream;
+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;
+
+use crate::{Diagnostic, GraphicalReportHandler, NarratableReportHandler};
+use error::ErrorImpl;
+
+mod context;
+mod error;
+mod fmt;
+mod into_diagnostic;
+mod kind;
+mod macros;
+mod wrapper;
+
+/**
+Core Diagnostic wrapper type.
+*/
+pub struct Report {
+    inner: ManuallyDrop<Box<ErrorImpl<()>>>,
+}
+
+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 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> {
+    let fancy = if let Ok(string) = std::env::var("NO_COLOR") {
+        string == "0"
+    } else if let Ok(string) = std::env::var("CLICOLOR") {
+        string != "0" || string == "1"
+    } else {
+        atty::is(Stream::Stdout) && atty::is(Stream::Stderr) && !ci_info::is_ci()
+    };
+    if fancy {
+        Box::new(GraphicalReportHandler::new())
+    } else {
+        Box::new(NarratableReportHandler)
+    }
+}
+
+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 miette::Diagnostic;
+    /// use miette::ReportHandler;
+    /// use indenter::indented;
+    ///
+    /// pub struct Handler;
+    ///
+    /// impl ReportHandler for Handler {
+    ///     fn debug(
+    ///         &self,
+    ///         error: &(dyn Diagnostic + 'static),
+    ///         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 + 'static),
+        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>) {}
+}
+
+/// Iterator of a chain of source errors.
+///
+/// This type is the iterator returned by [`Report::chain`].
+///
+/// # Example
+///
+/// ```
+/// use miette::Report;
+/// use std::io;
+///
+/// pub fn underlying_io_error_kind(error: &Report) -> Option<io::ErrorKind> {
+///     for cause in error.chain() {
+///         if let Some(io_error) = cause.downcast_ref::<io::Error>() {
+///             return Some(io_error.kind());
+///         }
+///     }
+///     None
+/// }
+/// ```
+#[derive(Clone)]
+#[allow(missing_debug_implementations)]
+pub struct Chain<'a> {
+    state: crate::chain::ChainState<'a>,
+}
+
+/// type alias for `Result<T, Report>`
+///
+/// This is a reasonable return type to use throughout your application but also for `fn 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(())
+/// }
+/// ```
+pub type Result<T, E = Report> = core::result::Result<T, E>;
+
+/// Provides the `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;
+/// use std::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 Don't impl `Error` (e.g. `&str` and `Box<dyn Error>`)
+///
+/// Due to restrictions for coherence `Report` cannot impl `From` for types that don't impl
+/// `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 std::error::Error;
+/// use miette::{WrapErr, Report, miette};
+///
+/// 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, Eyre'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 Eyre, 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;
+}
+
+// Not public 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)
+    }
+}
diff --git a/src/eyreish/wrapper.rs b/src/eyreish/wrapper.rs
new file mode 100644
index 0000000..90e58c1
--- /dev/null
+++ b/src/eyreish/wrapper.rs
@@ -0,0 +1,88 @@
+use core::fmt::{self, Debug, Display};
+
+use std::error::Error as StdError;
+
+use crate::Diagnostic;
+
+#[repr(transparent)]
+pub(crate) struct DisplayError<M>(pub(crate) M);
+
+#[repr(transparent)]
+pub(crate) struct MessageError<M>(pub(crate) M);
+
+pub(crate) struct NoneError;
+
+impl<M> Debug for DisplayError<M>
+where
+    M: Display,
+{
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        Display::fmt(&self.0, f)
+    }
+}
+
+impl<M> Display for DisplayError<M>
+where
+    M: Display,
+{
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        Display::fmt(&self.0, f)
+    }
+}
+
+impl<M> StdError for DisplayError<M> where M: Display + 'static {}
+impl<M> Diagnostic for DisplayError<M> where M: Display + 'static {}
+
+impl<M> Debug for MessageError<M>
+where
+    M: Display + Debug,
+{
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        Debug::fmt(&self.0, f)
+    }
+}
+
+impl<M> Display for MessageError<M>
+where
+    M: Display + Debug,
+{
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        Display::fmt(&self.0, f)
+    }
+}
+
+impl<M> StdError for MessageError<M> where M: Display + Debug + 'static {}
+impl<M> Diagnostic for MessageError<M> where M: Display + Debug + 'static {}
+
+impl Debug for NoneError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        Debug::fmt("Option was None", f)
+    }
+}
+
+impl Display for NoneError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        Display::fmt("Option was None", f)
+    }
+}
+
+impl StdError for NoneError {}
+impl Diagnostic for NoneError {}
+
+#[repr(transparent)]
+pub(crate) struct BoxedError(pub(crate) Box<dyn Diagnostic + Send + Sync>);
+
+impl Debug for BoxedError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        Debug::fmt(&self.0, f)
+    }
+}
+
+impl Display for BoxedError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        Display::fmt(&self.0, f)
+    }
+}
+
+impl StdError for BoxedError {}
+impl Diagnostic for BoxedError {}
diff --git a/src/printer/graphical_printer.rs b/src/handlers/graphical.rs
similarity index 93%
rename from src/printer/graphical_printer.rs
rename to src/handlers/graphical.rs
index 3625996..b3f9d96 100644
--- a/src/printer/graphical_printer.rs
+++ b/src/handlers/graphical.rs
@@ -3,34 +3,29 @@ use std::fmt;
 use owo_colors::{OwoColorize, Style};
 
 use crate::chain::Chain;
-use crate::printer::theme::*;
-use crate::protocol::{Diagnostic, DiagnosticReportPrinter, DiagnosticSnippet, Severity};
-use crate::{SourceSpan, SpanContents};
+use crate::handlers::theme::*;
+use crate::protocol::{Diagnostic, DiagnosticSnippet, Severity};
+use crate::{ReportHandler, SourceSpan, SpanContents};
 
 /**
-A [DiagnosticReportPrinter] that displays a given [crate::DiagnosticReport] in a quasi-graphical way, using terminal colors, unicode drawing characters, and other such things.
+A [ReportHandler] that displays a given [crate::Report] in a quasi-graphical
+way, using terminal colors, unicode drawing characters, and other such things.
 
 This is the default reporter bundled with `miette`.
 
-This printer can be customized by using `new_themed()` and handing it a [GraphicalTheme] of your own creation (or using one of its own defaults!)
+This printer can be customized by using `new_themed()` and handing it a
+[GraphicalTheme] of your own creation (or using one of its own defaults!)
 
-See [crate::set_printer] for more details on customizing your global printer.
-
-## Example
-
-```
-use miette::{GraphicalReportPrinter, GraphicalTheme};
-miette::set_printer(GraphicalReportPrinter::new_themed(GraphicalTheme::unicode_nocolor()));
-```
+See [crate::set_hook] for more details on customizing your global printer.
 */
 #[derive(Debug, Clone)]
-pub struct GraphicalReportPrinter {
+pub struct GraphicalReportHandler {
     pub(crate) linkify_code: bool,
     pub(crate) theme: GraphicalTheme,
 }
 
-impl GraphicalReportPrinter {
-    /// Create a new [GraphicalReportPrinter] with the default
+impl GraphicalReportHandler {
+    /// Create a new [GraphicalReportHandler] with the default
     /// [GraphicalTheme]. This will use both unicode characters and colors.
     pub fn new() -> Self {
         Self {
@@ -39,7 +34,7 @@ impl GraphicalReportPrinter {
         }
     }
 
-    ///Create a new [GraphicalReportPrinter] with a given [GraphicalTheme].
+    ///Create a new [GraphicalReportHandler] with a given [GraphicalTheme].
     pub fn new_themed(theme: GraphicalTheme) -> Self {
         Self {
             linkify_code: true,
@@ -54,15 +49,15 @@ impl GraphicalReportPrinter {
     }
 }
 
-impl Default for GraphicalReportPrinter {
+impl Default for GraphicalReportHandler {
     fn default() -> Self {
         Self::new()
     }
 }
 
-impl GraphicalReportPrinter {
+impl GraphicalReportHandler {
     /// Render a [Diagnostic]. This function is mostly internal and meant to
-    /// be called by the toplevel [DiagnosticReportPrinter] handler, but is
+    /// be called by the toplevel [ReportHandler] handler, but is
     /// made public to make it easier (possible) to test in isolation from
     /// global state.
     pub fn render_report(
@@ -91,13 +86,18 @@ impl GraphicalReportPrinter {
             Some(Severity::Advice) => (self.theme.styles.advice, self.theme.characters.point_right),
         };
         write!(f, "{}", self.theme.characters.hbar.to_string().repeat(4))?;
-        if self.linkify_code && diagnostic.url().is_some() {
+        if self.linkify_code && diagnostic.url().is_some() && diagnostic.code().is_some() {
             let url = diagnostic.url().unwrap(); // safe
-            let code = format!("{} (click for details)", diagnostic.code());
+            let code = format!(
+                "{} (click for details)",
+                diagnostic
+                    .code()
+                    .expect("MIETTE BUG: already got checked for None")
+            );
             let link = format!("\u{1b}]8;;{}\u{1b}\\{}\u{1b}]8;;\u{1b}\\", url, code);
             write!(f, "[{}]", link.style(self.theme.styles.code))?;
-        } else {
-            write!(f, "[{}]", diagnostic.code().style(self.theme.styles.code))?;
+        } else if let Some(code) = diagnostic.code() {
+            write!(f, "[{}]", code.style(self.theme.styles.code))?;
         }
         writeln!(f, "{}", self.theme.characters.hbar.to_string().repeat(20),)?;
         writeln!(f)?;
@@ -510,7 +510,7 @@ impl GraphicalReportPrinter {
     }
 }
 
-impl DiagnosticReportPrinter for GraphicalReportPrinter {
+impl ReportHandler for GraphicalReportHandler {
     fn debug(&self, diagnostic: &(dyn Diagnostic), f: &mut fmt::Formatter<'_>) -> fmt::Result {
         if f.alternate() {
             return fmt::Debug::fmt(diagnostic, f);
diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs
new file mode 100644
index 0000000..521fe50
--- /dev/null
+++ b/src/handlers/mod.rs
@@ -0,0 +1,14 @@
+/*!
+Reporters included with `miette`.
+*/
+
+#[allow(unreachable_pub)]
+pub use graphical::*;
+#[allow(unreachable_pub)]
+pub use narratable::*;
+#[allow(unreachable_pub)]
+pub use theme::*;
+
+mod graphical;
+mod narratable;
+mod theme;
diff --git a/src/printer/narratable_printer.rs b/src/handlers/narratable.rs
similarity index 91%
rename from src/printer/narratable_printer.rs
rename to src/handlers/narratable.rs
index d7ea72c..6603f35 100644
--- a/src/printer/narratable_printer.rs
+++ b/src/handlers/narratable.rs
@@ -1,34 +1,34 @@
 use std::fmt;
 
 use crate::chain::Chain;
-use crate::protocol::{Diagnostic, DiagnosticReportPrinter, DiagnosticSnippet, Severity};
-use crate::{SourceSpan, SpanContents};
+use crate::protocol::{Diagnostic, DiagnosticSnippet, Severity};
+use crate::{ReportHandler, SourceSpan, SpanContents};
 
 /**
-[DiagnosticReportPrinter] that renders plain text and avoids extraneous graphics.
+[ReportHandler] that renders plain text and avoids extraneous graphics.
 It's optimized for screen readers and braille users, but is also used in any
 non-graphical environments, such as non-TTY output.
 */
 #[derive(Debug, Clone)]
-pub struct NarratableReportPrinter;
+pub struct NarratableReportHandler;
 
-impl NarratableReportPrinter {
-    /// Create a new [NarratableReportPrinter]. There are no customization
+impl NarratableReportHandler {
+    /// Create a new [NarratableReportHandler]. There are no customization
     /// options.
     pub fn new() -> Self {
         Self
     }
 }
 
-impl Default for NarratableReportPrinter {
+impl Default for NarratableReportHandler {
     fn default() -> Self {
         Self::new()
     }
 }
 
-impl NarratableReportPrinter {
+impl NarratableReportHandler {
     /// Render a [Diagnostic]. This function is mostly internal and meant to
-    /// be called by the toplevel [DiagnosticReportPrinter] handler, but is
+    /// be called by the toplevel [ReportHandler] handler, but is
     /// made public to make it easier (possible) to test in isolation from
     /// global state.
     pub fn render_report(
@@ -75,7 +75,9 @@ impl NarratableReportPrinter {
         if let Some(help) = diagnostic.help() {
             writeln!(f, "diagnostic help: {}", help)?;
         }
-        writeln!(f, "diagnostic error code: {}", diagnostic.code())?;
+        if let Some(code) = diagnostic.code() {
+            writeln!(f, "diagnostic code: {}", code)?;
+        }
         if let Some(url) = diagnostic.url() {
             writeln!(f, "For more details, see {}", url)?;
         }
@@ -192,7 +194,7 @@ impl NarratableReportPrinter {
     }
 }
 
-impl DiagnosticReportPrinter for NarratableReportPrinter {
+impl ReportHandler for NarratableReportHandler {
     fn debug(&self, diagnostic: &(dyn Diagnostic), f: &mut fmt::Formatter<'_>) -> fmt::Result {
         if f.alternate() {
             return fmt::Debug::fmt(diagnostic, f);
diff --git a/src/printer/theme.rs b/src/handlers/theme.rs
similarity index 96%
rename from src/printer/theme.rs
rename to src/handlers/theme.rs
index 6f2b464..bd744f0 100644
--- a/src/printer/theme.rs
+++ b/src/handlers/theme.rs
@@ -2,7 +2,7 @@ use atty::Stream;
 use owo_colors::Style;
 
 /**
-Theme used by [crate::GraphicalReportPrinter] to render fancy [crate::Diagnostic] reports.
+Theme used by [crate::GraphicalReportHandler] to render fancy [crate::Diagnostic] reports.
 
 A theme consists of two things: the set of characters to be used for drawing,
 and the [owo_colors::Style]s to be used to paint various items.
@@ -47,8 +47,8 @@ impl GraphicalTheme {
     /// A "basic" graphical theme that skips colors and unicode characters and
     /// just does monochrome ascii art. If you want a completely non-graphical
     /// rendering of your `Diagnostic`s, check out
-    /// [crate::NarratableReportPrinter], or write your own
-    /// [crate::DiagnosticReportPrinter]!
+    /// [crate::NarratableReportHandler], or write your own
+    /// [crate::ReportHandler]!
     pub fn none() -> Self {
         Self {
             characters: ThemeCharacters::ascii(),
@@ -68,7 +68,7 @@ impl Default for GraphicalTheme {
 }
 
 /**
-Styles for various parts of graphical rendering for the [crate::GraphicalReportPrinter].
+Styles for various parts of graphical rendering for the [crate::GraphicalReportHandler].
 */
 #[derive(Debug, Clone)]
 pub struct ThemeStyles {
@@ -150,7 +150,7 @@ impl ThemeStyles {
 // Most of these characters were taken from
 // https://github.com/zesterer/ariadne/blob/e3cb394cb56ecda116a0a1caecd385a49e7f6662/src/draw.rs
 
-/// Characters to be used when drawing when using [crate::GraphicalReportPrinter].
+/// Characters to be used when drawing when using [crate::GraphicalReportHandler].
 #[allow(missing_docs)]
 #[derive(Debug, Clone, Eq, PartialEq)]
 pub struct ThemeCharacters {
diff --git a/src/lib.rs b/src/lib.rs
index 1068ba7..6ad4027 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -5,16 +5,18 @@
 pub use miette_derive::*;
 
 pub use error::*;
-pub use printer::*;
+pub use eyreish::*;
+pub use handlers::*;
+pub use named_source::*;
 pub use protocol::*;
-pub use utils::*;
 
 mod chain;
 mod error;
-mod printer;
+mod eyreish;
+mod handlers;
+mod named_source;
 mod protocol;
 mod source_impls;
-mod utils;
 
 #[cfg(doctest)]
 mod compile_test;
diff --git a/src/named_source.rs b/src/named_source.rs
new file mode 100644
index 0000000..0a6270d
--- /dev/null
+++ b/src/named_source.rs
@@ -0,0 +1,38 @@
+use crate::Source;
+
+/// Utility struct for when you have a regular [Source] type, such as a String,
+/// that doesn't implement `name`, or if you want to override the `.name()`
+/// returned by the `Source`.
+#[derive(Debug)]
+pub struct NamedSource {
+    source: Box<dyn Source + Send + Sync + 'static>,
+    name: String,
+}
+
+impl NamedSource {
+    /// Create a new [NamedSource] using a regular [Source] and giving it a [Source::name].
+    pub fn new(name: impl AsRef<str>, source: impl Source + Send + Sync + 'static) -> Self {
+        Self {
+            source: Box::new(source),
+            name: name.as_ref().to_string(),
+        }
+    }
+
+    /// Returns a reference the inner [Source] type for this [NamedSource].
+    pub fn inner(&self) -> &(dyn Source + Send + Sync + 'static) {
+        &*self.source
+    }
+}
+
+impl Source for NamedSource {
+    fn read_span<'a>(
+        &'a self,
+        span: &crate::SourceSpan,
+    ) -> Result<Box<dyn crate::SpanContents + 'a>, crate::MietteError> {
+        self.source.read_span(span)
+    }
+
+    fn name(&self) -> Option<String> {
+        Some(self.name.clone())
+    }
+}
diff --git a/src/printer/mod.rs b/src/printer/mod.rs
deleted file mode 100644
index 25f21e1..0000000
--- a/src/printer/mod.rs
+++ /dev/null
@@ -1,55 +0,0 @@
-/*!
-Reporters included with `miette`.
-*/
-use atty::Stream;
-use once_cell::sync::OnceCell;
-
-use crate::protocol::DiagnosticReportPrinter;
-use crate::MietteError;
-
-// NOTE(zkat): I don't understand why these three are "unreachable" when
-// they're clearly being exported? Maybe a bug?
-#[allow(unreachable_pub)]
-pub use graphical_printer::*;
-#[allow(unreachable_pub)]
-pub use narratable_printer::*;
-#[allow(unreachable_pub)]
-pub use theme::*;
-
-mod graphical_printer;
-mod narratable_printer;
-mod theme;
-
-static REPORTER: OnceCell<Box<dyn DiagnosticReportPrinter + Send + Sync + 'static>> =
-    OnceCell::new();
-
-/// Set the global [DiagnosticReportPrinter] that will be used when you report
-/// using [crate::DiagnosticReport].
-pub fn set_printer(
-    reporter: impl DiagnosticReportPrinter + Send + Sync + 'static,
-) -> Result<(), MietteError> {
-    REPORTER
-        .set(Box::new(reporter))
-        .map_err(|_| MietteError::SetPrinterFailure)
-}
-
-/// Used by [crate::DiagnosticReport] to fetch the reporter that will be used to
-/// print stuff out.
-pub(crate) fn get_printer() -> &'static (dyn DiagnosticReportPrinter + Send + Sync + 'static) {
-    &**REPORTER.get_or_init(get_default_printer)
-}
-
-fn get_default_printer() -> Box<dyn DiagnosticReportPrinter + Send + Sync + 'static> {
-    let fancy = if let Ok(string) = std::env::var("NO_COLOR") {
-        string == "0"
-    } else if let Ok(string) = std::env::var("CLICOLOR") {
-        string != "0" || string == "1"
-    } else {
-        atty::is(Stream::Stdout) && atty::is(Stream::Stderr) && !ci_info::is_ci()
-    };
-    if fancy {
-        Box::new(GraphicalReportPrinter::new())
-    } else {
-        Box::new(NarratableReportPrinter)
-    }
-}
diff --git a/src/protocol.rs b/src/protocol.rs
index 3c80f9b..5df665d 100644
--- a/src/protocol.rs
+++ b/src/protocol.rs
@@ -13,7 +13,7 @@ use std::{
 use crate::MietteError;
 
 /**
-Adds rich metadata to your Error that can be used by [DiagnosticReportPrinter] to print
+Adds rich metadata to your Error that can be used by [Report] to print
 really nice and human-friendly error messages.
 */
 pub trait Diagnostic: std::error::Error {
@@ -22,9 +22,11 @@ pub trait Diagnostic: std::error::Error {
     /// the toplevel crate's documentation for easy searching. Rust path
     /// format (`foo::bar::baz`) is recommended, but more classic codes like
     /// `E0123` or Enums will work just fine.
-    fn code<'a>(&'a self) -> Box<dyn Display + 'a>;
+    fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
+        None
+    }
 
-    /// Diagnostic severity. This may be used by [DiagnosticReportPrinter]s to change the
+    /// Diagnostic severity. This may be used by [ReportHandler]s to change the
     /// display format of this diagnostic.
     ///
     /// If `None`, reporters should treat this as [Severity::Error]
@@ -72,62 +74,69 @@ impl<T: Diagnostic + Send + Sync + 'static> From<T> for Box<dyn Diagnostic + 'st
     }
 }
 
-/**
-When used with `?`/`From`, this will wrap any Diagnostics and, when
-formatted with `Debug`, will fetch the current [DiagnosticReportPrinter] and
-use it to format the inner [Diagnostic].
-*/
-pub struct DiagnosticReport {
-    diagnostic: Box<dyn Diagnostic + Send + Sync + 'static>,
+impl From<&str> for Box<dyn Diagnostic> {
+    fn from(s: &str) -> Self {
+        From::from(String::from(s))
+    }
 }
 
-impl DiagnosticReport {
-    /// Return a reference to the inner [Diagnostic].
-    pub fn inner(&self) -> &(dyn Diagnostic + Send + Sync + 'static) {
-        &*self.diagnostic
+impl<'a> From<&str> for Box<dyn Diagnostic + Send + Sync + 'a> {
+    fn from(s: &str) -> Self {
+        From::from(String::from(s))
     }
 }
 
-impl std::fmt::Debug for DiagnosticReport {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        crate::get_printer().debug(&*self.diagnostic, f)
+impl From<String> for Box<dyn Diagnostic> {
+    fn from(s: String) -> Self {
+        let err1: Box<dyn Diagnostic + Send + Sync> = From::from(s);
+        let err2: Box<dyn Diagnostic> = err1;
+        err2
     }
 }
 
-impl<T: Diagnostic + Send + Sync + 'static> From<T> for DiagnosticReport {
-    fn from(diagnostic: T) -> Self {
-        DiagnosticReport {
-            diagnostic: Box::new(diagnostic),
+impl From<String> for Box<dyn Diagnostic + Send + Sync> {
+    fn from(s: String) -> Self {
+        struct StringError(String);
+
+        impl std::error::Error for StringError {}
+        impl Diagnostic for StringError {}
+
+        impl Display for StringError {
+            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+                Display::fmt(&self.0, f)
+            }
         }
+
+        // Purposefully skip printing "StringError(..)"
+        impl fmt::Debug for StringError {
+            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+                fmt::Debug::fmt(&self.0, f)
+            }
+        }
+
+        Box::new(StringError(s))
     }
 }
 
-/**
-Protocol for [Diagnostic] handlers, which are responsible for actually printing out Diagnostics.
+impl From<Box<dyn std::error::Error + Send + Sync>> for Box<dyn Diagnostic + Send + Sync> {
+    fn from(s: Box<dyn std::error::Error + Send + Sync>) -> Self {
+        #[derive(thiserror::Error)]
+        #[error(transparent)]
+        struct BoxedDiagnostic(Box<dyn std::error::Error + Send + Sync>);
+        impl fmt::Debug for BoxedDiagnostic {
+            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+                fmt::Debug::fmt(&self.0, f)
+            }
+        }
 
-Blatantly based on [EyreHandler](https://docs.rs/eyre/0.6.5/eyre/trait.EyreHandler.html) (thanks, Jane!)
-*/
-pub trait DiagnosticReportPrinter: core::any::Any + Send + Sync {
-    /// Define the report format.
-    fn debug(
-        &self,
-        diagnostic: &(dyn Diagnostic),
-        f: &mut core::fmt::Formatter<'_>,
-    ) -> core::fmt::Result;
-
-    /// Override for the `Display` format.
-    fn display(
-        &self,
-        diagnostic: &(dyn Diagnostic),
-        f: &mut core::fmt::Formatter<'_>,
-    ) -> core::fmt::Result {
-        write!(f, "{}", diagnostic)?;
-        Ok(())
+        impl Diagnostic for BoxedDiagnostic {}
+
+        Box::new(BoxedDiagnostic(s))
     }
 }
 
 /**
-[Diagnostic] severity. Intended to be used by [DiagnosticReportPrinter]s to change the
+[Diagnostic] severity. Intended to be used by [ReportHandler]s to change the
 way different Diagnostics are displayed.
 */
 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
diff --git a/src/utils.rs b/src/utils.rs
deleted file mode 100644
index 6b8d4c9..0000000
--- a/src/utils.rs
+++ /dev/null
@@ -1,86 +0,0 @@
-use std::fmt;
-
-use thiserror::Error;
-
-use crate::{Diagnostic, DiagnosticReport, Source};
-
-/// Convenience alias. This is intended to be used as the return type for `main()`
-pub type DiagnosticResult<T> = Result<T, DiagnosticReport>;
-
-/// Convenience [Diagnostic] that can be used as an "anonymous" wrapper for
-/// Errors. This is intended to be paired with [IntoDiagnostic].
-#[derive(Debug, Error)]
-#[error("{}", self.error)]
-pub struct DiagnosticError {
-    #[source]
-    error: Box<dyn std::error::Error + Send + Sync + 'static>,
-    code: String,
-}
-
-impl DiagnosticError {
-    /// Return a reference to the inner Error type.
-    pub fn inner(&self) -> &(dyn std::error::Error + Send + Sync + 'static) {
-        &*self.error
-    }
-}
-
-impl Diagnostic for DiagnosticError {
-    fn code<'a>(&'a self) -> Box<dyn std::fmt::Display + 'a> {
-        Box::new(&self.code)
-    }
-}
-
-/**
-Convenience trait that adds a `.into_diagnostic()` method that converts a type to a `Result<T, DiagnosticError>`.
-*/
-pub trait IntoDiagnostic<T, E> {
-    /// Converts [Result]-like types that return regular errors into a
-    /// `Result` that returns a [Diagnostic].
-    fn into_diagnostic(self, code: impl fmt::Display) -> Result<T, DiagnosticError>;
-}
-
-impl<T, E: std::error::Error + Send + Sync + 'static> IntoDiagnostic<T, E> for Result<T, E> {
-    fn into_diagnostic(self, code: impl fmt::Display) -> Result<T, DiagnosticError> {
-        self.map_err(|e| DiagnosticError {
-            error: Box::new(e),
-            code: format!("{}", code),
-        })
-    }
-}
-
-/// Utility struct for when you have a regular [Source] type, such as a String,
-/// that doesn't implement `name`, or if you want to override the `.name()`
-/// returned by the `Source`.
-#[derive(Debug)]
-pub struct NamedSource {
-    source: Box<dyn Source + Send + Sync + 'static>,
-    name: String,
-}
-
-impl NamedSource {
-    /// Create a new [NamedSource] using a regular [Source] and giving it a [Source::name].
-    pub fn new(name: impl AsRef<str>, source: impl Source + Send + Sync + 'static) -> Self {
-        Self {
-            source: Box::new(source),
-            name: name.as_ref().to_string(),
-        }
-    }
-
-    /// Returns a reference the inner [Source] type for this [NamedSource].
-    pub fn inner(&self) -> &(dyn Source + Send + Sync + 'static) {
-        &*self.source
-    }
-}
-
-impl Source for NamedSource {
-    fn read_span<'a>(
-        &'a self,
-        span: &crate::SourceSpan,
-    ) -> Result<Box<dyn crate::SpanContents + 'a>, crate::MietteError> {
-        self.source.read_span(span)
-    }
-
-    fn name(&self) -> Option<String> {
-        Some(self.name.clone())
-    }
-}


Test patch:
diff --git a/tests/common/mod.rs b/tests/common/mod.rs
new file mode 100644
index 0000000..223810c
--- /dev/null
+++ b/tests/common/mod.rs
@@ -0,0 +1,14 @@
+use miette::{bail, Result};
+use std::io;
+
+pub fn bail_literal() -> Result<()> {
+    bail!("oh no!");
+}
+
+pub fn bail_fmt() -> Result<()> {
+    bail!("{} {}!", "oh", "no");
+}
+
+pub fn bail_error() -> Result<()> {
+    bail!(io::Error::new(io::ErrorKind::Other, "oh no!"));
+}
diff --git a/tests/compiletest.rs b/tests/compiletest.rs
new file mode 100644
index 0000000..f9aea23
--- /dev/null
+++ b/tests/compiletest.rs
@@ -0,0 +1,6 @@
+#[rustversion::attr(not(nightly), ignore)]
+#[test]
+fn ui() {
+    let t = trybuild::TestCases::new();
+    t.compile_fail("tests/ui/*.rs");
+}
diff --git a/tests/derive.rs b/tests/derive.rs
index 97c391b..9257650 100644
--- a/tests/derive.rs
+++ b/tests/derive.rs
@@ -12,7 +12,7 @@ fn basic_struct() {
     )]
     struct Foo;
 
-    assert_eq!("foo::bar::baz".to_string(), Foo.code().to_string());
+    assert_eq!("foo::bar::baz".to_string(), Foo.code().unwrap().to_string());
 
     assert_eq!(Some(Severity::Error), Foo.severity());
 
@@ -39,11 +39,11 @@ fn basic_enum() {
         Z { prop: String },
     }
 
-    assert_eq!("foo::x".to_string(), Foo::X.code().to_string());
-    assert_eq!("foo::y".to_string(), Foo::Y(1).code().to_string());
+    assert_eq!("foo::x".to_string(), Foo::X.code().unwrap().to_string());
+    assert_eq!("foo::y".to_string(), Foo::Y(1).code().unwrap().to_string());
     assert_eq!(
         "foo::z".to_string(),
-        Foo::Z { prop: "bar".into() }.code().to_string()
+        Foo::Z { prop: "bar".into() }.code().unwrap().to_string()
     );
 
     assert_eq!(Some(Severity::Warning), Foo::X.severity());
@@ -57,7 +57,10 @@ fn paren_code() {
     #[diagnostic(code("foo::bar::baz"))]
     struct FooStruct;
 
-    assert_eq!("foo::bar::baz".to_string(), FooStruct.code().to_string());
+    assert_eq!(
+        "foo::bar::baz".to_string(),
+        FooStruct.code().unwrap().to_string()
+    );
 
     #[derive(Debug, Diagnostic, Error)]
     #[error("welp")]
@@ -66,7 +69,7 @@ fn paren_code() {
         X,
     }
 
-    assert_eq!("foo::x".to_string(), FooEnum::X.code().to_string());
+    assert_eq!("foo::x".to_string(), FooEnum::X.code().unwrap().to_string());
 }
 
 #[test]
@@ -76,7 +79,10 @@ fn path_code() {
     #[diagnostic(code(foo::bar::baz))]
     struct FooStruct;
 
-    assert_eq!("foo::bar::baz".to_string(), FooStruct.code().to_string());
+    assert_eq!(
+        "foo::bar::baz".to_string(),
+        FooStruct.code().unwrap().to_string()
+    );
 
     #[derive(Debug, Diagnostic, Error)]
     #[error("welp")]
@@ -85,7 +91,7 @@ fn path_code() {
         X,
     }
 
-    assert_eq!("foo::x".to_string(), FooEnum::X.code().to_string());
+    assert_eq!("foo::x".to_string(), FooEnum::X.code().unwrap().to_string());
 }
 
 #[test]
@@ -341,7 +347,7 @@ impl ForwardsTo {
 
 fn check_snippets(diag: &impl Diagnostic) {
     // check Diagnostic impl forwards all these methods
-    assert_eq!(diag.code().to_string(), "foo::bar::baz");
+    assert_eq!(diag.code().unwrap().to_string(), "foo::bar::baz");
     assert_eq!(diag.url().unwrap().to_string(), "https://example.com");
     assert_eq!(diag.help().unwrap().to_string(), "help");
     assert_eq!(diag.severity().unwrap(), miette::Severity::Warning);
@@ -413,7 +419,10 @@ fn test_transparent_enum_named() {
     check_snippets(&variant);
 
     let bar_variant = Enum::BarVariant;
-    assert_eq!(bar_variant.code().to_string(), "foo::bar::bar_variant");
+    assert_eq!(
+        bar_variant.code().unwrap().to_string(),
+        "foo::bar::bar_variant"
+    );
 }
 
 #[test]
diff --git a/tests/drop/mod.rs b/tests/drop/mod.rs
new file mode 100644
index 0000000..9a1eabc
--- /dev/null
+++ b/tests/drop/mod.rs
@@ -0,0 +1,55 @@
+use std::error::Error as StdError;
+use std::fmt::{self, Display};
+use std::sync::atomic::AtomicBool;
+use std::sync::atomic::Ordering::SeqCst;
+use std::sync::Arc;
+
+use miette::Diagnostic;
+
+#[derive(Debug)]
+pub struct Flag {
+    atomic: Arc<AtomicBool>,
+}
+
+impl Flag {
+    pub fn new() -> Self {
+        Flag {
+            atomic: Arc::new(AtomicBool::new(false)),
+        }
+    }
+
+    pub fn get(&self) -> bool {
+        self.atomic.load(SeqCst)
+    }
+}
+
+#[derive(Debug)]
+pub struct DetectDrop {
+    has_dropped: Flag,
+}
+
+impl DetectDrop {
+    pub fn new(has_dropped: &Flag) -> Self {
+        DetectDrop {
+            has_dropped: Flag {
+                atomic: Arc::clone(&has_dropped.atomic),
+            },
+        }
+    }
+}
+
+impl StdError for DetectDrop {}
+impl Diagnostic for DetectDrop {}
+
+impl Display for DetectDrop {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "oh no!")
+    }
+}
+
+impl Drop for DetectDrop {
+    fn drop(&mut self) {
+        let already_dropped = self.has_dropped.atomic.swap(true, SeqCst);
+        assert!(!already_dropped);
+    }
+}
diff --git a/tests/narrated.rs b/tests/narrated.rs
index 778d3fe..4a30d33 100644
--- a/tests/narrated.rs
+++ b/tests/narrated.rs
@@ -1,19 +1,19 @@
 use miette::{
-    Diagnostic, DiagnosticReport, GraphicalReportPrinter, GraphicalTheme, MietteError, NamedSource,
-    NarratableReportPrinter, SourceSpan,
+    Diagnostic, GraphicalReportHandler, GraphicalTheme, MietteError, NamedSource,
+    NarratableReportHandler, Report, SourceSpan,
 };
 use thiserror::Error;
 
-fn fmt_report(diag: DiagnosticReport) -> String {
+fn fmt_report(diag: Report) -> String {
     let mut out = String::new();
     // Mostly for dev purposes.
     if std::env::var("STYLE").is_ok() {
-        GraphicalReportPrinter::new_themed(GraphicalTheme::unicode())
-            .render_report(&mut out, diag.inner())
+        GraphicalReportHandler::new_themed(GraphicalTheme::unicode())
+            .render_report(&mut out, diag.as_ref())
             .unwrap();
     } else {
-        NarratableReportPrinter
-            .render_report(&mut out, diag.inner())
+        NarratableReportHandler
+            .render_report(&mut out, diag.as_ref())
             .unwrap();
     };
     out
@@ -53,7 +53,7 @@ snippet line 2:   text
 snippet line 3:     here
 
 diagnostic help: try doing it better next time?
-diagnostic error code: oops::my::bad
+diagnostic code: oops::my::bad
 "#
     .trim_start()
     .to_string();
@@ -95,7 +95,7 @@ snippet line 2:   text
 snippet line 3:     here
 
 diagnostic help: try doing it better next time?
-diagnostic error code: oops::my::bad
+diagnostic code: oops::my::bad
 "#
     .trim_start()
     .to_string();
@@ -141,7 +141,7 @@ snippet line 2:   text text text text text
 snippet line 3:     here
 
 diagnostic help: try doing it better next time?
-diagnostic error code: oops::my::bad
+diagnostic code: oops::my::bad
 "#
     .trim_start()
     .to_string();
@@ -183,7 +183,7 @@ snippet line 2:   text
 snippet line 3:     here
 
 diagnostic help: try doing it better next time?
-diagnostic error code: oops::my::bad
+diagnostic code: oops::my::bad
 "#
     .trim_start()
     .to_string();
@@ -237,7 +237,7 @@ snippet line 4: line4
 snippet line 6: line5
 
 diagnostic help: try doing it better next time?
-diagnostic error code: oops::my::bad
+diagnostic code: oops::my::bad
 "#
     .trim_start()
     .to_string();
@@ -291,7 +291,7 @@ snippet line 4: line4
 snippet line 6: line5
 
 diagnostic help: try doing it better next time?
-diagnostic error code: oops::my::bad
+diagnostic code: oops::my::bad
 "#
     .trim_start()
     .to_string();
@@ -338,7 +338,7 @@ snippet line 3:     here
 snippet line 4: more here
 
 diagnostic help: try doing it better next time?
-diagnostic error code: oops::my::bad
+diagnostic code: oops::my::bad
 "#
     .trim_start()
     .to_string();
@@ -386,7 +386,7 @@ snippet line 2:   text
 snippet line 3:     here
 
 diagnostic help: try doing it better next time?
-diagnostic error code: oops::my::bad
+diagnostic code: oops::my::bad
 "#
     .trim_start()
     .to_string();
@@ -434,7 +434,7 @@ snippet line 2:   text
 snippet line 3:     here
 
 diagnostic help: try doing it better next time?
-diagnostic error code: oops::my::bad
+diagnostic code: oops::my::bad
 "#
     .trim_start()
     .to_string();
@@ -469,7 +469,7 @@ Begin snippet starting at line 1, column 1: This is the part that broke
 snippet line 1: source_text_here
 
 diagnostic help: try doing it better next time?
-diagnostic error code: oops::my::bad
+diagnostic code: oops::my::bad
 "#
     .trim_start();
     assert_eq!(out, expected);
diff --git a/tests/printer.rs b/tests/printer.rs
index 9465789..b485c5c 100644
--- a/tests/printer.rs
+++ b/tests/printer.rs
@@ -1,23 +1,23 @@
 use miette::{
-    Diagnostic, DiagnosticReport, GraphicalReportPrinter, GraphicalTheme, MietteError, NamedSource,
-    NarratableReportPrinter, SourceSpan,
+    Diagnostic, GraphicalReportHandler, GraphicalTheme, MietteError, NamedSource,
+    NarratableReportHandler, Report, SourceSpan,
 };
 use thiserror::Error;
 
-fn fmt_report(diag: DiagnosticReport) -> String {
+fn fmt_report(diag: Report) -> String {
     let mut out = String::new();
     // Mostly for dev purposes.
     if std::env::var("STYLE").is_ok() {
-        GraphicalReportPrinter::new_themed(GraphicalTheme::unicode())
-            .render_report(&mut out, diag.inner())
+        GraphicalReportHandler::new_themed(GraphicalTheme::unicode())
+            .render_report(&mut out, diag.as_ref())
             .unwrap();
     } else if std::env::var("NARRATED").is_ok() {
-        NarratableReportPrinter
-            .render_report(&mut out, diag.inner())
+        NarratableReportHandler
+            .render_report(&mut out, diag.as_ref())
             .unwrap();
     } else {
-        GraphicalReportPrinter::new_themed(GraphicalTheme::unicode_nocolor())
-            .render_report(&mut out, diag.inner())
+        GraphicalReportHandler::new_themed(GraphicalTheme::unicode_nocolor())
+            .render_report(&mut out, diag.as_ref())
             .unwrap();
     };
     out
@@ -481,7 +481,7 @@ fn disable_url_links() -> Result<(), MietteError> {
     struct MyBad;
     let err = MyBad;
     let mut out = String::new();
-    GraphicalReportPrinter::new_themed(GraphicalTheme::unicode_nocolor())
+    GraphicalReportHandler::new_themed(GraphicalTheme::unicode_nocolor())
         .without_code_linking()
         .render_report(&mut out, &err)
         .unwrap();
diff --git a/tests/test_autotrait.rs b/tests/test_autotrait.rs
new file mode 100644
index 0000000..a4b62f9
--- /dev/null
+++ b/tests/test_autotrait.rs
@@ -0,0 +1,13 @@
+use miette::Report;
+
+#[test]
+fn test_send() {
+    fn assert_send<T: Send>() {}
+    assert_send::<Report>();
+}
+
+#[test]
+fn test_sync() {
+    fn assert_sync<T: Sync>() {}
+    assert_sync::<Report>();
+}
diff --git a/tests/test_boxed.rs b/tests/test_boxed.rs
new file mode 100644
index 0000000..af16db7
--- /dev/null
+++ b/tests/test_boxed.rs
@@ -0,0 +1,76 @@
+use miette::{miette, Diagnostic, Report};
+use std::error::Error as StdError;
+use std::io;
+use thiserror::Error;
+
+#[derive(Error, Debug)]
+#[error("outer")]
+struct MyError {
+    source: io::Error,
+}
+impl Diagnostic for MyError {}
+
+#[test]
+fn test_boxed_str_diagnostic() {
+    let error = Box::<dyn Diagnostic + Send + Sync>::from("oh no!");
+    let error: Report = miette!(error);
+    assert_eq!("oh no!", error.to_string());
+    assert_eq!(
+        "oh no!",
+        error
+            .downcast_ref::<Box<dyn Diagnostic + Send + Sync>>()
+            .unwrap()
+            .to_string()
+    );
+}
+
+#[test]
+fn test_boxed_str_stderr() {
+    let error = Box::<dyn StdError + Send + Sync>::from("oh no!");
+    let error: Report = miette!(error);
+    assert_eq!("oh no!", error.to_string());
+    assert_eq!(
+        "oh no!",
+        error
+            .downcast_ref::<Box<dyn StdError + Send + Sync>>()
+            .unwrap()
+            .to_string()
+    );
+}
+
+#[test]
+fn test_boxed_thiserror() {
+    let error = MyError {
+        source: io::Error::new(io::ErrorKind::Other, "oh no!"),
+    };
+    let error: Report = miette!(error);
+    assert_eq!("oh no!", error.source().unwrap().to_string());
+}
+
+#[test]
+fn test_boxed_miette() {
+    let error: Report = miette!("oh no!").wrap_err("it failed");
+    let error = miette!(error);
+    assert_eq!("oh no!", error.source().unwrap().to_string());
+}
+
+#[test]
+#[ignore = "I don't know why this isn't working but it needs fixing."]
+fn test_boxed_sources() {
+    let error = MyError {
+        source: io::Error::new(io::ErrorKind::Other, "oh no!"),
+    };
+    let error = Box::<dyn Diagnostic + Send + Sync>::from(error);
+    let error: Report = miette!(error).wrap_err("it failed");
+    assert_eq!("it failed", error.to_string());
+    assert_eq!("outer", error.source().unwrap().to_string());
+    assert_eq!(
+        "oh no!",
+        error
+            .source()
+            .expect("outer")
+            .source()
+            .expect("inner")
+            .to_string()
+    );
+}
diff --git a/tests/test_chain.rs b/tests/test_chain.rs
new file mode 100644
index 0000000..3fdd4cd
--- /dev/null
+++ b/tests/test_chain.rs
@@ -0,0 +1,45 @@
+use miette::{miette, Report};
+
+fn error() -> Report {
+    miette!(0).wrap_err(1).wrap_err(2).wrap_err(3)
+}
+
+#[test]
+fn test_iter() {
+    let e = error();
+    let mut chain = e.chain();
+    assert_eq!("3", chain.next().unwrap().to_string());
+    assert_eq!("2", chain.next().unwrap().to_string());
+    assert_eq!("1", chain.next().unwrap().to_string());
+    assert_eq!("0", chain.next().unwrap().to_string());
+    assert!(chain.next().is_none());
+    assert!(chain.next_back().is_none());
+}
+
+#[test]
+fn test_rev() {
+    let e = error();
+    let mut chain = e.chain().rev();
+    assert_eq!("0", chain.next().unwrap().to_string());
+    assert_eq!("1", chain.next().unwrap().to_string());
+    assert_eq!("2", chain.next().unwrap().to_string());
+    assert_eq!("3", chain.next().unwrap().to_string());
+    assert!(chain.next().is_none());
+    assert!(chain.next_back().is_none());
+}
+
+#[test]
+fn test_len() {
+    let e = error();
+    let mut chain = e.chain();
+    assert_eq!(4, chain.len());
+    assert_eq!("3", chain.next().unwrap().to_string());
+    assert_eq!(3, chain.len());
+    assert_eq!("0", chain.next_back().unwrap().to_string());
+    assert_eq!(2, chain.len());
+    assert_eq!("2", chain.next().unwrap().to_string());
+    assert_eq!(1, chain.len());
+    assert_eq!("1", chain.next_back().unwrap().to_string());
+    assert_eq!(0, chain.len());
+    assert!(chain.next().is_none());
+}
diff --git a/tests/test_context.rs b/tests/test_context.rs
new file mode 100644
index 0000000..5f78572
--- /dev/null
+++ b/tests/test_context.rs
@@ -0,0 +1,160 @@
+mod drop;
+
+use crate::drop::{DetectDrop, Flag};
+use miette::{Diagnostic, IntoDiagnostic, Report, Result, WrapErr};
+use std::fmt::{self, Display};
+use thiserror::Error;
+
+// https://github.com/dtolnay/miette/issues/18
+#[test]
+fn test_inference() -> Result<()> {
+    let x = "1";
+    let y: u32 = x.parse().into_diagnostic().context("...")?;
+    assert_eq!(y, 1);
+    Ok(())
+}
+
+macro_rules! context_type {
+    ($name:ident) => {
+        #[derive(Debug)]
+        struct $name {
+            message: &'static str,
+            drop: DetectDrop,
+        }
+
+        impl Display for $name {
+            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+                f.write_str(self.message)
+            }
+        }
+    };
+}
+
+context_type!(HighLevel);
+context_type!(MidLevel);
+
+#[derive(Diagnostic, Error, Debug)]
+#[error("{message}")]
+#[diagnostic()] // TODO
+struct LowLevel {
+    message: &'static str,
+    drop: DetectDrop,
+}
+
+struct Dropped {
+    low: Flag,
+    mid: Flag,
+    high: Flag,
+}
+
+impl Dropped {
+    fn none(&self) -> bool {
+        !self.low.get() && !self.mid.get() && !self.high.get()
+    }
+
+    fn all(&self) -> bool {
+        self.low.get() && self.mid.get() && self.high.get()
+    }
+}
+
+fn make_chain() -> (Report, Dropped) {
+    let dropped = Dropped {
+        low: Flag::new(),
+        mid: Flag::new(),
+        high: Flag::new(),
+    };
+
+    let low = LowLevel {
+        message: "no such file or directory",
+        drop: DetectDrop::new(&dropped.low),
+    };
+
+    // impl Report for Result<T, E>
+    let mid = Err::<(), LowLevel>(low)
+        .wrap_err(MidLevel {
+            message: "failed to load config",
+            drop: DetectDrop::new(&dropped.mid),
+        })
+        .unwrap_err();
+
+    // impl Report for Result<T, Error>
+    let high = Err::<(), Report>(mid)
+        .wrap_err(HighLevel {
+            message: "failed to start server",
+            drop: DetectDrop::new(&dropped.high),
+        })
+        .unwrap_err();
+
+    (high, dropped)
+}
+
+#[test]
+fn test_downcast_ref() {
+    let (err, dropped) = make_chain();
+
+    assert!(!err.is::<String>());
+    assert!(err.downcast_ref::<String>().is_none());
+
+    assert!(err.is::<HighLevel>());
+    let high = err.downcast_ref::<HighLevel>().unwrap();
+    assert_eq!(high.to_string(), "failed to start server");
+
+    assert!(err.is::<MidLevel>());
+    let mid = err.downcast_ref::<MidLevel>().unwrap();
+    assert_eq!(mid.to_string(), "failed to load config");
+
+    assert!(err.is::<LowLevel>());
+    let low = err.downcast_ref::<LowLevel>().unwrap();
+    assert_eq!(low.to_string(), "no such file or directory");
+
+    assert!(dropped.none());
+    drop(err);
+    assert!(dropped.all());
+}
+
+#[test]
+fn test_downcast_high() {
+    let (err, dropped) = make_chain();
+
+    let err = err.downcast::<HighLevel>().unwrap();
+    assert!(!dropped.high.get());
+    assert!(dropped.low.get() && dropped.mid.get());
+
+    drop(err);
+    assert!(dropped.all());
+}
+
+#[test]
+fn test_downcast_mid() {
+    let (err, dropped) = make_chain();
+
+    let err = err.downcast::<MidLevel>().unwrap();
+    assert!(!dropped.mid.get());
+    assert!(dropped.low.get() && dropped.high.get());
+
+    drop(err);
+    assert!(dropped.all());
+}
+
+#[test]
+fn test_downcast_low() {
+    let (err, dropped) = make_chain();
+
+    let err = err.downcast::<LowLevel>().unwrap();
+    assert!(!dropped.low.get());
+    assert!(dropped.mid.get() && dropped.high.get());
+
+    drop(err);
+    assert!(dropped.all());
+}
+
+#[test]
+fn test_unsuccessful_downcast() {
+    let (err, dropped) = make_chain();
+
+    let err = err.downcast::<String>().unwrap_err();
+    assert!(dropped.none());
+
+    drop(err);
+    assert!(dropped.all());
+}
diff --git a/tests/test_context_access.rs b/tests/test_context_access.rs
new file mode 100644
index 0000000..804fbc4
--- /dev/null
+++ b/tests/test_context_access.rs
@@ -0,0 +1,7 @@
+#[test]
+fn test_context() {
+    use miette::{miette, Report};
+
+    let error: Report = miette!("oh no!");
+    let _ = error.context();
+}
diff --git a/tests/test_convert.rs b/tests/test_convert.rs
new file mode 100644
index 0000000..d43190b
--- /dev/null
+++ b/tests/test_convert.rs
@@ -0,0 +1,23 @@
+mod drop;
+
+use self::drop::{DetectDrop, Flag};
+use miette::{Diagnostic, Report, Result};
+
+#[test]
+fn test_convert() {
+    let has_dropped = Flag::new();
+    let error: Report = Report::new(DetectDrop::new(&has_dropped));
+    let box_dyn = Box::<dyn Diagnostic + Send + Sync>::from(error);
+    assert_eq!("oh no!", box_dyn.to_string());
+    drop(box_dyn);
+    assert!(has_dropped.get());
+}
+
+#[test]
+fn test_question_mark() -> Result<(), Box<dyn Diagnostic>> {
+    fn f() -> Result<()> {
+        Ok(())
+    }
+    f()?;
+    Ok(())
+}
diff --git a/tests/test_downcast.rs b/tests/test_downcast.rs
new file mode 100644
index 0000000..2823177
--- /dev/null
+++ b/tests/test_downcast.rs
@@ -0,0 +1,107 @@
+mod common;
+mod drop;
+
+use self::common::*;
+use self::drop::{DetectDrop, Flag};
+use miette::{Diagnostic, Report};
+use std::error::Error as StdError;
+use std::fmt::{self, Display};
+use std::io;
+
+#[test]
+fn test_downcast() {
+    assert_eq!(
+        "oh no!",
+        bail_literal().unwrap_err().downcast::<&str>().unwrap(),
+    );
+    assert_eq!(
+        "oh no!",
+        bail_fmt().unwrap_err().downcast::<String>().unwrap(),
+    );
+    assert_eq!(
+        "oh no!",
+        bail_error()
+            .unwrap_err()
+            .downcast::<io::Error>()
+            .unwrap()
+            .to_string(),
+    );
+}
+
+#[test]
+fn test_downcast_ref() {
+    assert_eq!(
+        "oh no!",
+        *bail_literal().unwrap_err().downcast_ref::<&str>().unwrap(),
+    );
+    assert_eq!(
+        "oh no!",
+        bail_fmt().unwrap_err().downcast_ref::<String>().unwrap(),
+    );
+    assert_eq!(
+        "oh no!",
+        bail_error()
+            .unwrap_err()
+            .downcast_ref::<io::Error>()
+            .unwrap()
+            .to_string(),
+    );
+}
+
+#[test]
+fn test_downcast_mut() {
+    assert_eq!(
+        "oh no!",
+        *bail_literal().unwrap_err().downcast_mut::<&str>().unwrap(),
+    );
+    assert_eq!(
+        "oh no!",
+        bail_fmt().unwrap_err().downcast_mut::<String>().unwrap(),
+    );
+    assert_eq!(
+        "oh no!",
+        bail_error()
+            .unwrap_err()
+            .downcast_mut::<io::Error>()
+            .unwrap()
+            .to_string(),
+    );
+}
+
+#[test]
+fn test_drop() {
+    let has_dropped = Flag::new();
+    let error: Report = Report::new(DetectDrop::new(&has_dropped));
+    drop(error.downcast::<DetectDrop>().unwrap());
+    assert!(has_dropped.get());
+}
+
+#[test]
+fn test_large_alignment() {
+    #[repr(align(64))]
+    #[derive(Debug)]
+    struct LargeAlignedError(&'static str);
+
+    impl Display for LargeAlignedError {
+        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+            f.write_str(self.0)
+        }
+    }
+
+    impl StdError for LargeAlignedError {}
+    impl Diagnostic for LargeAlignedError {}
+
+    let error = Report::new(LargeAlignedError("oh no!"));
+    assert_eq!(
+        "oh no!",
+        error.downcast_ref::<LargeAlignedError>().unwrap().0
+    );
+}
+
+#[test]
+fn test_unsuccessful_downcast() {
+    let mut error = bail_error().unwrap_err();
+    assert!(error.downcast_ref::<&str>().is_none());
+    assert!(error.downcast_mut::<&str>().is_none());
+    assert!(error.downcast::<&str>().is_err());
+}
diff --git a/tests/test_fmt.rs b/tests/test_fmt.rs
new file mode 100644
index 0000000..8f18216
--- /dev/null
+++ b/tests/test_fmt.rs
@@ -0,0 +1,95 @@
+use miette::{bail, Result, WrapErr};
+use std::io;
+
+fn f() -> Result<()> {
+    bail!(io::Error::new(io::ErrorKind::PermissionDenied, "oh no!"));
+}
+
+fn g() -> Result<()> {
+    f().wrap_err("f failed")
+}
+
+fn h() -> Result<()> {
+    g().wrap_err("g failed")
+}
+
+const EXPECTED_ALTDISPLAY_F: &str = "oh no!";
+
+const EXPECTED_ALTDISPLAY_G: &str = "f failed: oh no!";
+
+const EXPECTED_ALTDISPLAY_H: &str = "g failed: f failed: oh no!";
+
+const EXPECTED_DEBUG_F: &str = "oh no!";
+
+const EXPECTED_DEBUG_G: &str = "\
+f failed
+
+Caused by:
+    oh no!\
+";
+
+const EXPECTED_DEBUG_H: &str = "\
+g failed
+
+Caused by:
+   0: f failed
+   1: oh no!\
+";
+
+const EXPECTED_ALTDEBUG_F: &str = "\
+Custom {
+    kind: PermissionDenied,
+    error: \"oh no!\",
+}\
+";
+
+const EXPECTED_ALTDEBUG_G: &str = "\
+Error {
+    msg: \"f failed\",
+    source: Custom {
+        kind: PermissionDenied,
+        error: \"oh no!\",
+    },
+}\
+";
+
+const EXPECTED_ALTDEBUG_H: &str = "\
+Error {
+    msg: \"g failed\",
+    source: Error {
+        msg: \"f failed\",
+        source: Custom {
+            kind: PermissionDenied,
+            error: \"oh no!\",
+        },
+    },
+}\
+";
+
+#[test]
+fn test_display() {
+    assert_eq!("g failed", h().unwrap_err().to_string());
+}
+
+#[test]
+fn test_altdisplay() {
+    assert_eq!(EXPECTED_ALTDISPLAY_F, format!("{:#}", f().unwrap_err()));
+    assert_eq!(EXPECTED_ALTDISPLAY_G, format!("{:#}", g().unwrap_err()));
+    assert_eq!(EXPECTED_ALTDISPLAY_H, format!("{:#}", h().unwrap_err()));
+}
+
+#[test]
+#[ignore = "not really gonna work with the current printers"]
+#[cfg_attr(track_caller, ignore)]
+fn test_debug() {
+    assert_eq!(EXPECTED_DEBUG_F, format!("{:?}", f().unwrap_err()));
+    assert_eq!(EXPECTED_DEBUG_G, format!("{:?}", g().unwrap_err()));
+    assert_eq!(EXPECTED_DEBUG_H, format!("{:?}", h().unwrap_err()));
+}
+
+#[test]
+fn test_altdebug() {
+    assert_eq!(EXPECTED_ALTDEBUG_F, format!("{:#?}", f().unwrap_err()));
+    assert_eq!(EXPECTED_ALTDEBUG_G, format!("{:#?}", g().unwrap_err()));
+    assert_eq!(EXPECTED_ALTDEBUG_H, format!("{:#?}", h().unwrap_err()));
+}
diff --git a/tests/test_location.rs b/tests/test_location.rs
new file mode 100644
index 0000000..07cd754
--- /dev/null
+++ b/tests/test_location.rs
@@ -0,0 +1,104 @@
+use std::panic::Location;
+
+use miette::{Diagnostic, IntoDiagnostic, WrapErr};
+
+struct LocationHandler {
+    actual: Option<&'static str>,
+    expected: &'static str,
+}
+
+impl LocationHandler {
+    fn new(expected: &'static str) -> Self {
+        LocationHandler {
+            actual: None,
+            expected,
+        }
+    }
+}
+
+impl miette::ReportHandler for LocationHandler {
+    fn debug(
+        &self,
+        _error: &(dyn Diagnostic + 'static),
+        _f: &mut std::fmt::Formatter<'_>,
+    ) -> std::fmt::Result {
+        // we assume that if the compiler is new enough to support
+        // `track_caller` that we will always have `actual` be `Some`, so we can
+        // safely skip the assertion if the location is `None` which should only
+        // happen in older rust versions.
+        if let Some(actual) = self.actual {
+            assert_eq!(self.expected, actual);
+        }
+
+        Ok(())
+    }
+
+    fn track_caller(&mut self, location: &'static Location<'static>) {
+        dbg!(location);
+        self.actual = Some(location.file());
+    }
+}
+
+#[test]
+fn test_wrap_err() {
+    let _ = miette::set_hook(Box::new(|_e| {
+        let expected_location = file!();
+        Box::new(LocationHandler::new(expected_location))
+    }));
+
+    let err = std::fs::read_to_string("totally_fake_path")
+        .into_diagnostic()
+        .wrap_err("oopsie")
+        .unwrap_err();
+
+    // should panic if the location isn't in our crate
+    println!("{:?}", err);
+}
+
+#[test]
+fn test_wrap_err_with() {
+    let _ = miette::set_hook(Box::new(|_e| {
+        let expected_location = file!();
+        Box::new(LocationHandler::new(expected_location))
+    }));
+
+    let err = std::fs::read_to_string("totally_fake_path")
+        .into_diagnostic()
+        .wrap_err_with(|| "oopsie")
+        .unwrap_err();
+
+    // should panic if the location isn't in our crate
+    println!("{:?}", err);
+}
+
+#[test]
+fn test_context() {
+    let _ = miette::set_hook(Box::new(|_e| {
+        let expected_location = file!();
+        Box::new(LocationHandler::new(expected_location))
+    }));
+
+    let err = std::fs::read_to_string("totally_fake_path")
+        .into_diagnostic()
+        .context("oopsie")
+        .unwrap_err();
+
+    // should panic if the location isn't in our crate
+    println!("{:?}", err);
+}
+
+#[test]
+fn test_with_context() {
+    let _ = miette::set_hook(Box::new(|_e| {
+        let expected_location = file!();
+        Box::new(LocationHandler::new(expected_location))
+    }));
+
+    let err = std::fs::read_to_string("totally_fake_path")
+        .into_diagnostic()
+        .with_context(|| "oopsie")
+        .unwrap_err();
+
+    // should panic if the location isn't in our crate
+    println!("{:?}", err);
+}
diff --git a/tests/test_macros.rs b/tests/test_macros.rs
new file mode 100644
index 0000000..22bf8cc
--- /dev/null
+++ b/tests/test_macros.rs
@@ -0,0 +1,34 @@
+#![allow(clippy::eq_op)]
+mod common;
+
+use self::common::*;
+use miette::{ensure, Result};
+
+#[test]
+fn test_messages() {
+    assert_eq!("oh no!", bail_literal().unwrap_err().to_string());
+    assert_eq!("oh no!", bail_fmt().unwrap_err().to_string());
+    assert_eq!("oh no!", bail_error().unwrap_err().to_string());
+}
+
+#[test]
+fn test_ensure() {
+    let f = || -> Result<()> {
+        ensure!(1 + 1 == 2, "This is correct");
+        Ok(())
+    };
+    assert!(f().is_ok());
+
+    let v = 1;
+    let f = || -> Result<()> {
+        ensure!(v + v == 2, "This is correct, v: {}", v);
+        Ok(())
+    };
+    assert!(f().is_ok());
+
+    let f = || -> Result<()> {
+        ensure!(v + v == 1, "This is not correct, v: {}", v);
+        Ok(())
+    };
+    assert!(f().is_err());
+}
diff --git a/tests/test_repr.rs b/tests/test_repr.rs
new file mode 100644
index 0000000..4978fe5
--- /dev/null
+++ b/tests/test_repr.rs
@@ -0,0 +1,32 @@
+mod drop;
+
+use self::drop::{DetectDrop, Flag};
+use miette::Report;
+use std::marker::Unpin;
+use std::mem;
+
+#[test]
+fn test_error_size() {
+    assert_eq!(mem::size_of::<Report>(), mem::size_of::<usize>());
+}
+
+#[test]
+fn test_null_pointer_optimization() {
+    assert_eq!(
+        mem::size_of::<Result<(), Report>>(),
+        mem::size_of::<usize>()
+    );
+}
+
+#[test]
+fn test_autotraits() {
+    fn assert<E: Unpin + Send + Sync + 'static>() {}
+    assert::<Report>();
+}
+
+#[test]
+fn test_drop() {
+    let has_dropped = Flag::new();
+    drop(Report::new(DetectDrop::new(&has_dropped)));
+    assert!(has_dropped.get());
+}
diff --git a/tests/test_source.rs b/tests/test_source.rs
new file mode 100644
index 0000000..9511bdb
--- /dev/null
+++ b/tests/test_source.rs
@@ -0,0 +1,63 @@
+use miette::{miette, Report};
+use std::error::Error as StdError;
+use std::fmt::{self, Display};
+use std::io;
+
+#[derive(Debug)]
+enum TestError {
+    Io(io::Error),
+}
+
+impl Display for TestError {
+    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            TestError::Io(e) => Display::fmt(e, formatter),
+        }
+    }
+}
+
+impl StdError for TestError {
+    fn source(&self) -> Option<&(dyn StdError + 'static)> {
+        match self {
+            TestError::Io(io) => Some(io),
+        }
+    }
+}
+
+#[test]
+fn test_literal_source() {
+    let error: Report = miette!("oh no!");
+    assert!(error.source().is_none());
+}
+
+#[test]
+fn test_variable_source() {
+    let msg = "oh no!";
+    let error = miette!(msg);
+    assert!(error.source().is_none());
+
+    let msg = msg.to_owned();
+    let error: Report = miette!(msg);
+    assert!(error.source().is_none());
+}
+
+#[test]
+fn test_fmt_source() {
+    let error: Report = miette!("{} {}!", "oh", "no");
+    assert!(error.source().is_none());
+}
+
+#[test]
+#[ignore = "Again with the io::Error source issue?"]
+fn test_io_source() {
+    let io = io::Error::new(io::ErrorKind::Other, "oh no!");
+    let error: Report = miette!(TestError::Io(io));
+    assert_eq!("oh no!", error.source().unwrap().to_string());
+}
+
+#[test]
+fn test_miette_from_miette() {
+    let error: Report = miette!("oh no!").wrap_err("context");
+    let error = miette!(error);
+    assert_eq!("oh no!", error.source().unwrap().to_string());
+}