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