diff --git a/Cargo.toml b/Cargo.toml index c7867d2..b666123 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,10 +27,10 @@ winnow = { version = "=0.6.24", features = ["alloc", "unstable-recover"] } kdlv1 = { package = "kdl", version = "4.7.0", optional = true } [workspace.dependencies] -miette = { git = "https://github.com/TheLostLambda/miette.git" } +miette = { git = "https://github.com/zkat/miette.git", default-features = false } [dev-dependencies] -miette = { workspace = true, features = ["fancy"] } +miette = { workspace = true, features = ["derive", "fancy"] } thiserror = "2.0.12" pretty_assertions = "1.3.0" diff --git a/src/error.rs b/src/error.rs index d4b3a9a..71b0671 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,6 +1,6 @@ -use std::{error::Error, fmt::Display, sync::Arc}; +use std::{error::Error, fmt::Display, iter, sync::Arc}; -use miette::{Diagnostic, SourceSpan}; +use miette::{Diagnostic, LabeledSpan, Severity, SourceSpan}; #[cfg(doc)] use { @@ -33,14 +33,12 @@ use { /// ╰──── /// help: Floating point numbers must be base 10, and have numbers after the decimal point. /// ``` -#[derive(Debug, Diagnostic, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq)] pub struct KdlError { /// Original input that this failure came from. - #[source_code] pub input: Arc, /// Sub-diagnostics for this failure. - #[related] pub diagnostics: Vec, } @@ -51,17 +49,27 @@ impl Display for KdlError { } impl Error for KdlError {} +impl Diagnostic for KdlError { + fn source_code(&self) -> Option<&dyn miette::SourceCode> { + Some(&self.input) + } + + fn related<'a>(&'a self) -> Option + 'a>> { + Some(Box::new( + self.diagnostics.iter().map(|d| d as &dyn Diagnostic), + )) + } +} + /// An individual diagnostic message for a KDL parsing issue. /// /// While generally signifying errors, they can also be treated as warnings. -#[derive(Debug, Diagnostic, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq)] pub struct KdlDiagnostic { /// Shared source for the diagnostic. - #[source_code] pub input: Arc, /// Offset in chars of the error. - #[label("{}", label.clone().unwrap_or_else(|| "here".into()))] pub span: SourceSpan, /// Message for the error itself. @@ -71,12 +79,10 @@ pub struct KdlDiagnostic { pub label: Option, /// Suggestion for fixing the parser error. - #[help] pub help: Option, /// Severity level for the Diagnostic. - #[diagnostic(severity)] - pub severity: miette::Severity, + pub severity: Severity, } impl Display for KdlDiagnostic { @@ -90,6 +96,27 @@ impl Display for KdlDiagnostic { } impl Error for KdlDiagnostic {} +impl Diagnostic for KdlDiagnostic { + fn source_code(&self) -> Option<&dyn miette::SourceCode> { + Some(&self.input) + } + + fn severity(&self) -> Option { + Some(self.severity) + } + + fn help<'a>(&'a self) -> Option> { + self.help.as_ref().map(|s| Box::new(s) as Box) + } + + fn labels(&self) -> Option + '_>> { + let label = self.label.clone().unwrap_or_else(|| "here".to_owned()); + let labeled_span = LabeledSpan::new_with_span(Some(label), self.span); + + Some(Box::new(iter::once(labeled_span))) + } +} + #[cfg(feature = "v1")] impl From for KdlError { fn from(value: kdlv1::KdlError) -> Self { @@ -102,7 +129,7 @@ impl From for KdlError { message: Some(format!("{}", value.kind)), label: value.label.map(|x| x.into()), help: value.help.map(|x| x.into()), - severity: miette::Severity::Error, + severity: Severity::Error, }], } } @@ -110,32 +137,54 @@ impl From for KdlError { #[cfg(test)] mod tests { - use std::error::Error; - use super::*; #[test] fn kdl_error() { - let kdl_error = KdlError { + let kdl_diagnostic = KdlDiagnostic { input: Default::default(), - diagnostics: Default::default(), - }; - - assert_eq!(kdl_error.to_string(), "Failed to parse KDL document"); - assert!(kdl_error.source().is_none()); - } - - #[test] - fn kdl_diagnostic() { - let mut kdl_diagnostic = KdlDiagnostic { - input: Default::default(), - span: SourceSpan::new(0.into(), 1), + span: SourceSpan::new(0.into(), 0), message: Default::default(), label: Default::default(), help: Default::default(), severity: Default::default(), }; + let kdl_error = KdlError { + input: Arc::new("bark? i guess?".to_owned()), + diagnostics: vec![kdl_diagnostic.clone(), kdl_diagnostic], + }; + + // Test `Error` impl + assert_eq!(kdl_error.to_string(), "Failed to parse KDL document"); + assert!(kdl_error.source().is_none()); + + // Test `Diagnostic` impl + let related: Vec<_> = kdl_error.related().unwrap().collect(); + assert_eq!(related.len(), 2); + assert_eq!( + kdl_error + .source_code() + .unwrap() + .read_span(&SourceSpan::new(0.into(), 5), 0, 0) + .unwrap() + .data(), + b"bark?" + ); + } + + #[test] + fn kdl_diagnostic() { + let mut kdl_diagnostic = KdlDiagnostic { + input: Arc::new("Catastrophic failure!!!".to_owned()), + span: SourceSpan::new(0.into(), 3), + message: None, + label: Some("cute".to_owned()), + help: Some("try harder?".to_owned()), + severity: Severity::Error, + }; + + // Test `Error` impl assert_eq!(kdl_diagnostic.to_string(), "Unexpected error"); assert!(kdl_diagnostic.source().is_none()); @@ -143,5 +192,21 @@ mod tests { assert_eq!(kdl_diagnostic.to_string(), "mega bad news, kiddo"); assert!(kdl_diagnostic.source().is_none()); + + // Test `Diagnostic` impl + let labels: Vec<_> = kdl_diagnostic.labels().unwrap().collect(); + assert_eq!(labels.len(), 1); + assert_eq!(labels[0].label().unwrap(), "cute"); + assert_eq!( + kdl_diagnostic + .source_code() + .unwrap() + .read_span(labels[0].inner(), 0, 0) + .unwrap() + .data(), + b"Cat" + ); + assert_eq!(kdl_diagnostic.help().unwrap().to_string(), "try harder?"); + assert_eq!(kdl_diagnostic.severity().unwrap(), Severity::Error); } }