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: 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)]
29pub enum ErrorKind {
30 Io(io::Error),
32 UnexpectedEof,
34 Syntax(String),
36 InvalidName(String),
38 MissingAttribute(String),
40 UnexpectedElement(String),
42 UnexpectedAttribute(String),
44 InvalidValue(String),
46 UnclosedTag(String),
48 MismatchedTag {
50 expected: String,
52 found: String,
54 },
55 InvalidEscape(String),
57 InvalidUtf8,
59 Custom(String),
61 Unsupported(String),
63}
64
65impl Error {
66 #[inline]
68 pub fn new(kind: ErrorKind) -> Self {
69 Self { kind, position: None }
70 }
71
72 #[inline]
74 pub fn with_position(mut self, position: Position) -> Self {
75 self.position = Some(position);
76 self
77 }
78
79 #[inline]
81 pub fn kind(&self) -> &ErrorKind {
82 &self.kind
83 }
84
85 #[inline]
87 pub fn position(&self) -> Option<Position> {
88 self.position
89 }
90
91 #[inline]
93 pub fn unexpected_eof() -> Self {
94 Self::new(ErrorKind::UnexpectedEof)
95 }
96
97 #[inline]
99 pub fn syntax<S: Into<String>>(msg: S) -> Self {
100 Self::new(ErrorKind::Syntax(msg.into()))
101 }
102
103 #[inline]
105 pub fn invalid_name<S: Into<String>>(name: S) -> Self {
106 Self::new(ErrorKind::InvalidName(name.into()))
107 }
108
109 #[inline]
111 pub fn invalid_value<S: Into<String>>(msg: S) -> Self {
112 Self::new(ErrorKind::InvalidValue(msg.into()))
113 }
114
115 #[inline]
117 pub fn unclosed_tag<S: Into<String>>(tag: S) -> Self {
118 Self::new(ErrorKind::UnclosedTag(tag.into()))
119 }
120
121 #[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 #[inline]
132 pub fn invalid_escape<S: Into<String>>(seq: S) -> Self {
133 Self::new(ErrorKind::InvalidEscape(seq.into()))
134 }
135
136 #[inline]
138 pub fn custom<S: Into<String>>(msg: S) -> Self {
139 Self::new(ErrorKind::Custom(msg.into()))
140 }
141
142 #[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}