Skip to main content

oxixml_xml/
errors.rs

1//! Error management for the crate.
2
3use crate::encoding::EncodingError;
4use crate::escape::EscapeError;
5use crate::events::attributes::AttrError;
6use crate::name::NamespaceError;
7use std::fmt;
8use std::io::{Error as IoError, ErrorKind as IoErrorKind};
9use std::sync::Arc;
10
11/// An error returned when a parsed document does not conform to the XML grammar,
12/// for example a tag opened by `<` that is not closed with `>`.
13///
14/// This error does not represent invalid XML *constructs* — for example the tags
15/// `<>` and `</>` are well-formed from a syntactic point of view.
16#[derive(Copy, Clone, Debug, PartialEq, Eq)]
17pub enum SyntaxError {
18    /// The parser started to parse `<!`, but the input ended before it could
19    /// recognize anything.
20    InvalidBangMarkup,
21    /// The parser started to parse a processing instruction (`<?`), but the input
22    /// ended before the `?>` sequence was found.
23    UnclosedPI,
24    /// The parser started to parse an XML declaration (`<?xml` followed by white
25    /// space or `?`), but the input ended before the `?>` sequence was found.
26    UnclosedXmlDecl,
27    /// The parser started to parse a comment (`<!--`), but the input ended before
28    /// the `-->` sequence was found.
29    UnclosedComment,
30    /// The parser started to parse a DTD (`<!DOCTYPE`), but the input ended before
31    /// the closing `>` character was found.
32    UnclosedDoctype,
33    /// The parser started to parse `<![CDATA[`, but the input ended before the
34    /// `]]>` sequence was found.
35    UnclosedCData,
36    /// The parser started to parse tag content, but the input ended before the
37    /// closing `>` character was found.
38    UnclosedTag,
39    /// The parser was inside a single-quoted attribute value, but the input ended
40    /// before the closing quote was found.
41    UnclosedSingleQuotedAttributeValue,
42    /// The parser was inside a double-quoted attribute value, but the input ended
43    /// before the closing quote was found.
44    UnclosedDoubleQuotedAttributeValue,
45}
46
47impl fmt::Display for SyntaxError {
48    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
49        match self {
50            Self::InvalidBangMarkup => f.write_str("unknown or missed symbol in markup"),
51            Self::UnclosedPI => {
52                f.write_str("processing instruction not closed: `?>` not found before end of input")
53            }
54            Self::UnclosedXmlDecl => {
55                f.write_str("XML declaration not closed: `?>` not found before end of input")
56            }
57            Self::UnclosedComment => {
58                f.write_str("comment not closed: `-->` not found before end of input")
59            }
60            Self::UnclosedDoctype => {
61                f.write_str("DOCTYPE not closed: `>` not found before end of input")
62            }
63            Self::UnclosedCData => {
64                f.write_str("CDATA not closed: `]]>` not found before end of input")
65            }
66            Self::UnclosedTag => f.write_str("tag not closed: `>` not found before end of input"),
67            Self::UnclosedSingleQuotedAttributeValue => {
68                f.write_str("attribute value not closed: `'` not found before end of input")
69            }
70            Self::UnclosedDoubleQuotedAttributeValue => {
71                f.write_str("attribute value not closed: `\"` not found before end of input")
72            }
73        }
74    }
75}
76
77impl std::error::Error for SyntaxError {}
78
79////////////////////////////////////////////////////////////////////////////////////////////////////
80
81/// An error returned when a parsed document is not [well-formed], for example an
82/// opened tag that is not closed before the end of input.
83///
84/// These errors are not fatal: parsing can continue after encountering one.
85///
86/// [well-formed]: https://www.w3.org/TR/xml11/#dt-wellformed
87#[derive(Clone, Debug, PartialEq, Eq)]
88pub enum IllFormedError {
89    /// A `version` attribute was not found in an XML declaration or is not the
90    /// first attribute. Contains the name of the found attribute, if any.
91    MissingDeclVersion(Option<String>),
92    /// The XML version specified in the declaration is neither 1.0 nor 1.1.
93    UnknownVersion,
94    /// A document type definition (`<!DOCTYPE foo>`) does not contain a name.
95    MissingDoctypeName,
96    /// An end tag was not found while reading a sub-tree of elements because an
97    /// EOF was encountered.
98    MissingEndTag(String),
99    /// An end tag was encountered without a corresponding open tag at the same
100    /// level of the hierarchy.
101    UnmatchedEndTag(String),
102    /// An end tag does not match the start tag at that nesting level.
103    MismatchedEndTag {
104        /// Name of the open tag that is expected to be closed.
105        expected: String,
106        /// Name of the tag that was actually closed.
107        found: String,
108    },
109    /// A comment contains the forbidden double-hyphen (`--`) sequence.
110    DoubleHyphenInComment,
111    /// The parser started to parse an entity or character reference (`&...;`), but
112    /// the input ended before the closing `;` character was found.
113    UnclosedReference,
114}
115
116impl fmt::Display for IllFormedError {
117    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
118        match self {
119            Self::MissingDeclVersion(None) => {
120                f.write_str("an XML declaration does not contain `version` attribute")
121            }
122            Self::MissingDeclVersion(Some(attr)) => {
123                write!(
124                    f,
125                    "an XML declaration must start with `version` attribute, but in starts with `{}`",
126                    attr
127                )
128            }
129            Self::UnknownVersion => {
130                f.write_str("unknown XML version: either 1.0 or 1.1 is expected")
131            }
132            Self::MissingDoctypeName => {
133                f.write_str("`<!DOCTYPE>` declaration does not contain a name of a document type")
134            }
135            Self::MissingEndTag(tag) => write!(
136                f,
137                "start tag not closed: `</{}>` not found before end of input",
138                tag,
139            ),
140            Self::UnmatchedEndTag(tag) => {
141                write!(f, "close tag `</{}>` does not match any open tag", tag)
142            }
143            Self::MismatchedEndTag { expected, found } => {
144                write!(
145                    f,
146                    "expected `</{}>`, but `</{}>` was found",
147                    expected, found
148                )
149            }
150            Self::DoubleHyphenInComment => {
151                f.write_str("forbidden string `--` was found in a comment")
152            }
153            Self::UnclosedReference => f.write_str(
154                "entity or character reference not closed: `;` not found before end of input",
155            ),
156        }
157    }
158}
159
160impl std::error::Error for IllFormedError {}
161
162////////////////////////////////////////////////////////////////////////////////////////////////////
163
164/// A violation of an XML [well-formedness constraint] that is reported only when
165/// [`Config::check_well_formedness`] is enabled.
166///
167/// The default parsing path of this crate is deliberately permissive so that it
168/// stays a drop-in replacement for `quick-xml`; enabling the flag turns the
169/// [`Reader`] into a conforming non-validating XML processor and these errors
170/// become observable.
171///
172/// [well-formedness constraint]: https://www.w3.org/TR/xml/#dt-wellformed
173/// [`Config::check_well_formedness`]: crate::reader::Config::check_well_formedness
174/// [`Reader`]: crate::reader::Reader
175#[derive(Clone, Debug, PartialEq, Eq)]
176#[non_exhaustive]
177pub enum WellFormedError {
178    /// A character that the [`Char`] production of the document's XML version
179    /// does not allow was found.
180    ///
181    /// [`Char`]: https://www.w3.org/TR/xml/#NT-Char
182    InvalidCharacter(char),
183    /// A token that must match the [`Name`] production does not.
184    ///
185    /// [`Name`]: https://www.w3.org/TR/xml/#NT-Name
186    InvalidName(String),
187    /// A public identifier contains a character outside the [`PubidChar`]
188    /// production.
189    ///
190    /// [`PubidChar`]: https://www.w3.org/TR/xml/#NT-PubidChar
191    InvalidPublicId(String),
192    /// A `&...;` entity or character reference does not match the [`Reference`]
193    /// production, or a character reference denotes a code point that is not a
194    /// legal XML character.
195    ///
196    /// [`Reference`]: https://www.w3.org/TR/xml/#NT-Reference
197    InvalidReference(String),
198    /// A general entity was referenced but never declared, violating the
199    /// [Entity Declared] constraint.
200    ///
201    /// [Entity Declared]: https://www.w3.org/TR/xml/#wf-entdeclared
202    UndeclaredEntity(String),
203    /// A general entity refers to itself, directly or indirectly, violating the
204    /// [No Recursion] constraint.
205    ///
206    /// [No Recursion]: https://www.w3.org/TR/xml/#norecursion
207    RecursiveEntity(String),
208    /// Expanding general entities exceeded the limits this processor imposes on
209    /// the total number of expansions or on the total size of the expanded text.
210    ///
211    /// Entity expansion is not recursive in the sense of the [No Recursion]
212    /// constraint when every entity has a different name, yet a document such as
213    /// the "billion laughs" bomb can still expand exponentially. Since a
214    /// non-validating processor is not required to expand entities without
215    /// bound, such a document is rejected instead of being expanded.
216    ///
217    /// [No Recursion]: https://www.w3.org/TR/xml/#norecursion
218    EntityExpansionLimit {
219        /// Name of the entity whose expansion exhausted the budget.
220        entity: String,
221        /// Description of the limit that was hit.
222        limit: &'static str,
223    },
224    /// An unparsed (`NDATA`) entity was referenced from content or from an
225    /// attribute value, violating the [Parsed Entity] constraint.
226    ///
227    /// [Parsed Entity]: https://www.w3.org/TR/xml/#wf-parsedent
228    UnparsedEntityReference(String),
229    /// The replacement text of a general entity is not well-formed on its own,
230    /// violating the [Legal Character] / balanced-markup constraints. Contains
231    /// the entity name and the nested failure.
232    IllFormedEntity {
233        /// Name of the entity whose replacement text is at fault.
234        entity: String,
235        /// Description of the problem found inside the replacement text.
236        reason: String,
237    },
238    /// An attribute value contains a literal `<`, violating the [No `<` in
239    /// Attribute Values] constraint. Contains the attribute name.
240    ///
241    /// [No `<` in Attribute Values]: https://www.w3.org/TR/xml/#CleanAttrVals
242    LessThanInAttributeValue(String),
243    /// An attribute value refers, directly or indirectly, to an external entity,
244    /// violating the [No External Entity References] constraint.
245    ///
246    /// [No External Entity References]: https://www.w3.org/TR/xml/#NoExternalRefs
247    ExternalEntityInAttributeValue(String),
248    /// The same attribute name appears twice on one tag, violating the
249    /// [Unique Att Spec] constraint.
250    ///
251    /// [Unique Att Spec]: https://www.w3.org/TR/xml/#uniqattspec
252    DuplicateAttribute(String),
253    /// A name does not match the [`QName`] production of Namespaces in XML, or a
254    /// name that must be an `NCName` contains a colon. Only reported while
255    /// namespace processing is active, that is for the
256    /// [`NsReader`](crate::reader::NsReader).
257    ///
258    /// [`QName`]: https://www.w3.org/TR/xml-names/#NT-QName
259    InvalidQName(String),
260    /// A namespace declaration binds a prefix to the empty namespace name, which
261    /// [Namespaces in XML 1.0] does not allow.
262    ///
263    /// [Namespaces in XML 1.0]: https://www.w3.org/TR/xml-names/#nsc-NSDeclared
264    EmptyPrefixBinding(String),
265    /// A start, end or empty-element tag does not match the [`STag`] production.
266    /// Contains a description of the expectation that was violated.
267    ///
268    /// [`STag`]: https://www.w3.org/TR/xml/#NT-STag
269    MalformedTag(String),
270    /// Character data contains the `]]>` sequence outside of a CDATA section.
271    CDataEndInContent,
272    /// A processing instruction uses the reserved target `xml` (in any case
273    /// combination).
274    ReservedPiTarget(String),
275    /// An XML declaration was found somewhere other than at the very beginning
276    /// of the document entity.
277    MisplacedXmlDeclaration,
278    /// The XML declaration does not match the [`XMLDecl`] production. Contains a
279    /// description of the expectation that was violated.
280    ///
281    /// [`XMLDecl`]: https://www.w3.org/TR/xml/#NT-XMLDecl
282    InvalidXmlDeclaration(String),
283    /// A `<!DOCTYPE>` declaration appears after the root element started, or a
284    /// second one appears.
285    MisplacedDoctype,
286    /// The document contains no root element.
287    MissingRootElement,
288    /// A second top-level element was found after the root element was closed.
289    MultipleRootElements,
290    /// Character data, a CDATA section or an entity reference was found outside
291    /// of the root element.
292    ContentOutsideRoot,
293    /// The document type declaration does not match the [`doctypedecl`]
294    /// production. Contains a description of the expectation that was violated.
295    ///
296    /// [`doctypedecl`]: https://www.w3.org/TR/xml/#NT-doctypedecl
297    InvalidDoctype(String),
298}
299
300impl fmt::Display for WellFormedError {
301    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
302        match self {
303            Self::InvalidCharacter(ch) => {
304                write!(
305                    f,
306                    "character U+{:04X} is not allowed by the `Char` production",
307                    *ch as u32
308                )
309            }
310            Self::InvalidName(name) => {
311                write!(f, "`{}` does not match the `Name` production", name)
312            }
313            Self::InvalidPublicId(id) => write!(
314                f,
315                "public identifier `{}` contains a character outside `PubidChar`",
316                id
317            ),
318            Self::InvalidReference(reference) => {
319                write!(
320                    f,
321                    "`{}` is not a valid entity or character reference",
322                    reference
323                )
324            }
325            Self::UndeclaredEntity(name) => {
326                write!(f, "reference to the undeclared entity `{}`", name)
327            }
328            Self::RecursiveEntity(name) => {
329                write!(f, "entity `{}` refers to itself", name)
330            }
331            Self::EntityExpansionLimit { entity, limit } => write!(
332                f,
333                "expanding the entity `{}` exceeds the {} of this processor",
334                entity, limit
335            ),
336            Self::UnparsedEntityReference(name) => write!(
337                f,
338                "reference to the unparsed (NDATA) entity `{}` is not allowed here",
339                name
340            ),
341            Self::IllFormedEntity { entity, reason } => write!(
342                f,
343                "replacement text of the entity `{}` is not well-formed: {}",
344                entity, reason
345            ),
346            Self::LessThanInAttributeValue(name) => write!(
347                f,
348                "value of the attribute `{}` contains a literal `<`",
349                name
350            ),
351            Self::ExternalEntityInAttributeValue(name) => write!(
352                f,
353                "an attribute value cannot refer to the external entity `{}`",
354                name
355            ),
356            Self::DuplicateAttribute(name) => {
357                write!(f, "the attribute `{}` is specified more than once", name)
358            }
359            Self::InvalidQName(name) => {
360                write!(f, "`{}` does not match the `QName` production", name)
361            }
362            Self::EmptyPrefixBinding(prefix) => write!(
363                f,
364                "the prefix `{}` cannot be bound to the empty namespace name",
365                prefix
366            ),
367            Self::MalformedTag(reason) => write!(f, "malformed tag: {}", reason),
368            Self::CDataEndInContent => {
369                f.write_str("the sequence `]]>` is not allowed in character data")
370            }
371            Self::ReservedPiTarget(target) => write!(
372                f,
373                "`{}` is a reserved processing instruction target",
374                target
375            ),
376            Self::MisplacedXmlDeclaration => f.write_str(
377                "the XML declaration must be at the very beginning of the document entity",
378            ),
379            Self::InvalidXmlDeclaration(reason) => {
380                write!(f, "malformed XML declaration: {}", reason)
381            }
382            Self::MisplacedDoctype => {
383                f.write_str("`<!DOCTYPE>` must appear once, before the root element")
384            }
385            Self::MissingRootElement => f.write_str("the document has no root element"),
386            Self::MultipleRootElements => {
387                f.write_str("the document has more than one root element")
388            }
389            Self::ContentOutsideRoot => {
390                f.write_str("character data is not allowed outside of the root element")
391            }
392            Self::InvalidDoctype(reason) => {
393                write!(f, "malformed document type declaration: {}", reason)
394            }
395        }
396    }
397}
398
399impl std::error::Error for WellFormedError {}
400
401////////////////////////////////////////////////////////////////////////////////////////////////////
402
403/// The error type used by this crate.
404#[derive(Clone, Debug)]
405pub enum Error {
406    /// An XML document cannot be read from the underlying source.
407    ///
408    /// Contains a reference-counted I/O error to make the error type `Clone`able.
409    Io(Arc<IoError>),
410    /// The document does not correspond to the XML grammar.
411    Syntax(SyntaxError),
412    /// The document is not [well-formed](https://www.w3.org/TR/xml11/#dt-wellformed).
413    IllFormed(IllFormedError),
414    /// The document violates a well-formedness constraint that is checked only
415    /// when [`Config::check_well_formedness`] is enabled.
416    ///
417    /// [`Config::check_well_formedness`]: crate::reader::Config::check_well_formedness
418    NotWellFormed(WellFormedError),
419    /// Attribute parsing error.
420    InvalidAttr(AttrError),
421    /// Encoding error.
422    Encoding(EncodingError),
423    /// Escape error.
424    Escape(EscapeError),
425    /// The parsed XML has some namespace-related problem.
426    Namespace(NamespaceError),
427}
428
429impl Error {
430    /// Builds the error returned when a [`read_to_end`] family method reaches
431    /// end of input before finding the closing tag for `name`.
432    ///
433    /// [`read_to_end`]: crate::reader::Reader::read_to_end
434    pub(crate) fn missed_end(name: crate::name::QName, decoder: crate::encoding::Decoder) -> Self {
435        match decoder.decode(name.as_ref()) {
436            Ok(name) => IllFormedError::MissingEndTag(name.into_owned()).into(),
437            Err(err) => err.into(),
438        }
439    }
440}
441
442impl From<IoError> for Error {
443    /// Creates a new [`Error::Io`] from the given error.
444    #[inline]
445    fn from(error: IoError) -> Error {
446        match error.kind() {
447            IoErrorKind::InvalidData => match error.downcast::<EncodingError>() {
448                Ok(err) => Self::Encoding(err),
449                Err(err) => Self::Io(Arc::new(err)),
450            },
451            _ => Self::Io(Arc::new(error)),
452        }
453    }
454}
455
456impl From<SyntaxError> for Error {
457    /// Creates a new [`Error::Syntax`] from the given error.
458    #[inline]
459    fn from(error: SyntaxError) -> Self {
460        Self::Syntax(error)
461    }
462}
463
464impl From<WellFormedError> for Error {
465    /// Creates a new [`Error::NotWellFormed`] from the given error.
466    #[inline]
467    fn from(error: WellFormedError) -> Self {
468        Self::NotWellFormed(error)
469    }
470}
471
472impl From<IllFormedError> for Error {
473    /// Creates a new [`Error::IllFormed`] from the given error.
474    #[inline]
475    fn from(error: IllFormedError) -> Self {
476        Self::IllFormed(error)
477    }
478}
479
480impl From<EncodingError> for Error {
481    /// Creates a new [`Error::Encoding`] from the given error.
482    #[inline]
483    fn from(error: EncodingError) -> Error {
484        Self::Encoding(error)
485    }
486}
487
488impl From<EscapeError> for Error {
489    /// Creates a new [`Error::Escape`] from the given error.
490    #[inline]
491    fn from(error: EscapeError) -> Error {
492        Self::Escape(error)
493    }
494}
495
496impl From<AttrError> for Error {
497    #[inline]
498    fn from(error: AttrError) -> Self {
499        Self::InvalidAttr(error)
500    }
501}
502
503impl From<NamespaceError> for Error {
504    #[inline]
505    fn from(error: NamespaceError) -> Self {
506        Self::Namespace(error)
507    }
508}
509
510/// A specialized [`Result`](std::result::Result) with the error hard-wired to [`Error`].
511pub type Result<T> = std::result::Result<T, Error>;
512
513impl fmt::Display for Error {
514    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
515        match self {
516            Self::Io(e) => write!(f, "I/O error: {}", e),
517            Self::Syntax(e) => write!(f, "syntax error: {}", e),
518            Self::IllFormed(e) => write!(f, "ill-formed document: {}", e),
519            Self::NotWellFormed(e) => write!(f, "not well-formed document: {}", e),
520            Self::InvalidAttr(e) => write!(f, "error while parsing attribute: {}", e),
521            Self::Encoding(e) => e.fmt(f),
522            Self::Escape(e) => e.fmt(f),
523            Self::Namespace(e) => e.fmt(f),
524        }
525    }
526}
527
528impl std::error::Error for Error {
529    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
530        match self {
531            Self::Io(e) => Some(e),
532            Self::Syntax(e) => Some(e),
533            Self::IllFormed(e) => Some(e),
534            Self::NotWellFormed(e) => Some(e),
535            Self::InvalidAttr(e) => Some(e),
536            Self::Encoding(e) => Some(e),
537            Self::Escape(e) => Some(e),
538            Self::Namespace(e) => Some(e),
539        }
540    }
541}
542
543#[cfg(feature = "serialize")]
544#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
545pub mod serialize {
546    //! Serde (de)serialization errors.
547    //!
548    //! Mirrors the `quick_xml::errors::serialize` module of quick-xml 0.41.
549
550    use super::*;
551    use crate::utils::write_byte_string;
552    use std::borrow::Cow;
553    #[cfg(feature = "overlapped-lists")]
554    use std::num::NonZeroUsize;
555    use std::str::Utf8Error;
556
557    /// Deserialization error.
558    #[derive(Clone, Debug)]
559    pub enum DeError {
560        /// Serde custom error.
561        Custom(String),
562        /// XML parsing error.
563        InvalidXml(Error),
564        /// This error indicates an error in the [`Deserialize`](serde::Deserialize)
565        /// implementation when reading a map or a struct: `MapAccess::next_value[_seed]`
566        /// was called before `MapAccess::next_key[_seed]`.
567        ///
568        /// You should check your types that implement the corresponding trait.
569        KeyNotRead,
570        /// The deserializer encountered a start tag with a specified name when it
571        /// was not expecting one. This happens when you try to deserialize a
572        /// primitive value (numbers, strings, booleans) from an XML element.
573        UnexpectedStart(Vec<u8>),
574        /// The [`Reader`] produced [`Event::Eof`] when it was not expected, for
575        /// example after producing [`Event::Start`] but before the corresponding
576        /// [`Event::End`].
577        ///
578        /// [`Reader`]: crate::reader::Reader
579        /// [`Event::Eof`]: crate::events::Event::Eof
580        /// [`Event::Start`]: crate::events::Event::Start
581        /// [`Event::End`]: crate::events::Event::End
582        UnexpectedEof,
583        /// Too many events were skipped while deserializing a sequence; the event
584        /// limit was exceeded. The limit is provided as an argument.
585        #[cfg(feature = "overlapped-lists")]
586        #[cfg_attr(docsrs, doc(cfg(feature = "overlapped-lists")))]
587        TooManyEvents(NonZeroUsize),
588    }
589
590    impl fmt::Display for DeError {
591        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
592            match self {
593                Self::Custom(s) => f.write_str(s),
594                Self::InvalidXml(e) => e.fmt(f),
595                Self::KeyNotRead => f.write_str("invalid `Deserialize` implementation: `MapAccess::next_value[_seed]` was called before `MapAccess::next_key[_seed]`"),
596                Self::UnexpectedStart(e) => {
597                    f.write_str("unexpected `Event::Start(")?;
598                    write_byte_string(f, e)?;
599                    f.write_str(")`")
600                }
601                Self::UnexpectedEof => f.write_str("unexpected `Event::Eof`"),
602                #[cfg(feature = "overlapped-lists")]
603                Self::TooManyEvents(s) => write!(f, "deserializer buffered {} events, limit exceeded", s),
604            }
605        }
606    }
607
608    impl std::error::Error for DeError {
609        fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
610            match self {
611                Self::InvalidXml(e) => Some(e),
612                _ => None,
613            }
614        }
615    }
616
617    impl serde::de::Error for DeError {
618        fn custom<T: fmt::Display>(msg: T) -> Self {
619            Self::Custom(msg.to_string())
620        }
621    }
622
623    impl From<Error> for DeError {
624        #[inline]
625        fn from(e: Error) -> Self {
626            Self::InvalidXml(e)
627        }
628    }
629
630    impl From<EscapeError> for DeError {
631        #[inline]
632        fn from(e: EscapeError) -> Self {
633            Self::InvalidXml(e.into())
634        }
635    }
636
637    impl From<EncodingError> for DeError {
638        #[inline]
639        fn from(e: EncodingError) -> Self {
640            Self::InvalidXml(e.into())
641        }
642    }
643
644    impl From<AttrError> for DeError {
645        #[inline]
646        fn from(e: AttrError) -> Self {
647            Self::InvalidXml(e.into())
648        }
649    }
650
651    /// Serialization error.
652    #[derive(Clone, Debug)]
653    pub enum SeError {
654        /// Serde custom error.
655        Custom(String),
656        /// An XML document cannot be written to the underlying source.
657        ///
658        /// Contains a reference-counted I/O error to make the error type `Clone`able.
659        Io(Arc<IoError>),
660        /// Some value could not be formatted.
661        Fmt(std::fmt::Error),
662        /// The serialized type cannot be represented in XML due to a violation of
663        /// the XML rules in the final XML document. For example, an attempt to
664        /// serialize a `HashMap<{integer}, ...>` would cause this error because an
665        /// [XML name] cannot start with a digit or a hyphen (minus sign). The same
666        /// result would occur if the map key is a complex type that cannot be
667        /// serialized as a primitive type (i.e. string, char, bool, unit struct or
668        /// unit variant).
669        ///
670        /// [XML name]: https://www.w3.org/TR/xml11/#sec-common-syn
671        Unsupported(Cow<'static, str>),
672        /// Some value could not be turned into UTF-8.
673        NonEncodable(Utf8Error),
674    }
675
676    impl fmt::Display for SeError {
677        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
678            match self {
679                Self::Custom(s) => f.write_str(s),
680                Self::Io(e) => write!(f, "I/O error: {}", e),
681                Self::Fmt(e) => write!(f, "formatting error: {}", e),
682                Self::Unsupported(s) => write!(f, "unsupported value: {}", s),
683                Self::NonEncodable(e) => write!(f, "malformed UTF-8: {}", e),
684            }
685        }
686    }
687
688    impl ::std::error::Error for SeError {
689        fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
690            match self {
691                Self::Io(e) => Some(e),
692                _ => None,
693            }
694        }
695    }
696
697    impl serde::ser::Error for SeError {
698        fn custom<T: fmt::Display>(msg: T) -> Self {
699            Self::Custom(msg.to_string())
700        }
701    }
702
703    impl From<IoError> for SeError {
704        #[inline]
705        fn from(e: IoError) -> Self {
706            Self::Io(Arc::new(e))
707        }
708    }
709
710    impl From<Utf8Error> for SeError {
711        #[inline]
712        fn from(e: Utf8Error) -> Self {
713            Self::NonEncodable(e)
714        }
715    }
716
717    impl From<fmt::Error> for SeError {
718        #[inline]
719        fn from(e: fmt::Error) -> Self {
720            Self::Fmt(e)
721        }
722    }
723}
724
725#[cfg(test)]
726mod tests {
727    use super::*;
728
729    #[test]
730    fn display_and_source() {
731        let err: Error = SyntaxError::UnclosedTag.into();
732        assert_eq!(
733            err.to_string(),
734            "syntax error: tag not closed: `>` not found before end of input"
735        );
736        assert!(std::error::Error::source(&err).is_some());
737    }
738
739    #[test]
740    fn io_invalid_data_stays_io() {
741        let io = IoError::new(IoErrorKind::UnexpectedEof, "boom");
742        let err: Error = io.into();
743        assert!(matches!(err, Error::Io(_)));
744    }
745}