Skip to content

Commit

Permalink
feat(handlers): Add JSON handler (#90)
Browse files Browse the repository at this point in the history
  • Loading branch information
dvermd committed Nov 15, 2021
1 parent fe77d8c commit 53b2468
Show file tree
Hide file tree
Showing 3 changed files with 921 additions and 0 deletions.
129 changes: 129 additions & 0 deletions src/handlers/json.rs
@@ -0,0 +1,129 @@
use std::fmt;

use crate::{protocol::Diagnostic, ReportHandler, Severity};

/**
[ReportHandler] that renders json output.
It's a machine-readable output.
*/
#[derive(Debug, Clone)]
pub struct JSONReportHandler;

impl JSONReportHandler {
/// Create a new [JSONReportHandler]. There are no customization
/// options.
pub fn new() -> Self {
Self
}
}

impl Default for JSONReportHandler {
fn default() -> Self {
Self::new()
}
}

fn escape(input: &str) -> String {
input
.chars()
.map(|c| match c {
'"' => "\\\\\"".to_string(),

This comment has been minimized.

Copy link
@tailhook

tailhook Jan 6, 2022

Contributor

Quote and other chars get quoted with double backslash. So unexpected " get's rendered as "unexpected \\"", and this is invalid JSON (backslash escapes backslash, and then quote end the string).

Also I don't see backslash itself escaped. So string containing backslash will probably fail too.

Hint: use r#"raw strings"# to avoid calculating slashes. r#"\""# is easier to get right than "\\\"".

'\'' => "\\\\'".to_string(),
'\r' => "\\\\r".to_string(),
'\n' => "\\\\n".to_string(),
'\t' => "\\\\t".to_string(),
'\u{08}' => "\\\\b".to_string(),
'\u{0c}' => "\\\\f".to_string(),
c => format!("{}", c),
})
.collect()
}

impl JSONReportHandler {
/// Render a [Diagnostic]. This function is mostly internal and meant to
/// be called by the toplevel [ReportHandler] handler, but is
/// made public to make it easier (possible) to test in isolation from
/// global state.
pub fn render_report(
&self,
f: &mut impl fmt::Write,
diagnostic: &(dyn Diagnostic),
) -> fmt::Result {
write!(f, r#"{{"message": "{}","#, escape(&diagnostic.to_string()))?;
if let Some(code) = diagnostic.code() {
write!(f, r#""code": "{}","#, escape(&code.to_string()))?;
}
let severity = match diagnostic.severity() {
Some(Severity::Error) | None => "error",
Some(Severity::Warning) => "warning",
Some(Severity::Advice) => "advice",
};
write!(f, r#""severity": "{:}","#, severity)?;
if let Some(url) = diagnostic.url() {
write!(f, r#""url": "{}","#, &url.to_string())?;
}
if let Some(help) = diagnostic.help() {
write!(f, r#""help": "{}","#, escape(&help.to_string()))?;
}
if diagnostic.source_code().is_some() {
self.render_snippets(f, diagnostic)?;
}
if let Some(labels) = diagnostic.labels() {
write!(f, r#""labels": ["#)?;
let mut add_comma = false;
for label in labels {
if add_comma {
write!(f, ",")?;
} else {
add_comma = true;
}
write!(f, "{{")?;
if let Some(label_name) = label.label() {
write!(f, r#""label": "{}","#, escape(label_name))?;
}
write!(f, r#""span": {{"#)?;
write!(f, r#""offset": {},"#, label.offset())?;
write!(f, r#""length": {}"#, label.len())?;

write!(f, "}}}}")?;
}
write!(f, "],")?;
} else {
write!(f, r#""labels": [],"#)?;
}
if let Some(relateds) = diagnostic.related() {
write!(f, r#""related": ["#)?;
for related in relateds {
self.render_report(f, related)?;

This comment has been minimized.

Copy link
@tailhook

tailhook Jan 6, 2022

Contributor

No comma between multiple related errors. It ends up rendering like this [{...}{...}]

}
write!(f, "]")?;
} else {
write!(f, r#""related": []"#)?;
}
write!(f, "}}")
}

fn render_snippets(
&self,
f: &mut impl fmt::Write,
diagnostic: &(dyn Diagnostic),
) -> fmt::Result {
if let Some(source) = diagnostic.source_code() {
if let Some(mut labels) = diagnostic.labels() {
if let Some(label) = labels.next() {
if let Ok(span_content) = source.read_span(label.inner(), 0, 0) {
let filename = span_content.name().unwrap_or_default();
return write!(f, r#""filename": "{}","#, escape(filename));
}
}
}
}
write!(f, r#""filename": "","#)
}
}

impl ReportHandler for JSONReportHandler {
fn debug(&self, diagnostic: &(dyn Diagnostic), f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.render_report(f, diagnostic)
}
}
3 changes: 3 additions & 0 deletions src/handlers/mod.rs
Expand Up @@ -8,6 +8,8 @@ pub use debug::*;
#[cfg(feature = "fancy")]
pub use graphical::*;
#[allow(unreachable_pub)]
pub use json::*;
#[allow(unreachable_pub)]
pub use narratable::*;
#[allow(unreachable_pub)]
#[cfg(feature = "fancy")]
Expand All @@ -16,6 +18,7 @@ pub use theme::*;
mod debug;
#[cfg(feature = "fancy")]
mod graphical;
mod json;
mod narratable;
#[cfg(feature = "fancy")]
mod theme;

0 comments on commit 53b2468

Please sign in to comment.