lib.rs

  1#![cfg_attr(docsrs, feature(doc_cfg))]
  2#![forbid(unsafe_code)]
  3#![warn(missing_docs)]
  4/*!
  5# XML Streamed Objects -- serde-like parsing for XML
  6
  7This crate provides the traits for parsing XML data into Rust structs, and
  8vice versa.
  9
 10While it is in 0.0.x versions, many features still need to be developed, but
 11rest assured that there is a solid plan to get it fully usable for even
 12advanced XML scenarios.
 13
 14XSO is an acronym for XML Stream(ed) Objects, referring to the main field of
 15use of this library in parsing XML streams like specified in RFC 6120.
 16*/
 17
 18// Copyright (c) 2024 Jonas Schäfer <jonas@zombofant.net>
 19//
 20// This Source Code Form is subject to the terms of the Mozilla Public
 21// License, v. 2.0. If a copy of the MPL was not distributed with this
 22// file, You can obtain one at http://mozilla.org/MPL/2.0/.
 23pub mod error;
 24#[cfg(feature = "minidom")]
 25#[cfg_attr(docsrs, doc(cfg(feature = "minidom")))]
 26pub mod minidom_compat;
 27mod rxml_util;
 28pub mod text;
 29
 30#[doc(hidden)]
 31pub mod exports {
 32    #[cfg(feature = "minidom")]
 33    pub use minidom;
 34    pub use rxml;
 35}
 36
 37use std::borrow::Cow;
 38
 39#[doc(inline)]
 40pub use text::TextCodec;
 41
 42#[doc(inline)]
 43pub use rxml_util::Item;
 44
 45#[doc = include_str!("from_xml_doc.md")]
 46#[doc(inline)]
 47#[cfg(feature = "macros")]
 48pub use xso_proc::FromXml;
 49
 50/// # Make a struct or enum serialisable to XML
 51///
 52/// This derives the [`AsXml`] trait on a struct or enum. It is the
 53/// counterpart to [`macro@FromXml`].
 54///
 55/// The attributes necessary and available for the derivation to work are
 56/// documented on [`macro@FromXml`].
 57#[doc(inline)]
 58#[cfg(feature = "macros")]
 59pub use xso_proc::AsXml;
 60
 61/// Trait allowing to iterate a struct's contents as serialisable
 62/// [`Item`]s.
 63///
 64/// **Important:** Changing the [`ItemIter`][`Self::ItemIter`] associated
 65/// type is considered a non-breaking change for any given implementation of
 66/// this trait. Always refer to a type's iterator type using fully-qualified
 67/// notation, for example: `<T as xso::AsXml>::ItemIter`.
 68pub trait AsXml {
 69    /// The iterator type.
 70    ///
 71    /// **Important:** Changing this type is considered a non-breaking change
 72    /// for any given implementation of this trait. Always refer to a type's
 73    /// iterator type using fully-qualified notation, for example:
 74    /// `<T as xso::AsXml>::ItemIter`.
 75    type ItemIter<'x>: Iterator<Item = Result<Item<'x>, self::error::Error>>
 76    where
 77        Self: 'x;
 78
 79    /// Return an iterator which emits the contents of the struct or enum as
 80    /// serialisable [`Item`] items.
 81    fn as_xml_iter(&self) -> Result<Self::ItemIter<'_>, self::error::Error>;
 82}
 83
 84/// Trait for a temporary object allowing to construct a struct from
 85/// [`rxml::Event`] items.
 86///
 87/// Objects of this type are generally constructed through
 88/// [`FromXml::from_events`] and are used to build Rust structs or enums from
 89/// XML data. The XML data must be fed as `rxml::Event` to the
 90/// [`feed`][`Self::feed`] method.
 91pub trait FromEventsBuilder {
 92    /// The type which will be constructed by this builder.
 93    type Output;
 94
 95    /// Feed another [`rxml::Event`] into the element construction
 96    /// process.
 97    ///
 98    /// Once the construction process completes, `Ok(Some(_))` is returned.
 99    /// When valid data has been fed but more events are needed to fully
100    /// construct the resulting struct, `Ok(None)` is returned.
101    ///
102    /// If the construction fails, `Err(_)` is returned. Errors are generally
103    /// fatal and the builder should be assumed to be broken at that point.
104    /// Feeding more events after an error may result in panics, errors or
105    /// inconsistent result data, though it may never result in unsound or
106    /// unsafe behaviour.
107    fn feed(&mut self, ev: rxml::Event) -> Result<Option<Self::Output>, self::error::Error>;
108}
109
110/// Trait allowing to construct a struct from a stream of
111/// [`rxml::Event`] items.
112///
113/// To use this, first call [`FromXml::from_events`] with the qualified
114/// name and the attributes of the corresponding
115/// [`rxml::Event::StartElement`] event. If the call succeeds, the
116/// returned builder object must be fed with the events representing the
117/// contents of the element, and then with the `EndElement` event.
118///
119/// The `StartElement` passed to `from_events` must not be passed to `feed`.
120///
121/// **Important:** Changing the [`Builder`][`Self::Builder`] associated type
122/// is considered a non-breaking change for any given implementation of this
123/// trait. Always refer to a type's builder type using fully-qualified
124/// notation, for example: `<T as xso::FromXml>::Builder`.
125pub trait FromXml {
126    /// A builder type used to construct the element.
127    ///
128    /// **Important:** Changing this type is considered a non-breaking change
129    /// for any given implementation of this trait. Always refer to a type's
130    /// builder type using fully-qualified notation, for example:
131    /// `<T as xso::FromXml>::Builder`.
132    type Builder: FromEventsBuilder<Output = Self>;
133
134    /// Attempt to initiate the streamed construction of this struct from XML.
135    ///
136    /// If the passed qualified `name` and `attrs` match the element's type,
137    /// the [`Self::Builder`] is returned and should be fed with XML events
138    /// by the caller.
139    ///
140    /// Otherwise, an appropriate error is returned.
141    fn from_events(
142        name: rxml::QName,
143        attrs: rxml::AttrMap,
144    ) -> Result<Self::Builder, self::error::FromEventsError>;
145}
146
147/// Trait allowing to convert XML text to a value.
148///
149/// This trait is similar to [`std::str::FromStr`], however, due to
150/// restrictions imposed by the orphan rule, a separate trait is needed.
151/// Implementations for many standard library types are available. In
152/// addition, the following feature flags can enable more implementations:
153///
154/// - `jid`: `jid::Jid`, `jid::BareJid`, `jid::FullJid`
155/// - `uuid`: `uuid::Uuid`
156///
157/// Because of this unfortunate situation, we are **extremely liberal** with
158/// accepting optional dependencies for this purpose. You are very welcome to
159/// make merge requests against this crate adding support for parsing
160/// third-party crates.
161pub trait FromXmlText: Sized {
162    /// Convert the given XML text to a value.
163    fn from_xml_text(data: String) -> Result<Self, self::error::Error>;
164}
165
166impl FromXmlText for String {
167    /// Return the string unchanged.
168    fn from_xml_text(data: String) -> Result<Self, self::error::Error> {
169        Ok(data)
170    }
171}
172
173impl<T: FromXmlText, B: ToOwned<Owned = T>> FromXmlText for Cow<'_, B> {
174    /// Return a [`Cow::Owned`] containing the parsed value.
175    fn from_xml_text(data: String) -> Result<Self, self::error::Error> {
176        Ok(Cow::Owned(T::from_xml_text(data)?))
177    }
178}
179
180impl<T: FromXmlText> FromXmlText for Option<T> {
181    /// Return a [`Some`] containing the parsed value.
182    fn from_xml_text(data: String) -> Result<Self, self::error::Error> {
183        Ok(Some(T::from_xml_text(data)?))
184    }
185}
186
187impl<T: FromXmlText> FromXmlText for Box<T> {
188    /// Return a [`Box`] containing the parsed value.
189    fn from_xml_text(data: String) -> Result<Self, self::error::Error> {
190        Ok(Box::new(T::from_xml_text(data)?))
191    }
192}
193
194/// Trait to convert a value to an XML text string.
195///
196/// This trait is implemented for many standard library types implementing
197/// [`std::fmt::Display`]. In addition, the following feature flags can enable
198/// more implementations:
199///
200/// - `jid`: `jid::Jid`, `jid::BareJid`, `jid::FullJid`
201/// - `uuid`: `uuid::Uuid`
202///
203/// Because of the unfortunate situation as described in [`FromXmlText`], we
204/// are **extremely liberal** with accepting optional dependencies for this
205/// purpose. You are very welcome to make merge requests against this crate
206/// adding support for parsing third-party crates.
207pub trait AsXmlText {
208    /// Convert the value to an XML string in a context where an absent value
209    /// cannot be represented.
210    fn as_xml_text(&self) -> Result<Cow<'_, str>, self::error::Error>;
211
212    /// Convert the value to an XML string in a context where an absent value
213    /// can be represented.
214    ///
215    /// The provided implementation will always return the result of
216    /// [`Self::as_xml_text`] wrapped into `Some(.)`. By re-implementing
217    /// this method, implementors can customize the behaviour for certain
218    /// values.
219    fn as_optional_xml_text(&self) -> Result<Option<Cow<'_, str>>, self::error::Error> {
220        Ok(Some(self.as_xml_text()?))
221    }
222}
223
224impl AsXmlText for String {
225    /// Return the borrowed string contents.
226    fn as_xml_text(&self) -> Result<Cow<'_, str>, self::error::Error> {
227        Ok(Cow::Borrowed(self.as_str()))
228    }
229}
230
231impl AsXmlText for str {
232    /// Return the borrowed string contents.
233    fn as_xml_text(&self) -> Result<Cow<'_, str>, self::error::Error> {
234        Ok(Cow::Borrowed(&*self))
235    }
236}
237
238impl<T: AsXmlText> AsXmlText for Box<T> {
239    /// Return the borrowed [`Box`] contents.
240    fn as_xml_text(&self) -> Result<Cow<'_, str>, self::error::Error> {
241        T::as_xml_text(self)
242    }
243}
244
245impl<B: AsXmlText + ToOwned> AsXmlText for Cow<'_, B> {
246    /// Return the borrowed [`Cow`] contents.
247    fn as_xml_text(&self) -> Result<Cow<'_, str>, self::error::Error> {
248        B::as_xml_text(self.as_ref())
249    }
250}
251
252impl<T: AsXmlText> AsXmlText for &T {
253    /// Delegate to the `AsXmlText` implementation on `T`.
254    fn as_xml_text(&self) -> Result<Cow<'_, str>, self::error::Error> {
255        T::as_xml_text(*self)
256    }
257}
258
259/// Specialized variant of [`AsXmlText`].
260///
261/// Do **not** implement this unless you cannot implement [`AsXmlText`]:
262/// implementing [`AsXmlText`] is more versatile and an
263/// [`AsOptionalXmlText`] implementation is automatically provided.
264///
265/// If you need to customize the behaviour of the [`AsOptionalXmlText`]
266/// blanket implementation, implement a custom
267/// [`AsXmlText::as_optional_xml_text`] instead.
268pub trait AsOptionalXmlText {
269    /// Convert the value to an XML string in a context where an absent value
270    /// can be represented.
271    fn as_optional_xml_text(&self) -> Result<Option<Cow<'_, str>>, self::error::Error>;
272}
273
274impl<T: AsXmlText> AsOptionalXmlText for T {
275    fn as_optional_xml_text(&self) -> Result<Option<Cow<'_, str>>, self::error::Error> {
276        <Self as AsXmlText>::as_optional_xml_text(self)
277    }
278}
279
280impl<T: AsXmlText> AsOptionalXmlText for Option<T> {
281    fn as_optional_xml_text(&self) -> Result<Option<Cow<'_, str>>, self::error::Error> {
282        self.as_ref()
283            .map(T::as_optional_xml_text)
284            .transpose()
285            .map(Option::flatten)
286    }
287}
288
289/// Attempt to transform a type implementing [`AsXml`] into another
290/// type which implements [`FromXml`].
291pub fn transform<T: FromXml, F: AsXml>(from: F) -> Result<T, self::error::Error> {
292    let mut iter = self::rxml_util::ItemToEvent::new(from.as_xml_iter()?);
293    let (qname, attrs) = match iter.next() {
294        Some(Ok(rxml::Event::StartElement(_, qname, attrs))) => (qname, attrs),
295        Some(Err(e)) => return Err(e),
296        _ => panic!("into_event_iter did not start with StartElement event!"),
297    };
298    let mut sink = match T::from_events(qname, attrs) {
299        Ok(v) => v,
300        Err(self::error::FromEventsError::Mismatch { .. }) => {
301            return Err(self::error::Error::TypeMismatch)
302        }
303        Err(self::error::FromEventsError::Invalid(e)) => return Err(e),
304    };
305    for event in iter {
306        let event = event?;
307        if let Some(v) = sink.feed(event)? {
308            return Ok(v);
309        }
310    }
311    Err(self::error::Error::XmlError(
312        rxml::error::XmlError::InvalidEof("during transform"),
313    ))
314}
315
316/// Attempt to convert a [`minidom::Element`] into a type implementing
317/// [`FromXml`], fallably.
318///
319/// Unlike [`transform`] (which can also be used with an element), this
320/// function will return the element unharmed if its element header does not
321/// match the expectations of `T`.
322#[cfg(feature = "minidom")]
323#[cfg_attr(docsrs, doc(cfg(feature = "minidom")))]
324pub fn try_from_element<T: FromXml>(
325    from: minidom::Element,
326) -> Result<T, self::error::FromElementError> {
327    let (qname, attrs) = minidom_compat::make_start_ev_parts(&from)?;
328    let mut sink = match T::from_events(qname, attrs) {
329        Ok(v) => v,
330        Err(self::error::FromEventsError::Mismatch { .. }) => {
331            return Err(self::error::FromElementError::Mismatch(from))
332        }
333        Err(self::error::FromEventsError::Invalid(e)) => {
334            return Err(self::error::FromElementError::Invalid(e))
335        }
336    };
337
338    let mut iter = from.as_xml_iter()?;
339    // consume the element header
340    for item in &mut iter {
341        let item = item?;
342        match item {
343            // discard the element header
344            Item::XmlDeclaration(..) => (),
345            Item::ElementHeadStart(..) => (),
346            Item::Attribute(..) => (),
347            Item::ElementHeadEnd => {
348                // now that the element header is over, we break out
349                break;
350            }
351            Item::Text(..) => panic!("text before end of element header"),
352            Item::ElementFoot => panic!("element foot before end of element header"),
353        }
354    }
355    let iter = self::rxml_util::ItemToEvent::new(iter);
356    for event in iter {
357        let event = event?;
358        if let Some(v) = sink.feed(event)? {
359            return Ok(v);
360        }
361    }
362    // unreachable! instead of error here, because minidom::Element always
363    // produces the complete event sequence of a single element, and FromXml
364    // implementations must be constructible from that.
365    unreachable!("minidom::Element did not produce enough events to complete element")
366}
367
368fn map_nonio_error<T>(r: Result<T, rxml::Error>) -> Result<T, self::error::Error> {
369    match r {
370        Ok(v) => Ok(v),
371        Err(rxml::Error::IO(_)) => unreachable!(),
372        Err(rxml::Error::Xml(e)) => Err(e.into()),
373        Err(rxml::Error::InvalidUtf8Byte(_)) => Err(self::error::Error::Other("invalid utf-8")),
374        Err(rxml::Error::InvalidChar(_)) => {
375            Err(self::error::Error::Other("non-character encountered"))
376        }
377        Err(rxml::Error::RestrictedXml(_)) => Err(self::error::Error::Other("restricted xml")),
378    }
379}
380
381fn read_start_event<I: std::io::BufRead>(
382    r: &mut rxml::Reader<I>,
383) -> Result<(rxml::QName, rxml::AttrMap), self::error::Error> {
384    for ev in r {
385        match map_nonio_error(ev)? {
386            rxml::Event::XmlDeclaration(_, rxml::XmlVersion::V1_0) => (),
387            rxml::Event::StartElement(_, name, attrs) => return Ok((name, attrs)),
388            _ => {
389                return Err(self::error::Error::Other(
390                    "Unexpected event at start of document",
391                ))
392            }
393        }
394    }
395    Err(self::error::Error::XmlError(
396        rxml::error::XmlError::InvalidEof("before start of element"),
397    ))
398}
399
400/// Attempt to parse a type implementing [`FromXml`] from a byte buffer
401/// containing XML data.
402pub fn from_bytes<T: FromXml>(mut buf: &[u8]) -> Result<T, self::error::Error> {
403    let mut reader = rxml::Reader::new(&mut buf);
404    let (name, attrs) = read_start_event(&mut reader)?;
405    let mut builder = match T::from_events(name, attrs) {
406        Ok(v) => v,
407        Err(self::error::FromEventsError::Mismatch { .. }) => {
408            return Err(self::error::Error::TypeMismatch)
409        }
410        Err(self::error::FromEventsError::Invalid(e)) => return Err(e),
411    };
412    for ev in reader {
413        if let Some(v) = builder.feed(map_nonio_error(ev)?)? {
414            return Ok(v);
415        }
416    }
417    Err(self::error::Error::XmlError(
418        rxml::error::XmlError::InvalidEof("while parsing FromXml impl"),
419    ))
420}