miette/miette_diagnostic.rs
1use std::{
2 error::Error,
3 fmt::{Debug, Display},
4};
5
6#[cfg(feature = "serde")]
7use serde::{Deserialize, Serialize};
8
9use crate::{Diagnostic, LabeledSpan, Severity};
10
11/// Diagnostic that can be created at runtime.
12#[derive(Debug, Clone, PartialEq, Eq)]
13#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
14pub struct MietteDiagnostic {
15 /// Displayed diagnostic message
16 pub message: String,
17 /// Unique diagnostic code to look up more information
18 /// about this Diagnostic. Ideally also globally unique, and documented
19 /// in the toplevel crate's documentation for easy searching.
20 /// Rust path format (`foo::bar::baz`) is recommended, but more classic
21 /// codes like `E0123` will work just fine
22 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
23 pub code: Option<String>,
24 /// [`Diagnostic`] severity. Intended to be used by
25 /// [`ReportHandler`](crate::ReportHandler)s to change the way different
26 /// [`Diagnostic`]s are displayed. Defaults to [`Severity::Error`]
27 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
28 pub severity: Option<Severity>,
29 /// Additional help text related to this Diagnostic
30 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
31 pub help: Option<String>,
32 /// URL to visit for a more detailed explanation/help about this
33 /// [`Diagnostic`].
34 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
35 pub url: Option<String>,
36 /// Labels to apply to this `Diagnostic`'s [`Diagnostic::source_code`]
37 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
38 pub labels: Option<Vec<LabeledSpan>>,
39}
40
41impl Display for MietteDiagnostic {
42 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43 write!(f, "{}", &self.message)
44 }
45}
46
47impl Error for MietteDiagnostic {}
48
49impl Diagnostic for MietteDiagnostic {
50 fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
51 self.code
52 .as_ref()
53 .map(Box::new)
54 .map(|c| c as Box<dyn Display>)
55 }
56
57 fn severity(&self) -> Option<Severity> {
58 self.severity
59 }
60
61 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
62 self.help
63 .as_ref()
64 .map(Box::new)
65 .map(|c| c as Box<dyn Display>)
66 }
67
68 fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
69 self.url
70 .as_ref()
71 .map(Box::new)
72 .map(|c| c as Box<dyn Display>)
73 }
74
75 fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
76 self.labels
77 .as_ref()
78 .map(|ls| ls.iter().cloned())
79 .map(Box::new)
80 .map(|b| b as Box<dyn Iterator<Item = LabeledSpan>>)
81 }
82}
83
84impl MietteDiagnostic {
85 /// Create a new dynamic diagnostic with the given message.
86 ///
87 /// # Examples
88 /// ```
89 /// use miette::{Diagnostic, MietteDiagnostic, Severity};
90 ///
91 /// let diag = MietteDiagnostic::new("Oops, something went wrong!");
92 /// assert_eq!(diag.to_string(), "Oops, something went wrong!");
93 /// assert_eq!(diag.message, "Oops, something went wrong!");
94 /// ```
95 pub fn new(message: impl Into<String>) -> Self {
96 Self {
97 message: message.into(),
98 labels: None,
99 severity: None,
100 code: None,
101 help: None,
102 url: None,
103 }
104 }
105
106 /// Return new diagnostic with the given code.
107 ///
108 /// # Examples
109 /// ```
110 /// use miette::{Diagnostic, MietteDiagnostic};
111 ///
112 /// let diag = MietteDiagnostic::new("Oops, something went wrong!").with_code("foo::bar::baz");
113 /// assert_eq!(diag.message, "Oops, something went wrong!");
114 /// assert_eq!(diag.code, Some("foo::bar::baz".to_string()));
115 /// ```
116 pub fn with_code(mut self, code: impl Into<String>) -> Self {
117 self.code = Some(code.into());
118 self
119 }
120
121 /// Return new diagnostic with the given severity.
122 ///
123 /// # Examples
124 /// ```
125 /// use miette::{Diagnostic, MietteDiagnostic, Severity};
126 ///
127 /// let diag = MietteDiagnostic::new("I warn you to stop!").with_severity(Severity::Warning);
128 /// assert_eq!(diag.message, "I warn you to stop!");
129 /// assert_eq!(diag.severity, Some(Severity::Warning));
130 /// ```
131 pub fn with_severity(mut self, severity: Severity) -> Self {
132 self.severity = Some(severity);
133 self
134 }
135
136 /// Return new diagnostic with the given help message.
137 ///
138 /// # Examples
139 /// ```
140 /// use miette::{Diagnostic, MietteDiagnostic};
141 ///
142 /// let diag = MietteDiagnostic::new("PC is not working").with_help("Try to reboot it again");
143 /// assert_eq!(diag.message, "PC is not working");
144 /// assert_eq!(diag.help, Some("Try to reboot it again".to_string()));
145 /// ```
146 pub fn with_help(mut self, help: impl Into<String>) -> Self {
147 self.help = Some(help.into());
148 self
149 }
150
151 /// Return new diagnostic with the given URL.
152 ///
153 /// # Examples
154 /// ```
155 /// use miette::{Diagnostic, MietteDiagnostic};
156 ///
157 /// let diag = MietteDiagnostic::new("PC is not working")
158 /// .with_url("https://letmegooglethat.com/?q=Why+my+pc+doesn%27t+work");
159 /// assert_eq!(diag.message, "PC is not working");
160 /// assert_eq!(
161 /// diag.url,
162 /// Some("https://letmegooglethat.com/?q=Why+my+pc+doesn%27t+work".to_string())
163 /// );
164 /// ```
165 pub fn with_url(mut self, url: impl Into<String>) -> Self {
166 self.url = Some(url.into());
167 self
168 }
169
170 /// Return new diagnostic with the given label.
171 ///
172 /// Discards previous labels
173 ///
174 /// # Examples
175 /// ```
176 /// use miette::{Diagnostic, LabeledSpan, MietteDiagnostic};
177 ///
178 /// let source = "cpp is the best language";
179 ///
180 /// let label = LabeledSpan::at(0..3, "This should be Rust");
181 /// let diag = MietteDiagnostic::new("Wrong best language").with_label(label.clone());
182 /// assert_eq!(diag.message, "Wrong best language");
183 /// assert_eq!(diag.labels, Some(vec![label]));
184 /// ```
185 pub fn with_label(mut self, label: impl Into<LabeledSpan>) -> Self {
186 self.labels = Some(vec![label.into()]);
187 self
188 }
189
190 /// Return new diagnostic with the given labels.
191 ///
192 /// Discards previous labels
193 ///
194 /// # Examples
195 /// ```
196 /// use miette::{Diagnostic, LabeledSpan, MietteDiagnostic};
197 ///
198 /// let source = "helo wrld";
199 ///
200 /// let labels = vec![
201 /// LabeledSpan::at_offset(3, "add 'l'"),
202 /// LabeledSpan::at_offset(6, "add 'r'"),
203 /// ];
204 /// let diag = MietteDiagnostic::new("Typos in 'hello world'").with_labels(labels.clone());
205 /// assert_eq!(diag.message, "Typos in 'hello world'");
206 /// assert_eq!(diag.labels, Some(labels));
207 /// ```
208 pub fn with_labels(mut self, labels: impl IntoIterator<Item = LabeledSpan>) -> Self {
209 self.labels = Some(labels.into_iter().collect());
210 self
211 }
212
213 /// Return new diagnostic with new label added to the existing ones.
214 ///
215 /// # Examples
216 /// ```
217 /// use miette::{Diagnostic, LabeledSpan, MietteDiagnostic};
218 ///
219 /// let source = "helo wrld";
220 ///
221 /// let label1 = LabeledSpan::at_offset(3, "add 'l'");
222 /// let label2 = LabeledSpan::at_offset(6, "add 'r'");
223 /// let diag = MietteDiagnostic::new("Typos in 'hello world'")
224 /// .and_label(label1.clone())
225 /// .and_label(label2.clone());
226 /// assert_eq!(diag.message, "Typos in 'hello world'");
227 /// assert_eq!(diag.labels, Some(vec![label1, label2]));
228 /// ```
229 pub fn and_label(mut self, label: impl Into<LabeledSpan>) -> Self {
230 let mut labels = self.labels.unwrap_or_default();
231 labels.push(label.into());
232 self.labels = Some(labels);
233 self
234 }
235
236 /// Return new diagnostic with new labels added to the existing ones.
237 ///
238 /// # Examples
239 /// ```
240 /// use miette::{Diagnostic, LabeledSpan, MietteDiagnostic};
241 ///
242 /// let source = "helo wrld";
243 ///
244 /// let label1 = LabeledSpan::at_offset(3, "add 'l'");
245 /// let label2 = LabeledSpan::at_offset(6, "add 'r'");
246 /// let label3 = LabeledSpan::at_offset(9, "add '!'");
247 /// let diag = MietteDiagnostic::new("Typos in 'hello world!'")
248 /// .and_label(label1.clone())
249 /// .and_labels([label2.clone(), label3.clone()]);
250 /// assert_eq!(diag.message, "Typos in 'hello world!'");
251 /// assert_eq!(diag.labels, Some(vec![label1, label2, label3]));
252 /// ```
253 pub fn and_labels(mut self, labels: impl IntoIterator<Item = LabeledSpan>) -> Self {
254 let mut all_labels = self.labels.unwrap_or_default();
255 all_labels.extend(labels);
256 self.labels = Some(all_labels);
257 self
258 }
259}
260
261#[cfg(feature = "serde")]
262#[test]
263fn test_serialize_miette_diagnostic() {
264 use serde_json::json;
265
266 use crate::diagnostic;
267
268 let diag = diagnostic!("message");
269 let json = json!({ "message": "message" });
270 assert_eq!(json!(diag), json);
271
272 let diag = diagnostic!(
273 code = "code",
274 help = "help",
275 url = "url",
276 labels = [
277 LabeledSpan::at_offset(0, "label1"),
278 LabeledSpan::at(1..3, "label2")
279 ],
280 severity = Severity::Warning,
281 "message"
282 );
283 let json = json!({
284 "message": "message",
285 "code": "code",
286 "help": "help",
287 "url": "url",
288 "severity": "Warning",
289 "labels": [
290 {
291 "span": {
292 "offset": 0,
293 "length": 0
294 },
295 "label": "label1",
296 "primary": false
297 },
298 {
299 "span": {
300 "offset": 1,
301 "length": 2
302 },
303 "label": "label2",
304 "primary": false
305 }
306 ]
307 });
308 assert_eq!(json!(diag), json);
309}
310
311#[cfg(feature = "serde")]
312#[test]
313fn test_deserialize_miette_diagnostic() {
314 use serde_json::json;
315
316 use crate::diagnostic;
317
318 let json = json!({ "message": "message" });
319 let diag = diagnostic!("message");
320 assert_eq!(diag, serde_json::from_value(json).unwrap());
321
322 let json = json!({
323 "message": "message",
324 "help": null,
325 "code": null,
326 "severity": null,
327 "url": null,
328 "labels": null
329 });
330 assert_eq!(diag, serde_json::from_value(json).unwrap());
331
332 let diag = diagnostic!(
333 code = "code",
334 help = "help",
335 url = "url",
336 labels = [
337 LabeledSpan::at_offset(0, "label1"),
338 LabeledSpan::at(1..3, "label2")
339 ],
340 severity = Severity::Warning,
341 "message"
342 );
343 let json = json!({
344 "message": "message",
345 "code": "code",
346 "help": "help",
347 "url": "url",
348 "severity": "Warning",
349 "labels": [
350 {
351 "span": {
352 "offset": 0,
353 "length": 0
354 },
355 "label": "label1",
356 "primary": false
357 },
358 {
359 "span": {
360 "offset": 1,
361 "length": 2
362 },
363 "label": "label2",
364 "primary": false
365 }
366 ]
367 });
368 assert_eq!(diag, serde_json::from_value(json).unwrap());
369}