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:
-
type: the type for whichDeserializeXmlshould be implemented. -
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 providetag_contentsfor this argument. -
body: a block that produces aResult<type>, wheretypeis what was provided as the first argument. A variable which holds the tag contents as aStringis available for use in this block; its name will be whatever value you provided fortag_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
DeserializeXmlgenerated by the derive macro also available from this crate. In other words, the implementation generated byimpl_deserialize_xml_helpercan’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.