Coverage Report

Created: 2026-10-28 07:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/miette-7.6.0/src/handlers/json.rs
Line
Count
Source
1
use std::fmt::{self, Write};
2
3
use crate::{
4
    diagnostic_chain::DiagnosticChain, protocol::Diagnostic, ReportHandler, Severity, SourceCode,
5
};
6
7
/**
8
[`ReportHandler`] that renders JSON output. It's a machine-readable output.
9
*/
10
#[derive(Debug, Clone)]
11
pub struct JSONReportHandler;
12
13
impl JSONReportHandler {
14
    /// Create a new [`JSONReportHandler`]. There are no customization
15
    /// options.
16
0
    pub const fn new() -> Self {
17
0
        Self
18
0
    }
19
}
20
21
impl Default for JSONReportHandler {
22
0
    fn default() -> Self {
23
0
        Self::new()
24
0
    }
25
}
26
27
struct Escape<'a>(&'a str);
28
29
impl fmt::Display for Escape<'_> {
30
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31
0
        for c in self.0.chars() {
32
0
            let escape = match c {
33
0
                '\\' => Some(r"\\"),
34
0
                '"' => Some(r#"\""#),
35
0
                '\r' => Some(r"\r"),
36
0
                '\n' => Some(r"\n"),
37
0
                '\t' => Some(r"\t"),
38
0
                '\u{08}' => Some(r"\b"),
39
0
                '\u{0c}' => Some(r"\f"),
40
0
                _ => None,
41
            };
42
0
            if let Some(escape) = escape {
43
0
                f.write_str(escape)?;
44
            } else {
45
0
                f.write_char(c)?;
46
            }
47
        }
48
0
        Ok(())
49
0
    }
50
}
51
52
0
const fn escape(input: &'_ str) -> Escape<'_> {
53
0
    Escape(input)
54
0
}
55
56
impl JSONReportHandler {
57
    /// Render a [`Diagnostic`]. This function is mostly internal and meant to
58
    /// be called by the toplevel [`ReportHandler`] handler, but is made public
59
    /// to make it easier (possible) to test in isolation from global state.
60
0
    pub fn render_report(
61
0
        &self,
62
0
        f: &mut impl fmt::Write,
63
0
        diagnostic: &(dyn Diagnostic),
64
0
    ) -> fmt::Result {
65
0
        self._render_report(f, diagnostic, None)
66
0
    }
67
68
0
    fn _render_report(
69
0
        &self,
70
0
        f: &mut impl fmt::Write,
71
0
        diagnostic: &(dyn Diagnostic),
72
0
        parent_src: Option<&dyn SourceCode>,
73
0
    ) -> fmt::Result {
74
0
        write!(f, r#"{{"message": "{}","#, escape(&diagnostic.to_string()))?;
75
0
        if let Some(code) = diagnostic.code() {
76
0
            write!(f, r#""code": "{}","#, escape(&code.to_string()))?;
77
0
        }
78
0
        let severity = match diagnostic.severity() {
79
0
            Some(Severity::Error) | None => "error",
80
0
            Some(Severity::Warning) => "warning",
81
0
            Some(Severity::Advice) => "advice",
82
        };
83
0
        write!(f, r#""severity": "{:}","#, severity)?;
84
0
        if let Some(cause_iter) = diagnostic
85
0
            .diagnostic_source()
86
0
            .map(DiagnosticChain::from_diagnostic)
87
0
            .or_else(|| diagnostic.source().map(DiagnosticChain::from_stderror))
88
        {
89
0
            write!(f, r#""causes": ["#)?;
90
0
            let mut add_comma = false;
91
0
            for error in cause_iter {
92
0
                if add_comma {
93
0
                    write!(f, ",")?;
94
0
                } else {
95
0
                    add_comma = true;
96
0
                }
97
0
                write!(f, r#""{}""#, escape(&error.to_string()))?;
98
            }
99
0
            write!(f, "],")?;
100
        } else {
101
0
            write!(f, r#""causes": [],"#)?;
102
        }
103
0
        if let Some(url) = diagnostic.url() {
104
0
            write!(f, r#""url": "{}","#, &url.to_string())?;
105
0
        }
106
0
        if let Some(help) = diagnostic.help() {
107
0
            write!(f, r#""help": "{}","#, escape(&help.to_string()))?;
108
0
        }
109
0
        let src = diagnostic.source_code().or(parent_src);
110
0
        if let Some(src) = src {
111
0
            self.render_snippets(f, diagnostic, src)?;
112
0
        }
113
0
        if let Some(labels) = diagnostic.labels() {
114
0
            write!(f, r#""labels": ["#)?;
115
0
            let mut add_comma = false;
116
0
            for label in labels {
117
0
                if add_comma {
118
0
                    write!(f, ",")?;
119
0
                } else {
120
0
                    add_comma = true;
121
0
                }
122
0
                write!(f, "{{")?;
123
0
                if let Some(label_name) = label.label() {
124
0
                    write!(f, r#""label": "{}","#, escape(label_name))?;
125
0
                }
126
0
                write!(f, r#""span": {{"#)?;
127
0
                write!(f, r#""offset": {},"#, label.offset())?;
128
0
                write!(f, r#""length": {}"#, label.len())?;
129
130
0
                write!(f, "}}}}")?;
131
            }
132
0
            write!(f, "],")?;
133
        } else {
134
0
            write!(f, r#""labels": [],"#)?;
135
        }
136
0
        if let Some(relateds) = diagnostic.related() {
137
0
            write!(f, r#""related": ["#)?;
138
0
            let mut add_comma = false;
139
0
            for related in relateds {
140
0
                if add_comma {
141
0
                    write!(f, ",")?;
142
0
                } else {
143
0
                    add_comma = true;
144
0
                }
145
0
                self._render_report(f, related, src)?;
146
            }
147
0
            write!(f, "]")?;
148
        } else {
149
0
            write!(f, r#""related": []"#)?;
150
        }
151
0
        write!(f, "}}")
152
0
    }
153
154
0
    fn render_snippets(
155
0
        &self,
156
0
        f: &mut impl fmt::Write,
157
0
        diagnostic: &(dyn Diagnostic),
158
0
        source: &dyn SourceCode,
159
0
    ) -> fmt::Result {
160
0
        if let Some(mut labels) = diagnostic.labels() {
161
0
            if let Some(label) = labels.next() {
162
0
                if let Ok(span_content) = source.read_span(label.inner(), 0, 0) {
163
0
                    let filename = span_content.name().unwrap_or_default();
164
0
                    return write!(f, r#""filename": "{}","#, escape(filename));
165
0
                }
166
0
            }
167
0
        }
168
0
        write!(f, r#""filename": "","#)
169
0
    }
170
}
171
172
impl ReportHandler for JSONReportHandler {
173
0
    fn debug(&self, diagnostic: &(dyn Diagnostic), f: &mut fmt::Formatter<'_>) -> fmt::Result {
174
0
        self.render_report(f, diagnostic)
175
0
    }
176
}
177
178
#[test]
179
fn test_escape() {
180
    assert_eq!(escape("a\nb").to_string(), r"a\nb");
181
    assert_eq!(escape("C:\\Miette").to_string(), r"C:\\Miette");
182
}