use crate::spanned::Spanned;
use core::fmt;
#[derive(Debug)]
struct SpannedDiagnostic {
message: String,
labels: Vec<miette::LabeledSpan>,
source_code: String,
}
impl fmt::Display for SpannedDiagnostic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.message)
}
}
impl std::error::Error for SpannedDiagnostic {}
impl miette::Diagnostic for SpannedDiagnostic {
fn code<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
Some(Box::new("noyalib::validation"))
}
fn source_code(&self) -> Option<&dyn miette::SourceCode> {
Some(&self.source_code)
}
fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
if self.labels.is_empty() {
None
} else {
Some(Box::new(self.labels.iter().cloned()))
}
}
}
pub fn spanned_error<T, M: fmt::Display>(
source: &str,
span: &Spanned<T>,
message: M,
) -> miette::Report {
let msg = message.to_string();
let start = span.start.index();
let end = span.end.index();
let len = if end > start { end - start } else { 1 };
miette::Report::new(SpannedDiagnostic {
message: msg.clone(),
labels: vec![miette::LabeledSpan::new(Some(msg), start, len)],
source_code: source.to_owned(),
})
}
pub fn spanned_error_with_context<T, U, M: fmt::Display, C: fmt::Display>(
source: &str,
primary_span: &Spanned<T>,
primary_message: M,
context_span: &Spanned<U>,
context_message: C,
) -> miette::Report {
let p_msg = primary_message.to_string();
let p_start = primary_span.start.index();
let p_end = primary_span.end.index();
let p_len = if p_end > p_start { p_end - p_start } else { 1 };
let c_msg = context_message.to_string();
let c_start = context_span.start.index();
let c_end = context_span.end.index();
let c_len = if c_end > c_start { c_end - c_start } else { 1 };
miette::Report::new(SpannedDiagnostic {
message: p_msg.clone(),
labels: vec![
miette::LabeledSpan::new(Some(p_msg), p_start, p_len),
miette::LabeledSpan::new(Some(c_msg), c_start, c_len),
],
source_code: source.to_owned(),
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Spanned;
#[test]
fn spanned_error_creates_report() {
let yaml = "port: 80\n";
#[derive(serde::Deserialize)]
struct Cfg {
port: Spanned<u16>,
}
let cfg: Cfg = crate::from_str(yaml).unwrap();
let report = spanned_error(yaml, &cfg.port, "port must be >= 1024");
let msg = format!("{report}");
assert!(msg.contains("port must be >= 1024"));
}
#[test]
fn spanned_error_diagnostic_has_labels() {
use miette::Diagnostic;
let yaml = "value: 42\n";
#[derive(serde::Deserialize)]
struct Cfg {
value: Spanned<i32>,
}
let cfg: Cfg = crate::from_str(yaml).unwrap();
let report = spanned_error(yaml, &cfg.value, "too small");
let diag: &dyn Diagnostic = report.as_ref();
assert!(diag.labels().is_some());
assert!(diag.code().is_some());
assert!(diag.source_code().is_some());
}
}