//! # Generic builder type implementations //! //! This module contains [`FromEventsBuilder`] implementations for types from //! foreign libraries (such as the standard library). //! //! In order to not clutter the `xso` crate's main namespace, they are //! stashed away in a separate module. // Copyright (c) 2024 Jonas Schäfer // // 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 alloc::boxed::Box; use crate::error::{Error, FromEventsError}; use crate::{FromEventsBuilder, FromXml}; /// Match an XML element qualified name. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum XmlNameMatcher<'x> { /// Match any XML element Any, /// Match any XML element in the given namespace. InNamespace(&'x str), /// Match any XML element with the exact namespace/name combination. Specific(&'x str, &'x str), } impl<'x> XmlNameMatcher<'x> { /// Return the superset of two `XmlNameMatcher` instances. pub const fn superset(self, other: Self) -> Self { match self { Self::Any => Self::Any, Self::InNamespace(my_namespace) => match other { Self::Any => Self::Any, Self::InNamespace(other_namespace) | Self::Specific(other_namespace, _) => { if crate::util::const_str_eq(my_namespace, other_namespace) { Self::InNamespace(my_namespace) } else { Self::Any } } }, Self::Specific(my_namespace, my_name) => match other { Self::Any => Self::Any, Self::InNamespace(other_namespace) => { if crate::util::const_str_eq(my_namespace, other_namespace) { Self::InNamespace(my_namespace) } else { Self::Any } } Self::Specific(other_namespace, other_name) => { if crate::util::const_str_eq(my_namespace, other_namespace) { if crate::util::const_str_eq(my_name, other_name) { Self::Specific(my_name, other_name) } else { Self::InNamespace(my_namespace) } } else { Self::Any } } }, } } /// Return true if the given `qname` matches this matcher. pub fn matches(&self, qname: &rxml::QName) -> bool { match self { Self::Any => true, Self::InNamespace(ns) => qname.0.as_str() == *ns, Self::Specific(ns, name) => qname.0.as_str() == *ns && qname.1.as_str() == *name, } } } /// # Parsing context for [`FromEventsBuilder`] /// /// For the most part, [`FromEventsBuilder`] implementations can work with /// only the information inside the [`rxml::Event`] which is delivered to /// them (and any information they may have stored from previous events). /// /// However, there is (currently) one special case: the `xml:lang` attribute. /// That attribute is inherited across the entire document tree hierarchy. If /// the parsed element is not the top-level element, there may be an implicit /// value for `xml:lang`. #[derive(Debug)] #[doc(hidden)] pub struct Context<'x> { language: Option<&'x str>, } impl<'x> Context<'x> { /// A context suitable for the beginning of the document. /// /// `xml:lang` is assumed to be unset. pub fn empty() -> Self { Self { language: None } } /// Set the effective `xml:lang` value on the context and return it. pub fn with_language(mut self, language: Option<&'x str>) -> Self { self.language = language; self } /// Return the `xml:lang` value in effect at the end of the event which /// is currently being processed. pub fn language(&self) -> Option<&str> { self.language } } /// Helper struct to construct an `Option` from XML events. pub struct OptionBuilder(T); impl FromEventsBuilder for OptionBuilder { type Output = Option; fn feed(&mut self, ev: rxml::Event, ctx: &Context<'_>) -> Result, Error> { self.0.feed(ev, ctx).map(|ok| ok.map(Some)) } } /// Parsers `T` into `Some(.)`. /// /// Note that this never generates `None`: The main use case is to allow /// external (i.e. without calling `from_events`) defaulting to `None` and /// for optional serialisation (the [`AsXml`][`crate::AsXml`] implementation /// on `Option` emits nothing for `None`). impl FromXml for Option { type Builder = OptionBuilder; fn from_events( name: rxml::QName, attrs: rxml::AttrMap, ctx: &Context<'_>, ) -> Result { Ok(OptionBuilder(T::from_events(name, attrs, ctx)?)) } } /// Helper struct to construct an `Box` from XML events. pub struct BoxBuilder(Box); impl FromEventsBuilder for BoxBuilder { type Output = Box; fn feed(&mut self, ev: rxml::Event, ctx: &Context<'_>) -> Result, Error> { self.0.feed(ev, ctx).map(|ok| ok.map(Box::new)) } } /// Parses `T` into a `Box`. impl FromXml for Box { type Builder = BoxBuilder; fn from_events( name: rxml::QName, attrs: rxml::AttrMap, ctx: &Context<'_>, ) -> Result { Ok(BoxBuilder(Box::new(T::from_events(name, attrs, ctx)?))) } } impl FromEventsBuilder for Box { type Output = T::Output; fn feed(&mut self, ev: rxml::Event, ctx: &Context<'_>) -> Result, Error> { (**self).feed(ev, ctx) } } #[derive(Debug)] enum FallibleBuilderInner { Processing { depth: usize, builder: T }, Failed { depth: usize, err: Option }, Done, } /// Build a `Result` from XML. /// /// This builder, invoked generally via the [`FromXml`] implementation on /// `Result where T: FromXml, E: From`, allows to fallably parse /// an XSO from XML. /// /// If an error occurs while parsing the XSO, the remaining events which /// belong to that XSO are discarded. Once all events have been seen, the /// error is returned as `Err(.)` value. /// /// If parsing succeeds, the parsed XSO is returned as `Ok(.)` value. #[derive(Debug)] pub struct FallibleBuilder(FallibleBuilderInner); impl> FromEventsBuilder for FallibleBuilder { type Output = Result; fn feed(&mut self, ev: rxml::Event, ctx: &Context<'_>) -> Result, Error> { match self.0 { FallibleBuilderInner::Processing { ref mut depth, ref mut builder, } => { let new_depth = match ev { rxml::Event::StartElement(..) => match depth.checked_add(1) { // I *think* it is OK to return an err here // instead of panicking. The reason is that anyone // who intends to resume processing at the level // of where we started to parse this thing in case // of an error either has to: // - Use this fallible implementation and rely on // it capturing the error (which we don't in // this case). // - Or count the depth themselves, which will // either fail in the same way, or they use a // wider type (in which case it's ok). None => { self.0 = FallibleBuilderInner::Done; return Err(Error::Other("maximum XML nesting depth exceeded")); } Some(v) => Some(v), }, // In case of an element end, underflow means that we // have reached the end of the XSO we wanted to process. // We handle that case at the end of the outer match's // body: Either we have returned a value then (good), or, // if we reach the end there with a new_depth == None, // something went horribly wrong (and we panic). rxml::Event::EndElement(..) => depth.checked_sub(1), // Text and XML declarations have no influence on parsing // depth. rxml::Event::XmlDeclaration(..) | rxml::Event::Text(..) => Some(*depth), }; match builder.feed(ev, ctx) { Ok(Some(v)) => { self.0 = FallibleBuilderInner::Done; return Ok(Some(Ok(v))); } Ok(None) => { // continue processing in the next round. } Err(e) => { // We are now officially failed .. match new_depth { // .. but we are not done yet, so enter the // failure backtracking state. Some(depth) => { self.0 = FallibleBuilderInner::Failed { depth, err: Some(e.into()), }; return Ok(None); } // .. and we are done with parsing, so we return // the error as value. None => { self.0 = FallibleBuilderInner::Done; return Ok(Some(Err(e.into()))); } } } }; *depth = match new_depth { Some(v) => v, None => unreachable!("fallible parsing continued beyond end of element"), }; // Need more events. Ok(None) } FallibleBuilderInner::Failed { ref mut depth, ref mut err, } => { *depth = match ev { rxml::Event::StartElement(..) => match depth.checked_add(1) { // See above for error return rationale. None => { self.0 = FallibleBuilderInner::Done; return Err(Error::Other("maximum XML nesting depth exceeded")); } Some(v) => v, }, rxml::Event::EndElement(..) => match depth.checked_sub(1) { Some(v) => v, None => { // We are officially done, return a value, switch // states, and be done with it. let err = err.take().expect("fallible parsing somehow lost its error"); self.0 = FallibleBuilderInner::Done; return Ok(Some(Err(err))); } }, // Text and XML declarations have no influence on parsing // depth. rxml::Event::XmlDeclaration(..) | rxml::Event::Text(..) => *depth, }; // Need more events Ok(None) } FallibleBuilderInner::Done => { panic!("FromEventsBuilder called after it returned a value") } } } } /// Parsers `T` fallibly. See [`FallibleBuilder`] for details. impl> FromXml for Result { type Builder = FallibleBuilder; fn from_events( name: rxml::QName, attrs: rxml::AttrMap, ctx: &Context<'_>, ) -> Result { match T::from_events(name, attrs, ctx) { Ok(builder) => Ok(FallibleBuilder(FallibleBuilderInner::Processing { depth: 0, builder, })), Err(FromEventsError::Mismatch { name, attrs }) => { Err(FromEventsError::Mismatch { name, attrs }) } Err(FromEventsError::Invalid(e)) => Ok(FallibleBuilder(FallibleBuilderInner::Failed { depth: 0, err: Some(e.into()), })), } } } /// Builder which discards an entire child tree without inspecting the /// contents. #[derive(Debug, Default)] pub struct Discard { depth: usize, } impl Discard { /// Create a new discarding builder. pub fn new() -> Self { Self::default() } } impl FromEventsBuilder for Discard { type Output = (); fn feed(&mut self, ev: rxml::Event, _ctx: &Context<'_>) -> Result, Error> { match ev { rxml::Event::StartElement(..) => { self.depth = match self.depth.checked_add(1) { Some(v) => v, None => return Err(Error::Other("maximum XML nesting depth exceeded")), }; Ok(None) } rxml::Event::EndElement(..) => match self.depth.checked_sub(1) { None => Ok(Some(())), Some(v) => { self.depth = v; Ok(None) } }, _ => Ok(None), } } } /// Builder which discards the contents (or raises on unexpected contents). /// /// This builder is only to be used from within the proc macros and is not /// stable, public API. #[doc(hidden)] #[cfg(feature = "macros")] pub struct EmptyBuilder { childerr: &'static str, texterr: &'static str, } #[cfg(feature = "macros")] impl FromEventsBuilder for EmptyBuilder { type Output = (); fn feed(&mut self, ev: rxml::Event, _ctx: &Context<'_>) -> Result, Error> { match ev { rxml::Event::EndElement(..) => Ok(Some(())), rxml::Event::StartElement(..) => Err(Error::Other(self.childerr)), rxml::Event::Text(..) => Err(Error::Other(self.texterr)), _ => Err(Error::Other( "unexpected content in supposed-to-be-empty element", )), } } } /// Precursor struct for [`EmptyBuilder`]. /// /// This struct is only to be used from within the proc macros and is not /// stable, public API. #[doc(hidden)] #[cfg(feature = "macros")] pub struct Empty { pub attributeerr: &'static str, pub childerr: &'static str, pub texterr: &'static str, } #[cfg(feature = "macros")] impl Empty { pub fn start(self, attr: rxml::AttrMap) -> Result { if !attr.is_empty() { return Err(Error::Other(self.attributeerr)); } Ok(EmptyBuilder { childerr: self.childerr, texterr: self.texterr, }) } } #[cfg(test)] mod tests { use super::*; use alloc::borrow::ToOwned; use rxml::{parser::EventMetrics, Event, Namespace, NcName}; macro_rules! null_builder { ($name:ident for $output:ident) => { #[derive(Debug)] enum $name {} impl FromEventsBuilder for $name { type Output = $output; fn feed( &mut self, _: Event, _: &Context<'_>, ) -> Result, Error> { unreachable!(); } } }; } null_builder!(AlwaysMismatchBuilder for AlwaysMismatch); null_builder!(InitialErrorBuilder for InitialError); #[derive(Debug)] struct AlwaysMismatch; impl FromXml for AlwaysMismatch { type Builder = AlwaysMismatchBuilder; fn from_events( name: rxml::QName, attrs: rxml::AttrMap, _ctx: &Context<'_>, ) -> Result { Err(FromEventsError::Mismatch { name, attrs }) } } #[derive(Debug)] struct InitialError; impl FromXml for InitialError { type Builder = InitialErrorBuilder; fn from_events( _: rxml::QName, _: rxml::AttrMap, _: &Context<'_>, ) -> Result { Err(FromEventsError::Invalid(Error::Other("some error"))) } } #[derive(Debug)] struct FailOnContentBuilder; impl FromEventsBuilder for FailOnContentBuilder { type Output = FailOnContent; fn feed(&mut self, _: Event, _: &Context<'_>) -> Result, Error> { Err(Error::Other("content error")) } } #[derive(Debug)] struct FailOnContent; impl FromXml for FailOnContent { type Builder = FailOnContentBuilder; fn from_events( _: rxml::QName, _: rxml::AttrMap, _: &Context<'_>, ) -> Result { Ok(FailOnContentBuilder) } } fn qname() -> rxml::QName { (Namespace::NONE, NcName::try_from("test").unwrap()) } fn attrs() -> rxml::AttrMap { rxml::AttrMap::new() } #[test] fn fallible_builder_mismatch_passthrough() { match Result::::from_events(qname(), attrs(), &Context::empty()) { Err(FromEventsError::Mismatch { .. }) => (), other => panic!("unexpected result: {:?}", other), } } #[test] fn fallible_builder_initial_error_capture() { let ctx = Context::empty(); let mut builder = match Result::::from_events(qname(), attrs(), &ctx) { Ok(v) => v, other => panic!("unexpected result: {:?}", other), }; match builder.feed( Event::Text(EventMetrics::zero(), "hello world!".to_owned()), &ctx, ) { Ok(None) => (), other => panic!("unexpected result: {:?}", other), }; match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) { Ok(Some(Err(Error::Other("some error")))) => (), other => panic!("unexpected result: {:?}", other), }; } #[test] fn fallible_builder_initial_error_capture_allows_nested_stuff() { let ctx = Context::empty(); let mut builder = match Result::::from_events(qname(), attrs(), &ctx) { Ok(v) => v, other => panic!("unexpected result: {:?}", other), }; match builder.feed( Event::StartElement(EventMetrics::zero(), qname(), attrs()), &ctx, ) { Ok(None) => (), other => panic!("unexpected result: {:?}", other), }; match builder.feed( Event::Text(EventMetrics::zero(), "hello world!".to_owned()), &ctx, ) { Ok(None) => (), other => panic!("unexpected result: {:?}", other), }; match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) { Ok(None) => (), other => panic!("unexpected result: {:?}", other), }; match builder.feed( Event::Text(EventMetrics::zero(), "hello world!".to_owned()), &ctx, ) { Ok(None) => (), other => panic!("unexpected result: {:?}", other), }; match builder.feed( Event::StartElement(EventMetrics::zero(), qname(), attrs()), &ctx, ) { Ok(None) => (), other => panic!("unexpected result: {:?}", other), }; match builder.feed( Event::StartElement(EventMetrics::zero(), qname(), attrs()), &ctx, ) { Ok(None) => (), other => panic!("unexpected result: {:?}", other), }; match builder.feed( Event::Text(EventMetrics::zero(), "hello world!".to_owned()), &ctx, ) { Ok(None) => (), other => panic!("unexpected result: {:?}", other), }; match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) { Ok(None) => (), other => panic!("unexpected result: {:?}", other), }; match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) { Ok(None) => (), other => panic!("unexpected result: {:?}", other), }; match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) { Ok(Some(Err(Error::Other("some error")))) => (), other => panic!("unexpected result: {:?}", other), }; } #[test] fn fallible_builder_content_error_capture() { let ctx = Context::empty(); let mut builder = match Result::::from_events(qname(), attrs(), &ctx) { Ok(v) => v, other => panic!("unexpected result: {:?}", other), }; match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) { Ok(Some(Err(Error::Other("content error")))) => (), other => panic!("unexpected result: {:?}", other), }; } #[test] fn fallible_builder_content_error_capture_with_more_content() { let ctx = Context::empty(); let mut builder = match Result::::from_events(qname(), attrs(), &ctx) { Ok(v) => v, other => panic!("unexpected result: {:?}", other), }; match builder.feed( Event::Text(EventMetrics::zero(), "hello world!".to_owned()), &ctx, ) { Ok(None) => (), other => panic!("unexpected result: {:?}", other), }; match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) { Ok(Some(Err(Error::Other("content error")))) => (), other => panic!("unexpected result: {:?}", other), }; } #[test] fn fallible_builder_content_error_capture_with_nested_content() { let ctx = Context::empty(); let mut builder = match Result::::from_events(qname(), attrs(), &ctx) { Ok(v) => v, other => panic!("unexpected result: {:?}", other), }; match builder.feed( Event::StartElement(EventMetrics::zero(), qname(), attrs()), &ctx, ) { Ok(None) => (), other => panic!("unexpected result: {:?}", other), }; match builder.feed( Event::Text(EventMetrics::zero(), "hello world!".to_owned()), &ctx, ) { Ok(None) => (), other => panic!("unexpected result: {:?}", other), }; match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) { Ok(None) => (), other => panic!("unexpected result: {:?}", other), }; match builder.feed( Event::Text(EventMetrics::zero(), "hello world!".to_owned()), &ctx, ) { Ok(None) => (), other => panic!("unexpected result: {:?}", other), }; match builder.feed( Event::StartElement(EventMetrics::zero(), qname(), attrs()), &ctx, ) { Ok(None) => (), other => panic!("unexpected result: {:?}", other), }; match builder.feed( Event::StartElement(EventMetrics::zero(), qname(), attrs()), &ctx, ) { Ok(None) => (), other => panic!("unexpected result: {:?}", other), }; match builder.feed( Event::Text(EventMetrics::zero(), "hello world!".to_owned()), &ctx, ) { Ok(None) => (), other => panic!("unexpected result: {:?}", other), }; match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) { Ok(None) => (), other => panic!("unexpected result: {:?}", other), }; match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) { Ok(None) => (), other => panic!("unexpected result: {:?}", other), }; match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) { Ok(Some(Err(Error::Other("content error")))) => (), other => panic!("unexpected result: {:?}", other), }; } }