From 7f5542e8d47495db49798cded3b9788525830057 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Sch=C3=A4fer?= Date: Fri, 2 May 2025 13:15:16 +0200 Subject: [PATCH] xso: add support for dynamically typed XSOs --- xso/ChangeLog | 2 + xso/src/dynxso.rs | 886 ++++++++++++++++++++++++++++++++++++++++++++++ xso/src/lib.rs | 2 + 3 files changed, 890 insertions(+) create mode 100644 xso/src/dynxso.rs diff --git a/xso/ChangeLog b/xso/ChangeLog index e7ff5c185ce985181566fa05fff3b8189250a3b0..eceefdff300b66b8d71e6b5f1630b455c3b31111 100644 --- a/xso/ChangeLog +++ b/xso/ChangeLog @@ -57,6 +57,8 @@ Version NEXT: - `FromXml::xml_name_matcher()` and related types, which allow `FromXml` implementors to provide cachable hints to callers about which XML elements are valid candidates for parsing (!573). + - `Xso`, a Box-like struct which can hold a dynamically-typed + XSO, alongside related traits and (declarative) macros (!573). * Changes - Generated AsXml iterator and FromXml builder types are now doc(hidden), to not clutter hand-written documentation with auto diff --git a/xso/src/dynxso.rs b/xso/src/dynxso.rs new file mode 100644 index 0000000000000000000000000000000000000000..5bb6f89ccd8afcf420e949e2f72b72e1d220427b --- /dev/null +++ b/xso/src/dynxso.rs @@ -0,0 +1,886 @@ +// Copyright (c) 2025 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/. + +//! # Dynamically-typed XSOs +//! +//! This module provides the utilities to make dynamically-typed XSOs work. +//! Dynamically typed XSOs are contained in [`Xso`][`Xso`], where +//! `Trait` is a trait provided by the user. +//! +//! The given `Trait` constrains the specific types which can be used in the +//! `Xso` box. This allows users to provide additional methods on +//! the trait which are available on all `Xso` objects via the +//! [`Deref`][`core::ops::Deref`] and [`DerefMut`][`core::ops::DerefMut`] +//! implementations. +//! +//! ## Creating a new `Trait` +//! +//! In order to be usable within `Xso`, a trait must satisfy the +//! following constraints: +//! +//! - `dyn Trait` must implement [`DynXso`]. +//! - `dyn Trait` must implement [`MayContain`] for all `T` which implement +//! `Trait`. +//! +//! The easiest and most forward-compatible way of providing these +//! implementations is the [`derive_dyn_traits`][`crate::derive_dyn_traits`] +//! macro. +//! +//! ## Example +//! +#![cfg_attr( + not(feature = "macros"), + doc = "Because the macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n" +)] +#![cfg_attr(feature = "macros", doc = "\n```\n")] +//! # use core::any::Any; +//! # use xso::{dynxso::{Xso, BuilderRegistry}, FromXml, from_bytes, derive_dyn_traits}; +//! trait MyPayload: Any {} +//! +//! derive_dyn_traits!(MyPayload); +//! +//! #[derive(FromXml, Debug, PartialEq)] +//! #[xml(namespace = "urn:example", name = "foo")] +//! struct Foo; +//! impl MyPayload for Foo {} +//! Xso::::register_type::(); +//! +//! #[derive(FromXml, Debug, PartialEq)] +//! #[xml(namespace = "urn:example", name = "bar")] +//! struct Bar; +//! impl MyPayload for Bar {} +//! Xso::::register_type::(); +//! +//! let x: Xso = from_bytes("".as_bytes()).unwrap(); +//! assert_eq!(Foo, *x.downcast::().unwrap()); +//! +//! let x: Xso = from_bytes("".as_bytes()).unwrap(); +//! assert_eq!(Bar, *x.downcast::().unwrap()); +//! ``` + +use alloc::{boxed::Box, vec::Vec}; +use core::{ + any::{Any, TypeId}, + fmt, + marker::PhantomData, + ops::{Deref, DerefMut}, +}; +use std::sync::Mutex; + +use crate::{ + asxml::AsXmlDyn, + error::{Error, FromEventsError}, + fromxml::XmlNameMatcher, + AsXml, Context, FromEventsBuilder, FromXml, Item, +}; + +/// # Generate `DynXso` and `MayContain` trait implementations +/// +/// This macro generates trait [`DynXso`] and [`MayContain`] trait +/// implementations for a given trait. For more background information on when +/// that is a useful thing to have, see the [`dynxso`][`crate::dynxso`] +/// module. +/// +/// ## Example +/// +/// ``` +/// # use core::any::Any; +/// use xso::derive_dyn_traits; +/// trait Foo: Any {} +/// derive_dyn_traits!(Foo); +/// ``` +/// +/// Note that the trait this macro is called on **must** have a bound on +/// `Any`, otherwise the generated code will not compile: +/// +/// ```compile_fail +/// use xso::derive_dyn_traits; +/// trait Foo {} +/// derive_dyn_traits!(Foo); +/// // ↑ will generate a bunch of errors about incompatible types +/// ``` +#[macro_export] +macro_rules! derive_dyn_traits { + ($trait:ident) => { + impl $crate::dynxso::DynXso for dyn $trait { + fn registry() -> &'static $crate::dynxso::BuilderRegistry { + static DATA: $crate::dynxso::BuilderRegistry = + $crate::dynxso::BuilderRegistry::new(); + &DATA + } + + fn try_downcast( + self: $crate::exports::alloc::boxed::Box, + ) -> Result< + $crate::exports::alloc::boxed::Box, + $crate::exports::alloc::boxed::Box, + > + where + Self: $crate::dynxso::MayContain, + { + if (&*self as &dyn core::any::Any).is::() { + match (self as $crate::exports::alloc::boxed::Box) + .downcast() + { + Ok(v) => Ok(v), + Err(_) => unreachable!("Any::is and Any::downcast disagree!"), + } + } else { + Err(self) + } + } + + fn try_downcast_ref(&self) -> Option<&T> + where + Self: $crate::dynxso::MayContain, + { + (&*self as &dyn core::any::Any).downcast_ref() + } + + fn try_downcast_mut(&mut self) -> Option<&mut T> + where + Self: $crate::dynxso::MayContain, + { + (&mut *self as &mut dyn core::any::Any).downcast_mut() + } + + fn is(&self) -> bool + where + Self: $crate::dynxso::MayContain, + { + (&*self as &dyn core::any::Any).is::() + } + + fn type_id(&self) -> core::any::TypeId { + (&*self as &dyn core::any::Any).type_id() + } + } + + impl $crate::dynxso::MayContain for dyn $trait { + fn upcast_into(other: T) -> Box { + Box::new(other) + } + } + }; +} + +type BuilderRegistryBuilder = Box< + dyn Fn( + rxml::QName, + rxml::AttrMap, + &Context<'_>, + ) -> Result>>, FromEventsError> + + Send + + Sync + + 'static, +>; + +struct BuilderRegistryEntry { + matcher: XmlNameMatcher<'static>, + ty: TypeId, + builder: BuilderRegistryBuilder, +} + +impl fmt::Debug for BuilderRegistryEntry { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("BuilderRegistryEntry") + .field("matcher", &self.matcher) + .field("ty", &self.ty) + .finish_non_exhaustive() + } +} + +/// # Registry for type-erased [`FromXml`] builders which construct `T` +/// +/// This registry holds type-erased [`FromXml::from_events`] implementations. +/// It can be used to dynamically dispatch to a set of `FromXml` +/// implementations which is not known at compile time. +/// +/// Under the hood, this is used by the `FromXml` implementation on +/// [`Xso`][`Xso`], via the [`DynXso`] trait. +/// +/// Note that this registry does not allow to add arbitrary builders. All +/// builders must originate in a [`FromXml`] implementation and their output +/// must be convertible to `T`. +/// +/// ## Performance trade-offs +/// +/// The implementation of the `BuilderRegistry` is optimized toward specific +/// usages. Other usages may see negative performance impacts. The following +/// assumptions are made: +/// +/// - Types are added only once at startup and a matching +/// [`reserve`][`Self::reserve`] call is made beforehands. +/// - There are many different types. +/// - [`FromXml::xml_name_matcher`] returns a different value for most of the +/// types which are added. +/// +/// The lookup algorithms in particular are geared toward the implications of +/// the last two assumptions and may be rather inefficient otherwise. +/// +/// ## Example +/// +/// This example illustrates the manual usage of the `BuilderRegistry`. +/// +#[cfg_attr( + not(feature = "macros"), + doc = "Because the macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n" +)] +#[cfg_attr(feature = "macros", doc = "\n```\n")] +/// # use xso::{dynxso::{BuilderRegistry, MayContain}, FromXml, from_bytes, exports::rxml::{Namespace, AttrMap, QName, Event, parser::EventMetrics, xml_ncname}, Context, FromEventsBuilder, error::FromEventsError}; +/// #[derive(FromXml, Debug, PartialEq)] +/// #[xml(namespace = "urn:example", name = "foo")] +/// struct Foo; +/// +/// #[derive(FromXml, Debug, PartialEq)] +/// #[xml(namespace = "urn:example", name = "bar")] +/// struct Bar; +/// +/// #[derive(Debug, PartialEq)] +/// enum Either { +/// A(Foo), +/// B(Bar), +/// } +/// +/// // BuilderRegistry::add:: requires that Either: MayContain, in order +/// // to be able to wrap the U::Builder such that it outputs a Xso +/// // instead. +/// impl MayContain for Either { +/// fn upcast_into(other: Foo) -> Box { Box::new(Self::A(other)) } +/// } +/// +/// impl MayContain for Either { +/// fn upcast_into(other: Bar) -> Box { Box::new(Self::B(other)) } +/// } +/// +/// let registry = BuilderRegistry::::new(); +/// registry.add::(); +/// registry.add::(); +/// +/// let mut builder = registry.make_builder( +/// ( +/// Namespace::from_str("urn:example"), +/// xml_ncname!("foo").to_owned(), // <- Selects the Foo variant +/// ), +/// AttrMap::new(), +/// &Context::empty(), +/// ).unwrap(); +/// let x = builder.feed(Event::EndElement(EventMetrics::zero()), &Context::empty()).unwrap().unwrap(); +/// assert_eq!(Either::A(Foo), *x); +/// +/// let mut builder = registry.make_builder( +/// ( +/// Namespace::from_str("urn:example"), +/// xml_ncname!("bar").to_owned(), // <- Selects the Bar variant +/// ), +/// AttrMap::new(), +/// &Context::empty(), +/// ).unwrap(); +/// let x = builder.feed(Event::EndElement(EventMetrics::zero()), &Context::empty()).unwrap().unwrap(); +/// assert_eq!(Either::B(Bar), *x); +/// ``` +/// +/// Implementing `FromXml` on the `Either` type in the above example would +/// be trivial and look like this: +/// +#[cfg_attr( + not(feature = "macros"), + doc = "Because the macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n" +)] +#[cfg_attr(feature = "macros", doc = "\n```\n")] +/// # use xso::{dynxso::{BuilderRegistry, MayContain}, FromXml, from_bytes, exports::rxml::{Namespace, AttrMap, QName, Event, parser::EventMetrics}, Context, FromEventsBuilder, error::FromEventsError}; +/// # #[derive(FromXml, Debug, PartialEq)] +/// # #[xml(namespace = "urn:example", name = "foo")] +/// # struct Foo; +/// # +/// # #[derive(FromXml, Debug, PartialEq)] +/// # #[xml(namespace = "urn:example", name = "bar")] +/// # struct Bar; +/// # +/// # #[derive(Debug, PartialEq)] +/// # enum Either { +/// # A(Foo), +/// # B(Bar), +/// # } +/// # +/// # impl MayContain for Either { +/// # fn upcast_into(other: Foo) -> Box { Box::new(Self::A(other)) } +/// # } +/// # +/// # impl MayContain for Either { +/// # fn upcast_into(other: Bar) -> Box { Box::new(Self::B(other)) } +/// # } +/// # +/// // ... code from the previous example ... +/// use xso::dynxso::UnboxBuilder; +/// // In order to be able to use the registry from the FromXml implementation, +/// // it must be a static (as opposed to a local variable as in the previous +/// // example). +/// static REGISTRY: BuilderRegistry = BuilderRegistry::new(); +/// REGISTRY.add::(); +/// REGISTRY.add::(); +/// +/// impl FromXml for Either { +/// type Builder = UnboxBuilder>>>; +/// +/// fn from_events(name: QName, attrs: AttrMap, ctx: &Context<'_>) -> Result { +/// REGISTRY.make_builder(name, attrs, ctx).map(UnboxBuilder::wrap) +/// } +/// } +/// +/// assert_eq!( +/// Either::A(Foo), +/// from_bytes("".as_bytes()).unwrap(), +/// ); +/// +/// assert_eq!( +/// Either::B(Bar), +/// from_bytes("".as_bytes()).unwrap(), +/// ); +/// ``` +#[derive(Debug)] +pub struct BuilderRegistry { + inner: Mutex>>, +} + +impl BuilderRegistry { + /// Create an empty registry. + pub const fn new() -> Self { + Self { + inner: Mutex::new(Vec::new()), + } + } + + fn insert( + &self, + matcher: XmlNameMatcher<'static>, + type_id: TypeId, + builder: BuilderRegistryBuilder, + ) { + let mut registry = self.inner.lock().unwrap(); + let start_scan_at = registry.partition_point(|entry| entry.matcher < matcher); + let insert_at = 'outer: { + let mut i = start_scan_at; + while i < registry.len() { + let entry = ®istry[i]; + if entry.matcher == matcher { + if entry.ty == type_id { + // Already inserted. + return; + } + // Still entries with the same matcher -> continue. + i += 1; + continue; + } + + // Found insertion point; + break 'outer i; + } + + // The entire (rest of the) registry contains items matching + // `matcher`, but none matching the given `type_id` -> insert at + // the end. + registry.len() + }; + + registry.insert( + insert_at, + BuilderRegistryEntry { + matcher, + ty: type_id, + builder, + }, + ); + } + + /// Reserve space for at least `n` additional types. + pub fn reserve(&self, n: usize) { + self.inner.lock().unwrap().reserve(n); + } + + /// Add a new builder to the registry. + /// + /// This allows to add any `FromXml` implementation whose output can be + /// converted to `T`. + pub fn add(&self) + where + T: MayContain, + { + struct Wrapper { + inner: B, + output: PhantomData, + } + + impl> FromEventsBuilder for Wrapper + where + X: MayContain, + { + type Output = Box; + + fn feed( + &mut self, + ev: rxml::Event, + ctx: &Context<'_>, + ) -> Result, Error> { + self.inner + .feed(ev, ctx) + .map(|x| x.map(|x| >::upcast_into(x))) + } + } + + self.insert( + U::xml_name_matcher(), + TypeId::of::(), + Box::new(|name, attrs, ctx| { + U::from_events(name, attrs, ctx).map(|builder| { + Box::new(Wrapper { + inner: builder, + output: PhantomData, + }) as Box>> + }) + }), + ) + } + + fn try_build( + inner: &mut Vec>, + mut name: rxml::QName, + mut attrs: rxml::AttrMap, + ctx: &Context<'_>, + matcher_builder: impl for<'x> FnOnce(&'x rxml::QName) -> XmlNameMatcher<'x>, + ) -> Result>>, FromEventsError> { + let matcher = matcher_builder(&name); + let start_scan_at = inner.partition_point(|entry| entry.matcher < matcher); + + for entry in &inner[start_scan_at..] { + if !entry.matcher.matches(&name) { + return Err(FromEventsError::Mismatch { name, attrs }); + } + + match (entry.builder)(name, attrs, ctx) { + Ok(v) => return Ok(v), + Err(FromEventsError::Invalid(e)) => return Err(FromEventsError::Invalid(e)), + Err(FromEventsError::Mismatch { + name: new_name, + attrs: new_attrs, + }) => { + name = new_name; + attrs = new_attrs; + } + } + } + + Err(FromEventsError::Mismatch { name, attrs }) + } + + /// Make a builder for the given element header. + /// + /// This tries all applicable `FromXml` implementations which have + /// previously been added via [`add`][`Self::add`] in unspecified order. + /// The first implementation to either fail or succeed at constructing a + /// builder determines the result. Implementations which return a + /// [`FromEventsError::Mismatch`][`crate::error::FromEventsError::Mismatch`] + /// are ignored. + /// + /// If all applicable implementations return `Mismatch`, this function + /// returns `Mismatch`, too. + pub fn make_builder( + &self, + name: rxml::QName, + attrs: rxml::AttrMap, + ctx: &Context<'_>, + ) -> Result>>, FromEventsError> { + let mut inner = self.inner.lock().unwrap(); + let (name, attrs) = match Self::try_build(&mut *inner, name, attrs, ctx, |qname| { + XmlNameMatcher::Specific(qname.0.as_str(), qname.1.as_str()) + }) { + Ok(v) => return Ok(v), + Err(FromEventsError::Invalid(e)) => return Err(FromEventsError::Invalid(e)), + Err(FromEventsError::Mismatch { name, attrs }) => (name, attrs), + }; + + let (name, attrs) = match Self::try_build(&mut *inner, name, attrs, ctx, |qname| { + XmlNameMatcher::InNamespace(qname.0.as_str()) + }) { + Ok(v) => return Ok(v), + Err(FromEventsError::Invalid(e)) => return Err(FromEventsError::Invalid(e)), + Err(FromEventsError::Mismatch { name, attrs }) => (name, attrs), + }; + + Self::try_build(&mut *inner, name, attrs, ctx, |_| XmlNameMatcher::Any) + } +} + +/// # Dynamic XSO type +/// +/// This trait provides the infrastructure for dynamic XSO types. In +/// particular, it provides: +/// +/// - Access to a [`BuilderRegistry`] which allows constructing an instance of +/// the dynamic XSO type from XML. +/// - Downcasts to specific types. +/// +/// Like [`MayContain`], it is typically implemented on `dyn Trait` for some +/// `Trait` and it is best generated using +/// [`derive_dyn_traits`][`crate::derive_dyn_traits`]. +/// +/// This trait explicitly provides the methods provided by [`Any`]. The reason +/// for this duplication is that with `DynXso` being intended to be +/// implemented on `dyn Trait`, code using this trait cannot cast the value +/// to `dyn Any` to access the `downcast`-related methods (`type_id` would, +/// in fact, work if `DynXso` had a bound on `Any`, but not the downcasts). +/// +/// *Hint*: It should not be necessary for user code to directly interact +/// with this trait. +pub trait DynXso: 'static { + /// Return the builder registry for this dynamic type. + /// + /// The builder registry is used by [`Xso::register_type`] and the + /// `FromXml` implementation. + fn registry() -> &'static BuilderRegistry; + + /// Try to downcast a boxed dynamic XSO to a specific type. + /// + /// If `self` contains a `T` (and thus, the downcast succeeds), `Ok(_)` + /// is returned. Otherwise, `Err(self)` is returned, allowing to chain + /// this function with other downcast attempts. + /// + /// This is similar to `downcast` on [`dyn Any`][`core::any::Any`]. + fn try_downcast(self: Box) -> Result, Box> + where + Self: MayContain; + + /// Try to downcast a dynamic XSO to a reference to a specific type. + /// + /// If `self` contains a `T` (and thus, the downcast succeeds), `Some(_)` + /// is returned. Otherwise, `None`. + /// + /// This is similar to `downcast_ref` on [`dyn Any`][`core::any::Any`]. + fn try_downcast_ref(&self) -> Option<&T> + where + Self: MayContain; + + /// Try to downcast a dynamic XSO to a mutable reference to a specific + /// type. + /// + /// If `self` contains a `T` (and thus, the downcast succeeds), `Some(_)` + /// is returned. Otherwise, `None`. + /// + /// This is similar to `downcast_mut` on [`dyn Any`][`core::any::Any`]. + fn try_downcast_mut(&mut self) -> Option<&mut T> + where + Self: MayContain; + + /// Return true if `self` contains a `T`. + /// + /// This is similar to `is` on [`dyn Any`][`core::any::Any`]. + fn is(&self) -> bool + where + Self: MayContain; + + /// Return the [`TypeId`] of `self`. + /// + /// This is similar to `type_id` on [`dyn Any`][`core::any::Any`]. + fn type_id(&self) -> TypeId; +} + +/// # Declare that `T` may be held by `Box` +/// +/// This trait is used to constrain which types can be put in +/// [`Xso`][`Xso`]. It is typically implemented on `dyn Trait` for all +/// `T: Trait`. +/// +/// To automatically generate suitable implementations of this trait, see +/// [`derive_dyn_traits`][`crate::derive_dyn_traits`]. +/// +/// Implementation-wise, this trait is very similar to `Box: From`. +/// However, `From` is also used in many different circumstances and it cannot +/// be suitably overloaded on `Box<_>`, so a new trait was introduced for this +/// particular purpose. +pub trait MayContain { + /// Convert a value of `T` into `Box`. + fn upcast_into(other: T) -> Box; +} + +/// # Dynamic XSO container +/// +/// This container is very similar to `Box<_>`, but geared specifically toward +/// the use with `T` being a `dyn Trait`. It also implements [`FromXml`] +/// (unconditionally) and [`AsXml`] if `T` implements [`AsXmlDyn`]. +/// +/// In order to provide these features, `T` must implement [`DynXso`] and +/// [`MayContain`]. Implementations for these traits can be generated using +/// [`derive_dyn_traits`][`crate::derive_dyn_traits`]. +/// +/// Most methods on `Xso` which take type parameters are only +/// available for types `U` implementing `Trait` (or, more precisely, where +/// `dyn Trait` implements `MayContain`). +#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone)] +#[repr(transparent)] +pub struct Xso { + inner: Box, +} + +impl Deref for Xso { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.inner.deref() + } +} + +impl DerefMut for Xso { + fn deref_mut(&mut self) -> &mut Self::Target { + self.inner.deref_mut() + } +} + +impl fmt::Debug for Xso { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Xso") + .field("inner", &self.inner_type_id()) + .finish() + } +} + +impl Xso { + /// Wrap a value into a `Xso`. + /// + /// ``` + /// # use core::any::Any; + /// # use xso::{dynxso::Xso, derive_dyn_traits}; + /// trait Trait: Any {} + /// derive_dyn_traits!(Trait); + /// + /// struct Foo; + /// impl Trait for Foo {} + /// + /// let x: Xso = Xso::wrap(Foo); + /// ``` + pub fn wrap(value: U) -> Self + where + T: MayContain, + { + Self { + inner: T::upcast_into(value), + } + } + + /// Convert `Xso` into `Box`. + /// + /// ``` + /// # use core::any::Any; + /// # use xso::{dynxso::Xso, derive_dyn_traits}; + /// trait Trait: Any {} + /// derive_dyn_traits!(Trait); + /// + /// struct Foo; + /// impl Trait for Foo {} + /// + /// let x: Xso = Xso::wrap(Foo); + /// let x: Box = x.into_boxed(); + /// ``` + pub fn into_boxed(self) -> Box { + self.inner + } +} + +impl Xso { + /// Downcast `self` to `Box`. + /// + /// If the downcast fails, `self` is returned without change. + /// + /// ``` + /// # use core::any::Any; + /// # use xso::{dynxso::Xso, derive_dyn_traits}; + /// trait Trait: Any {} + /// derive_dyn_traits!(Trait); + /// + /// struct Foo; + /// impl Trait for Foo {} + /// + /// struct Bar; + /// impl Trait for Bar {} + /// + /// let x: Xso = Xso::wrap(Foo); + /// // Does not contain a Bar, so downcast fails. + /// let x: Xso = x.downcast::().err().unwrap(); + /// // *Does* contain a Foo, so downcast succeeds. + /// let f: Foo = *x.downcast().unwrap(); + /// ``` + pub fn downcast(self) -> Result, Self> + where + T: MayContain, + { + match self.inner.try_downcast() { + Ok(v) => Ok(v), + Err(inner) => Err(Self { inner }), + } + } + + /// Downcast `&self` to `&U`. + /// + /// ``` + /// # use core::any::Any; + /// # use xso::{dynxso::Xso, derive_dyn_traits}; + /// trait Trait: Any {} + /// derive_dyn_traits!(Trait); + /// + /// struct Foo; + /// impl Trait for Foo {} + /// + /// struct Bar; + /// impl Trait for Bar {} + /// + /// let x: Xso = Xso::wrap(Foo); + /// // Does not contain a Bar, so downcast fails. + /// assert!(x.downcast_ref::().is_none()); + /// // *Does* contain a Foo, so downcast succeeds. + /// let f: &Foo = x.downcast_ref().unwrap(); + /// ``` + pub fn downcast_ref(&self) -> Option<&U> + where + T: MayContain, + { + self.inner.try_downcast_ref() + } + + /// Downcast `&mut self` to `&mut U`. + /// + /// ``` + /// # use core::any::Any; + /// # use xso::{dynxso::Xso, derive_dyn_traits}; + /// trait Trait: Any {} + /// derive_dyn_traits!(Trait); + /// + /// struct Foo; + /// impl Trait for Foo {} + /// + /// struct Bar; + /// impl Trait for Bar {} + /// + /// let mut x: Xso = Xso::wrap(Foo); + /// // Does not contain a Bar, so downcast fails. + /// assert!(x.downcast_mut::().is_none()); + /// // *Does* contain a Foo, so downcast succeeds. + /// let f: &mut Foo = x.downcast_mut().unwrap(); + /// ``` + pub fn downcast_mut(&mut self) -> Option<&mut U> + where + T: MayContain, + { + self.inner.try_downcast_mut() + } + + fn inner_type_id(&self) -> TypeId { + (&self.inner as &dyn Any).type_id() + } + + /// Register a new type to be constructible. + /// + /// Only types registered through this function can be parsed from XML via + /// the [`FromXml`] implementation on `Xso`. See + /// [`dynxso`][`crate::dynxso`] for details. + /// + #[cfg_attr( + not(feature = "macros"), + doc = "Because the macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n" + )] + #[cfg_attr(feature = "macros", doc = "\n```\n")] + /// # use core::any::Any; + /// # use xso::{dynxso::Xso, derive_dyn_traits, from_bytes, FromXml}; + /// trait Trait: Any {} + /// derive_dyn_traits!(Trait); + /// + /// #[derive(FromXml, PartialEq, Debug)] + /// #[xml(namespace = "urn:example", name = "foo")] + /// struct Foo; + /// impl Trait for Foo {} + /// + /// // Parsing fails, because register_type() has not been called for + /// // Foo: + /// assert!(from_bytes::>("".as_bytes()).is_err()); + /// + /// Xso::::register_type::(); + /// // After registering Foo with Xso, parsing succeeds and + /// // we can downcast to Foo: + /// let x: Xso = from_bytes("".as_bytes()).unwrap(); + /// assert_eq!(Foo, *x.downcast().unwrap()); + /// ``` + pub fn register_type() + where + T: MayContain, + { + T::registry().add::() + } +} + +/// Wrapper around a `FromEventsBuilder` to convert a `Box` output to a +/// `Xso` output. +/// +/// Not constructible by users, only for internal use. +pub struct DynBuilder { + inner: B, +} + +impl>> FromEventsBuilder + for DynBuilder +{ + type Output = Xso; + + fn feed(&mut self, ev: rxml::Event, ctx: &Context) -> Result, Error> { + self.inner + .feed(ev, ctx) + .map(|x| x.map(|inner| Xso { inner })) + } +} + +/// Wrapper around a `FromEventsBuilder` to convert a `Box` output to a +/// `T` output. +pub struct UnboxBuilder { + inner: T, +} + +impl>> UnboxBuilder { + /// Wrap a `FromEventsBuilder` which generates `Box`. + pub fn wrap(inner: T) -> Self { + Self { inner } + } +} + +impl>> FromEventsBuilder for UnboxBuilder { + type Output = O; + + fn feed(&mut self, ev: rxml::Event, ctx: &Context) -> Result, Error> { + self.inner.feed(ev, ctx).map(|x| x.map(|inner| *inner)) + } +} + +impl FromXml for Xso { + type Builder = DynBuilder>>>; + + fn from_events( + name: rxml::QName, + attrs: rxml::AttrMap, + ctx: &Context<'_>, + ) -> Result { + T::registry() + .make_builder(name, attrs, ctx) + .map(|inner| DynBuilder { inner }) + } +} + +impl AsXml for Xso { + type ItemIter<'x> = Box, Error>> + 'x>; + + fn as_xml_iter(&self) -> Result, Error> { + self.inner.as_xml_dyn_iter() + } + + fn as_xml_dyn_iter(&self) -> Result, Error> { + self.inner.as_xml_dyn_iter() + } +} diff --git a/xso/src/lib.rs b/xso/src/lib.rs index d5a547f63a69560a4ba2c7b557e1586ff1fd80d0..b456106312df9021d816d4206f22ad252e60e2db 100644 --- a/xso/src/lib.rs +++ b/xso/src/lib.rs @@ -31,6 +31,8 @@ extern crate std; use std::io; pub mod asxml; +#[cfg(feature = "std")] +pub mod dynxso; pub mod error; pub mod fromxml; #[cfg(feature = "minidom")]