Skip to main content

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