Skip to content

Commit

Permalink
feat(reporter): Overhauled experience for main() reporting.
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 8117c21
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 38 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
56 changes: 28 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,19 +48,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 +71,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 Box 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<(), DiagnosticOutput>
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,
}
55 changes: 50 additions & 5 deletions src/reporter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,63 @@ 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::MietteError;

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

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

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

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

/// When used with `?`/`From`, this will wrap any boxed Diagnostics and, when
/// formatted with `Debug`, will fetch the current [DiagnosticReporter] and
/// use it to format the inner [Diagnostic].
pub struct DiagnosticOutput {
diagnostic: Box<dyn Diagnostic + Send + Sync + 'static>,
}

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

impl<T: Diagnostic + Send + Sync + 'static> From<T> for DiagnosticOutput {
fn from(diagnostic: T) -> Self {
DiagnosticOutput {
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.
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 DefaultReporter;

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

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

Expand Down
3 changes: 0 additions & 3 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,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
4 changes: 2 additions & 2 deletions tests/reporter.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::fmt;

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

Expand All @@ -16,7 +16,7 @@ struct MyBad {

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

Expand Down

0 comments on commit 8117c21

Please sign in to comment.