use std::{error::Error, fmt::Display, iter, sync::Arc};
use miette::{Diagnostic, LabeledSpan, Severity, SourceSpan};
#[cfg(doc)]
use {
crate::KdlNode,
std::convert::{TryFrom, TryInto},
};
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct KdlError {
pub input: Arc<String>,
pub diagnostics: Vec<KdlDiagnostic>,
}
impl Display for KdlError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Failed to parse KDL document")
}
}
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<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
Some(Box::new(
self.diagnostics.iter().map(|d| d as &dyn Diagnostic),
))
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct KdlDiagnostic {
pub input: Arc<String>,
pub span: SourceSpan,
pub message: Option<String>,
pub label: Option<String>,
pub help: Option<String>,
pub severity: Severity,
}
impl Display for KdlDiagnostic {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let message = self
.message
.clone()
.unwrap_or_else(|| "Unexpected error".into());
write!(f, "{message}")
}
}
impl Error for KdlDiagnostic {}
impl Diagnostic for KdlDiagnostic {
fn source_code(&self) -> Option<&dyn miette::SourceCode> {
Some(&self.input)
}
fn severity(&self) -> Option<Severity> {
Some(self.severity)
}
fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
self.help.as_ref().map(|s| Box::new(s) as Box<dyn Display>)
}
fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
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<kdlv1::KdlError> for KdlError {
fn from(value: kdlv1::KdlError) -> Self {
let input = Arc::new(value.input);
Self {
input: input.clone(),
diagnostics: vec![KdlDiagnostic {
input,
span: SourceSpan::new(value.span.offset().into(), value.span.len()),
message: Some(format!("{}", value.kind)),
label: value.label.map(|x| x.into()),
help: value.help.map(|x| x.into()),
severity: Severity::Error,
}],
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn kdl_error() {
let kdl_diagnostic = KdlDiagnostic {
input: Default::default(),
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],
};
assert_eq!(kdl_error.to_string(), "Failed to parse KDL document");
assert!(kdl_error.source().is_none());
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,
};
assert_eq!(kdl_diagnostic.to_string(), "Unexpected error");
assert!(kdl_diagnostic.source().is_none());
kdl_diagnostic.message = Some("mega bad news, kiddo".to_owned());
assert_eq!(kdl_diagnostic.to_string(), "mega bad news, kiddo");
assert!(kdl_diagnostic.source().is_none());
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);
}
}