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