| 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()); |
| } |