Repository: zkat/miette
Pull request #266 (2023-05-14 11:17:18)

doc: document `#[diagnostic(transparent)]`

gavrilikhin-d: `cargo make readme` doesn't work:
```
$ cargo make readme
error: no such command: `make`

        Did you mean `add`?

        View all installed commands with `cargo --list`
```
zkat: You need to install cargo-make first

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e3542f2..0b842de 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,18 @@
 # `miette` Release Changelog
 
+<a name="5.9.0"></a>
+## 5.9.0 (2023-05-18)
+
+### Features
+
+* **serde:** Add `serde` support (#264) ([c25676cb](https://github.com/zkat/miette/commit/c25676cb1f4266c2607836e6359f15b9cbd8637e))
+* **const:** Constify various functions (#263) ([46adb3bc](https://github.com/zkat/miette/commit/46adb3bc6aa6518d82a4187b34c56e287922136f))
+* **nested:** Render inner diagnostics (#170) ([aefe3237](https://github.com/zkat/miette/commit/aefe323780bda4e60feb44bb96ee98634ad677ad))
+
+### Bug Fixes
+
+* **misc:** Correct some typos (#255) ([675f3411](https://github.com/zkat/miette/commit/675f3411e33d5fae86d4018c3b72f751a4c4bc2f))
+
 <a name="5.8.0"></a>
 ## 5.8.0 (2023-04-18)
 
diff --git a/Cargo.toml b/Cargo.toml
index 96b9c9f..160556f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "miette"
-version = "5.8.0"
+version = "5.9.0"
 authors = ["Kat Marchán <kzm@zkat.tech>"]
 description = "Fancy diagnostic reporting library and protocol for us mere mortals who aren't compiler hackers."
 categories = ["rust-patterns"]
@@ -14,7 +14,7 @@ exclude = ["images/", "tests/", "miette-derive/"]
 
 [dependencies]
 thiserror = "1.0.40"
-miette-derive = { path = "miette-derive", version = "=5.8.0" }
+miette-derive = { path = "miette-derive", version = "=5.9.0" }
 once_cell = "1.8.0"
 unicode-width = "0.1.9"
 
diff --git a/README.md b/README.md
index ff25b3a..a60ef11 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
 You run miette? You run her code like the software? Oh. Oh! Error code for
 coder! Error code for One Thousand Lines!
 
-## About
+### About
 
 `miette` is a diagnostic library for Rust. It includes a series of
 traits/protocols that allow you to hook into its error reporting facilities,
@@ -32,7 +32,7 @@ output like in the screenshots above.** You should only do this in your
 toplevel crate, as the fancy feature pulls in a number of dependencies that
 libraries and such might not want.
 
-## Table of Contents <!-- omit in toc -->
+### Table of Contents <!-- omit in toc -->
 
 - [About](#about)
 - [Features](#features)
@@ -51,7 +51,7 @@ libraries and such might not want.
 - [Acknowledgements](#acknowledgements)
 - [License](#license)
 
-## Features
+### Features
 
 - Generic [`Diagnostic`] protocol, compatible (and dependent on)
   [`std::error::Error`].
@@ -76,7 +76,7 @@ the following features:
 - Cause chain printing
 - Turns diagnostic codes into links in [supported terminals](https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda).
 
-## Installing
+### Installing
 
 ```sh
 $ cargo add miette
@@ -88,7 +88,7 @@ If you want to use the fancy printer in all these screenshots:
 $ cargo add miette --features fancy
 ```
 
-## Example
+### Example
 
 ```rust
 /*
@@ -96,7 +96,7 @@ You can derive a `Diagnostic` from any `std::error::Error` type.
 
 `thiserror` is a great way to define them, and plays nicely with `miette`!
 */
-use miette::{Diagnostic, SourceSpan};
+use miette::{Diagnostic, NamedSource, Result, SourceSpan};
 use thiserror::Error;
 
 #[derive(Error, Debug, Diagnostic)]
@@ -123,12 +123,10 @@ 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::{NamedSource, Result};
 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();
-    let len = src.len();
 
     Err(MyBad {
         src: NamedSource::new("bad_file.rs", src),
@@ -170,9 +168,9 @@ diagnostic help: Change int or string to be the right types and try again.
 diagnostic code: nu::parser::unsupported_operation
 For more details, see https://docs.rs/nu-parser/0.1.0/nu-parser/enum.ParseError.html#variant.UnsupportedOperation">
 
-## Using
+### Using
 
-### ... in libraries
+#### ... in libraries
 
 `miette` is _fully compatible_ with library usage. Consumers who don't know
 about, or don't want, `miette` features can safely use its error types as
@@ -187,7 +185,7 @@ the trait directly, just like with `std::error::Error`.
 
 ```rust
 // lib/error.rs
-use miette::Diagnostic;
+use miette::{Diagnostic, SourceSpan};
 use thiserror::Error;
 
 #[derive(Error, Diagnostic, Debug)]
@@ -199,6 +197,18 @@ pub enum MyLibError {
     #[error("Oops it blew up")]
     #[diagnostic(code(my_lib::bad_code))]
     BadThingHappened,
+
+    #[error(transparent)]
+    // Use `#[diagnostic(transparent)]` to wrap another [`Diagnostic`]. You won't see labels otherwise
+    #[diagnostic(transparent)]
+    AnotherError(#[from] AnotherError),
+}
+
+#[derive(Error, Diagnostic, Debug)]
+#[error("another error")]
+pub struct AnotherError {
+   #[label("here")]
+   pub at: SourceSpan
 }
 ```
 
@@ -206,7 +216,7 @@ 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 [`Report`] in a library.
 
-### ... in application code
+#### ... in application code
 
 Application code tends to work a little differently than libraries. You
 don't always need or care to define dedicated error wrappers for errors
@@ -248,8 +258,7 @@ pub fn some_tool() -> Result<Version> {
 }
 ```
 
-To construct your own simple adhoc error use the [`miette!`] macro:
-
+To construct your own simple adhoc error use the [miette!] macro:
 ```rust
 // my_app/lib/my_internal_file.rs
 use miette::{miette, IntoDiagnostic, Result, WrapErr};
@@ -262,8 +271,9 @@ pub fn some_tool() -> Result<Version> {
         .map_err(|_| miette!("Invalid version {}", version))?)
 }
 ```
+There are also similar [bail!] and [ensure!] macros.
 
-### ... in `main()`
+#### ... in `main()`
 
 `main()` is just like any other part of your application-internal code. Use
 `Result` as your return value, and it will pretty-print your diagnostics
@@ -293,7 +303,7 @@ enabled:
 miette = { version = "X.Y.Z", features = ["fancy"] }
 ```
 
-### ... diagnostic code URLs
+#### ... diagnostic code URLs
 
 `miette` supports providing a URL for individual diagnostics. This URL will
 be displayed as an actual link in supported terminals, like so:
@@ -346,7 +356,7 @@ use thiserror::Error;
 struct MyErr;
 ```
 
-### ... snippets
+#### ... snippets
 
 Along with its general error handling and reporting features, `miette` also
 includes facilities for adding error spans/annotations/labels to your
@@ -394,7 +404,7 @@ pub struct MyErrorType {
 }
 ```
 
-#### ... help text
+##### ... help text
 `miette` provides two facilities for supplying help text for your errors:
 
 The first is the `#[help()]` format attribute that applies to structs or
@@ -430,7 +440,7 @@ let err = Foo {
 };
 ```
 
-### ... multiple related errors
+#### ... multiple related errors
 
 `miette` supports collecting multiple errors into a single diagnostic, and
 printing them all together nicely.
@@ -450,7 +460,7 @@ struct MyError {
 }
 ```
 
-### ... delayed source code
+#### ... delayed source code
 
 Sometimes it makes sense to add source code to the error message later.
 One option is to use [`with_source_code()`](Report::with_source_code)
@@ -533,7 +543,7 @@ fn main() -> miette::Result<()> {
 }
 ```
 
-### ... Diagnostic-based error sources.
+#### ... Diagnostic-based error sources.
 
 When one uses the `#[source]` attribute on a field, that usually comes
 from `thiserror`, and implements a method for
@@ -566,7 +576,7 @@ struct MyError {
 struct OtherError;
 ```
 
-### ... handler options
+#### ... handler options
 
 [`MietteHandler`] is the default handler, and is very customizable. In
 most cases, you can simply use [`MietteHandlerOpts`] to tweak its behavior
@@ -585,12 +595,13 @@ miette::set_hook(Box::new(|_| {
             .build(),
     )
 }))
+
 ```
 
 See the docs for [`MietteHandlerOpts`] for more details on what you can
 customize!
 
-### ... dynamic diagnostics
+#### ... dynamic diagnostics
 
 If you...
 - ...don't know all the possible errors upfront
@@ -599,6 +610,7 @@ then you may want to use [`miette!`], [`diagnostic!`] macros or
 [`MietteDiagnostic`] directly to create diagnostic on the fly.
 
 ```rust
+
 let source = "2 + 2 * 2 = 8".to_string();
 let report = miette!(
   labels = vec[
@@ -610,26 +622,25 @@ let report = miette!(
 println!("{:?}", report)
 ```
 
-## Acknowledgements
+### Acknowledgements
 
 `miette` was not developed in a void. It owes enormous credit to various
 other projects and their authors:
 
-- [`anyhow`](http://crates.io/crates/anyhow) and
-  [`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 `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 `miette`'s derive macro.
+- [`anyhow`](http://crates.io/crates/anyhow) and [`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 `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
+  `miette`'s derive macro.
 - `rustc` and [@estebank](https://github.com/estebank) for their
   state-of-the-art work in compiler diagnostics.
 - [`ariadne`](https://crates.io/crates/ariadne) for pushing forward how
   _pretty_ these diagnostics can really look!
 
-## License
+### License
 
 `miette` is released to the Rust community under the [Apache license
 2.0](./LICENSE).
@@ -648,7 +659,7 @@ under the Apache License. Some code is taken from
 [`MietteHandler`]: https://docs.rs/miette/latest/miette/struct.MietteHandler.html
 [`MietteDiagnostic`]: https://docs.rs/miette/latest/miette/struct.MietteDiagnostic.html
 [`Report`]: https://docs.rs/miette/latest/miette/struct.Report.html
-[`ReportHandler`]: https://docs.rs/miette/latest/miette/struct.ReportHandler.html
+[`ReportHandler`]: https://docs.rs/miette/latest/miette/trait.ReportHandler.html
 [`Result`]: https://docs.rs/miette/latest/miette/type.Result.html
-[`SourceCode`]: https://docs.rs/miette/latest/miette/struct.SourceCode.html
+[`SourceCode`]: https://docs.rs/miette/latest/miette/trait.SourceCode.html
 [`SourceSpan`]: https://docs.rs/miette/latest/miette/struct.SourceSpan.html
diff --git a/README.tpl b/README.tpl
index d598eb8..f1126f1 100644
--- a/README.tpl
+++ b/README.tpl
@@ -12,7 +12,7 @@
 [`MietteHandler`]: https://docs.rs/miette/latest/miette/struct.MietteHandler.html
 [`MietteDiagnostic`]: https://docs.rs/miette/latest/miette/struct.MietteDiagnostic.html
 [`Report`]: https://docs.rs/miette/latest/miette/struct.Report.html
-[`ReportHandler`]: https://docs.rs/miette/latest/miette/struct.ReportHandler.html
+[`ReportHandler`]: https://docs.rs/miette/latest/miette/trait.ReportHandler.html
 [`Result`]: https://docs.rs/miette/latest/miette/type.Result.html
-[`SourceCode`]: https://docs.rs/miette/latest/miette/struct.SourceCode.html
+[`SourceCode`]: https://docs.rs/miette/latest/miette/trait.SourceCode.html
 [`SourceSpan`]: https://docs.rs/miette/latest/miette/struct.SourceSpan.html
diff --git a/miette-derive/Cargo.toml b/miette-derive/Cargo.toml
index ee79d66..461a328 100644
--- a/miette-derive/Cargo.toml
+++ b/miette-derive/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "miette-derive"
-version = "5.8.0"
+version = "5.9.0"
 authors = ["Kat Marchán <kzm@zkat.tech>"]
 edition = "2018"
 license = "Apache-2.0"
diff --git a/src/eyreish/macros.rs b/src/eyreish/macros.rs
index 938ac32..e13309f 100644
--- a/src/eyreish/macros.rs
+++ b/src/eyreish/macros.rs
@@ -222,6 +222,9 @@ macro_rules! ensure {
 /// ## `anyhow`/`eyre` Users
 ///
 /// You can just replace `use`s of the `anyhow!`/`eyre!` macros with `miette!`.
+///
+/// [`diagnostic!`]: crate::diagnostic!
+/// [`Report`]: crate::Report
 #[macro_export]
 macro_rules! miette {
     ($($key:ident = $value:expr,)* $fmt:literal $($arg:tt)*) => {
@@ -282,6 +285,8 @@ macro_rules! miette {
 )]
 /// assert_eq!(diag.message, "1 + 2 = 3");
 /// ```
+///
+/// [`MietteDiagnostic`]: crate::MietteDiagnostic
 #[macro_export]
 macro_rules! diagnostic {
     ($fmt:literal $($arg:tt)*) => {{
diff --git a/src/eyreish/ptr.rs b/src/eyreish/ptr.rs
index c8d63d2..fa954d1 100644
--- a/src/eyreish/ptr.rs
+++ b/src/eyreish/ptr.rs
@@ -43,7 +43,7 @@ where
         Box::from_raw(self.ptr.as_ptr())
     }
 
-    pub(crate) fn by_ref<'a>(&self) -> Ref<'a, T> {
+    pub(crate) const fn by_ref<'a>(&self) -> Ref<'a, T> {
         Ref {
             ptr: self.ptr,
             lifetime: PhantomData,
@@ -91,7 +91,7 @@ where
         }
     }
 
-    pub(crate) fn from_raw(ptr: NonNull<T>) -> Self {
+    pub(crate) const fn from_raw(ptr: NonNull<T>) -> Self {
         Ref {
             ptr,
             lifetime: PhantomData,
@@ -112,7 +112,7 @@ where
         }
     }
 
-    pub(crate) fn as_ptr(self) -> *const T {
+    pub(crate) const fn as_ptr(self) -> *const T {
         self.ptr.as_ptr() as *const T
     }
 
@@ -154,7 +154,7 @@ where
         }
     }
 
-    pub(crate) fn by_ref(self) -> Ref<'a, T> {
+    pub(crate) const fn by_ref(self) -> Ref<'a, T> {
         Ref {
             ptr: self.ptr,
             lifetime: PhantomData,
diff --git a/src/handlers/debug.rs b/src/handlers/debug.rs
index a9460d1..50450a4 100644
--- a/src/handlers/debug.rs
+++ b/src/handlers/debug.rs
@@ -13,7 +13,7 @@ pub struct DebugReportHandler;
 impl DebugReportHandler {
     /// Create a new [`NarratableReportHandler`](crate::NarratableReportHandler)
     /// There are no customization options.
-    pub fn new() -> Self {
+    pub const fn new() -> Self {
         Self
     }
 }
diff --git a/src/handlers/graphical.rs b/src/handlers/graphical.rs
index 1ab09fa..b5dd754 100644
--- a/src/handlers/graphical.rs
+++ b/src/handlers/graphical.rs
@@ -3,7 +3,7 @@ use std::fmt::{self, Write};
 use owo_colors::{OwoColorize, Style};
 use unicode_width::UnicodeWidthChar;
 
-use crate::diagnostic_chain::DiagnosticChain;
+use crate::diagnostic_chain::{DiagnosticChain, ErrorKind};
 use crate::handlers::theme::*;
 use crate::protocol::{Diagnostic, Severity};
 use crate::{LabeledSpan, MietteError, ReportHandler, SourceCode, SourceSpan, SpanContents};
@@ -151,7 +151,6 @@ impl GraphicalReportHandler {
         diagnostic: &(dyn Diagnostic),
     ) -> fmt::Result {
         self.render_header(f, diagnostic)?;
-        writeln!(f)?;
         self.render_causes(f, diagnostic)?;
         let src = diagnostic.source_code();
         self.render_snippets(f, diagnostic, src)?;
@@ -190,6 +189,7 @@ impl GraphicalReportHandler {
             );
             write!(header, "{}", link)?;
             writeln!(f, "{}", header)?;
+            writeln!(f)?;
         } else if let Some(code) = diagnostic.code() {
             write!(header, "{}", code.style(severity_style),)?;
             if self.links == LinkStyle::Text && diagnostic.url().is_some() {
@@ -197,6 +197,7 @@ impl GraphicalReportHandler {
                 write!(header, " ({})", url.style(self.theme.styles.link))?;
             }
             writeln!(f, "{}", header)?;
+            writeln!(f)?;
         }
         Ok(())
     }
@@ -253,7 +254,22 @@ impl GraphicalReportHandler {
                 let opts = textwrap::Options::new(width)
                     .initial_indent(&initial_indent)
                     .subsequent_indent(&rest_indent);
-                writeln!(f, "{}", textwrap::fill(&error.to_string(), opts))?;
+                match error {
+                    ErrorKind::Diagnostic(diag) => {
+                        let mut inner = String::new();
+
+                        // Don't print footer for inner errors
+                        let mut inner_renderer = self.clone();
+                        inner_renderer.footer = None;
+                        inner_renderer.with_cause_chain = false;
+                        inner_renderer.render_report(&mut inner, diag)?;
+
+                        writeln!(f, "{}", textwrap::fill(&inner, opts))?;
+                    }
+                    ErrorKind::StdError(err) => {
+                        writeln!(f, "{}", textwrap::fill(&err.to_string(), opts))?;
+                    }
+                }
             }
         }
 
@@ -287,7 +303,6 @@ impl GraphicalReportHandler {
                     Some(Severity::Advice) => write!(f, "Advice: ")?,
                 };
                 self.render_header(f, rel)?;
-                writeln!(f)?;
                 self.render_causes(f, rel)?;
                 let src = rel.source_code().or(parent_src);
                 self.render_snippets(f, rel, src)?;
diff --git a/src/handlers/json.rs b/src/handlers/json.rs
index ec214ca..29e21a0 100644
--- a/src/handlers/json.rs
+++ b/src/handlers/json.rs
@@ -13,7 +13,7 @@ pub struct JSONReportHandler;
 impl JSONReportHandler {
     /// Create a new [`JSONReportHandler`]. There are no customization
     /// options.
-    pub fn new() -> Self {
+    pub const fn new() -> Self {
         Self
     }
 }
@@ -49,7 +49,7 @@ impl fmt::Display for Escape<'_> {
     }
 }
 
-fn escape(input: &'_ str) -> Escape<'_> {
+const fn escape(input: &'_ str) -> Escape<'_> {
     Escape(input)
 }
 
diff --git a/src/handlers/narratable.rs b/src/handlers/narratable.rs
index 5af4b97..c809124 100644
--- a/src/handlers/narratable.rs
+++ b/src/handlers/narratable.rs
@@ -21,7 +21,7 @@ pub struct NarratableReportHandler {
 impl NarratableReportHandler {
     /// Create a new [`NarratableReportHandler`]. There are no customization
     /// options.
-    pub fn new() -> Self {
+    pub const fn new() -> Self {
         Self {
             footer: None,
             context_lines: 1,
@@ -31,13 +31,13 @@ impl NarratableReportHandler {
 
     /// Include the cause chain of the top-level error in the report, if
     /// available.
-    pub fn with_cause_chain(mut self) -> Self {
+    pub const fn with_cause_chain(mut self) -> Self {
         self.with_cause_chain = true;
         self
     }
 
     /// Do not include the cause chain of the top-level error in the report.
-    pub fn without_cause_chain(mut self) -> Self {
+    pub const fn without_cause_chain(mut self) -> Self {
         self.with_cause_chain = false;
         self
     }
@@ -49,7 +49,7 @@ impl NarratableReportHandler {
     }
 
     /// Sets the number of lines of context to show around each error.
-    pub fn with_context_lines(mut self, lines: usize) -> Self {
+    pub const fn with_context_lines(mut self, lines: usize) -> Self {
         self.context_lines = lines;
         self
     }
diff --git a/src/lib.rs b/src/lib.rs
index cc113ce..20589ef 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -186,7 +186,7 @@
 //!
 //! ```rust
 //! // lib/error.rs
-//! use miette::Diagnostic;
+//! use miette::{Diagnostic, SourceSpan};
 //! use thiserror::Error;
 //!
 //! #[derive(Error, Diagnostic, Debug)]
@@ -198,6 +198,18 @@
 //!     #[error("Oops it blew up")]
 //!     #[diagnostic(code(my_lib::bad_code))]
 //!     BadThingHappened,
+//!
+//!     #[error(transparent)]
+//!     // Use `#[diagnostic(transparent)]` to wrap another [`Diagnostic`]. You won't see labels otherwise
+//!     #[diagnostic(transparent)]
+//!     AnotherError(#[from] AnotherError),
+//! }
+//!
+//! #[derive(Error, Diagnostic, Debug)]
+//! #[error("another error")]
+//! pub struct AnotherError {
+//!    #[label("here")]
+//!    pub at: SourceSpan
 //! }
 //! ```
 //!
diff --git a/src/protocol.rs b/src/protocol.rs
index 611b827..f516984 100644
--- a/src/protocol.rs
+++ b/src/protocol.rs
@@ -69,14 +69,26 @@ pub trait Diagnostic: std::error::Error {
     }
 }
 
-impl std::error::Error for Box<dyn Diagnostic> {
-    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
-        (**self).source()
+macro_rules! box_impls {
+    ($($box_type:ty),*) => {
+        $(
+            impl std::error::Error for $box_type {
+                fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+                    (**self).source()
+                }
+
+                fn cause(&self) -> Option<&dyn std::error::Error> {
+                    self.source()
+                }
+            }
+        )*
     }
+}
 
-    fn cause(&self) -> Option<&dyn std::error::Error> {
-        self.source()
-    }
+box_impls! {
+    Box<dyn Diagnostic>,
+    Box<dyn Diagnostic + Send>,
+    Box<dyn Diagnostic + Send + Sync>
 }
 
 impl<T: Diagnostic + Send + Sync + 'static> From<T>
@@ -241,10 +253,10 @@ pub struct LabeledSpan {
 
 impl LabeledSpan {
     /// Makes a new labeled span.
-    pub fn new(label: Option<String>, offset: ByteOffset, len: usize) -> Self {
+    pub const fn new(label: Option<String>, offset: ByteOffset, len: usize) -> Self {
         Self {
             label,
-            span: (offset, len).into(),
+            span: SourceSpan::new(SourceOffset(offset), SourceOffset(len)),
         }
     }
 
@@ -310,22 +322,22 @@ impl LabeledSpan {
     }
 
     /// Returns a reference to the inner [`SourceSpan`].
-    pub fn inner(&self) -> &SourceSpan {
+    pub const fn inner(&self) -> &SourceSpan {
         &self.span
     }
 
     /// Returns the 0-based starting byte offset.
-    pub fn offset(&self) -> usize {
+    pub const fn offset(&self) -> usize {
         self.span.offset()
     }
 
     /// Returns the number of bytes this `LabeledSpan` spans.
-    pub fn len(&self) -> usize {
+    pub const fn len(&self) -> usize {
         self.span.len()
     }
 
     /// True if this `LabeledSpan` is empty.
-    pub fn is_empty(&self) -> bool {
+    pub const fn is_empty(&self) -> bool {
         self.span.is_empty()
     }
 }
@@ -422,7 +434,7 @@ pub struct MietteSpanContents<'a> {
 
 impl<'a> MietteSpanContents<'a> {
     /// Make a new [`MietteSpanContents`] object.
-    pub fn new(
+    pub const fn new(
         data: &'a [u8],
         span: SourceSpan,
         line: usize,
@@ -440,7 +452,7 @@ impl<'a> MietteSpanContents<'a> {
     }
 
     /// Make a new [`MietteSpanContents`] object, with a name for its 'file'.
-    pub fn new_named(
+    pub const fn new_named(
         name: String,
         data: &'a [u8],
         span: SourceSpan,
@@ -492,7 +504,7 @@ pub struct SourceSpan {
 
 impl SourceSpan {
     /// Create a new [`SourceSpan`].
-    pub fn new(start: SourceOffset, length: SourceOffset) -> Self {
+    pub const fn new(start: SourceOffset, length: SourceOffset) -> Self {
         Self {
             offset: start,
             length: length.offset(),
@@ -500,18 +512,18 @@ impl SourceSpan {
     }
 
     /// The absolute offset, in bytes, from the beginning of a [`SourceCode`].
-    pub fn offset(&self) -> usize {
+    pub const fn offset(&self) -> usize {
         self.offset.offset()
     }
 
     /// Total length of the [`SourceSpan`], in bytes.
-    pub fn len(&self) -> usize {
+    pub const fn len(&self) -> usize {
         self.length
     }
 
     /// Whether this [`SourceSpan`] has a length of zero. It may still be useful
     /// to point to a specific point.
-    pub fn is_empty(&self) -> bool {
+    pub const fn is_empty(&self) -> bool {
         self.length == 0
     }
 }
@@ -589,7 +601,7 @@ pub struct SourceOffset(ByteOffset);
 
 impl SourceOffset {
     /// Actual byte offset.
-    pub fn offset(&self) -> ByteOffset {
+    pub const fn offset(&self) -> ByteOffset {
         self.0
     }
 


Test patch:
diff --git a/tests/graphical.rs b/tests/graphical.rs
index f929884..0c69470 100644
--- a/tests/graphical.rs
+++ b/tests/graphical.rs
@@ -85,8 +85,7 @@ fn single_line_highlight_span_full_line() {
     let out = fmt_report(err.into());
     println!("Error: {}", out);
 
-    let expected = r#"
-  × oops!
+    let expected = r#"  × oops!
    ╭─[issue:1:1]
  1 │ source
  2 │ text
@@ -1201,8 +1200,7 @@ fn zero_length_eol_span() {
     let out = fmt_report(err.into());
     println!("Error: {}", out);
 
-    let expected = r#"
-  × oops!
+    let expected = r#"  × oops!
    ╭─[issue:1:1]
  1 │ this is the first line
  2 │ this is the second line
diff --git a/tests/test_derive_source_chain.rs b/tests/test_derive_source_chain.rs
new file mode 100644
index 0000000..fb7ddb2
--- /dev/null
+++ b/tests/test_derive_source_chain.rs
@@ -0,0 +1,69 @@
+use std::sync::Arc;
+
+use miette::{miette, Diagnostic};
+use thiserror::Error;
+
+#[test]
+fn test_source() {
+    #[derive(Debug, Diagnostic, Error)]
+    #[error("Bar")]
+    struct Bar;
+
+    #[derive(Debug, Diagnostic, Error)]
+    #[error("Foo")]
+    struct Foo {
+        #[source]
+        bar: Bar,
+    }
+
+    let e = miette!(Foo { bar: Bar });
+    let mut chain = e.chain();
+
+    assert_eq!("Foo", chain.next().unwrap().to_string());
+    assert_eq!("Bar", chain.next().unwrap().to_string());
+    assert!(chain.next().is_none());
+}
+
+#[test]
+fn test_source_boxed() {
+    #[derive(Debug, Diagnostic, Error)]
+    #[error("Bar")]
+    struct Bar;
+
+    #[derive(Debug, Diagnostic, Error)]
+    #[error("Foo")]
+    struct Foo {
+        #[source]
+        bar: Box<dyn Diagnostic + Send + Sync>,
+    }
+
+    let error = miette!(Foo { bar: Box::new(Bar) });
+
+    let mut chain = error.chain();
+
+    assert_eq!("Foo", chain.next().unwrap().to_string());
+    assert_eq!("Bar", chain.next().unwrap().to_string());
+    assert!(chain.next().is_none());
+}
+
+#[test]
+fn test_source_arc() {
+    #[derive(Debug, Diagnostic, Error)]
+    #[error("Bar")]
+    struct Bar;
+
+    #[derive(Debug, Diagnostic, Error)]
+    #[error("Foo")]
+    struct Foo {
+        #[source]
+        bar: Arc<dyn Diagnostic + Send + Sync>,
+    }
+
+    let error = miette!(Foo { bar: Arc::new(Bar) });
+
+    let mut chain = error.chain();
+
+    assert_eq!("Foo", chain.next().unwrap().to_string());
+    assert_eq!("Bar", chain.next().unwrap().to_string());
+    assert!(chain.next().is_none());
+}
diff --git a/tests/test_diagnostic_source_macro.rs b/tests/test_diagnostic_source_macro.rs
index 8af2e88..536aedf 100644
--- a/tests/test_diagnostic_source_macro.rs
+++ b/tests/test_diagnostic_source_macro.rs
@@ -1,5 +1,16 @@
 use miette::Diagnostic;
 
+#[derive(Debug, miette::Diagnostic, thiserror::Error)]
+#[error("A complex error happened")]
+struct SourceError {
+    #[source_code]
+    code: String,
+    #[help]
+    help: String,
+    #[label("here")]
+    label: (usize, usize),
+}
+
 #[derive(Debug, miette::Diagnostic, thiserror::Error)]
 #[error("AnErr")]
 struct AnErr;
@@ -8,7 +19,7 @@ struct AnErr;
 #[error("TestError")]
 struct TestStructError {
     #[diagnostic_source]
-    asdf_inner_foo: AnErr,
+    asdf_inner_foo: SourceError,
 }
 
 #[derive(Debug, miette::Diagnostic, thiserror::Error)]
@@ -37,7 +48,11 @@ struct TestArcedError(#[diagnostic_source] std::sync::Arc<dyn Diagnostic>);
 #[test]
 fn test_diagnostic_source() {
     let error = TestStructError {
-        asdf_inner_foo: AnErr,
+        asdf_inner_foo: SourceError {
+            code: String::new(),
+            help: String::new(),
+            label: (0, 0),
+        },
     };
     assert!(error.diagnostic_source().is_some());
 
@@ -59,3 +74,120 @@ fn test_diagnostic_source() {
     let error = TestArcedError(std::sync::Arc::new(AnErr));
     assert!(error.diagnostic_source().is_some());
 }
+
+#[test]
+fn test_diagnostic_source_pass_extra_info() {
+    let diag = TestBoxedError(Box::new(SourceError {
+        code: String::from("Hello\nWorld!"),
+        help: format!("Have you tried turning it on and off again?"),
+        label: (1, 4),
+    }));
+    let mut out = String::new();
+    miette::GraphicalReportHandler::new_themed(miette::GraphicalTheme::unicode_nocolor())
+        .with_width(80)
+        .with_footer("this is a footer".into())
+        .render_report(&mut out, &diag)
+        .unwrap();
+    println!("Error: {}", out);
+    let expected = r#"  × TestError
+  ╰─▶   × A complex error happened
+         ╭─[1:1]
+       1 │ Hello
+         ·  ──┬─
+         ·    ╰── here
+       2 │ World!
+         ╰────
+        help: Have you tried turning it on and off again?
+      
+
+  this is a footer
+"#
+    .to_string();
+    assert_eq!(expected, out);
+}
+
+#[test]
+fn test_diagnostic_source_is_output() {
+    let diag = TestStructError {
+        asdf_inner_foo: SourceError {
+            code: String::from("right here"),
+            help: String::from("That's where the error is!"),
+            label: (6, 4),
+        },
+    };
+    let mut out = String::new();
+    miette::GraphicalReportHandler::new_themed(miette::GraphicalTheme::unicode_nocolor())
+        .with_width(80)
+        .render_report(&mut out, &diag)
+        .unwrap();
+    println!("{}", out);
+
+    let expected = r#"  × TestError
+  ╰─▶   × A complex error happened
+         ╭────
+       1 │ right here
+         ·       ──┬─
+         ·         ╰── here
+         ╰────
+        help: That's where the error is!
+      
+"#;
+
+    assert_eq!(expected, out);
+}
+
+#[derive(Debug, miette::Diagnostic, thiserror::Error)]
+#[error("A nested error happened")]
+struct NestedError {
+    #[source_code]
+    code: String,
+    #[label("here")]
+    label: (usize, usize),
+    #[diagnostic_source]
+    the_other_err: Box<dyn Diagnostic>,
+}
+
+#[test]
+fn test_nested_diagnostic_source_is_output() {
+    let inner_error = TestStructError {
+        asdf_inner_foo: SourceError {
+            code: String::from("This is another error"),
+            help: String::from("You should fix this"),
+            label: (3, 4),
+        },
+    };
+    let diag = NestedError {
+        code: String::from("right here"),
+        label: (6, 4),
+        the_other_err: Box::new(inner_error),
+    };
+    let mut out = String::new();
+    miette::GraphicalReportHandler::new_themed(miette::GraphicalTheme::unicode_nocolor())
+        .with_width(80)
+        .with_footer("Yooo, a footer".to_string())
+        .render_report(&mut out, &diag)
+        .unwrap();
+    println!("{}", out);
+
+    let expected = r#"  × A nested error happened
+  ├─▶   × TestError
+  │   
+  ╰─▶   × A complex error happened
+         ╭────
+       1 │ This is another error
+         ·    ──┬─
+         ·      ╰── here
+         ╰────
+        help: You should fix this
+      
+   ╭────
+ 1 │ right here
+   ·       ──┬─
+   ·         ╰── here
+   ╰────
+
+  Yooo, a footer
+"#;
+
+    assert_eq!(expected, out);
+}