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