| use std::{ | |
| error::Error, | |
| fmt::{Debug, Display}, | |
| }; | |
| #[cfg(feature = "serde")] | |
| use serde::{Deserialize, Serialize}; | |
| use crate::{Diagnostic, LabeledSpan, Severity}; | |
| /// Diagnostic that can be created at runtime. | |
| #[derive(Debug, Clone, PartialEq, Eq)] | |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | |
| pub struct MietteDiagnostic { | |
| /// Displayed diagnostic message | |
| pub message: String, | |
| /// Unique diagnostic code to look up more information | |
| /// about this Diagnostic. Ideally also globally unique, and documented | |
| /// in the toplevel crate's documentation for easy searching. | |
| /// Rust path format (`foo::bar::baz`) is recommended, but more classic | |
| /// codes like `E0123` will work just fine | |
| #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] | |
| pub code: Option<String>, | |
| /// [`Diagnostic`] severity. Intended to be used by | |
| /// [`ReportHandler`](crate::ReportHandler)s to change the way different | |
| /// [`Diagnostic`]s are displayed. Defaults to [`Severity::Error`] | |
| #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] | |
| pub severity: Option<Severity>, | |
| /// Additional help text related to this Diagnostic | |
| #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] | |
| pub help: Option<String>, | |
| /// URL to visit for a more detailed explanation/help about this | |
| /// [`Diagnostic`]. | |
| #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] | |
| pub url: Option<String>, | |
| /// Labels to apply to this `Diagnostic`'s [`Diagnostic::source_code`] | |
| #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] | |
| pub labels: Option<Vec<LabeledSpan>>, | |
| } | |
| impl Display for MietteDiagnostic { | |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
| write!(f, "{}", &self.message) | |
| } | |
| } | |
| impl Error for MietteDiagnostic {} | |
| impl Diagnostic for MietteDiagnostic { | |
| fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> { | |
| self.code | |
| .as_ref() | |
| .map(Box::new) | |
| .map(|c| c as Box<dyn Display>) | |
| } | |
| fn severity(&self) -> Option<Severity> { | |
| self.severity | |
| } | |
| fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> { | |
| self.help | |
| .as_ref() | |
| .map(Box::new) | |
| .map(|c| c as Box<dyn Display>) | |
| } | |
| fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> { | |
| self.url | |
| .as_ref() | |
| .map(Box::new) | |
| .map(|c| c as Box<dyn Display>) | |
| } | |
| fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> { | |
| self.labels | |
| .as_ref() | |
| .map(|ls| ls.iter().cloned()) | |
| .map(Box::new) | |
| .map(|b| b as Box<dyn Iterator<Item = LabeledSpan>>) | |
| } | |
| } | |
| impl MietteDiagnostic { | |
| /// Create a new dynamic diagnostic with the given message. | |
| /// | |
| /// # Examples | |
| /// ``` | |
| /// use miette::{Diagnostic, MietteDiagnostic, Severity}; | |
| /// | |
| /// let diag = MietteDiagnostic::new("Oops, something went wrong!"); | |
| /// assert_eq!(diag.to_string(), "Oops, something went wrong!"); | |
| /// assert_eq!(diag.message, "Oops, something went wrong!"); | |
| /// ``` | |
| pub fn new(message: impl Into<String>) -> Self { | |
| Self { | |
| message: message.into(), | |
| labels: None, | |
| severity: None, | |
| code: None, | |
| help: None, | |
| url: None, | |
| } | |
| } | |
| /// Return new diagnostic with the given code. | |
| /// | |
| /// # Examples | |
| /// ``` | |
| /// use miette::{Diagnostic, MietteDiagnostic}; | |
| /// | |
| /// let diag = MietteDiagnostic::new("Oops, something went wrong!").with_code("foo::bar::baz"); | |
| /// assert_eq!(diag.message, "Oops, something went wrong!"); | |
| /// assert_eq!(diag.code, Some("foo::bar::baz".to_string())); | |
| /// ``` | |
| pub fn with_code(mut self, code: impl Into<String>) -> Self { | |
| self.code = Some(code.into()); | |
| self | |
| } | |
| /// Return new diagnostic with the given severity. | |
| /// | |
| /// # Examples | |
| /// ``` | |
| /// use miette::{Diagnostic, MietteDiagnostic, Severity}; | |
| /// | |
| /// let diag = MietteDiagnostic::new("I warn you to stop!").with_severity(Severity::Warning); | |
| /// assert_eq!(diag.message, "I warn you to stop!"); | |
| /// assert_eq!(diag.severity, Some(Severity::Warning)); | |
| /// ``` | |
| pub fn with_severity(mut self, severity: Severity) -> Self { | |
| self.severity = Some(severity); | |
| self | |
| } | |
| /// Return new diagnostic with the given help message. | |
| /// | |
| /// # Examples | |
| /// ``` | |
| /// use miette::{Diagnostic, MietteDiagnostic}; | |
| /// | |
| /// let diag = MietteDiagnostic::new("PC is not working").with_help("Try to reboot it again"); | |
| /// assert_eq!(diag.message, "PC is not working"); | |
| /// assert_eq!(diag.help, Some("Try to reboot it again".to_string())); | |
| /// ``` | |
| pub fn with_help(mut self, help: impl Into<String>) -> Self { | |
| self.help = Some(help.into()); | |
| self | |
| } | |
| /// Return new diagnostic with the given URL. | |
| /// | |
| /// # Examples | |
| /// ``` | |
| /// use miette::{Diagnostic, MietteDiagnostic}; | |
| /// | |
| /// let diag = MietteDiagnostic::new("PC is not working") | |
| /// .with_url("https://letmegooglethat.com/?q=Why+my+pc+doesn%27t+work"); | |
| /// assert_eq!(diag.message, "PC is not working"); | |
| /// assert_eq!( | |
| /// diag.url, | |
| /// Some("https://letmegooglethat.com/?q=Why+my+pc+doesn%27t+work".to_string()) | |
| /// ); | |
| /// ``` | |
| pub fn with_url(mut self, url: impl Into<String>) -> Self { | |
| self.url = Some(url.into()); | |
| self | |
| } | |
| /// Return new diagnostic with the given label. | |
| /// | |
| /// Discards previous labels | |
| /// | |
| /// # Examples | |
| /// ``` | |
| /// use miette::{Diagnostic, LabeledSpan, MietteDiagnostic}; | |
| /// | |
| /// let source = "cpp is the best language"; | |
| /// | |
| /// let label = LabeledSpan::at(0..3, "This should be Rust"); | |
| /// let diag = MietteDiagnostic::new("Wrong best language").with_label(label.clone()); | |
| /// assert_eq!(diag.message, "Wrong best language"); | |
| /// assert_eq!(diag.labels, Some(vec![label])); | |
| /// ``` | |
| pub fn with_label(mut self, label: impl Into<LabeledSpan>) -> Self { | |
| self.labels = Some(vec![label.into()]); | |
| self | |
| } | |
| /// Return new diagnostic with the given labels. | |
| /// | |
| /// Discards previous labels | |
| /// | |
| /// # Examples | |
| /// ``` | |
| /// use miette::{Diagnostic, LabeledSpan, MietteDiagnostic}; | |
| /// | |
| /// let source = "helo wrld"; | |
| /// | |
| /// let labels = vec![ | |
| /// LabeledSpan::at_offset(3, "add 'l'"), | |
| /// LabeledSpan::at_offset(6, "add 'r'"), | |
| /// ]; | |
| /// let diag = MietteDiagnostic::new("Typos in 'hello world'").with_labels(labels.clone()); | |
| /// assert_eq!(diag.message, "Typos in 'hello world'"); | |
| /// assert_eq!(diag.labels, Some(labels)); | |
| /// ``` | |
| pub fn with_labels(mut self, labels: impl IntoIterator<Item = LabeledSpan>) -> Self { | |
| self.labels = Some(labels.into_iter().collect()); | |
| self | |
| } | |
| /// Return new diagnostic with new label added to the existing ones. | |
| /// | |
| /// # Examples | |
| /// ``` | |
| /// use miette::{Diagnostic, LabeledSpan, MietteDiagnostic}; | |
| /// | |
| /// let source = "helo wrld"; | |
| /// | |
| /// let label1 = LabeledSpan::at_offset(3, "add 'l'"); | |
| /// let label2 = LabeledSpan::at_offset(6, "add 'r'"); | |
| /// let diag = MietteDiagnostic::new("Typos in 'hello world'") | |
| /// .and_label(label1.clone()) | |
| /// .and_label(label2.clone()); | |
| /// assert_eq!(diag.message, "Typos in 'hello world'"); | |
| /// assert_eq!(diag.labels, Some(vec![label1, label2])); | |
| /// ``` | |
| pub fn and_label(mut self, label: impl Into<LabeledSpan>) -> Self { | |
| let mut labels = self.labels.unwrap_or_default(); | |
| labels.push(label.into()); | |
| self.labels = Some(labels); | |
| self | |
| } | |
| /// Return new diagnostic with new labels added to the existing ones. | |
| /// | |
| /// # Examples | |
| /// ``` | |
| /// use miette::{Diagnostic, LabeledSpan, MietteDiagnostic}; | |
| /// | |
| /// let source = "helo wrld"; | |
| /// | |
| /// let label1 = LabeledSpan::at_offset(3, "add 'l'"); | |
| /// let label2 = LabeledSpan::at_offset(6, "add 'r'"); | |
| /// let label3 = LabeledSpan::at_offset(9, "add '!'"); | |
| /// let diag = MietteDiagnostic::new("Typos in 'hello world!'") | |
| /// .and_label(label1.clone()) | |
| /// .and_labels([label2.clone(), label3.clone()]); | |
| /// assert_eq!(diag.message, "Typos in 'hello world!'"); | |
| /// assert_eq!(diag.labels, Some(vec![label1, label2, label3])); | |
| /// ``` | |
| pub fn and_labels(mut self, labels: impl IntoIterator<Item = LabeledSpan>) -> Self { | |
| let mut all_labels = self.labels.unwrap_or_default(); | |
| all_labels.extend(labels.into_iter()); | |
| self.labels = Some(all_labels); | |
| self | |
| } | |
| } | |
| #[cfg(feature = "serde")] | |
| #[test] | |
| fn test_serialize_miette_diagnostic() { | |
| use serde_json::json; | |
| use crate::diagnostic; | |
| let diag = diagnostic!("message"); | |
| let json = json!({ "message": "message" }); | |
| assert_eq!(json!(diag), json); | |
| let diag = diagnostic!( | |
| code = "code", | |
| help = "help", | |
| url = "url", | |
| labels = [ | |
| LabeledSpan::at_offset(0, "label1"), | |
| LabeledSpan::at(1..3, "label2") | |
| ], | |
| severity = Severity::Warning, | |
| "message" | |
| ); | |
| let json = json!({ | |
| "message": "message", | |
| "code": "code", | |
| "help": "help", | |
| "url": "url", | |
| "severity": "Warning", | |
| "labels": [ | |
| { | |
| "span": { | |
| "offset": 0, | |
| "length": 0 | |
| }, | |
| "label": "label1" | |
| }, | |
| { | |
| "span": { | |
| "offset": 1, | |
| "length": 2 | |
| }, | |
| "label": "label2" | |
| } | |
| ] | |
| }); | |
| assert_eq!(json!(diag), json); | |
| } | |
| #[cfg(feature = "serde")] | |
| #[test] | |
| fn test_deserialize_miette_diagnostic() { | |
| use serde_json::json; | |
| use crate::diagnostic; | |
| let json = json!({ "message": "message" }); | |
| let diag = diagnostic!("message"); | |
| assert_eq!(diag, serde_json::from_value(json).unwrap()); | |
| let json = json!({ | |
| "message": "message", | |
| "help": null, | |
| "code": null, | |
| "severity": null, | |
| "url": null, | |
| "labels": null | |
| }); | |
| assert_eq!(diag, serde_json::from_value(json).unwrap()); | |
| let diag = diagnostic!( | |
| code = "code", | |
| help = "help", | |
| url = "url", | |
| labels = [ | |
| LabeledSpan::at_offset(0, "label1"), | |
| LabeledSpan::at(1..3, "label2") | |
| ], | |
| severity = Severity::Warning, | |
| "message" | |
| ); | |
| let json = json!({ | |
| "message": "message", | |
| "code": "code", | |
| "help": "help", | |
| "url": "url", | |
| "severity": "Warning", | |
| "labels": [ | |
| { | |
| "span": { | |
| "offset": 0, | |
| "length": 0 | |
| }, | |
| "label": "label1" | |
| }, | |
| { | |
| "span": { | |
| "offset": 1, | |
| "length": 2 | |
| }, | |
| "label": "label2" | |
| } | |
| ] | |
| }); | |
| assert_eq!(diag, serde_json::from_value(json).unwrap()); | |
| } |