From cb3da52ba21fd1a0334a077ea626a2891cd90a63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Sch=C3=A4fer?= Date: Sun, 16 Jun 2024 10:14:18 +0200 Subject: [PATCH] parsers: add streamable parsing This adds shims which provide FromXml and IntoXml implementations to *all* macro-generated types in `xmpp_parsers`. Mind that this does not cover all types in `xmpp_parsers`, but a good share of them. This is another first step toward real, fully streamed parsing. --- Cargo.toml | 1 + parsers/Cargo.toml | 1 + parsers/src/util/error.rs | 15 +++++++++ parsers/src/util/macros.rs | 25 ++++++++++++++ xso/src/error.rs | 16 +++++++++ xso/src/lib.rs | 5 +++ xso/src/minidom_compat.rs | 68 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 131 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 6d20ee4d..67b32a3e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,3 +17,4 @@ sasl = { path = "sasl" } tokio-xmpp = { path = "tokio-xmpp" } xmpp-parsers = { path = "parsers" } xmpp = { path = "xmpp" } +xso = { path = "xso" } diff --git a/parsers/Cargo.toml b/parsers/Cargo.toml index e6f277b6..c1922438 100644 --- a/parsers/Cargo.toml +++ b/parsers/Cargo.toml @@ -24,6 +24,7 @@ chrono = { version = "0.4.5", default-features = false, features = ["std"] } # same repository dependencies jid = { version = "0.10", features = ["minidom"], path = "../jid" } minidom = { version = "0.15", path = "../minidom" } +xso = { version = "0.0.2" } [features] # Build xmpp-parsers to make components instead of clients. diff --git a/parsers/src/util/error.rs b/parsers/src/util/error.rs index b541266e..377d666c 100644 --- a/parsers/src/util/error.rs +++ b/parsers/src/util/error.rs @@ -131,3 +131,18 @@ impl From for Error { Error::ChronoParseError(err) } } + +impl From for xso::error::Error { + fn from(other: Error) -> Self { + match other { + Error::ParseError(e) => Self::Other(e.to_string().into()), + Error::TypeMismatch { .. } => Self::TypeMismatch, + Error::Base64Error(e) => Self::TextParseError(Box::new(e)), + Error::ParseIntError(e) => Self::TextParseError(Box::new(e)), + Error::ParseStringError(e) => Self::TextParseError(Box::new(e)), + Error::ParseAddrError(e) => Self::TextParseError(Box::new(e)), + Error::JidParseError(e) => Self::TextParseError(Box::new(e)), + Error::ChronoParseError(e) => Self::TextParseError(Box::new(e)), + } + } +} diff --git a/parsers/src/util/macros.rs b/parsers/src/util/macros.rs index 8815654c..e17d89ed 100644 --- a/parsers/src/util/macros.rs +++ b/parsers/src/util/macros.rs @@ -676,6 +676,23 @@ macro_rules! generate_element { )* } + impl ::xso::FromXml for $elem { + type Builder = ::xso::minidom_compat::FromEventsViaElement<$elem>; + + fn from_events( + qname: ::xso::exports::rxml::QName, + attrs: ::xso::exports::rxml::AttrMap, + ) -> Result { + if qname.0 != crate::ns::$ns || qname.1 != $name { + return Err(::xso::error::FromEventsError::Mismatch { + name: qname, + attrs, + }) + } + Self::Builder::new(qname, attrs) + } + } + impl ::std::convert::TryFrom for $elem { type Error = crate::util::error::Error; @@ -748,6 +765,14 @@ macro_rules! generate_element { builder.build() } } + + impl ::xso::IntoXml for $elem { + type EventIter = ::xso::minidom_compat::IntoEventsViaElement; + + fn into_event_iter(self) -> Result { + Self::EventIter::new(self) + } + } ); } diff --git a/xso/src/error.rs b/xso/src/error.rs index 5da472d4..15728f66 100644 --- a/xso/src/error.rs +++ b/xso/src/error.rs @@ -21,6 +21,9 @@ pub enum Error { /// Attempt to parse text data failed with the provided nested error. TextParseError(Box), + /// Generic, unspecified other error. + Other(Box), + /// An element header did not match an expected element. /// /// This is only rarely generated: most of the time, a mismatch of element @@ -35,6 +38,7 @@ impl fmt::Display for Error { Self::XmlError(ref e) => write!(f, "xml parse error: {}", e), Self::TextParseError(ref e) => write!(f, "text parse error: {}", e), Self::TypeMismatch => f.write_str("mismatch between expected and actual XML data"), + Self::Other(msg) => f.write_str(msg), } } } @@ -61,6 +65,12 @@ impl From for Error { } } +impl From for Error { + fn from(other: std::convert::Infallible) -> Self { + match other {} + } +} + /// Error returned from /// [`FromXml::from_events`][`crate::FromXml::from_events`]. #[derive(Debug)] @@ -87,6 +97,12 @@ impl From for FromEventsError { } } +impl From for FromEventsError { + fn from(other: std::convert::Infallible) -> Self { + match other {} + } +} + impl fmt::Display for FromEventsError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { diff --git a/xso/src/lib.rs b/xso/src/lib.rs index c38f386d..d9bd748a 100644 --- a/xso/src/lib.rs +++ b/xso/src/lib.rs @@ -20,6 +20,11 @@ use of this library in parsing XML streams like specified in RFC 6120. pub mod error; pub mod minidom_compat; +#[doc(hidden)] +pub mod exports { + pub use rxml; +} + /// Trait allowing to consume a struct and iterate its contents as /// serialisable [`rxml::Event`] items. pub trait IntoXml { diff --git a/xso/src/minidom_compat.rs b/xso/src/minidom_compat.rs index fffe2e2b..bbe0e194 100644 --- a/xso/src/minidom_compat.rs +++ b/xso/src/minidom_compat.rs @@ -4,6 +4,7 @@ // 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/. +use std::marker::PhantomData; use std::vec::IntoIter; use minidom::{Element, Node}; @@ -235,6 +236,73 @@ impl FromXml for Element { } } +/// Helper struct to streamingly parse a struct which implements conversion +/// from [`minidom::Element`]. +pub struct FromEventsViaElement { + inner: ElementFromEvents, + // needed here because we need to keep the type `T` around until + // `FromEventsBuilder` is done and it must always be the same type, so we + // have to nail it down in the struct's type, and to do that we need to + // bind it to a field. that's what PhantomData is for. + _phantom: PhantomData, +} + +impl> FromEventsViaElement +where + Error: From, +{ + /// Create a new streaming parser for `T`. + pub fn new(qname: rxml::QName, attrs: rxml::AttrMap) -> Result { + Ok(Self { + _phantom: PhantomData, + inner: Element::from_events(qname, attrs)?, + }) + } +} + +impl> FromEventsBuilder for FromEventsViaElement +where + Error: From, +{ + type Output = T; + + fn feed(&mut self, ev: Event) -> Result, Error> { + match self.inner.feed(ev) { + Ok(Some(v)) => Ok(Some(v.try_into()?)), + Ok(None) => Ok(None), + Err(e) => Err(e), + } + } +} + +/// Helper struct to stream a struct which implements conversion +/// to [`minidom::Element`]. +pub struct IntoEventsViaElement { + inner: IntoEvents, +} + +impl IntoEventsViaElement { + /// Create a new streaming parser for `T`. + pub fn new(value: T) -> Result + where + Error: From, + minidom::Element: TryFrom, + { + let element: minidom::Element = value.try_into()?; + Ok(Self { + inner: element.into_event_iter()?, + }) + } +} + +impl Iterator for IntoEventsViaElement { + type Item = Result; + + fn next(&mut self) -> Option { + self.inner.next() + } +} + #[cfg(test)] mod tests { use super::*;