1 use std::{
2     error::Error,
3     fmt::{Debug, Display},
4 };
5 
6 #[cfg(feature = "serde")]
7 use serde::{Deserialize, Serialize};
8 
9 use 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))]
14 pub 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 
41 impl Display for MietteDiagnostic {
fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result42     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43         write!(f, "{}", &self.message)
44     }
45 }
46 
47 impl Error for MietteDiagnostic {}
48 
49 impl Diagnostic for MietteDiagnostic {
code<'a>(&'a self) -> Option<Box<dyn Display + 'a>>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 
severity(&self) -> Option<Severity>57     fn severity(&self) -> Option<Severity> {
58         self.severity
59     }
60 
help<'a>(&'a self) -> Option<Box<dyn Display + 'a>>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 
url<'a>(&'a self) -> Option<Box<dyn Display + 'a>>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 
labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>>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 
84 impl 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     /// ```
new(message: impl Into<String>) -> Self95     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     /// ```
with_code(mut self, code: impl Into<String>) -> Self116     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     /// ```
with_severity(mut self, severity: Severity) -> Self131     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     /// ```
with_help(mut self, help: impl Into<String>) -> Self146     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     /// ```
with_url(mut self, url: impl Into<String>) -> Self165     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     /// ```
with_label(mut self, label: impl Into<LabeledSpan>) -> Self185     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     /// ```
with_labels(mut self, labels: impl IntoIterator<Item = LabeledSpan>) -> Self208     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     /// ```
and_label(mut self, label: impl Into<LabeledSpan>) -> Self229     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     /// ```
and_labels(mut self, labels: impl IntoIterator<Item = LabeledSpan>) -> Self253     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.into_iter());
256         self.labels = Some(all_labels);
257         self
258     }
259 }
260 
261 #[cfg(feature = "serde")]
262 #[test]
test_serialize_miette_diagnostic()263 fn 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             },
297             {
298                 "span": {
299                     "offset": 1,
300                     "length": 2
301                 },
302                 "label": "label2"
303             }
304         ]
305     });
306     assert_eq!(json!(diag), json);
307 }
308 
309 #[cfg(feature = "serde")]
310 #[test]
test_deserialize_miette_diagnostic()311 fn test_deserialize_miette_diagnostic() {
312     use serde_json::json;
313 
314     use crate::diagnostic;
315 
316     let json = json!({ "message": "message" });
317     let diag = diagnostic!("message");
318     assert_eq!(diag, serde_json::from_value(json).unwrap());
319 
320     let json = json!({
321         "message": "message",
322         "help": null,
323         "code": null,
324         "severity": null,
325         "url": null,
326         "labels": null
327     });
328     assert_eq!(diag, serde_json::from_value(json).unwrap());
329 
330     let diag = diagnostic!(
331         code = "code",
332         help = "help",
333         url = "url",
334         labels = [
335             LabeledSpan::at_offset(0, "label1"),
336             LabeledSpan::at(1..3, "label2")
337         ],
338         severity = Severity::Warning,
339         "message"
340     );
341     let json = json!({
342         "message": "message",
343         "code": "code",
344         "help": "help",
345         "url": "url",
346         "severity": "Warning",
347         "labels": [
348             {
349                 "span": {
350                     "offset": 0,
351                     "length": 0
352                 },
353                 "label": "label1"
354             },
355             {
356                 "span": {
357                     "offset": 1,
358                     "length": 2
359                 },
360                 "label": "label2"
361             }
362         ]
363     });
364     assert_eq!(diag, serde_json::from_value(json).unwrap());
365 }
366