xso: extend the documentation overall

Jonas Schäfer created

Sprinkling it with examples, adding more words to make things
(hopefully) clearer, using a similar structure for different items etc.
etc.

Change summary

xso/src/lib.rs       | 164 ++++++++++++++++++++++++++++++++++++++++++---
xso/src/rxml_util.rs |   8 +
2 files changed, 159 insertions(+), 13 deletions(-)

Detailed changes

xso/src/lib.rs 🔗

@@ -342,6 +342,13 @@ impl AsXmlText for str {
     }
 }
 
+impl AsXmlText for &str {
+    /// Return the borrowed string contents.
+    fn as_xml_text(&self) -> Result<Cow<'_, str>, self::error::Error> {
+        Ok(Cow::Borrowed(self))
+    }
+}
+
 impl<T: AsXmlText> AsXmlText for Box<T> {
     /// Return the borrowed [`Box`] contents.
     fn as_xml_text(&self) -> Result<Cow<'_, str>, self::error::Error> {
@@ -398,7 +405,7 @@ impl<T: AsXmlText> AsOptionalXmlText for Option<T> {
     }
 }
 
-/// Control how unknown attributes are handled.
+/// # Control how unknown attributes are handled
 ///
 /// The variants of this enum are referenced in the
 /// `#[xml(on_unknown_attribute = ..)]` which can be used on structs and
@@ -435,7 +442,7 @@ impl UnknownAttributePolicy {
     }
 }
 
-/// Control how unknown children are handled.
+/// # Control how unknown child elements are handled
 ///
 /// The variants of this enum are referenced in the
 /// `#[xml(on_unknown_child = ..)]` which can be used on structs and
@@ -472,8 +479,44 @@ impl UnknownChildPolicy {
     }
 }
 
-/// Attempt to transform a type implementing [`AsXml`] into another
-/// type which implements [`FromXml`].
+/// # Transform a value into another value via XML
+///
+/// This function takes `from`, converts it into XML using its [`AsXml`]
+/// implementation and builds a `T` from it (without buffering the tree in
+/// memory).
+///
+/// If conversion fails, a [`Error`][`crate::error::Error`] is returned. In
+/// particular, if `T` expects a different element header than the header
+/// provided by `from`,
+/// [`Error::TypeMismatch`][`crate::error::Error::TypeMismatch`] is returned.
+///
+/// ## Example
+///
+#[cfg_attr(
+    not(all(feature = "std", feature = "macros")),
+    doc = "Because the std or macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n"
+)]
+#[cfg_attr(all(feature = "std", feature = "macros"), doc = "\n```\n")]
+/// # use xso::{AsXml, FromXml, transform};
+/// #[derive(AsXml)]
+/// #[xml(namespace = "urn:example", name = "foo")]
+/// struct Source {
+///     #[xml(attribute = "xml:lang")]
+///     lang: &'static str,
+/// }
+///
+/// #[derive(FromXml, PartialEq, Debug)]
+/// #[xml(namespace = "urn:example", name = "foo")]
+/// struct Dest {
+///     #[xml(lang)]
+///     lang: Option<String>,
+/// }
+///
+/// assert_eq!(
+///     Dest { lang: Some("en".to_owned()) },
+///     transform(&Source { lang: "en" }).unwrap(),
+/// );
+/// ```
 pub fn transform<T: FromXml, F: AsXml>(from: &F) -> Result<T, self::error::Error> {
     let mut languages = rxml::xml_lang::XmlLangStack::new();
     let mut iter = self::rxml_util::ItemToEvent::new(from.as_xml_iter()?);
@@ -568,8 +611,37 @@ pub fn try_from_element<T: FromXml>(
     unreachable!("minidom::Element did not produce enough events to complete element")
 }
 
-/// Attempt to parse a type implementing [`FromXml`] from a byte buffer
-/// containing XML data.
+/// # Parse a value from a byte slice containing XML data
+///
+/// This function parses the XML found in `buf`, assuming it contains a
+/// complete XML document (with optional XML declaration) and builds a `T`
+/// from it (without buffering the tree in memory).
+///
+/// If conversion fails, a [`Error`][`crate::error::Error`] is returned. In
+/// particular, if `T` expects a different element header than the element
+/// header at the root of the document in `bytes`,
+/// [`Error::TypeMismatch`][`crate::error::Error::TypeMismatch`] is returned.
+///
+/// ## 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 xso::{AsXml, FromXml, from_bytes};
+/// #[derive(FromXml, PartialEq, Debug)]
+/// #[xml(namespace = "urn:example", name = "foo")]
+/// struct Foo {
+///     #[xml(attribute)]
+///     a: String,
+/// }
+///
+/// assert_eq!(
+///     Foo { a: "some-value".to_owned() },
+///     from_bytes(b"<foo xmlns='urn:example' a='some-value'/>").unwrap(),
+/// );
+/// ```
 pub fn from_bytes<T: FromXml>(mut buf: &[u8]) -> Result<T, self::error::Error> {
     use rxml::{error::EndOrError, Parse};
 
@@ -649,7 +721,40 @@ fn read_start_event_io(
     ))
 }
 
-/// Attempt to parse a type implementing [`FromXml`] from a reader.
+/// # Parse a value from a [`io::BufRead`][`std::io::BufRead`]
+///
+/// This function parses the XML found in `r`, assuming it contains a
+/// complete XML document (with optional XML declaration) and builds a `T`
+/// from it (without buffering the tree in memory).
+///
+/// If conversion fails, a [`Error`][`crate::error::Error`] is returned. In
+/// particular, if `T` expects a different element header than the element
+/// header at the root of the document in `r`,
+/// [`Error::TypeMismatch`][`crate::error::Error::TypeMismatch`] is returned.
+///
+/// ## 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 xso::{AsXml, FromXml, from_reader};
+/// # use std::io::BufReader;
+/// #[derive(FromXml, PartialEq, Debug)]
+/// #[xml(namespace = "urn:example", name = "foo")]
+/// struct Foo {
+///     #[xml(attribute)]
+///     a: String,
+/// }
+///
+/// // let file = ..
+/// # let file = &mut &b"<foo xmlns='urn:example' a='some-value'/>"[..];
+/// assert_eq!(
+///     Foo { a: "some-value".to_owned() },
+///     from_reader(BufReader::new(file)).unwrap(),
+/// );
+/// ```
 #[cfg(feature = "std")]
 pub fn from_reader<T: FromXml, R: io::BufRead>(r: R) -> io::Result<T> {
     let mut reader = rxml::XmlLangTracker::wrap(rxml::Reader::new(r));
@@ -682,7 +787,34 @@ pub fn from_reader<T: FromXml, R: io::BufRead>(r: R) -> io::Result<T> {
     ))
 }
 
-/// Attempt to serialise a type implementing [`AsXml`] to a vector of bytes.
+/// # Serialize a value to UTF-8-encoded XML
+///
+/// This function takes `xso`, converts it into XML using its [`AsXml`]
+/// implementation and serialises the resulting XML events into a `Vec<u8>`.
+///
+/// If serialisation fails, an error is returned instead.
+///
+/// ## 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 xso::{AsXml, FromXml, to_vec};
+/// # use std::io::BufReader;
+/// #[derive(AsXml, PartialEq, Debug)]
+/// #[xml(namespace = "urn:example", name = "foo")]
+/// struct Foo {
+///     #[xml(attribute)]
+///     a: String,
+/// }
+///
+/// assert_eq!(
+///     b"<foo xmlns='urn:example' a='some-value'></foo>",
+///     &to_vec(&Foo { a: "some-value".to_owned() }).unwrap()[..],
+/// );
+/// ```
 pub fn to_vec<T: AsXml>(xso: &T) -> Result<Vec<u8>, self::error::Error> {
     let iter = xso.as_xml_iter()?;
     let mut writer = rxml::writer::Encoder::new();
@@ -694,10 +826,20 @@ pub fn to_vec<T: AsXml>(xso: &T) -> Result<Vec<u8>, self::error::Error> {
     Ok(buf)
 }
 
-/// Return true if the string contains exclusively XML whitespace.
+/// # Test if a string contains exclusively XML whitespace
+///
+/// This function returns true if `s` contains only XML whitespace. XML
+/// whitespace is defined as U+0020 (space), U+0009 (tab), U+000a (newline)
+/// and U+000d (carriage return), so this test is implemented on bytes instead
+/// of codepoints for efficiency.
+///
+/// # Example
 ///
-/// XML whitespace is defined as U+0020 (space), U+0009 (tab), U+000a
-/// (newline) and U+000d (carriage return).
+/// ```
+/// # use xso::is_xml_whitespace;
+/// assert!(is_xml_whitespace(" \t\r\n  "));
+/// assert!(!is_xml_whitespace("  hello  "));
+/// ```
 pub fn is_xml_whitespace<T: AsRef<[u8]>>(s: T) -> bool {
     s.as_ref()
         .iter()

xso/src/rxml_util.rs 🔗

@@ -10,9 +10,13 @@ use alloc::borrow::{Cow, ToOwned};
 
 use rxml::{parser::EventMetrics, AttrMap, Event, Namespace, NcName, NcNameStr, XmlVersion};
 
-/// An encodable item.
+/// # Serialisable piece of XML
 ///
-/// Unlike [`rxml::Item`], the contents of this item may either be owned or
+/// This item represents a piece of an XML document which can be serialised
+/// into bytes by converting it to an [`rxml::Item`] and then feeding it to a
+/// [`rxml::Encoder`].
+///
+/// Unlike `rxml::Item`, the contents of this item may either be owned or
 /// borrowed, individually. This enables the use in an [`crate::AsXml`] trait
 /// even if data needs to be generated during serialisation.
 #[derive(Debug)]