use std::fmt::{self, Display};
use std::io;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)]
pub struct Error {
kind: ErrorKind,
position: Option<Position>,
}
#[derive(Debug, Clone, Copy)]
pub struct Position {
pub line: usize,
pub column: usize,
pub offset: usize,
}
#[derive(Debug)]
pub enum ErrorKind {
Io(io::Error),
UnexpectedEof,
Syntax(String),
InvalidName(String),
MissingAttribute(String),
UnexpectedElement(String),
UnexpectedAttribute(String),
InvalidValue(String),
UnclosedTag(String),
MismatchedTag {
expected: String,
found: String,
},
InvalidEscape(String),
InvalidUtf8,
Custom(String),
Unsupported(String),
}
impl Error {
#[inline]
pub fn new(kind: ErrorKind) -> Self {
Self { kind, position: None }
}
#[inline]
pub fn with_position(mut self, position: Position) -> Self {
self.position = Some(position);
self
}
#[inline]
pub fn kind(&self) -> &ErrorKind {
&self.kind
}
#[inline]
pub fn position(&self) -> Option<Position> {
self.position
}
#[inline]
pub fn unexpected_eof() -> Self {
Self::new(ErrorKind::UnexpectedEof)
}
#[inline]
pub fn syntax<S: Into<String>>(msg: S) -> Self {
Self::new(ErrorKind::Syntax(msg.into()))
}
#[inline]
pub fn invalid_name<S: Into<String>>(name: S) -> Self {
Self::new(ErrorKind::InvalidName(name.into()))
}
#[inline]
pub fn invalid_value<S: Into<String>>(msg: S) -> Self {
Self::new(ErrorKind::InvalidValue(msg.into()))
}
#[inline]
pub fn unclosed_tag<S: Into<String>>(tag: S) -> Self {
Self::new(ErrorKind::UnclosedTag(tag.into()))
}
#[inline]
pub fn mismatched_tag<S: Into<String>>(expected: S, found: S) -> Self {
Self::new(ErrorKind::MismatchedTag {
expected: expected.into(),
found: found.into(),
})
}
#[inline]
pub fn invalid_escape<S: Into<String>>(seq: S) -> Self {
Self::new(ErrorKind::InvalidEscape(seq.into()))
}
#[inline]
pub fn custom<S: Into<String>>(msg: S) -> Self {
Self::new(ErrorKind::Custom(msg.into()))
}
#[inline]
pub fn unsupported<S: Into<String>>(msg: S) -> Self {
Self::new(ErrorKind::Unsupported(msg.into()))
}
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.kind {
ErrorKind::Io(e) => write!(f, "I/O error: {}", e),
ErrorKind::UnexpectedEof => write!(f, "unexpected end of input"),
ErrorKind::Syntax(msg) => write!(f, "syntax error: {}", msg),
ErrorKind::InvalidName(name) => write!(f, "invalid XML name: {}", name),
ErrorKind::MissingAttribute(name) => write!(f, "missing required attribute: {}", name),
ErrorKind::UnexpectedElement(name) => write!(f, "unexpected element: {}", name),
ErrorKind::UnexpectedAttribute(name) => write!(f, "unexpected attribute: {}", name),
ErrorKind::InvalidValue(msg) => write!(f, "invalid value: {}", msg),
ErrorKind::UnclosedTag(tag) => write!(f, "unclosed tag: <{}>", tag),
ErrorKind::MismatchedTag { expected, found } => {
write!(f, "mismatched closing tag: expected </{}>, found </{}>", expected, found)
}
ErrorKind::InvalidEscape(seq) => write!(f, "invalid escape sequence: {}", seq),
ErrorKind::InvalidUtf8 => write!(f, "invalid UTF-8"),
ErrorKind::Custom(msg) => write!(f, "{}", msg),
ErrorKind::Unsupported(msg) => write!(f, "unsupported: {}", msg),
}?;
if let Some(pos) = self.position {
write!(f, " at line {}, column {} (offset {})", pos.line, pos.column, pos.offset)?;
}
Ok(())
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match &self.kind {
ErrorKind::Io(e) => Some(e),
_ => None,
}
}
}
impl From<io::Error> for Error {
fn from(e: io::Error) -> Self {
Self::new(ErrorKind::Io(e))
}
}
impl serde::de::Error for Error {
fn custom<T: Display>(msg: T) -> Self {
Self::custom(msg.to_string())
}
}
impl serde::ser::Error for Error {
fn custom<T: Display>(msg: T) -> Self {
Self::custom(msg.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_display() {
let err = Error::syntax("expected '>'");
assert_eq!(err.to_string(), "syntax error: expected '>'");
}
#[test]
fn test_error_with_position() {
let err = Error::syntax("expected '>'")
.with_position(Position { line: 5, column: 10, offset: 42 });
assert_eq!(
err.to_string(),
"syntax error: expected '>' at line 5, column 10 (offset 42)"
);
}
#[test]
fn test_mismatched_tag_error() {
let err = Error::mismatched_tag("foo", "bar");
assert_eq!(
err.to_string(),
"mismatched closing tag: expected </foo>, found </bar>"
);
}
#[test]
fn test_io_error() {
let io_err = io::Error::new(io::ErrorKind::NotFound, "file not found");
let err = Error::from(io_err);
assert!(err.to_string().contains("I/O error"));
}
#[test]
fn test_custom_error() {
let err = Error::custom("something went wrong");
assert_eq!(err.to_string(), "something went wrong");
}
}