asxml.rs

  1//! # Generic iterator type implementations
  2//!
  3//! This module contains [`AsXml`] iterator implementations for types from
  4//! foreign libraries (such as the standard library).
  5//!
  6//! In order to not clutter the `xso` crate's main namespace, they are
  7//! stashed away in a separate module.
  8
  9// Copyright (c) 2024 Jonas Schäfer <jonas@zombofant.net>
 10//
 11// This Source Code Form is subject to the terms of the Mozilla Public
 12// License, v. 2.0. If a copy of the MPL was not distributed with this
 13// file, You can obtain one at http://mozilla.org/MPL/2.0/.
 14
 15use alloc::boxed::Box;
 16
 17use crate::error::Error;
 18use crate::rxml_util::Item;
 19use crate::AsXml;
 20
 21use core::fmt;
 22
 23use bytes::BytesMut;
 24
 25/// Helper iterator to convert an `Option<T>` to XML.
 26pub struct OptionAsXml<T: Iterator>(Option<T>);
 27
 28impl<T: Iterator> OptionAsXml<T> {
 29    /// Construct a new iterator, wrapping the given iterator.
 30    ///
 31    /// If `inner` is `None`, this iterator terminates immediately. Otherwise,
 32    /// it yields the elements yielded by `inner` until `inner` finishes,
 33    /// after which this iterator completes, too.
 34    pub fn new(inner: Option<T>) -> Self {
 35        Self(inner)
 36    }
 37}
 38
 39impl<'x, T: Iterator<Item = Result<Item<'x>, Error>>> Iterator for OptionAsXml<T> {
 40    type Item = Result<Item<'x>, Error>;
 41
 42    fn next(&mut self) -> Option<Self::Item> {
 43        self.0.as_mut()?.next()
 44    }
 45}
 46
 47/// Emits the contents of `Some(.)` unchanged if present and nothing
 48/// otherwise.
 49impl<T: AsXml> AsXml for Option<T> {
 50    type ItemIter<'x>
 51        = OptionAsXml<T::ItemIter<'x>>
 52    where
 53        T: 'x;
 54
 55    fn as_xml_iter(&self) -> Result<Self::ItemIter<'_>, Error> {
 56        match self {
 57            Some(ref value) => Ok(OptionAsXml(Some(T::as_xml_iter(value)?))),
 58            None => Ok(OptionAsXml(None)),
 59        }
 60    }
 61}
 62
 63/// Helper iterator to convert an `Box<T>` to XML.
 64pub struct BoxAsXml<T: Iterator>(Box<T>);
 65
 66impl<'x, T: Iterator<Item = Result<Item<'x>, Error>>> Iterator for BoxAsXml<T> {
 67    type Item = Result<Item<'x>, Error>;
 68
 69    fn next(&mut self) -> Option<Self::Item> {
 70        self.0.next()
 71    }
 72}
 73
 74/// Emits the contents of `T` unchanged.
 75impl<T: AsXml> AsXml for Box<T> {
 76    type ItemIter<'x>
 77        = BoxAsXml<T::ItemIter<'x>>
 78    where
 79        T: 'x;
 80
 81    fn as_xml_iter(&self) -> Result<Self::ItemIter<'_>, Error> {
 82        Ok(BoxAsXml(Box::new(T::as_xml_iter(self)?)))
 83    }
 84}
 85
 86/// Emits the items of `T` if `Ok(.)` or returns the error from `E` otherwise.
 87impl<T: AsXml, E> AsXml for Result<T, E>
 88where
 89    for<'a> Error: From<&'a E>,
 90{
 91    type ItemIter<'x>
 92        = T::ItemIter<'x>
 93    where
 94        Self: 'x;
 95
 96    fn as_xml_iter(&self) -> Result<Self::ItemIter<'_>, Error> {
 97        match self {
 98            Self::Ok(v) => Ok(v.as_xml_iter()?),
 99            Self::Err(e) => Err(e.into()),
100        }
101    }
102}
103
104/// Provides a helper which implements Display printing raw XML
105pub struct PrintRawXml<'x, T>(pub &'x T);
106
107impl<T: AsXml> fmt::Display for PrintRawXml<'_, T> {
108    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109        let iter = match self.0.as_xml_iter() {
110            Ok(iter) => iter,
111            Err(err) => return write!(f, "<failed to serialize PrintRawXml: {:?}>", err),
112        };
113        let mut writer = rxml::writer::Encoder::new();
114        let mut buf = BytesMut::new();
115        for item in iter {
116            let item = match item {
117                Ok(item) => item,
118                Err(err) => return write!(f, "<failed to serialize PrintRawXml: {:?}>", err),
119            };
120            if let Err(err) = writer.encode(item.as_rxml_item(), &mut buf) {
121                return write!(f, "<failed to serialize PrintRawXml: {:?}>", err);
122            }
123        }
124        // TODO: rxml guarantees us that we have utf8 here. This unwrap can nonetheless be removed
125        // if Write is implemented for rxml.
126        write!(f, "{}", core::str::from_utf8(&buf).unwrap())
127    }
128}
129
130/// Dyn-compatible version of [`AsXml`].
131///
132/// This trait is automatically implemented for all types which implement
133/// `AsXml`.
134pub trait AsXmlDyn {
135    /// Return an iterator which emits the contents of the struct or enum as
136    /// serialisable [`Item`] items.
137    fn as_xml_dyn_iter<'x>(
138        &'x self,
139    ) -> Result<Box<dyn Iterator<Item = Result<Item<'x>, Error>> + 'x>, Error>;
140}
141
142impl<T: AsXml> AsXmlDyn for T {
143    /// Return an iterator which emits the contents of the struct or enum as
144    /// serialisable [`Item`] items by calling [`AsXml::as_xml_dyn_iter`].
145    fn as_xml_dyn_iter<'x>(
146        &'x self,
147    ) -> Result<Box<dyn Iterator<Item = Result<Item<'x>, Error>> + 'x>, Error> {
148        <T as AsXml>::as_xml_dyn_iter(self)
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155
156    use alloc::{borrow::Cow, vec};
157
158    #[test]
159    fn option_as_xml_terminates_immediately_for_none() {
160        let mut iter = OptionAsXml::<core::iter::Empty<_>>(None);
161        match iter.next() {
162            None => (),
163            other => panic!("unexpected item: {:?}", other),
164        }
165    }
166
167    #[test]
168    fn option_as_xml_passes_values_from_inner_some() {
169        let inner = vec![
170            Ok(Item::Text(Cow::Borrowed("hello world"))),
171            Ok(Item::ElementFoot),
172        ];
173        let mut iter = OptionAsXml(Some(inner.into_iter()));
174        match iter.next() {
175            Some(Ok(Item::Text(text))) => {
176                assert_eq!(text, "hello world");
177            }
178            other => panic!("unexpected item: {:?}", other),
179        }
180        match iter.next() {
181            Some(Ok(Item::ElementFoot)) => (),
182            other => panic!("unexpected item: {:?}", other),
183        }
184        match iter.next() {
185            None => (),
186            other => panic!("unexpected item: {:?}", other),
187        }
188    }
189
190    #[test]
191    fn box_as_xml_passes_values_from_inner() {
192        let inner = vec![
193            Ok(Item::Text(Cow::Borrowed("hello world"))),
194            Ok(Item::ElementFoot),
195        ];
196        let mut iter = BoxAsXml(Box::new(inner.into_iter()));
197        match iter.next() {
198            Some(Ok(Item::Text(text))) => {
199                assert_eq!(text, "hello world");
200            }
201            other => panic!("unexpected item: {:?}", other),
202        }
203        match iter.next() {
204            Some(Ok(Item::ElementFoot)) => (),
205            other => panic!("unexpected item: {:?}", other),
206        }
207        match iter.next() {
208            None => (),
209            other => panic!("unexpected item: {:?}", other),
210        }
211    }
212}