impl_deserialize_xml_helper

Macro impl_deserialize_xml_helper 

Source
macro_rules! impl_deserialize_xml_helper {
    ($type:ty, $tag_contents_ident:ident, $body:block) => { ... };
}
Expand description

Helper macro to minimize boilerplate in custom DeserializeXml implementations.

As a motivating example, consider the task of parsing the date from a tag of the form <date>1918-11-11T11:00:00+01:00</date>. To do so, one could create a type and implement DeserializeXml for it from scratch, but doing so involves dealing with some uninteresting XML details (e.g., pop the start tag from the reader, ensure that the next tag is a Characters event, extract the actual contents from that event, etc.). Conceptually, one would rather ignore those complications and instead provide a function that parses the string 1918-11-11T11:00:00+01:00 to the appropriate type. This macro provides such an interface; it handles all necessary XML manipulation and calls the user-provided logic to produce a value from the tag contents. The result is an implementation of DeserializeXml for the specified type. This macro takes three arguments:

  1. type: the type for which DeserializeXml should be implemented.

  2. tag_contents_ident: the identifier to be used for the variable that represents the tag contents. Note: this is only required due to Rust’s hygiene requirement for macros; if in doubt, just provide tag_contents for this argument.

  3. body: a block that produces a Result<type>, where type is what was provided as the first argument. A variable which holds the tag contents as a String is available for use in this block; its name will be whatever value you provided for tag_contents_ident. Note that a blanket error conversion implementation, impl<T: std::error::Error> From<T> for deserialize_xml::Error, is provided, so in many cases calling the ? operator on any possible intermediate errors will propagate them correctly.

§Example

Here’s an example of how we can use this macro to support parsing dates:

use deserialize_xml::{DeserializeXml, impl_deserialize_xml_helper};
use chrono::prelude::*;

// See Caveats section for why this outer struct is necessary
#[derive(Default, Debug, DeserializeXml)]
#[deserialize_xml(tag="outer")]
struct CustomImplHelperOuter {
    #[deserialize_xml(tag="inner")]
    dt: CustomImplHelperInner,
}

#[derive(Default, Debug)]
struct CustomImplHelperInner(DateTime<Utc>);

impl_deserialize_xml_helper!(
    CustomImplHelperInner, /* type */
    tag_contents,          /* tag_contents_ident */
    {                      /* body */
    // Note: variable `tag_contents` is available here because
    // that is what was passed for the second argument
    let dt = tag_contents.parse::<DateTime<Utc>>()?;
    Ok(CustomImplHelperInner(dt))
    // Notice that our logic was entirely XML-agnostic!
});

let str_input = "<outer><inner>1918-11-11T11:00:00+01:00</inner></outer>";
// CustomImplHelperOuter::from_str    -> generated by derive macro; calls the below
// CustomImplHelperInner::from_reader -> generated by `impl_deserialize_xml_helper`
let result = CustomImplHelperOuter::from_str(str_input).unwrap();
assert_eq!(result.dt.0.year(), 1918);
assert_eq!(result.dt.0.month(), 11);
assert_eq!(result.dt.0.day(), 11);
assert_eq!(result.dt.0.hour(), 10);

§Caveats

  • This macro assumes that the implementation it generates will be called from within an implementation of DeserializeXml generated by the derive macro also available from this crate. In other words, the implementation generated by impl_deserialize_xml_helper can’t handle parsing a complete XML document itself; it can only parse the XML fragment associated with the type, and it depends on some other source telling it when to start. This is a somewhat artificial constraint that could probably be removed; however, my guess is that the common case is wanting to parse a large struct while possibly providing custom parsers for some of those struct’s fields, so I hope this won’t be too cumbersome in practice.