Skip to main content

quick_xml/
errors.rs

1//! Error management module
2
3use crate::encoding::EncodingError;
4use crate::escape::EscapeError;
5use crate::events::attributes::AttrError;
6use crate::name::{NamespaceError, QName};
7use std::fmt;
8use std::io::{Error as IoError, ErrorKind as IoErrorKind};
9use std::sync::Arc;
10
11/// An error returned if parsed document does not correspond to the XML grammar,
12/// for example, a tag opened by `<` not closed with `>`. This error does not
13/// represent invalid XML constructs, for example, tags `<>` and `</>` a well-formed
14/// from syntax point-of-view.
15#[derive(Copy, Clone, Debug, PartialEq, Eq)]
16pub enum SyntaxError {
17    /// The parser started to parse `<!`, but the input ended before it can recognize
18    /// anything.
19    InvalidBangMarkup,
20    /// The parser started to parse processing instruction (`<?`),
21    /// but the input ended before the `?>` sequence was found.
22    UnclosedPI,
23    /// The parser started to parse XML declaration (`<?xml` followed by `\t`, `\r`, `\n`, ` ` or `?`),
24    /// but the input ended before the `?>` sequence was found.
25    UnclosedXmlDecl,
26    /// The parser started to parse comment (`<!--`) content, but the input ended
27    /// before the `-->` sequence was found.
28    UnclosedComment,
29    /// The parser started to parse DTD (`<!DOCTYPE`) content, but the input ended
30    /// before the closing `>` character was found.
31    UnclosedDoctype,
32    /// The parser started to parse `<![CDATA[` content, but the input ended
33    /// before the `]]>` sequence was found.
34    UnclosedCData,
35    /// The parser started to parse tag content, but the input ended
36    /// before the closing `>` character was found.
37    UnclosedTag,
38    /// The parser started to parse tag content and currently inside of a quoted string
39    /// (i.e. in an attribute value), but the input ended before the closing quote was found.
40    ///
41    /// Note, that currently error location will point to a start of a tag (the `<` character)
42    /// instead of a start of an attribute value.
43    UnclosedSingleQuotedAttributeValue,
44    /// The parser started to parse tag content and currently inside of a quoted string
45    /// (i.e. in an attribute value), but the input ended before the closing quote was found.
46    ///
47    /// Note, that currently error location will point to a start of a tag (the `<` character)
48    /// instead of a start of an attribute value.
49    UnclosedDoubleQuotedAttributeValue,
50}
51
52impl fmt::Display for SyntaxError {
53    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
54        match self {
55            Self::InvalidBangMarkup => f.write_str("unknown or missed symbol in markup"),
56            Self::UnclosedPI => {
57                f.write_str("processing instruction not closed: `?>` not found before end of input")
58            }
59            Self::UnclosedXmlDecl => {
60                f.write_str("XML declaration not closed: `?>` not found before end of input")
61            }
62            Self::UnclosedComment => {
63                f.write_str("comment not closed: `-->` not found before end of input")
64            }
65            Self::UnclosedDoctype => {
66                f.write_str("DOCTYPE not closed: `>` not found before end of input")
67            }
68            Self::UnclosedCData => {
69                f.write_str("CDATA not closed: `]]>` not found before end of input")
70            }
71            Self::UnclosedTag => f.write_str("tag not closed: `>` not found before end of input"),
72            Self::UnclosedSingleQuotedAttributeValue => {
73                f.write_str("attribute value not closed: `'` not found before end of input")
74            }
75            Self::UnclosedDoubleQuotedAttributeValue => {
76                f.write_str("attribute value not closed: `\"` not found before end of input")
77            }
78        }
79    }
80}
81
82impl std::error::Error for SyntaxError {}
83
84////////////////////////////////////////////////////////////////////////////////////////////////////
85
86/// An error returned if parsed document is not [well-formed], for example,
87/// an opened tag is not closed before end of input.
88///
89/// Those errors are not fatal: after encountering an error you can continue
90/// parsing the document.
91///
92/// [well-formed]: https://www.w3.org/TR/xml11/#dt-wellformed
93#[derive(Clone, Debug, PartialEq, Eq)]
94pub enum IllFormedError {
95    /// A `version` attribute was not found in an XML declaration or is not the
96    /// first attribute.
97    ///
98    /// According to the [specification], the XML declaration (`<?xml ?>`) MUST contain
99    /// a `version` attribute and it MUST be the first attribute. This error indicates,
100    /// that the declaration does not contain attributes at all (if contains `None`)
101    /// or either `version` attribute is not present or not the first attribute in
102    /// the declaration. In the last case it contains the name of the found attribute.
103    ///
104    /// [specification]: https://www.w3.org/TR/xml11/#sec-prolog-dtd
105    MissingDeclVersion(Option<String>),
106    /// XML version specified in the declaration neither 1.0 or 1.1.
107    UnknownVersion,
108    /// A document type definition (DTD) does not contain a name of a root element.
109    ///
110    /// According to the [specification], document type definition (`<!DOCTYPE foo>`)
111    /// MUST contain a name which defines a document type (`foo`). If that name
112    /// is missed, this error is returned.
113    ///
114    /// [specification]: https://www.w3.org/TR/xml11/#NT-doctypedecl
115    MissingDoctypeName,
116    /// The end tag was not found during reading of a sub-tree of elements due to
117    /// encountering an EOF from the underlying reader. This error is returned from
118    /// [`Reader::read_to_end`].
119    ///
120    /// [`Reader::read_to_end`]: crate::reader::Reader::read_to_end
121    MissingEndTag(String),
122    /// The specified end tag was encountered without corresponding open tag at the
123    /// same level of hierarchy
124    UnmatchedEndTag(String),
125    /// The specified end tag does not match the start tag at that nesting level.
126    MismatchedEndTag {
127        /// Name of open tag, that is expected to be closed
128        expected: String,
129        /// Name of actually closed tag
130        found: String,
131    },
132    /// A comment contains forbidden double-hyphen (`--`) sequence inside.
133    ///
134    /// According to the [specification], for compatibility, comments MUST NOT contain
135    /// double-hyphen (`--`) sequence, in particular, they cannot end by `--->`.
136    ///
137    /// The quick-xml by default does not check that, because this restriction is
138    /// mostly artificial, but you can enable it in the [configuration].
139    ///
140    /// [specification]: https://www.w3.org/TR/xml11/#sec-comments
141    /// [configuration]: crate::reader::Config::check_comments
142    DoubleHyphenInComment,
143    /// The parser started to parse entity or character reference (`&...;`) in text,
144    /// but the input ended before the closing `;` character was found.
145    UnclosedReference,
146}
147
148impl fmt::Display for IllFormedError {
149    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
150        match self {
151            Self::MissingDeclVersion(None) => {
152                f.write_str("an XML declaration does not contain `version` attribute")
153            }
154            Self::MissingDeclVersion(Some(attr)) => {
155                write!(
156                    f,
157                    "an XML declaration must start with `version` attribute, but in starts with `{}`",
158                    attr
159                )
160            }
161            Self::UnknownVersion => {
162                f.write_str("unknown XML version: either 1.0 or 1.1 is expected")
163            }
164            Self::MissingDoctypeName => {
165                f.write_str("`<!DOCTYPE>` declaration does not contain a name of a document type")
166            }
167            Self::MissingEndTag(tag) => write!(
168                f,
169                "start tag not closed: `</{}>` not found before end of input",
170                tag,
171            ),
172            Self::UnmatchedEndTag(tag) => {
173                write!(f, "close tag `</{}>` does not match any open tag", tag)
174            }
175            Self::MismatchedEndTag { expected, found } => write!(
176                f,
177                "expected `</{}>`, but `</{}>` was found",
178                expected, found,
179            ),
180            Self::DoubleHyphenInComment => {
181                f.write_str("forbidden string `--` was found in a comment")
182            }
183            Self::UnclosedReference => f.write_str(
184                "entity or character reference not closed: `;` not found before end of input",
185            ),
186        }
187    }
188}
189
190impl std::error::Error for IllFormedError {}
191
192////////////////////////////////////////////////////////////////////////////////////////////////////
193
194/// The error type used by this crate.
195#[derive(Clone, Debug)]
196pub enum Error {
197    /// XML document cannot be read from underlying source.
198    ///
199    /// Contains the reference-counted I/O error to make the error type `Clone`able.
200    Io(Arc<IoError>),
201    /// The document does not corresponds to the XML grammar.
202    Syntax(SyntaxError),
203    /// The document is not [well-formed](https://www.w3.org/TR/xml11/#dt-wellformed).
204    IllFormed(IllFormedError),
205    /// Attribute parsing error
206    InvalidAttr(AttrError),
207    /// Encoding error
208    Encoding(EncodingError),
209    /// Escape error
210    Escape(EscapeError),
211    /// Parsed XML has some namespace-related problems
212    Namespace(NamespaceError),
213}
214
215impl Error {
216    pub(crate) fn missed_end(name: QName) -> Self {
217        IllFormedError::MissingEndTag(name.as_ref().to_string()).into()
218    }
219}
220
221impl From<IoError> for Error {
222    /// Creates a new `Error::Io` from the given error
223    #[inline]
224    fn from(error: IoError) -> Error {
225        match error.kind() {
226            IoErrorKind::InvalidData => match error.downcast::<EncodingError>() {
227                Ok(err) => Self::Encoding(err),
228                Err(err) => Self::Io(Arc::new(err)),
229            },
230            _ => Self::Io(Arc::new(error)),
231        }
232    }
233}
234
235impl From<SyntaxError> for Error {
236    /// Creates a new `Error::Syntax` from the given error
237    #[inline]
238    fn from(error: SyntaxError) -> Self {
239        Self::Syntax(error)
240    }
241}
242
243impl From<IllFormedError> for Error {
244    /// Creates a new `Error::IllFormed` from the given error
245    #[inline]
246    fn from(error: IllFormedError) -> Self {
247        Self::IllFormed(error)
248    }
249}
250
251impl From<EncodingError> for Error {
252    /// Creates a new `Error::EncodingError` from the given error
253    #[inline]
254    fn from(error: EncodingError) -> Error {
255        Self::Encoding(error)
256    }
257}
258
259impl From<std::str::Utf8Error> for Error {
260    #[inline]
261    fn from(error: std::str::Utf8Error) -> Error {
262        Self::Encoding(EncodingError::Utf8(error))
263    }
264}
265
266impl From<EscapeError> for Error {
267    /// Creates a new `Error::EscapeError` from the given error
268    #[inline]
269    fn from(error: EscapeError) -> Error {
270        Self::Escape(error)
271    }
272}
273
274impl From<AttrError> for Error {
275    #[inline]
276    fn from(error: AttrError) -> Self {
277        Self::InvalidAttr(error)
278    }
279}
280
281impl From<NamespaceError> for Error {
282    #[inline]
283    fn from(error: NamespaceError) -> Self {
284        Self::Namespace(error)
285    }
286}
287
288/// A specialized `Result` type where the error is hard-wired to [`Error`].
289pub type Result<T> = std::result::Result<T, Error>;
290
291impl fmt::Display for Error {
292    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
293        match self {
294            Self::Io(e) => write!(f, "I/O error: {}", e),
295            Self::Syntax(e) => write!(f, "syntax error: {}", e),
296            Self::IllFormed(e) => write!(f, "ill-formed document: {}", e),
297            Self::InvalidAttr(e) => write!(f, "error while parsing attribute: {}", e),
298            Self::Encoding(e) => e.fmt(f),
299            Self::Escape(e) => e.fmt(f),
300            Self::Namespace(e) => e.fmt(f),
301        }
302    }
303}
304
305impl std::error::Error for Error {
306    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
307        match self {
308            Self::Io(e) => Some(e),
309            Self::Syntax(e) => Some(e),
310            Self::IllFormed(e) => Some(e),
311            Self::InvalidAttr(e) => Some(e),
312            Self::Encoding(e) => Some(e),
313            Self::Escape(e) => Some(e),
314            Self::Namespace(e) => Some(e),
315        }
316    }
317}
318
319#[cfg(feature = "serialize")]
320pub mod serialize {
321    //! A module to handle serde (de)serialization errors
322
323    use super::*;
324    use std::borrow::Cow;
325    #[cfg(feature = "overlapped-lists")]
326    use std::num::NonZeroUsize;
327    use std::str::Utf8Error;
328
329    /// (De)serialization error
330    #[derive(Clone, Debug)]
331    pub enum DeError {
332        /// Serde custom error
333        Custom(String),
334        /// Xml parsing error
335        InvalidXml(Error),
336        /// This error indicates an error in the [`Deserialize`](serde::Deserialize)
337        /// implementation when read a map or a struct: `MapAccess::next_value[_seed]`
338        /// was called before `MapAccess::next_key[_seed]`.
339        ///
340        /// You should check your types, that implements corresponding trait.
341        KeyNotRead,
342        /// Deserializer encounter a start tag with a specified name when it is
343        /// not expecting. This happens when you try to deserialize a primitive
344        /// value (numbers, strings, booleans) from an XML element.
345        MixedContent(String),
346        /// The [`Reader`] produced [`Event::Eof`] when it is not expecting,
347        /// for example, after producing [`Event::Start`] but before corresponding
348        /// [`Event::End`].
349        ///
350        /// [`Reader`]: crate::reader::Reader
351        /// [`Event::Eof`]: crate::events::Event::Eof
352        /// [`Event::Start`]: crate::events::Event::Start
353        /// [`Event::End`]: crate::events::Event::End
354        UnexpectedEof,
355        /// The XML input exceeds the configured recursion limit.
356        ///
357        /// The contained value is the limit that was exceeded. This error is
358        /// returned when deserializing deeply nested XML structures to prevent
359        /// stack overflows.
360        TooDeeplyNested(usize),
361        /// Too many events were skipped while deserializing a sequence, event limit
362        /// exceeded. The limit was provided as an argument
363        #[cfg(feature = "overlapped-lists")]
364        TooManyEvents(NonZeroUsize),
365    }
366
367    impl fmt::Display for DeError {
368        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
369            match self {
370                Self::Custom(s) => f.write_str(s),
371                Self::InvalidXml(e) => e.fmt(f),
372                Self::KeyNotRead => f.write_str("invalid `Deserialize` implementation: `MapAccess::next_value[_seed]` was called before `MapAccess::next_key[_seed]`"),
373                Self::MixedContent(e) => write!(f, "cannot deserialize primitive type from mixed content, found unexpected tag <{}>", e),
374                Self::UnexpectedEof => f.write_str("unexpected `Event::Eof`"),
375                Self::TooDeeplyNested(limit) => write!(f, "XML is too deeply nested, recursion limit of {} exceeded", limit),
376                #[cfg(feature = "overlapped-lists")]
377                Self::TooManyEvents(s) => write!(f, "deserializer buffered {} events, limit exceeded", s),
378            }
379        }
380    }
381
382    impl std::error::Error for DeError {
383        fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
384            match self {
385                Self::InvalidXml(e) => Some(e),
386                _ => None,
387            }
388        }
389    }
390
391    impl serde::de::Error for DeError {
392        fn custom<T: fmt::Display>(msg: T) -> Self {
393            Self::Custom(msg.to_string())
394        }
395    }
396
397    impl From<Error> for DeError {
398        #[inline]
399        fn from(e: Error) -> Self {
400            Self::InvalidXml(e)
401        }
402    }
403
404    impl From<EscapeError> for DeError {
405        #[inline]
406        fn from(e: EscapeError) -> Self {
407            Self::InvalidXml(e.into())
408        }
409    }
410
411    impl From<EncodingError> for DeError {
412        #[inline]
413        fn from(e: EncodingError) -> Self {
414            Self::InvalidXml(e.into())
415        }
416    }
417
418    impl From<AttrError> for DeError {
419        #[inline]
420        fn from(e: AttrError) -> Self {
421            Self::InvalidXml(e.into())
422        }
423    }
424
425    impl From<NamespaceError> for DeError {
426        #[inline]
427        fn from(e: NamespaceError) -> Self {
428            Self::InvalidXml(e.into())
429        }
430    }
431
432    /// Serialization error
433    #[derive(Clone, Debug)]
434    pub enum SeError {
435        /// Serde custom error
436        Custom(String),
437        /// XML document cannot be written to underlying source.
438        ///
439        /// Contains the reference-counted I/O error to make the error type `Clone`able.
440        Io(Arc<IoError>),
441        /// Some value could not be formatted
442        Fmt(std::fmt::Error),
443        /// Serialized type cannot be represented in an XML due to violation of the
444        /// XML rules in the final XML document. For example, attempt to serialize
445        /// a `HashMap<{integer}, ...>` would cause this error because [XML name]
446        /// cannot start from a digit or a hyphen (minus sign). The same result
447        /// would occur if map key is a complex type that cannot be serialized as
448        /// a primitive type (i.e. string, char, bool, unit struct or unit variant).
449        ///
450        /// [XML name]: https://www.w3.org/TR/xml11/#sec-common-syn
451        Unsupported(Cow<'static, str>),
452        /// Some value could not be turned to UTF-8
453        NonEncodable(Utf8Error),
454    }
455
456    impl fmt::Display for SeError {
457        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
458            match self {
459                Self::Custom(s) => f.write_str(s),
460                Self::Io(e) => write!(f, "I/O error: {}", e),
461                Self::Fmt(e) => write!(f, "formatting error: {}", e),
462                Self::Unsupported(s) => write!(f, "unsupported value: {}", s),
463                Self::NonEncodable(e) => write!(f, "malformed UTF-8: {}", e),
464            }
465        }
466    }
467
468    impl ::std::error::Error for SeError {
469        fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
470            match self {
471                Self::Io(e) => Some(e),
472                _ => None,
473            }
474        }
475    }
476
477    impl serde::ser::Error for SeError {
478        fn custom<T: fmt::Display>(msg: T) -> Self {
479            Self::Custom(msg.to_string())
480        }
481    }
482
483    impl From<IoError> for SeError {
484        #[inline]
485        fn from(e: IoError) -> Self {
486            Self::Io(Arc::new(e))
487        }
488    }
489
490    impl From<Utf8Error> for SeError {
491        #[inline]
492        fn from(e: Utf8Error) -> Self {
493            Self::NonEncodable(e)
494        }
495    }
496
497    impl From<fmt::Error> for SeError {
498        #[inline]
499        fn from(e: fmt::Error) -> Self {
500            Self::Fmt(e)
501        }
502    }
503}