Implement macro for elements containing text

Rust Cambridge Mob created

Change summary

src/ibb.rs    | 45 +++++++++++++++------------------------------
src/macros.rs | 42 ++++++++++++++++++++++++++++++++++++++++++
2 files changed, 57 insertions(+), 30 deletions(-)

Detailed changes

src/ibb.rs 🔗

@@ -25,42 +25,27 @@ generate_element_with_only_attributes!(Open, "open", ns::IBB, [
     stanza: Stanza = "stanza" => default,
 ]);
 
-#[derive(Debug, Clone)]
-pub struct Data {
-    pub seq: u16,
-    pub sid: String,
-    pub data: Vec<u8>,
-}
+/// Codec wrapping base64 encode/decode
+struct Base64;
 
-impl TryFrom<Element> for Data {
-    type Err = Error;
-
-    fn try_from(elem: Element) -> Result<Data, Error> {
-        if !elem.is("data", ns::IBB) {
-            return Err(Error::ParseError("This is not a data element."));
-        }
-        for _ in elem.children() {
-            return Err(Error::ParseError("Unknown child in data element."));
-        }
-        Ok(Data {
-            seq: get_attr!(elem, "seq", required),
-            sid: get_attr!(elem, "sid", required),
-            data: base64::decode(&elem.text())?,
-        })
+impl Base64 {
+    fn decode(s: &str) -> Result<Vec<u8>, Error> {
+        Ok(base64::decode(s)?)
     }
-}
 
-impl From<Data> for Element {
-    fn from(data: Data) -> Element {
-        Element::builder("data")
-                .ns(ns::IBB)
-                .attr("seq", data.seq)
-                .attr("sid", data.sid)
-                .append(base64::encode(&data.data))
-                .build()
+    fn encode(b: &Vec<u8>) -> String {
+        base64::encode(b)
     }
 }
 
+generate_element_with_text!(Data, "data", ns::IBB,
+    [
+        seq: u16 = "seq" => required,
+        sid: String = "sid" => required
+    ],
+    data: Base64<Vec<u8>>
+);
+
 generate_element_with_only_attributes!(Close, "close", ns::IBB, [
     sid: String = "sid" => required,
 ]);

src/macros.rs 🔗

@@ -251,3 +251,45 @@ macro_rules! generate_elem_id {
         }
     );
 }
+
+macro_rules! generate_element_with_text {
+    ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:expr, [$($(#[$attr_meta:meta])* $attr:ident: $attr_type:ty = $attr_name:tt => $attr_action:tt),+], $text_ident:ident: $codec:ident < $text_type:ty >) => (
+        $(#[$meta])*
+        #[derive(Debug, Clone)]
+        pub struct $elem {
+            $(
+            $(#[$attr_meta])*
+            pub $attr: $attr_type
+            ),*,
+            pub $text_ident: $text_type,
+        }
+
+        impl TryFrom<Element> for $elem {
+            type Err = Error;
+
+            fn try_from(elem: Element) -> Result<$elem, Error> {
+                check_self!(elem, $name, $ns);
+                check_no_children!(elem, $name);
+                check_no_unknown_attributes!(elem, $name, [$($attr_name),*]);
+                Ok($elem {
+                    $(
+                    $attr: get_attr!(elem, $attr_name, $attr_action)
+                    ),*,
+                    $text_ident: $codec::decode(&elem.text())?,
+                })
+            }
+        }
+
+        impl From<$elem> for Element {
+            fn from(elem: $elem) -> Element {
+                Element::builder($name)
+                        .ns($ns)
+                        $(
+                        .attr($attr_name, elem.$attr)
+                        )*
+                        .append($codec::encode(&elem.$text_ident))
+                        .build()
+            }
+        }
+    );
+}