serde_xml/
error.rs

1//! Error types for XML serialization and deserialization.
2
3use std::fmt::{self, Display};
4use std::io;
5
6/// Result type alias for serde_xml operations.
7pub type Result<T> = std::result::Result<T, Error>;
8
9/// Error type for XML serialization and deserialization.
10#[derive(Debug)]
11pub struct Error {
12    kind: ErrorKind,
13    position: Option<Position>,
14}
15
16/// Position information for error reporting.
17#[derive(Debug, Clone, Copy)]
18pub struct Position {
19    /// Line number (1-indexed).
20    pub line: usize,
21    /// Column number (1-indexed).
22    pub column: usize,
23    /// Byte offset from start.
24    pub offset: usize,
25}
26
27/// The kind of error that occurred.
28#[derive(Debug)]
29pub enum ErrorKind {
30    /// An I/O error occurred.
31    Io(io::Error),
32    /// Unexpected end of input.
33    UnexpectedEof,
34    /// Invalid XML syntax.
35    Syntax(String),
36    /// Invalid XML name.
37    InvalidName(String),
38    /// Missing required attribute.
39    MissingAttribute(String),
40    /// Unexpected element.
41    UnexpectedElement(String),
42    /// Unexpected attribute.
43    UnexpectedAttribute(String),
44    /// Invalid value for type.
45    InvalidValue(String),
46    /// Unclosed tag.
47    UnclosedTag(String),
48    /// Mismatched closing tag.
49    MismatchedTag {
50        /// The expected tag name.
51        expected: String,
52        /// The actual tag name found.
53        found: String,
54    },
55    /// Invalid escape sequence.
56    InvalidEscape(String),
57    /// Invalid UTF-8.
58    InvalidUtf8,
59    /// Custom error message.
60    Custom(String),
61    /// Unsupported operation.
62    Unsupported(String),
63}
64
65impl Error {
66    /// Creates a new error with the given kind.
67    #[inline]
68    pub fn new(kind: ErrorKind) -> Self {
69        Self { kind, position: None }
70    }
71
72    /// Creates a new error with position information.
73    #[inline]
74    pub fn with_position(mut self, position: Position) -> Self {
75        self.position = Some(position);
76        self
77    }
78
79    /// Returns the error kind.
80    #[inline]
81    pub fn kind(&self) -> &ErrorKind {
82        &self.kind
83    }
84
85    /// Returns the position where the error occurred.
86    #[inline]
87    pub fn position(&self) -> Option<Position> {
88        self.position
89    }
90
91    /// Creates an unexpected EOF error.
92    #[inline]
93    pub fn unexpected_eof() -> Self {
94        Self::new(ErrorKind::UnexpectedEof)
95    }
96
97    /// Creates a syntax error.
98    #[inline]
99    pub fn syntax<S: Into<String>>(msg: S) -> Self {
100        Self::new(ErrorKind::Syntax(msg.into()))
101    }
102
103    /// Creates an invalid name error.
104    #[inline]
105    pub fn invalid_name<S: Into<String>>(name: S) -> Self {
106        Self::new(ErrorKind::InvalidName(name.into()))
107    }
108
109    /// Creates an invalid value error.
110    #[inline]
111    pub fn invalid_value<S: Into<String>>(msg: S) -> Self {
112        Self::new(ErrorKind::InvalidValue(msg.into()))
113    }
114
115    /// Creates an unclosed tag error.
116    #[inline]
117    pub fn unclosed_tag<S: Into<String>>(tag: S) -> Self {
118        Self::new(ErrorKind::UnclosedTag(tag.into()))
119    }
120
121    /// Creates a mismatched tag error.
122    #[inline]
123    pub fn mismatched_tag<S: Into<String>>(expected: S, found: S) -> Self {
124        Self::new(ErrorKind::MismatchedTag {
125            expected: expected.into(),
126            found: found.into(),
127        })
128    }
129
130    /// Creates an invalid escape error.
131    #[inline]
132    pub fn invalid_escape<S: Into<String>>(seq: S) -> Self {
133        Self::new(ErrorKind::InvalidEscape(seq.into()))
134    }
135
136    /// Creates a custom error.
137    #[inline]
138    pub fn custom<S: Into<String>>(msg: S) -> Self {
139        Self::new(ErrorKind::Custom(msg.into()))
140    }
141
142    /// Creates an unsupported operation error.
143    #[inline]
144    pub fn unsupported<S: Into<String>>(msg: S) -> Self {
145        Self::new(ErrorKind::Unsupported(msg.into()))
146    }
147}
148
149impl Display for Error {
150    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151        match &self.kind {
152            ErrorKind::Io(e) => write!(f, "I/O error: {}", e),
153            ErrorKind::UnexpectedEof => write!(f, "unexpected end of input"),
154            ErrorKind::Syntax(msg) => write!(f, "syntax error: {}", msg),
155            ErrorKind::InvalidName(name) => write!(f, "invalid XML name: {}", name),
156            ErrorKind::MissingAttribute(name) => write!(f, "missing required attribute: {}", name),
157            ErrorKind::UnexpectedElement(name) => write!(f, "unexpected element: {}", name),
158            ErrorKind::UnexpectedAttribute(name) => write!(f, "unexpected attribute: {}", name),
159            ErrorKind::InvalidValue(msg) => write!(f, "invalid value: {}", msg),
160            ErrorKind::UnclosedTag(tag) => write!(f, "unclosed tag: <{}>", tag),
161            ErrorKind::MismatchedTag { expected, found } => {
162                write!(f, "mismatched closing tag: expected </{}>, found </{}>", expected, found)
163            }
164            ErrorKind::InvalidEscape(seq) => write!(f, "invalid escape sequence: {}", seq),
165            ErrorKind::InvalidUtf8 => write!(f, "invalid UTF-8"),
166            ErrorKind::Custom(msg) => write!(f, "{}", msg),
167            ErrorKind::Unsupported(msg) => write!(f, "unsupported: {}", msg),
168        }?;
169
170        if let Some(pos) = self.position {
171            write!(f, " at line {}, column {} (offset {})", pos.line, pos.column, pos.offset)?;
172        }
173
174        Ok(())
175    }
176}
177
178impl std::error::Error for Error {
179    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
180        match &self.kind {
181            ErrorKind::Io(e) => Some(e),
182            _ => None,
183        }
184    }
185}
186
187impl From<io::Error> for Error {
188    fn from(e: io::Error) -> Self {
189        Self::new(ErrorKind::Io(e))
190    }
191}
192
193impl serde::de::Error for Error {
194    fn custom<T: Display>(msg: T) -> Self {
195        Self::custom(msg.to_string())
196    }
197}
198
199impl serde::ser::Error for Error {
200    fn custom<T: Display>(msg: T) -> Self {
201        Self::custom(msg.to_string())
202    }
203}
204
205#[cfg(test)]
206mod tests {
207    use super::*;
208
209    #[test]
210    fn test_error_display() {
211        let err = Error::syntax("expected '>'");
212        assert_eq!(err.to_string(), "syntax error: expected '>'");
213    }
214
215    #[test]
216    fn test_error_with_position() {
217        let err = Error::syntax("expected '>'")
218            .with_position(Position { line: 5, column: 10, offset: 42 });
219        assert_eq!(
220            err.to_string(),
221            "syntax error: expected '>' at line 5, column 10 (offset 42)"
222        );
223    }
224
225    #[test]
226    fn test_mismatched_tag_error() {
227        let err = Error::mismatched_tag("foo", "bar");
228        assert_eq!(
229            err.to_string(),
230            "mismatched closing tag: expected </foo>, found </bar>"
231        );
232    }
233
234    #[test]
235    fn test_io_error() {
236        let io_err = io::Error::new(io::ErrorKind::NotFound, "file not found");
237        let err = Error::from(io_err);
238        assert!(err.to_string().contains("I/O error"));
239    }
240
241    #[test]
242    fn test_custom_error() {
243        let err = Error::custom("something went wrong");
244        assert_eq!(err.to_string(), "something went wrong");
245    }
246}