summaryrefslogtreecommitdiff
path: root/vendor/miette/src/handlers/json.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/miette/src/handlers/json.rs')
-rw-r--r--vendor/miette/src/handlers/json.rs182
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");
+}