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<'x, T: AsXml> fmt::Display for PrintRawXml<'x, 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, "{}", std::str::from_utf8(&buf).unwrap())
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133
134 use alloc::{borrow::Cow, vec};
135
136 #[test]
137 fn option_as_xml_terminates_immediately_for_none() {
138 let mut iter = OptionAsXml::<core::iter::Empty<_>>(None);
139 match iter.next() {
140 None => (),
141 other => panic!("unexpected item: {:?}", other),
142 }
143 }
144
145 #[test]
146 fn option_as_xml_passes_values_from_inner_some() {
147 let inner = vec![
148 Ok(Item::Text(Cow::Borrowed("hello world"))),
149 Ok(Item::ElementFoot),
150 ];
151 let mut iter = OptionAsXml(Some(inner.into_iter()));
152 match iter.next() {
153 Some(Ok(Item::Text(text))) => {
154 assert_eq!(text, "hello world");
155 }
156 other => panic!("unexpected item: {:?}", other),
157 }
158 match iter.next() {
159 Some(Ok(Item::ElementFoot)) => (),
160 other => panic!("unexpected item: {:?}", other),
161 }
162 match iter.next() {
163 None => (),
164 other => panic!("unexpected item: {:?}", other),
165 }
166 }
167
168 #[test]
169 fn box_as_xml_passes_values_from_inner() {
170 let inner = vec![
171 Ok(Item::Text(Cow::Borrowed("hello world"))),
172 Ok(Item::ElementFoot),
173 ];
174 let mut iter = BoxAsXml(Box::new(inner.into_iter()));
175 match iter.next() {
176 Some(Ok(Item::Text(text))) => {
177 assert_eq!(text, "hello world");
178 }
179 other => panic!("unexpected item: {:?}", other),
180 }
181 match iter.next() {
182 Some(Ok(Item::ElementFoot)) => (),
183 other => panic!("unexpected item: {:?}", other),
184 }
185 match iter.next() {
186 None => (),
187 other => panic!("unexpected item: {:?}", other),
188 }
189 }
190}