xml-stinks 0.1.0

Powerful & easy manual XML deserialization using quick-xml
Documentation
//! Deserializer.
use std::convert::Infallible;

use crate::tagged::TagStart;
use crate::util::{feature_alternate, trait_alias};
use crate::DeserializeTagged;

pub mod buffered;

/// XML deserializer.
pub trait Deserializer
{
    /// Deserializes a tagged element.
    ///
    /// # Errors
    /// Returns `Err` if deserialization fails.
    fn de_tag<De: DeserializeTagged>(
        &mut self,
        tag_name: &str,
        ignore_end: IgnoreEnd,
    ) -> Result<De, Error<De::Error>>;

    /// Deserializes a tagged element using the given function.
    ///
    /// # Errors
    /// Returns `Err` if deserialization fails.
    fn de_tag_with<Output, Err, Func>(
        &mut self,
        tag_name: &str,
        ignore_end: IgnoreEnd,
        deserialize: Func,
    ) -> Result<Output, Error<Err>>
    where
        Output: MaybeStatic,
        Err: std::error::Error + Send + Sync + 'static,
        Func: FnOnce(&TagStart, &mut Self) -> Result<Output, Err> + MaybeStatic;

    /// Deserializes a list of tagged elements.
    ///
    /// # Errors
    /// Returns `Err` if deserialization fails.
    fn de_tag_list<De, TagName>(
        &mut self,
        tag_name: Option<TagName>,
    ) -> Result<Vec<De>, Error<De::Error>>
    where
        De: DeserializeTagged,
        TagName: AsRef<str> + MaybeStatic;

    /// Deserializes a text element.
    ///
    /// # Errors
    /// Returns `Err` if deserialization fails.
    fn de_text(&mut self) -> Result<String, Error<Infallible>>;

    /// Skips past all elements until a tagged element with the name `tag_name` is
    /// reached.
    ///
    /// # Errors
    /// Returns `Err` if unsuccessful.
    fn skip_to_tag_start(&mut self, tag_name: &str) -> Result<(), Error<Infallible>>;

    /// Skips past all elements until the end of a tagged element with the name `tag_name`
    /// is reached.
    ///
    /// # Errors
    /// Returns `Err` if unsuccessful.
    fn skip_to_tag_end(&mut self, tag_name: &str) -> Result<(), Error<Infallible>>;
}

trait_alias!(
    bounds_when_feature = "deserializer-static-generics",
    /// Bound to `'static` if the `deserializer-static-generics` feature is enabled.
    pub MaybeStatic: 'static;
);

/// Whether or not to skip the end tag of a tagged element.
///
/// **Should be `No`**.
#[derive(Debug, Default, PartialEq, Eq)]
pub enum IgnoreEnd
{
    /// Skip the end tag.
    ///
    /// **Will cause problems in most cases and should be used very carefully**.
    Yes,

    /// Don't skip the end tag.
    #[default]
    No,
}

/// [`Deserializer`] error.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error<DeError>
{
    /// A XML error occurred.
    #[error("A XML error occurred")]
    XMLError(#[source] XMLError),

    /// Failed to deserialize.
    #[error("Failed to deserialize")]
    DeserializeFailed(#[from] DeError),

    /// Unexpected event.
    #[error("Expected {expected_event_name} event. Found {found_event}")]
    UnexpectedEvent
    {
        /// The name of the expected event.
        expected_event_name: String,

        /// The found event.
        found_event: String,
    },

    /// Unexpected end of file.
    #[error("Unexpected end of file")]
    UnexpectedEndOfFile,
}

impl<DeError> Error<DeError>
{
    /// Returns `Self` with `DeError` as [`Infallible`].
    ///
    /// # Panics
    /// Will panic if `Self` is the `DeserializeFailed` variant.
    pub fn into_never_de_err(self) -> Error<Infallible>
    {
        match self {
            Self::XMLError(xml_err) => Error::XMLError(xml_err),
            Self::DeserializeFailed(_) => {
                panic!("is a deserialization error");
            }
            Self::UnexpectedEvent {
                expected_event_name,
                found_event,
            } => Error::UnexpectedEvent {
                expected_event_name,
                found_event,
            },
            Self::UnexpectedEndOfFile => Error::UnexpectedEndOfFile,
        }
    }
}

impl Error<Infallible>
{
    fn into_with_de_error<DeError>(self) -> Error<DeError>
    {
        match self {
            Self::XMLError(xml_err) => Error::XMLError(xml_err),
            Self::DeserializeFailed(_) => {
                unreachable!();
            }
            Self::UnexpectedEvent {
                expected_event_name,
                found_event,
            } => Error::UnexpectedEvent {
                expected_event_name,
                found_event,
            },
            Self::UnexpectedEndOfFile => Error::UnexpectedEndOfFile,
        }
    }
}

impl From<Error<Error<Infallible>>> for Error<Infallible>
{
    fn from(err: Error<Error<Infallible>>) -> Self
    {
        match err {
            Error::XMLError(xml_err) => Self::XMLError(xml_err),
            Error::DeserializeFailed(de_err) => de_err,
            Error::UnexpectedEvent {
                expected_event_name,
                found_event,
            } => Self::UnexpectedEvent {
                expected_event_name,
                found_event,
            },
            Error::UnexpectedEndOfFile => Self::UnexpectedEndOfFile,
        }
    }
}

impl<DeError> Error<DeError>
{
    /// Converts `Self` into `Err`.
    pub fn into_error<Err>(self) -> Err
    where
        Err: From<DeError> + From<Error<Infallible>>,
    {
        if let Error::DeserializeFailed(de_err) = self {
            return de_err.into();
        }

        self.into_never_de_err().into()
    }
}

/// XML error.
#[derive(Debug, thiserror::Error)]
#[error(transparent)]
pub struct XMLError(#[from] quick_xml::Error);

/// Implements conversion from [`Error`] with [`From`] for the given error type.
///
/// Allows for custom error types with source error types to easily be converted into with
/// `?`.
///
/// The given error type should implement `From<Error<Infallible>>`.
///
/// # Examples
/// ```
/// use std::convert::Infallible;
///
/// use xml_stinks::deserializer::Error as DeserializerError;
/// use xml_stinks::impl_from_deserializer_error;
///
/// #[derive(Debug, thiserror::Error)]
/// enum FooError
/// {
///     #[error("Deserialization failed")]
///     DeserializeFailed(#[from] DeserializerError<Infallible>),
///
///     #[error("Invalid bar")]
///     InvalidBar(#[from] BarError),
/// }
///
/// impl_from_deserializer_error!(FooError);
///
/// #[derive(Debug, thiserror::Error)]
/// enum BarError
/// {
///     #[error("Oops")]
///     Oops,
/// }
///
/// let err_a: FooError = DeserializerError::<Infallible>::UnexpectedEndOfFile.into();
///
/// assert!(matches!(
///     err_a,
///     FooError::DeserializeFailed(DeserializerError::UnexpectedEndOfFile)
/// ));
///
/// let err_b: FooError = DeserializerError::DeserializeFailed(BarError::Oops).into();
///
/// assert!(matches!(err_b, FooError::InvalidBar(BarError::Oops)));
/// ```
#[macro_export]
macro_rules! impl_from_deserializer_error {
    ($err: path) => {
        impl<DeError: Into<Self>> From<::xml_stinks::deserializer::Error<DeError>>
            for $err
        {
            fn from(err: ::xml_stinks::deserializer::Error<DeError>) -> Self
            {
                if let ::xml_stinks::deserializer::Error::DeserializeFailed(de_err) = err
                {
                    return de_err.into();
                }

                err.into_never_de_err().into()
            }
        }
    };
}

feature_alternate!(
    feature = "deserializer-static-generics",
    /// Conditional compilation based on whether or not the `deserializer-static-generics`
    /// feature is enabled.
    ///
    /// # Examples
    /// ```
    /// use std::io::Cursor;
    ///
    /// use xml_stinks::xml_stinks_if_deserializer_static_generics;
    /// use xml_stinks::deserializer::buffered::Buffered as BufferedDeserializer;
    /// use xml_stinks::deserializer::Deserializer;
    ///
    /// fn do_something(bytes: &[u8])
    /// {
    ///     let deserializer = xml_stinks_if_deserializer_static_generics!(then {
    ///         BufferedDeserializer::new(Cursor::new(bytes.to_vec()));
    ///     } else {
    ///         // This wouldn't compile if the deserializer-static-generics feature was
    ///         // enabled
    ///         BufferedDeserializer::new(bytes);
    ///     });
    ///
    ///     // ...
    /// }
    /// ```
    when_enabled =
        #[macro_export]
        macro_rules! xml_stinks_if_deserializer_static_generics {
            (then { $($then: tt)* }$(else { $($else: tt)* })?) => {
                $($then)*
            };
        },
    when_disabled =
        #[macro_export]
        macro_rules! xml_stinks_if_deserializer_static_generics {
            (then { $($then: tt)* }$(else { $($else: tt)* })?) => {
                $($($else)*)?
            };
        }
);