use std::fmt;
pub type XmlResult<T> = Result<T, XmlError>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ErrorKind {
InvalidName,
InvalidNamespace,
DuplicateNamespacePrefix,
UnknownNamespacePrefix,
InvalidOperation,
NotFound,
Parse,
Build,
Query,
Validation,
Signature,
Io,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Span {
line: Option<usize>,
column: Option<usize>,
}
impl Span {
pub fn new(line: usize, column: usize) -> Self {
Self {
line: Some(line),
column: Some(column),
}
}
pub fn unknown() -> Self {
Self {
line: None,
column: None,
}
}
pub fn line(&self) -> Option<usize> {
self.line
}
pub fn column(&self) -> Option<usize> {
self.column
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct XmlError {
kind: ErrorKind,
message: String,
span: Option<Span>,
}
impl XmlError {
pub fn new(kind: ErrorKind, message: impl Into<String>) -> Self {
Self {
kind,
message: message.into(),
span: None,
}
}
pub fn with_span(mut self, span: Span) -> Self {
self.span = Some(span);
self
}
pub fn invalid_name(name: impl AsRef<str>, reason: impl AsRef<str>) -> Self {
Self::new(
ErrorKind::InvalidName,
format!("invalid XML name `{}`: {}", name.as_ref(), reason.as_ref()),
)
}
pub fn kind(&self) -> &ErrorKind {
&self.kind
}
pub fn message(&self) -> &str {
&self.message
}
pub fn span(&self) -> Option<Span> {
self.span
}
}
impl fmt::Display for XmlError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.span {
Some(span) => match (span.line(), span.column()) {
(Some(line), Some(column)) => {
write!(
f,
"{:?}: {} at line {}, column {}",
self.kind, self.message, line, column
)
}
_ => write!(f, "{:?}: {} at unknown location", self.kind, self.message),
},
None => write!(f, "{:?}: {}", self.kind, self.message),
}
}
}
impl std::error::Error for XmlError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn error_includes_kind_and_message() {
let error = XmlError::new(ErrorKind::InvalidName, "bad name");
assert_eq!(error.kind(), &ErrorKind::InvalidName);
assert_eq!(error.message(), "bad name");
assert_eq!(error.span(), None);
}
#[test]
fn error_can_include_span() {
let error = XmlError::new(ErrorKind::Parse, "unexpected token").with_span(Span::new(3, 9));
assert_eq!(error.span(), Some(Span::new(3, 9)));
assert!(error.to_string().contains("line 3, column 9"));
}
}