-
-
Notifications
You must be signed in to change notification settings - Fork 120
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(reporter): dummy reporter implementation + tests
- Loading branch information
Showing
6 changed files
with
279 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,3 +11,7 @@ readme = "README.md" | |
edition = "2018" | ||
|
||
[dependencies] | ||
indenter = "0.3.3" | ||
|
||
[dev-dependencies] | ||
thiserror = "1.0.26" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(), | ||
}, | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(()) | ||
} |