Coverage Report

Created: 2026-09-04 07:16

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/quick-xml/src/errors.rs
Line
Count
Source
1
//! Error management module
2
3
use crate::encoding::EncodingError;
4
use crate::escape::EscapeError;
5
use crate::events::attributes::AttrError;
6
use crate::name::{NamespaceError, QName};
7
use std::fmt;
8
use std::io::{Error as IoError, ErrorKind as IoErrorKind};
9
use 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)]
16
pub 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
52
impl fmt::Display for SyntaxError {
53
0
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
54
0
        match self {
55
0
            Self::InvalidBangMarkup => f.write_str("unknown or missed symbol in markup"),
56
            Self::UnclosedPI => {
57
0
                f.write_str("processing instruction not closed: `?>` not found before end of input")
58
            }
59
            Self::UnclosedXmlDecl => {
60
0
                f.write_str("XML declaration not closed: `?>` not found before end of input")
61
            }
62
            Self::UnclosedComment => {
63
0
                f.write_str("comment not closed: `-->` not found before end of input")
64
            }
65
            Self::UnclosedDoctype => {
66
0
                f.write_str("DOCTYPE not closed: `>` not found before end of input")
67
            }
68
            Self::UnclosedCData => {
69
0
                f.write_str("CDATA not closed: `]]>` not found before end of input")
70
            }
71
0
            Self::UnclosedTag => f.write_str("tag not closed: `>` not found before end of input"),
72
            Self::UnclosedSingleQuotedAttributeValue => {
73
0
                f.write_str("attribute value not closed: `'` not found before end of input")
74
            }
75
            Self::UnclosedDoubleQuotedAttributeValue => {
76
0
                f.write_str("attribute value not closed: `\"` not found before end of input")
77
            }
78
        }
79
0
    }
80
}
81
82
impl 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)]
94
pub 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
148
impl fmt::Display for IllFormedError {
149
0
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
150
0
        match self {
151
            Self::MissingDeclVersion(None) => {
152
0
                f.write_str("an XML declaration does not contain `version` attribute")
153
            }
154
0
            Self::MissingDeclVersion(Some(attr)) => {
155
0
                write!(
156
0
                    f,
157
0
                    "an XML declaration must start with `version` attribute, but in starts with `{}`",
158
                    attr
159
                )
160
            }
161
            Self::UnknownVersion => {
162
0
                f.write_str("unknown XML version: either 1.0 or 1.1 is expected")
163
            }
164
            Self::MissingDoctypeName => {
165
0
                f.write_str("`<!DOCTYPE>` declaration does not contain a name of a document type")
166
            }
167
0
            Self::MissingEndTag(tag) => write!(
168
0
                f,
169
0
                "start tag not closed: `</{}>` not found before end of input",
170
                tag,
171
            ),
172
0
            Self::UnmatchedEndTag(tag) => {
173
0
                write!(f, "close tag `</{}>` does not match any open tag", tag)
174
            }
175
0
            Self::MismatchedEndTag { expected, found } => write!(
176
0
                f,
177
0
                "expected `</{}>`, but `</{}>` was found",
178
                expected, found,
179
            ),
180
            Self::DoubleHyphenInComment => {
181
0
                f.write_str("forbidden string `--` was found in a comment")
182
            }
183
0
            Self::UnclosedReference => f.write_str(
184
0
                "entity or character reference not closed: `;` not found before end of input",
185
            ),
186
        }
187
0
    }
188
}
189
190
impl std::error::Error for IllFormedError {}
191
192
////////////////////////////////////////////////////////////////////////////////////////////////////
193
194
/// The error type used by this crate.
195
#[derive(Clone, Debug)]
196
pub 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
215
impl Error {
216
0
    pub(crate) fn missed_end(name: QName) -> Self {
217
0
        IllFormedError::MissingEndTag(name.as_ref().to_string()).into()
218
0
    }
219
}
220
221
impl From<IoError> for Error {
222
    /// Creates a new `Error::Io` from the given error
223
    #[inline]
224
0
    fn from(error: IoError) -> Error {
225
0
        match error.kind() {
226
0
            IoErrorKind::InvalidData => match error.downcast::<EncodingError>() {
227
0
                Ok(err) => Self::Encoding(err),
228
0
                Err(err) => Self::Io(Arc::new(err)),
229
            },
230
0
            _ => Self::Io(Arc::new(error)),
231
        }
232
0
    }
Unexecuted instantiation: <quick_xml::errors::Error as core::convert::From<std::io::error::Error>>::from
Unexecuted instantiation: <quick_xml::errors::Error as core::convert::From<std::io::error::Error>>::from
Unexecuted instantiation: <quick_xml::errors::Error as core::convert::From<std::io::error::Error>>::from
Unexecuted instantiation: <quick_xml::errors::Error as core::convert::From<std::io::error::Error>>::from
233
}
234
235
impl From<SyntaxError> for Error {
236
    /// Creates a new `Error::Syntax` from the given error
237
    #[inline]
238
179
    fn from(error: SyntaxError) -> Self {
239
179
        Self::Syntax(error)
240
179
    }
241
}
242
243
impl From<IllFormedError> for Error {
244
    /// Creates a new `Error::IllFormed` from the given error
245
    #[inline]
246
0
    fn from(error: IllFormedError) -> Self {
247
0
        Self::IllFormed(error)
248
0
    }
249
}
250
251
impl 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
259
impl From<std::str::Utf8Error> for Error {
260
    #[inline]
261
2.03k
    fn from(error: std::str::Utf8Error) -> Error {
262
2.03k
        Self::Encoding(EncodingError::Utf8(error))
263
2.03k
    }
264
}
265
266
impl From<EscapeError> for Error {
267
    /// Creates a new `Error::EscapeError` from the given error
268
    #[inline]
269
21.7k
    fn from(error: EscapeError) -> Error {
270
21.7k
        Self::Escape(error)
271
21.7k
    }
272
}
273
274
impl From<AttrError> for Error {
275
    #[inline]
276
394
    fn from(error: AttrError) -> Self {
277
394
        Self::InvalidAttr(error)
278
394
    }
279
}
280
281
impl From<NamespaceError> for Error {
282
    #[inline]
283
172
    fn from(error: NamespaceError) -> Self {
284
172
        Self::Namespace(error)
285
172
    }
286
}
287
288
/// A specialized `Result` type where the error is hard-wired to [`Error`].
289
pub type Result<T> = std::result::Result<T, Error>;
290
291
impl fmt::Display for Error {
292
0
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
293
0
        match self {
294
0
            Self::Io(e) => write!(f, "I/O error: {}", e),
295
0
            Self::Syntax(e) => write!(f, "syntax error: {}", e),
296
0
            Self::IllFormed(e) => write!(f, "ill-formed document: {}", e),
297
0
            Self::InvalidAttr(e) => write!(f, "error while parsing attribute: {}", e),
298
0
            Self::Encoding(e) => e.fmt(f),
299
0
            Self::Escape(e) => e.fmt(f),
300
0
            Self::Namespace(e) => e.fmt(f),
301
        }
302
0
    }
303
}
304
305
impl std::error::Error for Error {
306
0
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
307
0
        match self {
308
0
            Self::Io(e) => Some(e),
309
0
            Self::Syntax(e) => Some(e),
310
0
            Self::IllFormed(e) => Some(e),
311
0
            Self::InvalidAttr(e) => Some(e),
312
0
            Self::Encoding(e) => Some(e),
313
0
            Self::Escape(e) => Some(e),
314
0
            Self::Namespace(e) => Some(e),
315
        }
316
0
    }
317
}
318
319
#[cfg(feature = "serialize")]
320
pub 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
}