xmpp-rs/xso/src/lib.rs

583 lines
21 KiB
Rust
Raw Normal View History

#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![forbid(unsafe_code)]
#![warn(missing_docs)]
/*!
# XML Streamed Objects -- serde-like parsing for XML
This crate provides the traits for parsing XML data into Rust structs, and
vice versa.
While it is in 0.0.x versions, many features still need to be developed, but
rest assured that there is a solid plan to get it fully usable for even
advanced XML scenarios.
XSO is an acronym for XML Stream(ed) Objects, referring to the main field of
use of this library in parsing XML streams like specified in RFC 6120.
*/
// Copyright (c) 2024 Jonas Schäfer <jonas@zombofant.net>
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
2024-08-09 14:48:40 +02:00
#![no_std]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
#[cfg(feature = "std")]
use std::io;
2024-08-09 14:48:40 +02:00
pub mod asxml;
pub mod error;
pub mod fromxml;
#[cfg(feature = "minidom")]
pub mod minidom_compat;
mod rxml_util;
pub mod text;
#[doc(hidden)]
pub mod exports {
#[cfg(feature = "minidom")]
pub use minidom;
pub use rxml;
}
use alloc::{
borrow::{Cow, ToOwned},
boxed::Box,
string::String,
vec::Vec,
};
xso: add traits for XML text <-> value conversion The traits have undergone a couple iterations and this is what we end up with. The core issue which makes this entire thing ugly is the Orphan Rule, preventing some trait implementations relating to types which haven't been defined in this crate. In an ideal world, we would implement FromXmlText and IntoXmlText for all types implementing FromStr and/or fmt::Display. This comes with two severe issues: 1. Downstream crates cannot chose to have different parsing/serialisation behaviour for "normal" text vs. xml. 2. We ourselves cannot define a behaviour for `Option<T>`. `Option<T>` does not implement `FromStr` (nor `Display`), but the standard library *could* do that at some point, and thus Rust doesn't let us implement e.g. `FromXmlText for Option<T> where T: FromXmlText`, if we also implement it on `T: FromStr`. The second one hurts particularly once we get to optional attributes: For these, we need to "detect" that the type is in fact `Option<T>`, because we then need to invoke `FromXmlText` on `T` instead of `Option<T>`. Unfortunately, we cannot do that: macros operate on token streams and we have no type information available. We can of course match on the name `Option`, but that breaks down when users re-import `Option` under a different name. Even just enumerating all the possible correct ways of using `Option` from the standard library (there are more than three) would be a nuisance at best. Hence, we need *another* trait or at least a specialized implementation of `FromXmlText for Option<T>`, and we cannot do that if we blanket-impl `FromXmlText` on `T: FromStr`. That makes the traits what they are, and introduces the requirement that we know about any upstream crate which anyone might want to parse from or to XML. This sucks a lot, but that's the state of the world. We are late to the party, and we cannot expect everyone to do the same they have done for `serde` (many crates have a `feature = "serde"` which then provides Serialize/Deserialize trait impls for their types).
2024-06-25 17:36:36 +02:00
#[doc(inline)]
pub use text::TextCodec;
#[doc(inline)]
pub use rxml_util::Item;
#[doc = include_str!("from_xml_doc.md")]
#[doc(inline)]
#[cfg(feature = "macros")]
pub use xso_proc::FromXml;
/// # Make a struct or enum serialisable to XML
///
2024-07-09 17:01:42 +02:00
/// This derives the [`AsXml`] trait on a struct or enum. It is the
/// counterpart to [`macro@FromXml`].
///
/// The attributes necessary and available for the derivation to work are
/// documented on [`macro@FromXml`].
#[doc(inline)]
#[cfg(feature = "macros")]
2024-07-09 17:01:42 +02:00
pub use xso_proc::AsXml;
/// Trait allowing to iterate a struct's contents as serialisable
/// [`Item`]s.
///
/// **Important:** Changing the [`ItemIter`][`Self::ItemIter`] associated
/// type is considered a non-breaking change for any given implementation of
/// this trait. Always refer to a type's iterator type using fully-qualified
/// notation, for example: `<T as xso::AsXml>::ItemIter`.
pub trait AsXml {
/// The iterator type.
///
/// **Important:** Changing this type is considered a non-breaking change
/// for any given implementation of this trait. Always refer to a type's
/// iterator type using fully-qualified notation, for example:
/// `<T as xso::AsXml>::ItemIter`.
type ItemIter<'x>: Iterator<Item = Result<Item<'x>, self::error::Error>>
where
Self: 'x;
/// Return an iterator which emits the contents of the struct or enum as
/// serialisable [`Item`] items.
fn as_xml_iter(&self) -> Result<Self::ItemIter<'_>, self::error::Error>;
}
/// Trait for a temporary object allowing to construct a struct from
/// [`rxml::Event`] items.
///
/// Objects of this type are generally constructed through
/// [`FromXml::from_events`] and are used to build Rust structs or enums from
/// XML data. The XML data must be fed as `rxml::Event` to the
/// [`feed`][`Self::feed`] method.
pub trait FromEventsBuilder {
/// The type which will be constructed by this builder.
type Output;
/// Feed another [`rxml::Event`] into the element construction
/// process.
///
/// Once the construction process completes, `Ok(Some(_))` is returned.
/// When valid data has been fed but more events are needed to fully
/// construct the resulting struct, `Ok(None)` is returned.
///
/// If the construction fails, `Err(_)` is returned. Errors are generally
/// fatal and the builder should be assumed to be broken at that point.
/// Feeding more events after an error may result in panics, errors or
/// inconsistent result data, though it may never result in unsound or
/// unsafe behaviour.
fn feed(&mut self, ev: rxml::Event) -> Result<Option<Self::Output>, self::error::Error>;
}
/// Trait allowing to construct a struct from a stream of
/// [`rxml::Event`] items.
///
/// To use this, first call [`FromXml::from_events`] with the qualified
/// name and the attributes of the corresponding
/// [`rxml::Event::StartElement`] event. If the call succeeds, the
/// returned builder object must be fed with the events representing the
/// contents of the element, and then with the `EndElement` event.
///
/// The `StartElement` passed to `from_events` must not be passed to `feed`.
///
/// **Important:** Changing the [`Builder`][`Self::Builder`] associated type
/// is considered a non-breaking change for any given implementation of this
/// trait. Always refer to a type's builder type using fully-qualified
/// notation, for example: `<T as xso::FromXml>::Builder`.
pub trait FromXml {
/// A builder type used to construct the element.
///
/// **Important:** Changing this type is considered a non-breaking change
/// for any given implementation of this trait. Always refer to a type's
/// builder type using fully-qualified notation, for example:
/// `<T as xso::FromXml>::Builder`.
type Builder: FromEventsBuilder<Output = Self>;
/// Attempt to initiate the streamed construction of this struct from XML.
///
/// If the passed qualified `name` and `attrs` match the element's type,
/// the [`Self::Builder`] is returned and should be fed with XML events
/// by the caller.
///
/// Otherwise, an appropriate error is returned.
fn from_events(
name: rxml::QName,
attrs: rxml::AttrMap,
) -> Result<Self::Builder, self::error::FromEventsError>;
}
xso: add traits for XML text <-> value conversion The traits have undergone a couple iterations and this is what we end up with. The core issue which makes this entire thing ugly is the Orphan Rule, preventing some trait implementations relating to types which haven't been defined in this crate. In an ideal world, we would implement FromXmlText and IntoXmlText for all types implementing FromStr and/or fmt::Display. This comes with two severe issues: 1. Downstream crates cannot chose to have different parsing/serialisation behaviour for "normal" text vs. xml. 2. We ourselves cannot define a behaviour for `Option<T>`. `Option<T>` does not implement `FromStr` (nor `Display`), but the standard library *could* do that at some point, and thus Rust doesn't let us implement e.g. `FromXmlText for Option<T> where T: FromXmlText`, if we also implement it on `T: FromStr`. The second one hurts particularly once we get to optional attributes: For these, we need to "detect" that the type is in fact `Option<T>`, because we then need to invoke `FromXmlText` on `T` instead of `Option<T>`. Unfortunately, we cannot do that: macros operate on token streams and we have no type information available. We can of course match on the name `Option`, but that breaks down when users re-import `Option` under a different name. Even just enumerating all the possible correct ways of using `Option` from the standard library (there are more than three) would be a nuisance at best. Hence, we need *another* trait or at least a specialized implementation of `FromXmlText for Option<T>`, and we cannot do that if we blanket-impl `FromXmlText` on `T: FromStr`. That makes the traits what they are, and introduces the requirement that we know about any upstream crate which anyone might want to parse from or to XML. This sucks a lot, but that's the state of the world. We are late to the party, and we cannot expect everyone to do the same they have done for `serde` (many crates have a `feature = "serde"` which then provides Serialize/Deserialize trait impls for their types).
2024-06-25 17:36:36 +02:00
/// Trait allowing to convert XML text to a value.
///
/// This trait is similar to [`core::str::FromStr`], however, due to
xso: add traits for XML text <-> value conversion The traits have undergone a couple iterations and this is what we end up with. The core issue which makes this entire thing ugly is the Orphan Rule, preventing some trait implementations relating to types which haven't been defined in this crate. In an ideal world, we would implement FromXmlText and IntoXmlText for all types implementing FromStr and/or fmt::Display. This comes with two severe issues: 1. Downstream crates cannot chose to have different parsing/serialisation behaviour for "normal" text vs. xml. 2. We ourselves cannot define a behaviour for `Option<T>`. `Option<T>` does not implement `FromStr` (nor `Display`), but the standard library *could* do that at some point, and thus Rust doesn't let us implement e.g. `FromXmlText for Option<T> where T: FromXmlText`, if we also implement it on `T: FromStr`. The second one hurts particularly once we get to optional attributes: For these, we need to "detect" that the type is in fact `Option<T>`, because we then need to invoke `FromXmlText` on `T` instead of `Option<T>`. Unfortunately, we cannot do that: macros operate on token streams and we have no type information available. We can of course match on the name `Option`, but that breaks down when users re-import `Option` under a different name. Even just enumerating all the possible correct ways of using `Option` from the standard library (there are more than three) would be a nuisance at best. Hence, we need *another* trait or at least a specialized implementation of `FromXmlText for Option<T>`, and we cannot do that if we blanket-impl `FromXmlText` on `T: FromStr`. That makes the traits what they are, and introduces the requirement that we know about any upstream crate which anyone might want to parse from or to XML. This sucks a lot, but that's the state of the world. We are late to the party, and we cannot expect everyone to do the same they have done for `serde` (many crates have a `feature = "serde"` which then provides Serialize/Deserialize trait impls for their types).
2024-06-25 17:36:36 +02:00
/// restrictions imposed by the orphan rule, a separate trait is needed.
/// Implementations for many standard library types are available. In
/// addition, the following feature flags can enable more implementations:
///
/// - `jid`: `jid::Jid`, `jid::BareJid`, `jid::FullJid`
/// - `uuid`: `uuid::Uuid`
///
/// Because of this unfortunate situation, we are **extremely liberal** with
/// accepting optional dependencies for this purpose. You are very welcome to
/// make merge requests against this crate adding support for parsing
/// third-party crates.
pub trait FromXmlText: Sized {
/// Convert the given XML text to a value.
fn from_xml_text(data: String) -> Result<Self, self::error::Error>;
}
impl FromXmlText for String {
2024-07-24 16:07:29 +02:00
/// Return the string unchanged.
xso: add traits for XML text <-> value conversion The traits have undergone a couple iterations and this is what we end up with. The core issue which makes this entire thing ugly is the Orphan Rule, preventing some trait implementations relating to types which haven't been defined in this crate. In an ideal world, we would implement FromXmlText and IntoXmlText for all types implementing FromStr and/or fmt::Display. This comes with two severe issues: 1. Downstream crates cannot chose to have different parsing/serialisation behaviour for "normal" text vs. xml. 2. We ourselves cannot define a behaviour for `Option<T>`. `Option<T>` does not implement `FromStr` (nor `Display`), but the standard library *could* do that at some point, and thus Rust doesn't let us implement e.g. `FromXmlText for Option<T> where T: FromXmlText`, if we also implement it on `T: FromStr`. The second one hurts particularly once we get to optional attributes: For these, we need to "detect" that the type is in fact `Option<T>`, because we then need to invoke `FromXmlText` on `T` instead of `Option<T>`. Unfortunately, we cannot do that: macros operate on token streams and we have no type information available. We can of course match on the name `Option`, but that breaks down when users re-import `Option` under a different name. Even just enumerating all the possible correct ways of using `Option` from the standard library (there are more than three) would be a nuisance at best. Hence, we need *another* trait or at least a specialized implementation of `FromXmlText for Option<T>`, and we cannot do that if we blanket-impl `FromXmlText` on `T: FromStr`. That makes the traits what they are, and introduces the requirement that we know about any upstream crate which anyone might want to parse from or to XML. This sucks a lot, but that's the state of the world. We are late to the party, and we cannot expect everyone to do the same they have done for `serde` (many crates have a `feature = "serde"` which then provides Serialize/Deserialize trait impls for their types).
2024-06-25 17:36:36 +02:00
fn from_xml_text(data: String) -> Result<Self, self::error::Error> {
Ok(data)
}
}
impl<T: FromXmlText, B: ToOwned<Owned = T>> FromXmlText for Cow<'_, B> {
2024-07-24 16:07:29 +02:00
/// Return a [`Cow::Owned`] containing the parsed value.
xso: add traits for XML text <-> value conversion The traits have undergone a couple iterations and this is what we end up with. The core issue which makes this entire thing ugly is the Orphan Rule, preventing some trait implementations relating to types which haven't been defined in this crate. In an ideal world, we would implement FromXmlText and IntoXmlText for all types implementing FromStr and/or fmt::Display. This comes with two severe issues: 1. Downstream crates cannot chose to have different parsing/serialisation behaviour for "normal" text vs. xml. 2. We ourselves cannot define a behaviour for `Option<T>`. `Option<T>` does not implement `FromStr` (nor `Display`), but the standard library *could* do that at some point, and thus Rust doesn't let us implement e.g. `FromXmlText for Option<T> where T: FromXmlText`, if we also implement it on `T: FromStr`. The second one hurts particularly once we get to optional attributes: For these, we need to "detect" that the type is in fact `Option<T>`, because we then need to invoke `FromXmlText` on `T` instead of `Option<T>`. Unfortunately, we cannot do that: macros operate on token streams and we have no type information available. We can of course match on the name `Option`, but that breaks down when users re-import `Option` under a different name. Even just enumerating all the possible correct ways of using `Option` from the standard library (there are more than three) would be a nuisance at best. Hence, we need *another* trait or at least a specialized implementation of `FromXmlText for Option<T>`, and we cannot do that if we blanket-impl `FromXmlText` on `T: FromStr`. That makes the traits what they are, and introduces the requirement that we know about any upstream crate which anyone might want to parse from or to XML. This sucks a lot, but that's the state of the world. We are late to the party, and we cannot expect everyone to do the same they have done for `serde` (many crates have a `feature = "serde"` which then provides Serialize/Deserialize trait impls for their types).
2024-06-25 17:36:36 +02:00
fn from_xml_text(data: String) -> Result<Self, self::error::Error> {
Ok(Cow::Owned(T::from_xml_text(data)?))
}
}
impl<T: FromXmlText> FromXmlText for Option<T> {
2024-07-24 16:07:29 +02:00
/// Return a [`Some`] containing the parsed value.
xso: add traits for XML text <-> value conversion The traits have undergone a couple iterations and this is what we end up with. The core issue which makes this entire thing ugly is the Orphan Rule, preventing some trait implementations relating to types which haven't been defined in this crate. In an ideal world, we would implement FromXmlText and IntoXmlText for all types implementing FromStr and/or fmt::Display. This comes with two severe issues: 1. Downstream crates cannot chose to have different parsing/serialisation behaviour for "normal" text vs. xml. 2. We ourselves cannot define a behaviour for `Option<T>`. `Option<T>` does not implement `FromStr` (nor `Display`), but the standard library *could* do that at some point, and thus Rust doesn't let us implement e.g. `FromXmlText for Option<T> where T: FromXmlText`, if we also implement it on `T: FromStr`. The second one hurts particularly once we get to optional attributes: For these, we need to "detect" that the type is in fact `Option<T>`, because we then need to invoke `FromXmlText` on `T` instead of `Option<T>`. Unfortunately, we cannot do that: macros operate on token streams and we have no type information available. We can of course match on the name `Option`, but that breaks down when users re-import `Option` under a different name. Even just enumerating all the possible correct ways of using `Option` from the standard library (there are more than three) would be a nuisance at best. Hence, we need *another* trait or at least a specialized implementation of `FromXmlText for Option<T>`, and we cannot do that if we blanket-impl `FromXmlText` on `T: FromStr`. That makes the traits what they are, and introduces the requirement that we know about any upstream crate which anyone might want to parse from or to XML. This sucks a lot, but that's the state of the world. We are late to the party, and we cannot expect everyone to do the same they have done for `serde` (many crates have a `feature = "serde"` which then provides Serialize/Deserialize trait impls for their types).
2024-06-25 17:36:36 +02:00
fn from_xml_text(data: String) -> Result<Self, self::error::Error> {
Ok(Some(T::from_xml_text(data)?))
}
}
impl<T: FromXmlText> FromXmlText for Box<T> {
2024-07-24 16:07:29 +02:00
/// Return a [`Box`] containing the parsed value.
xso: add traits for XML text <-> value conversion The traits have undergone a couple iterations and this is what we end up with. The core issue which makes this entire thing ugly is the Orphan Rule, preventing some trait implementations relating to types which haven't been defined in this crate. In an ideal world, we would implement FromXmlText and IntoXmlText for all types implementing FromStr and/or fmt::Display. This comes with two severe issues: 1. Downstream crates cannot chose to have different parsing/serialisation behaviour for "normal" text vs. xml. 2. We ourselves cannot define a behaviour for `Option<T>`. `Option<T>` does not implement `FromStr` (nor `Display`), but the standard library *could* do that at some point, and thus Rust doesn't let us implement e.g. `FromXmlText for Option<T> where T: FromXmlText`, if we also implement it on `T: FromStr`. The second one hurts particularly once we get to optional attributes: For these, we need to "detect" that the type is in fact `Option<T>`, because we then need to invoke `FromXmlText` on `T` instead of `Option<T>`. Unfortunately, we cannot do that: macros operate on token streams and we have no type information available. We can of course match on the name `Option`, but that breaks down when users re-import `Option` under a different name. Even just enumerating all the possible correct ways of using `Option` from the standard library (there are more than three) would be a nuisance at best. Hence, we need *another* trait or at least a specialized implementation of `FromXmlText for Option<T>`, and we cannot do that if we blanket-impl `FromXmlText` on `T: FromStr`. That makes the traits what they are, and introduces the requirement that we know about any upstream crate which anyone might want to parse from or to XML. This sucks a lot, but that's the state of the world. We are late to the party, and we cannot expect everyone to do the same they have done for `serde` (many crates have a `feature = "serde"` which then provides Serialize/Deserialize trait impls for their types).
2024-06-25 17:36:36 +02:00
fn from_xml_text(data: String) -> Result<Self, self::error::Error> {
Ok(Box::new(T::from_xml_text(data)?))
}
}
/// Trait to convert a value to an XML text string.
///
/// Implementing this trait for a type allows it to be used both for XML
/// character data within elements and for XML attributes. For XML attributes,
/// the behaviour is defined by [`AsXmlText::as_optional_xml_text`], while
/// XML element text content uses [`AsXmlText::as_xml_text`]. Implementing
/// [`AsXmlText`] automatically provides an implementation of
/// [`AsOptionalXmlText`].
///
/// If your type should only be used in XML attributes and has no correct
/// serialisation in XML text, you should *only* implement
/// [`AsOptionalXmlText`] and omit the [`AsXmlText`] implementation.
///
/// This trait is implemented for many standard library types implementing
/// [`core::fmt::Display`]. In addition, the following feature flags can enable
/// more implementations:
///
/// - `jid`: `jid::Jid`, `jid::BareJid`, `jid::FullJid`
/// - `uuid`: `uuid::Uuid`
///
/// Because of the unfortunate situation as described in [`FromXmlText`], we
/// are **extremely liberal** with accepting optional dependencies for this
/// purpose. You are very welcome to make merge requests against this crate
/// adding support for parsing third-party crates.
pub trait AsXmlText {
/// Convert the value to an XML string in a context where an absent value
/// cannot be represented.
fn as_xml_text(&self) -> Result<Cow<'_, str>, self::error::Error>;
/// Convert the value to an XML string in a context where an absent value
/// can be represented.
///
/// The provided implementation will always return the result of
/// [`Self::as_xml_text`] wrapped into `Some(.)`. By re-implementing
/// this method, implementors can customize the behaviour for certain
/// values.
fn as_optional_xml_text(&self) -> Result<Option<Cow<'_, str>>, self::error::Error> {
Ok(Some(self.as_xml_text()?))
}
}
impl AsXmlText for String {
2024-07-24 16:07:29 +02:00
/// Return the borrowed string contents.
fn as_xml_text(&self) -> Result<Cow<'_, str>, self::error::Error> {
Ok(Cow::Borrowed(self.as_str()))
}
}
2024-07-24 16:07:29 +02:00
impl AsXmlText for str {
/// Return the borrowed string contents.
fn as_xml_text(&self) -> Result<Cow<'_, str>, self::error::Error> {
2024-07-24 16:07:29 +02:00
Ok(Cow::Borrowed(&*self))
}
}
impl<T: AsXmlText> AsXmlText for Box<T> {
2024-07-24 16:07:29 +02:00
/// Return the borrowed [`Box`] contents.
fn as_xml_text(&self) -> Result<Cow<'_, str>, self::error::Error> {
2024-07-24 21:24:15 +02:00
T::as_xml_text(self)
}
}
impl<B: AsXmlText + ToOwned> AsXmlText for Cow<'_, B> {
2024-07-24 16:07:29 +02:00
/// Return the borrowed [`Cow`] contents.
fn as_xml_text(&self) -> Result<Cow<'_, str>, self::error::Error> {
B::as_xml_text(self.as_ref())
}
}
2024-07-24 16:07:29 +02:00
impl<T: AsXmlText> AsXmlText for &T {
/// Delegate to the `AsXmlText` implementation on `T`.
fn as_xml_text(&self) -> Result<Cow<'_, str>, self::error::Error> {
T::as_xml_text(*self)
}
}
/// Specialized variant of [`AsXmlText`].
///
/// Normally, it should not be necessary to implement this trait as it is
/// automatically implemented for all types implementing [`AsXmlText`].
/// However, if your type can only be serialised as an XML attribute (for
/// example because an absent value has a particular meaning), it is correct
/// to implement [`AsOptionalXmlText`] **instead of** [`AsXmlText`].
///
/// If your type can be serialised as both (text and attribute) but needs
/// special handling in attributes, implement [`AsXmlText`] but provide a
/// custom implementation of [`AsXmlText::as_optional_xml_text`].
pub trait AsOptionalXmlText {
/// Convert the value to an XML string in a context where an absent value
/// can be represented.
fn as_optional_xml_text(&self) -> Result<Option<Cow<'_, str>>, self::error::Error>;
}
impl<T: AsXmlText> AsOptionalXmlText for T {
fn as_optional_xml_text(&self) -> Result<Option<Cow<'_, str>>, self::error::Error> {
<Self as AsXmlText>::as_optional_xml_text(self)
}
}
impl<T: AsXmlText> AsOptionalXmlText for Option<T> {
fn as_optional_xml_text(&self) -> Result<Option<Cow<'_, str>>, self::error::Error> {
self.as_ref()
.map(T::as_optional_xml_text)
.transpose()
.map(Option::flatten)
}
}
/// Control how unknown attributes are handled.
///
/// The variants of this enum are referenced in the
/// `#[xml(on_unknown_attribute = ..)]` which can be used on structs and
/// enum variants. The specified variant controls how attributes, which are
/// not handled by any member of the compound, are handled during parsing.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
pub enum UnknownAttributePolicy {
/// All unknown attributes are discarded.
Discard,
/// The first unknown attribute which is encountered generates a fatal
/// parsing error.
///
/// This is the default policy.
#[default]
Fail,
}
impl UnknownAttributePolicy {
#[doc(hidden)]
/// Implementation of the policy.
///
/// This is an internal API and not subject to semver versioning.
pub fn apply_policy(&self, msg: &'static str) -> Result<(), self::error::Error> {
match self {
Self::Fail => Err(self::error::Error::Other(msg)),
Self::Discard => Ok(()),
}
}
}
/// Control how unknown children are handled.
///
/// The variants of this enum are referenced in the
/// `#[xml(on_unknown_child = ..)]` which can be used on structs and
/// enum variants. The specified variant controls how children, which are not
/// handled by any member of the compound, are handled during parsing.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
pub enum UnknownChildPolicy {
/// All unknown children are discarded.
Discard,
/// The first unknown child which is encountered generates a fatal
/// parsing error.
///
/// This is the default policy.
#[default]
Fail,
}
impl UnknownChildPolicy {
#[doc(hidden)]
/// Implementation of the policy.
///
/// This is an internal API and not subject to semver versioning.
pub fn apply_policy(&self, msg: &'static str) -> Result<(), self::error::Error> {
match self {
Self::Fail => Err(self::error::Error::Other(msg)),
Self::Discard => Ok(()),
}
}
}
2024-07-09 17:01:42 +02:00
/// Attempt to transform a type implementing [`AsXml`] into another
/// type which implements [`FromXml`].
2024-07-09 17:01:42 +02:00
pub fn transform<T: FromXml, F: AsXml>(from: F) -> Result<T, self::error::Error> {
let mut iter = self::rxml_util::ItemToEvent::new(from.as_xml_iter()?);
let (qname, attrs) = match iter.next() {
Some(Ok(rxml::Event::StartElement(_, qname, attrs))) => (qname, attrs),
Some(Err(e)) => return Err(e),
_ => panic!("into_event_iter did not start with StartElement event!"),
};
let mut sink = match T::from_events(qname, attrs) {
Ok(v) => v,
Err(self::error::FromEventsError::Mismatch { .. }) => {
return Err(self::error::Error::TypeMismatch)
}
Err(self::error::FromEventsError::Invalid(e)) => return Err(e),
};
for event in iter {
let event = event?;
2024-07-03 11:12:20 +02:00
if let Some(v) = sink.feed(event)? {
return Ok(v);
}
}
2024-08-09 14:48:40 +02:00
Err(self::error::Error::XmlError(rxml::Error::InvalidEof(None)))
}
/// Attempt to convert a [`minidom::Element`] into a type implementing
/// [`FromXml`], fallably.
///
/// Unlike [`transform`] (which can also be used with an element), this
/// function will return the element unharmed if its element header does not
/// match the expectations of `T`.
#[cfg(feature = "minidom")]
pub fn try_from_element<T: FromXml>(
from: minidom::Element,
) -> Result<T, self::error::FromElementError> {
let (qname, attrs) = minidom_compat::make_start_ev_parts(&from)?;
let mut sink = match T::from_events(qname, attrs) {
Ok(v) => v,
Err(self::error::FromEventsError::Mismatch { .. }) => {
return Err(self::error::FromElementError::Mismatch(from))
}
Err(self::error::FromEventsError::Invalid(e)) => {
return Err(self::error::FromElementError::Invalid(e))
}
};
2024-07-09 17:01:42 +02:00
let mut iter = from.as_xml_iter()?;
// consume the element header
for item in &mut iter {
let item = item?;
match item {
// discard the element header
Item::XmlDeclaration(..) => (),
Item::ElementHeadStart(..) => (),
Item::Attribute(..) => (),
Item::ElementHeadEnd => {
// now that the element header is over, we break out
break;
}
Item::Text(..) => panic!("text before end of element header"),
Item::ElementFoot => panic!("element foot before end of element header"),
}
}
let iter = self::rxml_util::ItemToEvent::new(iter);
for event in iter {
let event = event?;
2024-07-03 11:12:20 +02:00
if let Some(v) = sink.feed(event)? {
return Ok(v);
}
}
// unreachable! instead of error here, because minidom::Element always
// produces the complete event sequence of a single element, and FromXml
// implementations must be constructible from that.
unreachable!("minidom::Element did not produce enough events to complete element")
}
#[cfg(feature = "std")]
fn map_nonio_error<T>(r: Result<T, io::Error>) -> Result<T, self::error::Error> {
match r {
Ok(v) => Ok(v),
2024-08-09 14:48:40 +02:00
Err(e) => match e.downcast::<rxml::Error>() {
Ok(e) => Err(e.into()),
Err(_) => unreachable!("I/O error cannot be caused by &[]"),
},
}
}
#[cfg(feature = "std")]
fn read_start_event<I: io::BufRead>(
r: &mut rxml::Reader<I>,
) -> Result<(rxml::QName, rxml::AttrMap), self::error::Error> {
for ev in r {
match map_nonio_error(ev)? {
rxml::Event::XmlDeclaration(_, rxml::XmlVersion::V1_0) => (),
rxml::Event::StartElement(_, name, attrs) => return Ok((name, attrs)),
_ => {
return Err(self::error::Error::Other(
"Unexpected event at start of document",
))
}
}
}
2024-08-09 14:48:40 +02:00
Err(self::error::Error::XmlError(rxml::Error::InvalidEof(Some(
rxml::error::ErrorContext::DocumentBegin,
))))
}
/// Attempt to parse a type implementing [`FromXml`] from a byte buffer
/// containing XML data.
#[cfg(feature = "std")]
pub fn from_bytes<T: FromXml>(mut buf: &[u8]) -> Result<T, self::error::Error> {
let mut reader = rxml::Reader::new(&mut buf);
let (name, attrs) = read_start_event(&mut reader)?;
let mut builder = match T::from_events(name, attrs) {
Ok(v) => v,
Err(self::error::FromEventsError::Mismatch { .. }) => {
return Err(self::error::Error::TypeMismatch)
}
Err(self::error::FromEventsError::Invalid(e)) => return Err(e),
};
for ev in reader {
2024-07-03 11:12:20 +02:00
if let Some(v) = builder.feed(map_nonio_error(ev)?)? {
return Ok(v);
}
}
2024-08-09 14:48:40 +02:00
Err(self::error::Error::XmlError(rxml::Error::InvalidEof(None)))
}
#[cfg(feature = "std")]
fn read_start_event_io<I: io::BufRead>(
r: &mut rxml::Reader<I>,
) -> io::Result<(rxml::QName, rxml::AttrMap)> {
for ev in r {
match ev? {
rxml::Event::XmlDeclaration(_, rxml::XmlVersion::V1_0) => (),
rxml::Event::StartElement(_, name, attrs) => return Ok((name, attrs)),
_ => {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
self::error::Error::Other("Unexpected event at start of document"),
))
}
}
}
Err(io::Error::new(
io::ErrorKind::InvalidData,
self::error::Error::XmlError(rxml::Error::InvalidEof(Some(
rxml::error::ErrorContext::DocumentBegin,
))),
))
}
2024-10-03 12:06:42 +02:00
/// Attempt to parse a type implementing [`FromXml`] from a reader.
#[cfg(feature = "std")]
pub fn from_reader<T: FromXml, R: io::BufRead>(r: R) -> io::Result<T> {
2024-10-03 12:06:42 +02:00
let mut reader = rxml::Reader::new(r);
let (name, attrs) = read_start_event_io(&mut reader)?;
2024-10-03 12:06:42 +02:00
let mut builder = match T::from_events(name, attrs) {
Ok(v) => v,
Err(self::error::FromEventsError::Mismatch { .. }) => {
return Err(self::error::Error::TypeMismatch)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
2024-10-03 12:06:42 +02:00
}
Err(self::error::FromEventsError::Invalid(e)) => {
return Err(e).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
2024-10-03 12:06:42 +02:00
}
};
for ev in reader {
if let Some(v) = builder
.feed(ev?)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?
2024-10-03 12:06:42 +02:00
{
return Ok(v);
}
}
Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
2024-10-03 12:06:42 +02:00
self::error::Error::XmlError(rxml::Error::InvalidEof(None)),
))
}
/// Attempt to serialise a type implementing [`AsXml`] to a vector of bytes.
pub fn to_vec<T: AsXml>(xso: &T) -> Result<Vec<u8>, self::error::Error> {
let iter = xso.as_xml_iter()?;
let mut writer = rxml::writer::Encoder::new();
let mut buf = Vec::new();
for item in iter {
let item = item?;
writer.encode(item.as_rxml_item(), &mut buf)?;
}
Ok(buf)
}
/// Return true if the string contains exclusively XML whitespace.
///
/// XML whitespace is defined as U+0020 (space), U+0009 (tab), U+000a
/// (newline) and U+000d (carriage return).
pub fn is_xml_whitespace<T: AsRef<[u8]>>(s: T) -> bool {
s.as_ref()
.iter()
.all(|b| *b == b' ' || *b == b'\t' || *b == b'\r' || *b == b'\n')
}