mirror of https://github.com/zkat/miette.git
feat(reporter): dummy reporter implementation + tests
This commit is contained in:
parent
c3f41b972d
commit
a437f44511
|
|
@ -11,3 +11,7 @@ readme = "README.md"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
indenter = "0.3.3"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
thiserror = "1.0.26"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
/*!
|
||||||
|
Iterate over error `.source()` chains.
|
||||||
|
|
||||||
|
NOTE: This module is taken wholesale from https://crates.io/crates/eyre.
|
||||||
|
*/
|
||||||
|
use std::error::Error as StdError;
|
||||||
|
use std::vec;
|
||||||
|
|
||||||
|
use ChainState::*;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
#[allow(missing_debug_implementations)]
|
||||||
|
pub struct Chain<'a> {
|
||||||
|
state: crate::chain::ChainState<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(crate) enum ChainState<'a> {
|
||||||
|
Linked {
|
||||||
|
next: Option<&'a (dyn StdError + 'static)>,
|
||||||
|
},
|
||||||
|
Buffered {
|
||||||
|
rest: vec::IntoIter<&'a (dyn StdError + 'static)>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Chain<'a> {
|
||||||
|
pub fn new(head: &'a (dyn StdError + 'static)) -> Self {
|
||||||
|
Chain {
|
||||||
|
state: ChainState::Linked { next: Some(head) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for Chain<'a> {
|
||||||
|
type Item = &'a (dyn StdError + 'static);
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
match &mut self.state {
|
||||||
|
Linked { next } => {
|
||||||
|
let error = (*next)?;
|
||||||
|
*next = error.source();
|
||||||
|
Some(error)
|
||||||
|
}
|
||||||
|
Buffered { rest } => rest.next(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||||
|
let len = self.len();
|
||||||
|
(len, Some(len))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DoubleEndedIterator for Chain<'_> {
|
||||||
|
fn next_back(&mut self) -> Option<Self::Item> {
|
||||||
|
match &mut self.state {
|
||||||
|
Linked { mut next } => {
|
||||||
|
let mut rest = Vec::new();
|
||||||
|
while let Some(cause) = next {
|
||||||
|
next = cause.source();
|
||||||
|
rest.push(cause);
|
||||||
|
}
|
||||||
|
let mut rest = rest.into_iter();
|
||||||
|
let last = rest.next_back();
|
||||||
|
self.state = Buffered { rest };
|
||||||
|
last
|
||||||
|
}
|
||||||
|
Buffered { rest } => rest.next_back(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExactSizeIterator for Chain<'_> {
|
||||||
|
fn len(&self) -> usize {
|
||||||
|
match &self.state {
|
||||||
|
Linked { mut next } => {
|
||||||
|
let mut len = 0;
|
||||||
|
while let Some(cause) = next {
|
||||||
|
next = cause.source();
|
||||||
|
len += 1;
|
||||||
|
}
|
||||||
|
len
|
||||||
|
}
|
||||||
|
Buffered { rest } => rest.len(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Chain<'_> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Chain {
|
||||||
|
state: ChainState::Buffered {
|
||||||
|
rest: Vec::new().into_iter(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
#![doc = include_str!("../README.md")]
|
#![doc = include_str!("../README.md")]
|
||||||
|
|
||||||
|
pub use chain::*;
|
||||||
pub use protocol::*;
|
pub use protocol::*;
|
||||||
|
pub use reporter::*;
|
||||||
|
|
||||||
|
mod chain;
|
||||||
mod source_impls;
|
mod source_impls;
|
||||||
mod protocol;
|
mod protocol;
|
||||||
|
mod reporter;
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ pub enum Severity {
|
||||||
/**
|
/**
|
||||||
Represents a readable source of some sort: a source file, a String, etc.
|
Represents a readable source of some sort: a source file, a String, etc.
|
||||||
*/
|
*/
|
||||||
pub trait Source {
|
pub trait Source: std::fmt::Debug + Send + Sync + 'static {
|
||||||
/// Get a `Read`er from a given [Source].
|
/// Get a `Read`er from a given [Source].
|
||||||
fn open(&self) -> io::Result<Box<dyn Read>>;
|
fn open(&self) -> io::Result<Box<dyn Read>>;
|
||||||
}
|
}
|
||||||
|
|
@ -79,6 +79,7 @@ pub trait Source {
|
||||||
/**
|
/**
|
||||||
Details and additional context to be displayed.
|
Details and additional context to be displayed.
|
||||||
*/
|
*/
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct DiagnosticDetail {
|
pub struct DiagnosticDetail {
|
||||||
/// Explanation of this specific diagnostic detail.
|
/// Explanation of this specific diagnostic detail.
|
||||||
pub message: Option<String>,
|
pub message: Option<String>,
|
||||||
|
|
@ -95,6 +96,7 @@ pub struct DiagnosticDetail {
|
||||||
/**
|
/**
|
||||||
Span within a [Source] with an associated message.
|
Span within a [Source] with an associated message.
|
||||||
*/
|
*/
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct SourceSpan {
|
pub struct SourceSpan {
|
||||||
/// A name for the thing this SourceSpan is actually pointing to.
|
/// A name for the thing this SourceSpan is actually pointing to.
|
||||||
pub label: String,
|
pub label: String,
|
||||||
|
|
@ -107,6 +109,7 @@ pub struct SourceSpan {
|
||||||
/**
|
/**
|
||||||
Specific location in a [SourceSpan]
|
Specific location in a [SourceSpan]
|
||||||
*/
|
*/
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct SourceLocation {
|
pub struct SourceLocation {
|
||||||
/// 0-indexed column of location.
|
/// 0-indexed column of location.
|
||||||
pub column: usize,
|
pub column: usize,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
/*!
|
||||||
|
Basic reporter for Diagnostics. Probably good enough for most use-cases,
|
||||||
|
but largely meant to be an example.
|
||||||
|
*/
|
||||||
|
use indenter::indented;
|
||||||
|
|
||||||
|
use crate::chain::Chain;
|
||||||
|
use crate::protocol::{Diagnostic, DiagnosticDetail, DiagnosticReporter, Severity};
|
||||||
|
|
||||||
|
pub struct Reporter;
|
||||||
|
|
||||||
|
impl DiagnosticReporter for Reporter {
|
||||||
|
fn debug(
|
||||||
|
&self,
|
||||||
|
diagnostic: &(dyn Diagnostic),
|
||||||
|
f: &mut core::fmt::Formatter<'_>,
|
||||||
|
) -> core::fmt::Result {
|
||||||
|
use core::fmt::Write as _;
|
||||||
|
|
||||||
|
if f.alternate() {
|
||||||
|
return core::fmt::Debug::fmt(diagnostic, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
let sev = match diagnostic.severity() {
|
||||||
|
Severity::Error => "Error",
|
||||||
|
Severity::Warning => "Warning",
|
||||||
|
Severity::Advice => "Advice",
|
||||||
|
};
|
||||||
|
write!(f, "{}[{}]: {}", sev, diagnostic.code(), diagnostic)?;
|
||||||
|
|
||||||
|
if let Some(cause) = diagnostic.source() {
|
||||||
|
write!(f, "\n\nCaused by:")?;
|
||||||
|
let multiple = cause.source().is_some();
|
||||||
|
|
||||||
|
for (n, error) in Chain::new(cause).enumerate() {
|
||||||
|
writeln!(f)?;
|
||||||
|
if multiple {
|
||||||
|
write!(indented(f).ind(n), "{}", error)?;
|
||||||
|
} else {
|
||||||
|
write!(indented(f), "{}", error)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(details) = diagnostic.details() {
|
||||||
|
writeln!(f)?;
|
||||||
|
for DiagnosticDetail {
|
||||||
|
source_name,
|
||||||
|
message,
|
||||||
|
span,
|
||||||
|
other_spans,
|
||||||
|
..
|
||||||
|
} in details
|
||||||
|
{
|
||||||
|
write!(f, "\n[{}]", source_name)?;
|
||||||
|
if let Some(msg) = message {
|
||||||
|
write!(f, " {}:", msg)?;
|
||||||
|
}
|
||||||
|
writeln!(
|
||||||
|
indented(f),
|
||||||
|
"\n\n({}) @ line {}, col {} ",
|
||||||
|
span.label,
|
||||||
|
span.start.line + 1,
|
||||||
|
span.start.column + 1
|
||||||
|
)?;
|
||||||
|
if let Some(other_spans) = other_spans {
|
||||||
|
for span in other_spans {
|
||||||
|
writeln!(
|
||||||
|
indented(f),
|
||||||
|
"\n{} @ line {}, col {} ",
|
||||||
|
span.label,
|
||||||
|
span.start.line + 1,
|
||||||
|
span.start.column + 1
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(help) = diagnostic.help() {
|
||||||
|
writeln!(f)?;
|
||||||
|
for msg in help {
|
||||||
|
writeln!(f, "﹦{}", msg)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
use std::{fmt, io};
|
||||||
|
|
||||||
|
use miette::{
|
||||||
|
Diagnostic, DiagnosticDetail, DiagnosticReporter, Reporter, Severity, SourceLocation,
|
||||||
|
SourceSpan,
|
||||||
|
};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Error)]
|
||||||
|
#[error("oops!")]
|
||||||
|
struct MyBad {
|
||||||
|
details: Vec<DiagnosticDetail>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for MyBad {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
Reporter.debug(self, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Diagnostic for MyBad {
|
||||||
|
fn code(&self) -> &(dyn std::fmt::Display + 'static) {
|
||||||
|
&"oops::my::bad"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn severity(&self) -> Severity {
|
||||||
|
Severity::Error
|
||||||
|
}
|
||||||
|
|
||||||
|
fn help(&self) -> Option<&[&str]> {
|
||||||
|
Some(&["try doing it better next time?"])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn details(&self) -> Option<&[DiagnosticDetail]> {
|
||||||
|
Some(&self.details)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn basic() -> io::Result<()> {
|
||||||
|
let err = MyBad {
|
||||||
|
details: Vec::new(),
|
||||||
|
};
|
||||||
|
let out = format!("{:?}", err);
|
||||||
|
assert_eq!(
|
||||||
|
"Error[oops::my::bad]: oops!\n\n﹦try doing it better next time?\n".to_string(),
|
||||||
|
out
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fancy() -> io::Result<()> {
|
||||||
|
let err = MyBad {
|
||||||
|
details: vec![DiagnosticDetail {
|
||||||
|
message: Some("This is the part that broke".into()),
|
||||||
|
source_name: "bad_file.rs".into(),
|
||||||
|
source: Box::new("source_text".to_string()),
|
||||||
|
other_spans: None,
|
||||||
|
span: SourceSpan {
|
||||||
|
label: "this thing here is bad".into(),
|
||||||
|
start: SourceLocation {
|
||||||
|
line: 0,
|
||||||
|
column: 0,
|
||||||
|
offset: 0,
|
||||||
|
},
|
||||||
|
end: SourceLocation {
|
||||||
|
line: 0,
|
||||||
|
column: 4,
|
||||||
|
offset: 4,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
let out = format!("{:?}", err);
|
||||||
|
// println!("{}", out);
|
||||||
|
assert_eq!("Error[oops::my::bad]: oops!\n\n[bad_file.rs] This is the part that broke:\n\n (this thing here is bad) @ line 1, col 1 \n\n﹦try doing it better next time?\n".to_string(), out);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue