mirror of https://github.com/zkat/miette.git
feat(reporter): Overhauled return type/main/DiagnosticReport experience.
Fixes: https://github.com/zkat/miette/issues/13
This commit is contained in:
parent
4d45884362
commit
29c1403efd
|
|
@ -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"
|
||||
|
|
|
|||
67
README.md
67
README.md
|
|
@ -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!
|
||||
|
||||
|
|
@ -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]
|
||||
|
|
@ -48,12 +51,12 @@ $ 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),
|
||||
|
|
@ -61,6 +64,7 @@ use thiserror::Error;
|
|||
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")]
|
||||
|
|
@ -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::{DiagnosticReporter, MietteReporter};
|
||||
|
||||
impl fmt::Debug for MyBad {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
MietteReporter.debug(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Now we can use `miette`~
|
||||
*/
|
||||
use miette::{MietteError, SourceSpan};
|
||||
|
||||
fn pretend_this_is_main() -> Result<(), MyBad> {
|
||||
// You can use plain strings as a `Source`, bu the protocol is fully extensible!
|
||||
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();
|
||||
|
||||
Err(MyBad {
|
||||
src,
|
||||
snip: ("bad_file.rs", 0, len).into(),
|
||||
bad_bit: ("this bit here", 9, 3).into(),
|
||||
})
|
||||
bad_bit: ("this bit here", 9, 4).into(),
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/*
|
||||
Now to get everything printed nicely, just return a Result<(), DiagnosticReport>
|
||||
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() -> Result<()> {
|
||||
// kaboom~
|
||||
this_fails()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
@ -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]
|
||||
|
|
@ -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,
|
||||
|
|
@ -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)]
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<'_>,
|
||||
|
|
@ -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 _;
|
||||
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
14
src/utils.rs
14
src/utils.rs
|
|
@ -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 {
|
||||
|
|
@ -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].
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
@ -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")
|
||||
|
|
@ -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(())
|
||||
|
|
|
|||
Loading…
Reference in New Issue