error.rs

  1/*!
  2# Error types for XML parsing
  3
  4This module contains the error types used throughout the `xso` crate.
  5*/
  6
  7// Copyright (c) 2024 Jonas Schäfer <jonas@zombofant.net>
  8//
  9// This Source Code Form is subject to the terms of the Mozilla Public
 10// License, v. 2.0. If a copy of the MPL was not distributed with this
 11// file, You can obtain one at http://mozilla.org/MPL/2.0/.
 12
 13use alloc::{
 14    boxed::Box,
 15    string::{String, ToString},
 16};
 17use core::fmt;
 18
 19use rxml::Error as XmlError;
 20
 21/// Opaque string error.
 22///
 23/// This is exclusively used in the `From<&Error> for Error` implementation
 24/// in order to type-erase and "clone" the TextParseError.
 25///
 26/// That implementation, in turn, is primarily used by the
 27/// `AsXml for Result<T, E>` implementation. We intentionally do not implement
 28/// `Clone` using this type because it'd lose type information (which you
 29/// don't expect a clone to do).
 30#[derive(Debug)]
 31struct OpaqueError(String);
 32
 33impl fmt::Display for OpaqueError {
 34    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
 35        f.write_str(&self.0)
 36    }
 37}
 38
 39impl core::error::Error for OpaqueError {}
 40
 41/// Error variants generated while parsing or serialising XML data.
 42#[derive(Debug)]
 43pub enum Error {
 44    /// Invalid XML data encountered
 45    XmlError(XmlError),
 46
 47    /// Attempt to parse text data failed with the provided nested error.
 48    TextParseError(Box<dyn core::error::Error + Send + Sync + 'static>),
 49
 50    /// Generic, unspecified other error.
 51    Other(&'static str),
 52
 53    /// An element header did not match an expected element.
 54    ///
 55    /// This error condition is only generated when the top-level element
 56    /// passed to [`transform`][`crate::transform`] or similar does not match
 57    /// the type.
 58    TypeMismatch,
 59}
 60
 61impl Error {
 62    /// Convenience function to create a [`Self::TextParseError`] variant.
 63    ///
 64    /// This includes the `Box::new(.)` call, making it directly usable as
 65    /// argument to [`Result::map_err`].
 66    pub fn text_parse_error<T: core::error::Error + Send + Sync + 'static>(e: T) -> Self {
 67        Self::TextParseError(Box::new(e))
 68    }
 69}
 70
 71/// "Clone" an [`Error`] while discarding some information.
 72///
 73/// This discards the specific type information from the
 74/// [`TextParseError`][`Self::TextParseError`] variant and it may discard
 75/// more information in the future.
 76impl From<&Error> for Error {
 77    fn from(other: &Error) -> Self {
 78        match other {
 79            Self::XmlError(e) => Self::XmlError(*e),
 80            Self::TextParseError(e) => Self::TextParseError(Box::new(OpaqueError(e.to_string()))),
 81            Self::Other(e) => Self::Other(e),
 82            Self::TypeMismatch => Self::TypeMismatch,
 83        }
 84    }
 85}
 86
 87impl fmt::Display for Error {
 88    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
 89        match self {
 90            Self::XmlError(ref e) => write!(f, "xml parse error: {}", e),
 91            Self::TextParseError(ref e) => write!(f, "text parse error: {}", e),
 92            Self::TypeMismatch => f.write_str("mismatch between expected and actual XML data"),
 93            Self::Other(msg) => f.write_str(msg),
 94        }
 95    }
 96}
 97
 98impl core::error::Error for Error {
 99    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
100        match self {
101            Self::XmlError(ref e) => Some(e),
102            Self::TextParseError(ref e) => Some(&**e),
103            _ => None,
104        }
105    }
106}
107
108impl From<rxml::Error> for Error {
109    fn from(other: rxml::Error) -> Error {
110        Error::XmlError(other)
111    }
112}
113
114impl From<rxml::strings::Error> for Error {
115    fn from(other: rxml::strings::Error) -> Error {
116        Error::XmlError(other.into())
117    }
118}
119
120impl From<core::convert::Infallible> for Error {
121    fn from(other: core::convert::Infallible) -> Self {
122        match other {}
123    }
124}
125
126/// Error returned from
127/// [`FromXml::from_events`][`crate::FromXml::from_events`].
128#[derive(Debug)]
129pub enum FromEventsError {
130    /// The `name` and/or `attrs` passed to `FromXml::from_events` did not
131    /// match the element's type.
132    Mismatch {
133        /// The `name` passed to `from_events`.
134        name: rxml::QName,
135
136        /// The `attrs` passed to `from_events`.
137        attrs: rxml::AttrMap,
138    },
139
140    /// The `name` and `attrs` passed to `FromXml::from_events` matched the
141    /// element's type, but the data was invalid. Details are in the inner
142    /// error.
143    Invalid(Error),
144}
145
146impl From<Error> for FromEventsError {
147    fn from(other: Error) -> Self {
148        Self::Invalid(other)
149    }
150}
151
152impl From<core::convert::Infallible> for FromEventsError {
153    fn from(other: core::convert::Infallible) -> Self {
154        match other {}
155    }
156}
157
158impl fmt::Display for FromEventsError {
159    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
160        match self {
161            Self::Mismatch { .. } => f.write_str("element header did not match"),
162            Self::Invalid(ref e) => fmt::Display::fmt(e, f),
163        }
164    }
165}
166
167impl core::error::Error for FromEventsError {
168    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
169        match self {
170            Self::Mismatch { .. } => None,
171            Self::Invalid(ref e) => Some(e),
172        }
173    }
174}
175
176impl From<Error> for Result<minidom::Element, Error> {
177    fn from(other: Error) -> Self {
178        Self::Err(other)
179    }
180}
181
182/// Error returned by the `TryFrom<Element>` implementations.
183#[derive(Debug)]
184pub enum FromElementError {
185    /// The XML element header did not match the expectations of the type
186    /// implementing `TryFrom`.
187    ///
188    /// Contains the original `Element` unmodified.
189    Mismatch(minidom::Element),
190
191    /// During processing of the element, an (unrecoverable) error occurred.
192    Invalid(Error),
193}
194
195impl fmt::Display for FromElementError {
196    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
197        match self {
198            Self::Mismatch(ref el) => write!(
199                f,
200                "expected different XML element (got {} in namespace {})",
201                el.name(),
202                el.ns()
203            ),
204            Self::Invalid(ref e) => fmt::Display::fmt(e, f),
205        }
206    }
207}
208
209impl core::error::Error for FromElementError {
210    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
211        match self {
212            Self::Mismatch(_) => None,
213            Self::Invalid(ref e) => Some(e),
214        }
215    }
216}
217
218impl From<Result<minidom::Element, Error>> for FromElementError {
219    fn from(other: Result<minidom::Element, Error>) -> Self {
220        match other {
221            Ok(v) => Self::Mismatch(v),
222            Err(e) => Self::Invalid(e),
223        }
224    }
225}
226
227impl From<Error> for FromElementError {
228    fn from(other: Error) -> Self {
229        Self::Invalid(other)
230    }
231}
232
233impl From<FromElementError> for Error {
234    fn from(other: FromElementError) -> Self {
235        match other {
236            FromElementError::Invalid(e) => e,
237            FromElementError::Mismatch(..) => Self::TypeMismatch,
238        }
239    }
240}
241
242impl From<core::convert::Infallible> for FromElementError {
243    fn from(other: core::convert::Infallible) -> Self {
244        match other {}
245    }
246}