Skip to content

Commit

Permalink
feat(reporter): Overhauled return type/main/DiagnosticReport experience.
Browse files Browse the repository at this point in the history
Fixes: #13
  • Loading branch information
zkat committed Aug 18, 2021
1 parent 4d45884 commit 29c1403
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 62 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ edition = "2018"
indenter = "0.3.3"
thiserror = "1.0.26"
miette-derive = { version = "=0.10.0", path = "miette-derive" }
once_cell = "1.8.0"

[dev-dependencies]
thiserror = "1.0.26"
Expand Down
65 changes: 34 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
# miette

you run miette? You run her code like the software? Oh. Oh! Error code for
coder! Error code for One Thousand Lines!

Expand Down Expand Up @@ -28,8 +26,13 @@ adds various facilities like [Severity], error codes that could be looked up
by users, and snippet display with support for multiline reports, arbitrary
[Source]s, and pretty printing.

`miette` also includes a (lightweight) `anyhow`/`eyre`-style
[DiagnosticReport] type which can be returned from application-internal
functions to make the `?` experience nicer. It's extra easy to use when using
[DiagnosticResult]!

While the `miette` crate bundles some baseline implementations for [Source]
and [DiagnosticReporter], it's intended to define a protocol that other crates
and [DiagnosticReportPrinter], it's intended to define a protocol that other crates
can build on top of to provide rich error reporting, and encourage an
ecosystem that leans on this extra metadata to provide it for others in a way
that's compatible with [std::error::Error]
Expand All @@ -48,19 +51,20 @@ $ cargo add miette
/*
You can derive a Diagnostic from any `std::error::Error` type.
`thiserror` is a great way to define them so, and plays extremely nicely with `miette`!
`thiserror` is a great way to define them, and plays nicely with `miette`!
*/
use miette::Diagnostic;
use miette::{Diagnostic, SourceSpan};
use thiserror::Error;

#[derive(Error, Diagnostic)]
#[derive(Error, Debug, Diagnostic)]
#[error("oops it broke!")]
#[diagnostic(
code(oops::my::bad),
severity(Warning),
help("try doing it better next time?"),
)]
struct MyBad {
// The Source that we're gonna be printing snippets out of.
src: String,
// Snippets and highlights can be included in the diagnostic!
#[snippet(src, "This is the part that broke")]
Expand All @@ -70,40 +74,39 @@ struct MyBad {
}

/*
Then, we implement `std::fmt::Debug` using the included `MietteReporter`,
which is able to pretty print diagnostics reasonably well.
Now let's define a function!
You can use any reporter you want here, or no reporter at all,
but `Debug` is required by `std::error::Error`, so you need to at
least derive it.
Make sure you pull in the `miette::DiagnosticReporter` trait!.
Use this DiagnosticResult type (or its expanded version) as the return type
throughout your app (but NOT your libraries! Those should always return concrete
types!).
*/
use std::fmt;
use miette::DiagnosticResult as 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();

use miette::{DiagnosticReporter, MietteReporter};
Err(MyBad {
src,
snip: ("bad_file.rs", 0, len).into(),
bad_bit: ("this bit here", 9, 4).into(),
})?;

impl fmt::Debug for MyBad {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
MietteReporter.debug(self, f)
}
Ok(())
}

/*
Now we can use `miette`~
*/
use miette::{MietteError, SourceSpan};
Now to get everything printed nicely, just return a Result<(), DiagnosticReport>
and you're all set!
fn pretend_this_is_main() -> Result<(), MyBad> {
// You can use plain strings as a `Source`, bu the protocol is fully extensible!
let src = "source\n text\n here".to_string();
let len = src.len();
Note: You can swap out the default reporter for a custom one using `miette::set_reporter()`
*/
fn pretend_this_is_main() -> Result<()> {
// kaboom~
this_fails()?;

Err(MyBad {
src,
snip: ("bad_file.rs", 0, len).into(),
bad_bit: ("this bit here", 9, 3).into(),
})
Ok(())
}
```

Expand Down
2 changes: 2 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ pub enum MietteError {
IoError(#[from] io::Error),
#[error("The given offset is outside the bounds of its Source")]
OutOfBounds,
#[error("Failed to install reporter hook")]
ReporterInstallFailed,
}
10 changes: 5 additions & 5 deletions src/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use std::{fmt::Display, fs, panic::Location};
use crate::MietteError;

/**
Adds rich metadata to your Error that can be used by [DiagnosticReporter] to print
Adds rich metadata to your Error that can be used by [DiagnosticReportPrinter] to print
really nice and human-friendly error messages.
*/
pub trait Diagnostic: std::error::Error {
Expand All @@ -20,7 +20,7 @@ pub trait Diagnostic: std::error::Error {
/// `E0123` or Enums will work just fine.
fn code<'a>(&'a self) -> Box<dyn Display + 'a>;

/// Diagnostic severity. This may be used by [DiagnosticReporter]s to change the
/// Diagnostic severity. This may be used by [DiagnosticReportPrinter]s to change the
/// display format of this diagnostic.
///
/// If `None`, reporters should treat this as [Severity::Error]
Expand Down Expand Up @@ -68,7 +68,7 @@ Protocol for [Diagnostic] handlers, which are responsible for actually printing
Blatantly based on [EyreHandler](https://docs.rs/eyre/0.6.5/eyre/trait.EyreHandler.html) (thanks, Jane!)
*/
pub trait DiagnosticReporter: core::any::Any + Send + Sync {
pub trait DiagnosticReportPrinter: core::any::Any + Send + Sync {
/// Define the report format.
fn debug(
&self,
Expand All @@ -88,7 +88,7 @@ pub trait DiagnosticReporter: core::any::Any + Send + Sync {
}

/**
[Diagnostic] severity. Intended to be used by [DiagnosticReporter] to change the
[Diagnostic] severity. Intended to be used by [DiagnosticReportPrinter]s to change the
way different Diagnostics are displayed.
*/
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
Expand Down Expand Up @@ -316,7 +316,7 @@ impl SourceOffset {

/// Returns an offset for the _file_ location of wherever this function is
/// called. If you want to get _that_ caller's location, mark this
/// function's caller with #[track_caller] (and so on and so forth).
/// function's caller with `#[track_caller]` (and so on and so forth).
///
/// Returns both the filename that was given and the offset of the caller
/// as a SourceOffset
Expand Down
69 changes: 61 additions & 8 deletions src/reporter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,71 @@ but largely meant to be an example.
use std::fmt;

use indenter::indented;
use once_cell::sync::OnceCell;

use crate::chain::Chain;
use crate::protocol::{Diagnostic, DiagnosticReporter, DiagnosticSnippet, Severity};
use crate::protocol::{Diagnostic, DiagnosticReportPrinter, DiagnosticSnippet, Severity};
use crate::MietteError;

static REPORTER: OnceCell<Box<dyn DiagnosticReportPrinter + Send + Sync + 'static>> =
OnceCell::new();

/// Set the global [DiagnosticReportPrinter] that will be used when you report
/// using [DiagnosticReport].
pub fn set_reporter(
reporter: impl DiagnosticReportPrinter + Send + Sync + 'static,
) -> Result<(), MietteError> {
REPORTER
.set(Box::new(reporter))
.map_err(|_| MietteError::ReporterInstallFailed)
}

/// Used by [DiagnosticReport] to fetch the reporter that will be used to
/// print stuff out.
pub fn get_reporter() -> &'static (dyn DiagnosticReportPrinter + Send + Sync + 'static) {
&**REPORTER.get_or_init(|| Box::new(DefaultReportPrinter))
}

/// Convenience alias. This is intended to be used as the return type for `main()`
pub type DiagnosticResult<T> = Result<T, DiagnosticReport>;

/// 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 DiagnosticReport {
/// Return a reference to the inner [Diagnostic].
pub fn inner(&self) -> &(dyn Diagnostic + Send + Sync + 'static) {
&*self.diagnostic
}
}

impl std::fmt::Debug for DiagnosticReport {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
get_reporter().debug(&*self.diagnostic, f)
}
}

impl<T: Diagnostic + Send + Sync + 'static> From<T> for DiagnosticReport {
fn from(diagnostic: T) -> Self {
DiagnosticReport {
diagnostic: Box::new(diagnostic),
}
}
}

/**
Reference implementation of the [DiagnosticReporter] trait. This is generally
good enough for simple use-cases, but you might want to implement your own if
you want custom reporting for your tool or app.
Reference implementation of the [DiagnosticReportPrinter] trait. This is generally
good enough for simple use-cases, and is the default one installed with `miette`,
but you might want to implement your own if you want custom reporting for your
tool or app.
*/
pub struct MietteReporter;
pub struct DefaultReportPrinter;

impl MietteReporter {
impl DefaultReportPrinter {
fn render_snippet(
&self,
f: &mut fmt::Formatter<'_>,
Expand Down Expand Up @@ -102,7 +155,7 @@ impl MietteReporter {
}
}

impl DiagnosticReporter for MietteReporter {
impl DiagnosticReportPrinter for DefaultReportPrinter {
fn debug(&self, diagnostic: &(dyn Diagnostic), f: &mut fmt::Formatter<'_>) -> fmt::Result {
use fmt::Write as _;

Expand Down Expand Up @@ -155,7 +208,7 @@ impl DiagnosticReporter for MietteReporter {
/// Literally what it says on the tin.
pub struct JokeReporter;

impl DiagnosticReporter for JokeReporter {
impl DiagnosticReportPrinter for JokeReporter {
fn debug(&self, diagnostic: &(dyn Diagnostic), f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
return fmt::Debug::fmt(diagnostic, f);
Expand Down
14 changes: 9 additions & 5 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,15 @@ use crate::Diagnostic;
#[error("{}", self.error)]
pub struct DiagnosticError {
#[source]
pub error: Box<dyn std::error::Error + Send + Sync + 'static>,
pub code: String,
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 {
Expand All @@ -20,9 +27,6 @@ impl Diagnostic for DiagnosticError {
}
}

/// Utility Result type for functions that return boxed [Diagnostic]s.
pub type DiagnosticResult<T> = Result<T, Box<dyn Diagnostic + Send + Sync + 'static>>;

pub trait IntoDiagnostic<T, E> {
/// Converts [Result]-like types that return regular errors into a
/// `Result` that returns a [Diagnostic].
Expand Down
17 changes: 4 additions & 13 deletions tests/reporter.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
use std::fmt;

use miette::{
Diagnostic, DiagnosticReporter, DiagnosticSnippet, MietteError, MietteReporter, SourceSpan,
};
use miette::{Diagnostic, DiagnosticReport, DiagnosticSnippet, MietteError, SourceSpan};
use thiserror::Error;

#[derive(Error)]
#[derive(Debug, Error)]
#[error("oops!")]
struct MyBad {
message: String,
Expand All @@ -14,12 +10,6 @@ struct MyBad {
highlight: SourceSpan,
}

impl fmt::Debug for MyBad {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
MietteReporter.debug(self, f)
}
}

impl Diagnostic for MyBad {
fn code(&self) -> Box<dyn std::fmt::Display> {
Box::new(&"oops::my::bad")
Expand Down Expand Up @@ -52,7 +42,8 @@ fn fancy() -> Result<(), MietteError> {
ctx: ("bad_file.rs", 0, len).into(),
highlight: ("this bit here", 9, 4).into(),
};
let out = format!("{:?}", err);
let rep: DiagnosticReport = err.into();
let out = format!("{:?}", rep);
// println!("{}", out);
assert_eq!("Error[oops::my::bad]: oops!\n\n[bad_file.rs] This is the part that broke:\n\n 1 | source\n 2 | text\n ⫶ | ^^^^ this bit here\n 3 | here\n\n﹦try doing it better next time?\n".to_string(), out);
Ok(())
Expand Down

0 comments on commit 29c1403

Please sign in to comment.