use std::fmt;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("I/O error for {path}: {source}")]
Io {
path: String,
#[source]
source: std::io::Error,
},
#[error("XML parse error at line {line}, column {column}: {message}")]
XmlParse {
line: usize,
column: usize,
message: String,
},
#[error("schema error in <{element}>{}: {message}", OptionalAt(.location))]
Schema {
element: String,
location: Option<String>,
message: String,
},
#[error("XPath syntax error in {context}: {message}\n expression: {expression}\n{caret}")]
XPathSyntax {
context: String,
expression: String,
position: usize,
message: String,
caret: String,
},
#[error("XPath evaluation error in {context}: {message}")]
XPathEval {
context: String,
message: String,
},
#[error("unknown phase {phase:?}; the schema defines: {available}")]
UnknownPhase {
phase: String,
available: String,
},
#[error(
"unsupported query binding {binding:?}; this crate implements XPath 1.0, \
so the supported bindings are \"xslt\" and \"xpath\". \
Set allow_unknown_query_binding to compile it anyway."
)]
UnsupportedQueryBinding {
binding: String,
},
#[error("include cycle: {chain}")]
IncludeCycle {
chain: String,
},
#[error("include depth limit of {limit} exceeded at {href}")]
IncludeDepth {
limit: usize,
href: String,
},
#[error("cannot resolve {href}: {message}")]
Resolve {
href: String,
message: String,
},
#[error("streaming validation is not possible: {message}")]
Streaming {
message: String,
},
}
impl Error {
pub(crate) fn schema(
element: impl Into<String>,
location: Option<String>,
message: impl Into<String>,
) -> Self {
Error::Schema {
element: element.into(),
location,
message: message.into(),
}
}
pub(crate) fn xpath_syntax(
context: impl Into<String>,
expression: &str,
position: usize,
message: impl Into<String>,
) -> Self {
let prefix_width = expression
.get(..position.min(expression.len()))
.unwrap_or("")
.chars()
.count();
let caret = format!("{:width$}^", " ", width = prefix_width + 14);
Error::XPathSyntax {
context: context.into(),
expression: expression.to_string(),
position,
message: message.into(),
caret,
}
}
pub(crate) fn xpath_eval(context: impl Into<String>, message: impl Into<String>) -> Self {
Error::XPathEval {
context: context.into(),
message: message.into(),
}
}
pub(crate) fn streaming(message: impl Into<String>) -> Self {
Error::Streaming {
message: message.into(),
}
}
}
struct OptionalAt<'a>(&'a Option<String>);
impl fmt::Display for OptionalAt<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0 {
Some(location) => write!(f, " at {location}"),
None => Ok(()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn schema_error_includes_location_when_present() {
let e = Error::schema("rule", Some("pattern[1]/rule[2]".into()), "no context");
let text = e.to_string();
assert!(text.contains("<rule>"), "{text}");
assert!(text.contains("at pattern[1]/rule[2]"), "{text}");
}
#[test]
fn schema_error_omits_location_when_absent() {
let e = Error::schema("schema", None, "empty");
assert_eq!(e.to_string(), "schema error in <schema>: empty");
}
#[test]
fn xpath_syntax_error_renders_a_caret() {
let e = Error::xpath_syntax("test", "count(a", 7, "expected ')'");
let text = e.to_string();
assert!(text.contains("expected ')'"), "{text}");
assert!(text.contains('^'), "{text}");
}
}