diff options
Diffstat (limited to 'vendor/miette/src/miette_diagnostic.rs')
-rw-r--r-- | vendor/miette/src/miette_diagnostic.rs | 365 |
1 files changed, 365 insertions, 0 deletions
diff --git a/vendor/miette/src/miette_diagnostic.rs b/vendor/miette/src/miette_diagnostic.rs new file mode 100644 index 0000000..67b75d0 --- /dev/null +++ b/vendor/miette/src/miette_diagnostic.rs @@ -0,0 +1,365 @@ +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()); +} |