use std::ops::Range;
use std::path::PathBuf;
use roxmltree::Node;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum IncludeCause {
#[error("cyclic include: {}", cycle_display(chain))]
Cycle {
chain: Vec<PathBuf>,
},
#[error("cannot read {}: {source}", path.display())]
Io {
path: PathBuf,
#[source]
source: std::io::Error,
},
#[error("include file not found")]
NotFound,
}
fn cycle_display(chain: &[PathBuf]) -> String {
chain
.iter()
.map(|p| p.display().to_string())
.collect::<Vec<_>>()
.join(" -> ")
}
#[derive(Debug, thiserror::Error, miette::Diagnostic)]
#[non_exhaustive]
pub enum ParseError {
#[error("malformed XML: {message}")]
#[diagnostic(code(ergo_sbe::schema_parse::malformed_xml))]
MalformedXml {
message: String,
#[source_code]
source_code: miette::NamedSource<String>,
#[label("here")]
span: Option<miette::SourceSpan>,
},
#[error("missing {what}")]
#[diagnostic(code(ergo_sbe::schema_parse::missing))]
Missing {
what: String,
#[source_code]
source_code: miette::NamedSource<String>,
#[label("missing here")]
span: Option<miette::SourceSpan>,
},
#[error("invalid {what}: {value}")]
#[diagnostic(code(ergo_sbe::schema_parse::invalid))]
Invalid {
what: String,
value: String,
#[source_code]
source_code: miette::NamedSource<String>,
#[label("invalid here")]
span: Option<miette::SourceSpan>,
},
#[error("resolution error: {error}")]
#[diagnostic(code(ergo_sbe::schema_parse::resolve))]
Resolve {
#[source_code]
source_code: miette::NamedSource<String>,
#[label("here")]
span: Option<miette::SourceSpan>,
#[label("related")]
second_label: Option<miette::SourceSpan>,
#[source]
error: Box<crate::resolve::ResolveError>,
},
#[error("cannot read {}: {source}", path.display())]
#[diagnostic(code(ergo_sbe::schema_parse::io))]
Io {
path: PathBuf,
#[source]
source: std::io::Error,
},
#[error("include error for '{href}': {cause}")]
#[diagnostic(code(ergo_sbe::schema_parse::include))]
Include {
href: String,
attempted: Vec<PathBuf>,
#[source]
cause: IncludeCause,
#[source_code]
source_code: miette::NamedSource<String>,
#[label("include error here")]
span: Option<miette::SourceSpan>,
},
}
impl ParseError {
pub(crate) fn malformed_xml(name: &str, message: impl Into<String>, xml: &str) -> Self {
Self::MalformedXml {
message: message.into(),
source_code: named_source(name, xml),
span: None,
}
}
pub(crate) fn io(path: impl Into<PathBuf>, source: std::io::Error) -> Self {
Self::Io {
path: path.into(),
source,
}
}
pub(crate) fn from_fault(name: &str, fault: Fault, input: &str) -> Self {
let source_code = named_source(name, input);
let span = fault.span.map(miette::SourceSpan::from);
match fault.kind {
FaultKind::Missing { what } => Self::Missing {
what,
source_code,
span,
},
FaultKind::Invalid { what, value } => Self::Invalid {
what,
value,
source_code,
span,
},
FaultKind::Include {
href,
attempted,
cause,
} => Self::Include {
href,
attempted,
cause,
source_code,
span,
},
}
}
}
impl From<crate::resolve::ResolveError> for ParseError {
fn from(mut e: crate::resolve::ResolveError) -> Self {
let source_code = e
.take_source_code()
.unwrap_or_else(|| miette::NamedSource::new("schema.xml", String::new()));
let (span, second_label) = e.take_spans();
Self::Resolve {
source_code,
span,
second_label,
error: Box::new(e),
}
}
}
pub(crate) fn named_source(name: &str, xml: &str) -> miette::NamedSource<String> {
miette::NamedSource::new(name, xml.to_owned())
}
#[derive(Debug)]
pub(crate) struct Fault {
pub(crate) kind: FaultKind,
pub(crate) span: Option<Range<usize>>,
}
#[derive(Debug)]
pub(crate) enum FaultKind {
Missing {
what: String,
},
Invalid {
what: String,
value: String,
},
Include {
href: String,
attempted: Vec<PathBuf>,
cause: IncludeCause,
},
}
impl Fault {
pub(crate) fn missing(node: Node<'_, '_>, what: impl Into<String>) -> Self {
Self {
kind: FaultKind::Missing { what: what.into() },
span: Some(node.range()),
}
}
pub(crate) fn missing_no_node(what: impl Into<String>) -> Self {
Self {
kind: FaultKind::Missing { what: what.into() },
span: None,
}
}
pub(crate) fn invalid(
node: Node<'_, '_>,
what: impl Into<String>,
value: impl Into<String>,
) -> Self {
Self {
kind: FaultKind::Invalid {
what: what.into(),
value: value.into(),
},
span: Some(node.range()),
}
}
pub(crate) fn include(
href: impl Into<String>,
attempted: Vec<PathBuf>,
cause: IncludeCause,
) -> Self {
Self {
kind: FaultKind::Include {
href: href.into(),
attempted,
cause,
},
span: None,
}
}
}