#[allow(unused_imports)]
use crate::prelude::*;
#[allow(unused_imports)]
use crate::spanned::Spanned;
#[cfg(any(feature = "garde", feature = "validator"))]
fn spanned_byte_range<T>(spanned: &Spanned<T>, source: &str) -> core::ops::Range<usize> {
let len = source.len();
let start = spanned.start.index().min(len);
let end = spanned.end.index().max(start).min(len);
if end == start {
return start..(start + 1).min(len.max(start + 1));
}
start..end
}
#[cfg(any(feature = "garde", feature = "validator"))]
#[derive(Debug)]
struct ValidationDiagnostic {
summary: String,
src: miette::NamedSource<String>,
span: miette::SourceSpan,
}
#[cfg(any(feature = "garde", feature = "validator"))]
impl ValidationDiagnostic {
fn new<T>(spanned: &Spanned<T>, source: String, summary: String, name: String) -> Self {
let range = spanned_byte_range(spanned, &source);
let span: miette::SourceSpan = (range.start, range.end - range.start).into();
Self {
summary,
src: miette::NamedSource::new(name, source),
span,
}
}
}
#[cfg(any(feature = "garde", feature = "validator"))]
impl core::fmt::Display for ValidationDiagnostic {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(&self.summary)
}
}
#[cfg(any(feature = "garde", feature = "validator"))]
impl std::error::Error for ValidationDiagnostic {}
#[cfg(any(feature = "garde", feature = "validator"))]
impl miette::Diagnostic for ValidationDiagnostic {
fn code<'a>(&'a self) -> Option<Box<dyn core::fmt::Display + 'a>> {
Some(Box::new("noyalib::validated"))
}
fn source_code(&self) -> Option<&dyn miette::SourceCode> {
Some(&self.src)
}
fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
Some(Box::new(core::iter::once(
miette::LabeledSpan::new_with_span(
Some("validation failed here".to_string()),
self.span,
),
)))
}
}
#[cfg(all(feature = "miette", feature = "garde"))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "miette", feature = "garde"))))]
#[must_use]
pub fn garde_errors_to_miette<T>(
spanned: &Spanned<T>,
errors: &garde::Report,
source: &str,
name: impl Into<String>,
) -> miette::Report {
let summary = format_garde(errors);
let diag = ValidationDiagnostic::new(spanned, source.to_string(), summary, name.into());
miette::Report::new(diag)
}
#[cfg(all(feature = "miette", feature = "validator"))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "miette", feature = "validator"))))]
#[must_use]
pub fn validator_errors_to_miette<T>(
spanned: &Spanned<T>,
errors: &validator::ValidationErrors,
source: &str,
name: impl Into<String>,
) -> miette::Report {
let summary = format_validator(errors);
let diag = ValidationDiagnostic::new(spanned, source.to_string(), summary, name.into());
miette::Report::new(diag)
}
#[cfg(feature = "garde")]
fn format_garde(errors: &garde::Report) -> String {
use core::fmt::Write as _;
let mut out = String::with_capacity(64);
out.push_str("validation failed: ");
let mut first = true;
for (path, error) in errors.iter() {
if !first {
out.push_str("; ");
}
first = false;
let _ = write!(out, "{path}: {error}");
}
if first {
out.push_str("<no details>");
}
out
}
#[cfg(feature = "validator")]
fn format_validator(errors: &validator::ValidationErrors) -> String {
use core::fmt::Write as _;
let mut out = String::with_capacity(64);
out.push_str("validation failed: ");
let mut first = true;
for (field, errs) in errors.field_errors() {
if !first {
out.push_str("; ");
}
first = false;
let _ = write!(out, "{field}: ");
let mut f_first = true;
for e in errs {
if !f_first {
out.push_str(", ");
}
f_first = false;
let _ = write!(out, "{e}");
}
}
if first {
out.push_str("<no details>");
}
out
}
#[cfg(feature = "miette")]
#[cfg_attr(docsrs, doc(cfg(feature = "miette")))]
#[must_use]
pub fn clamped_span(start: usize, end: usize, source_len: usize) -> miette::SourceSpan {
let s = start.min(source_len);
let e = end.max(s).min(source_len);
let e = if e == s {
(s + 1).min(source_len.max(s + 1))
} else {
e
};
(s, e - s).into()
}