Implement macro for elements containing children

Rust Cambridge Mob created

Change summary

src/disco.rs  | 62 +++++++++++++---------------------------------------
src/macros.rs | 52 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 68 insertions(+), 46 deletions(-)

Detailed changes

src/disco.rs 🔗

@@ -201,52 +201,22 @@ Item, "item", ns::DISCO_ITEMS, [
     name: Option<String> = "name" => optional,
 ]);
 
-/// Structure representing a `<query
-/// xmlns='http://jabber.org/protocol/disco#items'/>` element.
-///
-/// It should only be used in an `<iq type='result'/>`, as it can only
-/// represent the result, and not a request.
-#[derive(Debug, Clone)]
-pub struct DiscoItemsResult {
-    /// Node on which we have done this discovery.
-    pub node: Option<String>,
-
-    /// List of items pointed by this entity.
-    pub items: Vec<Item>,
-}
-
-impl TryFrom<Element> for DiscoItemsResult {
-    type Err = Error;
-
-    fn try_from(elem: Element) -> Result<DiscoItemsResult, Error> {
-        check_self!(elem, "query", ns::DISCO_ITEMS, "disco#items query");
-        check_no_unknown_attributes!(elem, "disco#items query", ["node"]);
-
-        let mut items: Vec<Item> = vec!();
-        for child in elem.children() {
-            if child.is("item", ns::DISCO_ITEMS) {
-                items.push(Item::try_from(child.clone())?);
-            } else {
-                return Err(Error::ParseError("Unknown element in disco#items."));
-            }
-        }
-
-        Ok(DiscoItemsResult {
-            node: get_attr!(elem, "node", optional),
-            items: items,
-        })
-    }
-}
-
-impl From<DiscoItemsResult> for Element {
-    fn from(disco: DiscoItemsResult) -> Element {
-        Element::builder("query")
-                .ns(ns::DISCO_ITEMS)
-                .attr("node", disco.node)
-                .append(disco.items)
-                .build()
-    }
-}
+generate_element_with_children!(
+    /// Structure representing a `<query
+    /// xmlns='http://jabber.org/protocol/disco#items'/>` element.
+    ///
+    /// It should only be used in an `<iq type='result'/>`, as it can only
+    /// represent the result, and not a request.
+    DiscoItemsResult, "query", ns::DISCO_ITEMS,
+    attributes: [
+        /// Node on which we have done this discovery.
+        node: Option<String> = "node" => optional
+    ],
+    children: [
+        /// List of items pointed by this entity.
+        items: Vec<Item> = "item" => Item
+    ]
+);
 
 #[cfg(test)]
 mod tests {

src/macros.rs 🔗

@@ -293,3 +293,55 @@ macro_rules! generate_element_with_text {
         }
     );
 }
+
+macro_rules! generate_element_with_children {
+    ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:expr, attributes: [$($(#[$attr_meta:meta])* $attr:ident: $attr_type:ty = $attr_name:tt => $attr_action:tt),+], children: [$($(#[$child_meta:meta])* $child_ident:ident: Vec<$child_type:ty> = $child_name:tt => $child_constructor:ident),+]) => (
+        $(#[$meta])*
+        #[derive(Debug, Clone)]
+        pub struct $elem {
+            $(
+            $(#[$attr_meta])*
+            pub $attr: $attr_type
+            ),*,
+            $(
+            $(#[$child_meta])*
+            pub $child_ident: Vec<$child_type>
+            ),*
+        }
+
+        impl TryFrom<Element> for $elem {
+            type Err = Error;
+
+            fn try_from(elem: Element) -> Result<$elem, Error> {
+                check_self!(elem, $name, $ns);
+                check_no_unknown_attributes!(elem, $name, [$($attr_name),*]);
+                let mut parsed_children = vec!();
+                for child in elem.children() {
+                    $(
+                    let parsed_child = $child_constructor::try_from(child.clone())?;
+                    parsed_children.push(parsed_child);
+                    )*
+                }
+                Ok($elem {
+                    $(
+                    $attr: get_attr!(elem, $attr_name, $attr_action)
+                    ),*,
+                    $(
+                    $child_ident: parsed_children
+                    )*
+                })
+            }
+        }
+
+        impl From<$elem> for Element {
+            fn from(elem: $elem) -> Element {
+                Element::builder($name)
+                        .ns($ns)
+                        $(
+                        .attr($attr_name, elem.$attr)
+                        )*
+                        .build()
+            }
+        }
+    );
+}