diff options
Diffstat (limited to 'vendor/miette/src/handlers/json.rs')
-rw-r--r-- | vendor/miette/src/handlers/json.rs | 182 |
1 files changed, 182 insertions, 0 deletions
diff --git a/vendor/miette/src/handlers/json.rs b/vendor/miette/src/handlers/json.rs new file mode 100644 index 0000000..29e21a0 --- /dev/null +++ b/vendor/miette/src/handlers/json.rs @@ -0,0 +1,182 @@ +use std::fmt::{self, Write}; + +use crate::{ + diagnostic_chain::DiagnosticChain, protocol::Diagnostic, ReportHandler, Severity, SourceCode, +}; + +/** +[`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 const fn new() -> Self { + Self + } +} + +impl Default for JSONReportHandler { + fn default() -> Self { + Self::new() + } +} + +struct Escape<'a>(&'a str); + +impl fmt::Display for Escape<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for c in self.0.chars() { + let escape = match c { + '\\' => Some(r"\\"), + '"' => Some(r#"\""#), + '\r' => Some(r"\r"), + '\n' => Some(r"\n"), + '\t' => Some(r"\t"), + '\u{08}' => Some(r"\b"), + '\u{0c}' => Some(r"\f"), + _ => None, + }; + if let Some(escape) = escape { + f.write_str(escape)?; + } else { + f.write_char(c)?; + } + } + Ok(()) + } +} + +const fn escape(input: &'_ str) -> Escape<'_> { + Escape(input) +} + +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 { + self._render_report(f, diagnostic, None) + } + + fn _render_report( + &self, + f: &mut impl fmt::Write, + diagnostic: &(dyn Diagnostic), + parent_src: Option<&dyn SourceCode>, + ) -> 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(cause_iter) = diagnostic + .diagnostic_source() + .map(DiagnosticChain::from_diagnostic) + .or_else(|| diagnostic.source().map(DiagnosticChain::from_stderror)) + { + write!(f, r#""causes": ["#)?; + let mut add_comma = false; + for error in cause_iter { + if add_comma { + write!(f, ",")?; + } else { + add_comma = true; + } + write!(f, r#""{}""#, escape(&error.to_string()))?; + } + write!(f, "],")? + } else { + write!(f, r#""causes": [],"#)?; + } + 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()))?; + } + let src = diagnostic.source_code().or(parent_src); + if let Some(src) = src { + self.render_snippets(f, diagnostic, src)?; + } + 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": ["#)?; + let mut add_comma = false; + for related in relateds { + if add_comma { + write!(f, ",")?; + } else { + add_comma = true; + } + self._render_report(f, related, src)?; + } + write!(f, "]")?; + } else { + write!(f, r#""related": []"#)?; + } + write!(f, "}}") + } + + fn render_snippets( + &self, + f: &mut impl fmt::Write, + diagnostic: &(dyn Diagnostic), + source: &dyn SourceCode, + ) -> fmt::Result { + 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) + } +} + +#[test] +fn test_escape() { + assert_eq!(escape("a\nb").to_string(), r"a\nb"); + assert_eq!(escape("C:\\Miette").to_string(), r"C:\\Miette"); +} |