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"
|
||||
|
||||
[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")]
|
||||
|
||||
pub use chain::*;
|
||||
pub use protocol::*;
|
||||
pub use reporter::*;
|
||||
|
||||
mod chain;
|
||||
mod source_impls;
|
||||
mod protocol;
|
||||
mod reporter;
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ pub enum Severity {
|
|||
/**
|
||||
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].
|
||||
fn open(&self) -> io::Result<Box<dyn Read>>;
|
||||
}
|
||||
|
|
@ -79,6 +79,7 @@ pub trait Source {
|
|||
/**
|
||||
Details and additional context to be displayed.
|
||||
*/
|
||||
#[derive(Debug)]
|
||||
pub struct DiagnosticDetail {
|
||||
/// Explanation of this specific diagnostic detail.
|
||||
pub message: Option<String>,
|
||||
|
|
@ -95,6 +96,7 @@ pub struct DiagnosticDetail {
|
|||
/**
|
||||
Span within a [Source] with an associated message.
|
||||
*/
|
||||
#[derive(Debug)]
|
||||
pub struct SourceSpan {
|
||||
/// A name for the thing this SourceSpan is actually pointing to.
|
||||
pub label: String,
|
||||
|
|
@ -107,6 +109,7 @@ pub struct SourceSpan {
|
|||
/**
|
||||
Specific location in a [SourceSpan]
|
||||
*/
|
||||
#[derive(Debug)]
|
||||
pub struct SourceLocation {
|
||||
/// 0-indexed column of location.
|
||||
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