use super::ValidationError;
use miette::{Diagnostic, NamedSource, SourceSpan};
#[derive(Debug)]
pub struct ValidationDiagnostic {
pub error: ValidationError,
pub src: Option<NamedSource<String>>,
pub span: Option<SourceSpan>,
}
impl std::fmt::Display for ValidationDiagnostic {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(&self.error, f)
}
}
impl std::error::Error for ValidationDiagnostic {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.error.source()
}
}
impl Diagnostic for ValidationDiagnostic {
fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
self.error.code()
}
fn severity(&self) -> Option<miette::Severity> {
self.error.severity()
}
fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
self.error.help()
}
fn url<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
self.error.url()
}
fn source_code(&self) -> Option<&dyn miette::SourceCode> {
self.src
.as_ref()
.map(|s| s as &dyn miette::SourceCode)
.or_else(|| self.error.source_code())
}
fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
if let Some(span) = self.span {
Some(Box::new(std::iter::once(
miette::LabeledSpan::new_with_span(Some("here".to_string()), span),
)))
} else {
self.error.labels()
}
}
}
impl ValidationDiagnostic {
pub fn new(error: ValidationError, source: Option<(&str, &str)>) -> Self {
let (src, span) = match source {
Some((filename, contents)) => {
let span = find_offending_span(&error, contents);
(Some(NamedSource::new(filename, contents.to_string())), span)
}
None => (None, None),
};
Self { error, src, span }
}
}
fn find_offending_span(err: &ValidationError, src: &str) -> Option<SourceSpan> {
let needle: &str = match err {
ValidationError::DuplicateModuleName(n) => Some(n.as_str()),
ValidationError::InvalidModuleName(n, _) => Some(n.as_str()),
ValidationError::DuplicateFunctionName { function, .. } => Some(function.as_str()),
ValidationError::DuplicateParamName { param, .. } => Some(param.as_str()),
ValidationError::ReservedKeyword(n) => Some(n.as_str()),
ValidationError::InvalidIdentifier(n, _) => Some(n.as_str()),
ValidationError::DuplicateErrorName { name, .. } => Some(name.as_str()),
ValidationError::InvalidErrorCode { name, .. } => Some(name.as_str()),
ValidationError::NameCollisionWithErrorDomain { name, .. } => Some(name.as_str()),
ValidationError::DuplicateStructName { name, .. } => Some(name.as_str()),
ValidationError::DuplicateStructField { field, .. } => Some(field.as_str()),
ValidationError::EmptyStruct { name, .. } => Some(name.as_str()),
ValidationError::DuplicateEnumName { name, .. } => Some(name.as_str()),
ValidationError::EmptyEnum { name, .. } => Some(name.as_str()),
ValidationError::DuplicateEnumVariant { variant, .. } => Some(variant.as_str()),
ValidationError::UnknownTypeRef { name } => Some(name.as_str()),
ValidationError::DuplicateCallbackName { name, .. } => Some(name.as_str()),
ValidationError::UnsupportedCallbackParamType { param, .. } => Some(param.as_str()),
ValidationError::ListenerCallbackNotFound { callback, .. } => Some(callback.as_str()),
ValidationError::DuplicateListenerName { name, .. } => Some(name.as_str()),
ValidationError::BuilderStructEmpty { name, .. } => Some(name.as_str()),
ValidationError::UnsupportedSchemaVersion { version, .. } => Some(version.as_str()),
ValidationError::AsyncIteratorReturn { function, .. } => Some(function.as_str()),
_ => None,
}?;
let quoted = format!("\"{needle}\"");
if let Some(pos) = src.find("ed) {
return Some(SourceSpan::new(pos.into(), quoted.len()));
}
src.find(needle)
.map(|pos| SourceSpan::new(pos.into(), needle.len()))
}