vrl/diagnostic/
diagnostic.rs

1use std::ops::{Deref, DerefMut};
2
3use codespan_reporting::diagnostic;
4
5use super::{DiagnosticMessage, Label, Note, Severity, Span};
6
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct Diagnostic {
9    pub severity: Severity,
10    pub code: usize,
11    pub message: String,
12    pub labels: Vec<Label>,
13    pub notes: Vec<Note>,
14}
15
16impl Diagnostic {
17    pub fn error(code: usize, message: impl ToString) -> Self {
18        Self::new(Severity::Error, code, message, vec![], vec![])
19    }
20
21    pub fn bug(code: usize, message: impl ToString) -> Self {
22        Self::new(Severity::Bug, code, message, vec![], vec![])
23    }
24
25    pub fn new(
26        severity: Severity,
27        code: usize,
28        message: impl ToString,
29        labels: Vec<Label>,
30        notes: Vec<Note>,
31    ) -> Self {
32        Self {
33            severity,
34            code,
35            message: message.to_string(),
36            labels,
37            notes,
38        }
39    }
40
41    #[must_use]
42    pub fn with_primary(self, message: impl ToString, span: impl Into<Span>) -> Self {
43        self.with_label(Label::primary(message, span.into()))
44    }
45
46    #[must_use]
47    pub fn with_context(self, message: impl ToString, span: impl Into<Span>) -> Self {
48        self.with_label(Label::context(message, span.into()))
49    }
50
51    #[must_use]
52    pub fn with_label(mut self, label: Label) -> Self {
53        self.labels.push(label);
54        self
55    }
56
57    #[must_use]
58    pub fn with_note(mut self, note: Note) -> Self {
59        self.notes.push(note);
60        self
61    }
62
63    #[must_use]
64    pub fn severity(&self) -> Severity {
65        self.severity
66    }
67
68    #[must_use]
69    pub fn message(&self) -> &str {
70        &self.message
71    }
72
73    #[must_use]
74    pub fn notes(&self) -> &[Note] {
75        &self.notes
76    }
77
78    #[must_use]
79    pub fn labels(&self) -> &[Label] {
80        &self.labels
81    }
82
83    /// Returns `true` if the diagnostic represents either an
84    /// [error](Severity::Error) or [bug](Severity::Bug).
85    #[inline]
86    #[must_use]
87    pub fn is_problem(&self) -> bool {
88        self.severity.is_error() || self.severity.is_bug()
89    }
90
91    /// Returns `true` if the diagnostic represents a [bug](Severity::Bug).
92    #[inline]
93    #[must_use]
94    pub fn is_bug(&self) -> bool {
95        self.severity.is_bug()
96    }
97
98    /// Returns `true` if the diagnostic represents an [error](Severity::Error).
99    #[inline]
100    #[must_use]
101    pub fn is_error(&self) -> bool {
102        self.severity.is_error()
103    }
104
105    /// Returns `true` if the diagnostic represents a
106    /// [warning](Severity::Warning).
107    #[inline]
108    #[must_use]
109    pub fn is_warning(&self) -> bool {
110        self.severity.is_warning()
111    }
112
113    /// Returns `true` if the diagnostic represents a [note](Severity::Note).
114    #[inline]
115    #[must_use]
116    pub fn is_note(&self) -> bool {
117        self.severity.is_note()
118    }
119}
120
121impl From<Box<dyn DiagnosticMessage>> for Diagnostic {
122    fn from(message: Box<dyn DiagnosticMessage>) -> Self {
123        Self {
124            severity: message.severity(),
125            code: message.code(),
126            message: message.message(),
127            labels: message.labels(),
128            notes: message.notes(),
129        }
130    }
131}
132
133impl From<Diagnostic> for diagnostic::Diagnostic<()> {
134    fn from(diag: Diagnostic) -> Self {
135        let mut notes = diag.notes.clone();
136
137        // not all codes have a page on the site yet
138        if diag.code >= 100 && diag.code <= 110 {
139            notes.push(Note::SeeCodeDocs(diag.code));
140        }
141
142        notes.push(Note::SeeLangDocs);
143        notes.push(Note::SeeRepl);
144
145        diagnostic::Diagnostic {
146            severity: diag.severity.into(),
147            code: Some(format!("E{:03}", diag.code)),
148            message: diag.message.clone(),
149            labels: diag.labels.iter().cloned().map(Into::into).collect(),
150            notes: notes.iter().map(ToString::to_string).collect(),
151        }
152    }
153}
154
155// -----------------------------------------------------------------------------
156
157#[derive(Debug, Clone, Default, PartialEq, Eq)]
158pub struct DiagnosticList(Vec<Diagnostic>);
159
160impl DiagnosticList {
161    /// Turns the diagnostic list into a result type, the `Ok` variant is
162    /// returned if none of the diagnostics are errors or bugs. Otherwise the
163    /// `Err` variant is returned.
164    pub fn into_result(self) -> std::result::Result<DiagnosticList, DiagnosticList> {
165        if self.is_err() {
166            return Err(self);
167        }
168
169        Ok(self)
170    }
171
172    /// Returns `true` if there are any errors or bugs in the parsed source.
173    #[must_use]
174    pub fn is_err(&self) -> bool {
175        self.0.iter().any(Diagnostic::is_problem)
176    }
177
178    /// Returns the list of bug-level diagnostics.
179    #[must_use]
180    pub fn bugs(&self) -> Vec<&Diagnostic> {
181        self.0.iter().filter(|d| d.is_bug()).collect()
182    }
183
184    /// Returns the list of error-level diagnostics.
185    #[must_use]
186    pub fn errors(&self) -> Vec<&Diagnostic> {
187        self.0.iter().filter(|d| d.is_error()).collect()
188    }
189
190    /// Returns the list of warning-level diagnostics.
191    #[must_use]
192    pub fn warnings(&self) -> Vec<&Diagnostic> {
193        self.0.iter().filter(|d| d.is_warning()).collect()
194    }
195
196    /// Returns the list of note-level diagnostics.
197    #[must_use]
198    pub fn notes(&self) -> Vec<&Diagnostic> {
199        self.0.iter().filter(|d| d.is_note()).collect()
200    }
201
202    /// Returns `true` if there are any bug diagnostics.
203    #[must_use]
204    pub fn has_bugs(&self) -> bool {
205        self.0.iter().any(Diagnostic::is_bug)
206    }
207
208    /// Returns `true` if there are any error diagnostics.
209    #[must_use]
210    pub fn has_errors(&self) -> bool {
211        self.0.iter().any(Diagnostic::is_error)
212    }
213
214    /// Returns `true` if there are any warning diagnostics.
215    #[must_use]
216    pub fn has_warnings(&self) -> bool {
217        self.0.iter().any(Diagnostic::is_warning)
218    }
219
220    /// Returns `true` if there are any note diagnostics.
221    #[must_use]
222    pub fn has_notes(&self) -> bool {
223        self.0.iter().any(Diagnostic::is_note)
224    }
225}
226
227impl Deref for DiagnosticList {
228    type Target = Vec<Diagnostic>;
229
230    fn deref(&self) -> &Self::Target {
231        &self.0
232    }
233}
234
235impl DerefMut for DiagnosticList {
236    fn deref_mut(&mut self) -> &mut Self::Target {
237        &mut self.0
238    }
239}
240
241impl IntoIterator for DiagnosticList {
242    type Item = Diagnostic;
243    type IntoIter = std::vec::IntoIter<Diagnostic>;
244
245    fn into_iter(self) -> Self::IntoIter {
246        self.0.into_iter()
247    }
248}
249
250impl<T: Into<Diagnostic>> From<Vec<T>> for DiagnosticList {
251    fn from(diagnostics: Vec<T>) -> Self {
252        Self(diagnostics.into_iter().map(Into::into).collect())
253    }
254}
255
256impl<T: Into<Diagnostic>> From<T> for DiagnosticList {
257    fn from(diagnostic: T) -> Self {
258        Self(vec![diagnostic.into()])
259    }
260}