Add a generate_element_with_only_attributes macro, and use it wherever it makes sense.

Emmanuel Gil Peyrot created

Change summary

src/eme.rs             | 39 ++++-------------------
src/ibb.rs             | 72 ++++---------------------------------------
src/jingle_ft.rs       | 33 ++-----------------
src/jingle_ibb.rs      | 40 +++---------------------
src/jingle_s5b.rs      | 31 ++++--------------
src/lib.rs             | 40 ++++++++++++++++++++++++
src/message_correct.rs | 28 +---------------
src/muc/user.rs        | 39 +++--------------------
src/receipts.rs        | 65 ++-------------------------------------
src/stanza_id.rs       | 70 ++++-------------------------------------
10 files changed, 92 insertions(+), 365 deletions(-)

Detailed changes

src/eme.rs 🔗

@@ -13,39 +13,14 @@ use error::Error;
 use ns;
 
 /// Structure representing an `<encryption xmlns='urn:xmpp:eme:0'/>` element.
-#[derive(Debug, Clone)]
-pub struct ExplicitMessageEncryption {
-    /// Namespace of the encryption scheme used.
-    pub namespace: String,
+generate_element_with_only_attributes!(ExplicitMessageEncryption, "encryption", ns::EME, [
+    // Namespace of the encryption scheme used.
+    namespace: String = "namespace" => required,
 
-    /// User-friendly name for the encryption scheme, should be `None` for OTR,
-    /// legacy OpenPGP and OX.
-    pub name: Option<String>,
-}
-
-impl TryFrom<Element> for ExplicitMessageEncryption {
-    type Err = Error;
-
-    fn try_from(elem: Element) -> Result<ExplicitMessageEncryption, Error> {
-        check_self!(elem, "encryption", ns::EME);
-        check_no_children!(elem, "encryption");
-        check_no_unknown_attributes!(elem, "encryption", ["namespace", "name"]);
-        Ok(ExplicitMessageEncryption {
-            namespace: get_attr!(elem, "namespace", required),
-            name: get_attr!(elem, "name", optional),
-        })
-    }
-}
-
-impl From<ExplicitMessageEncryption> for Element {
-    fn from(eme: ExplicitMessageEncryption) -> Element {
-        Element::builder("encryption")
-                .ns(ns::EME)
-                .attr("namespace", eme.namespace)
-                .attr("name", eme.name)
-                .build()
-    }
-}
+    // User-friendly name for the encryption scheme, should be `None` for OTR,
+    // legacy OpenPGP and OX.
+    name: Option<String> = "name" => optional,
+]);
 
 #[cfg(test)]
 mod tests {

src/ibb.rs 🔗

@@ -19,41 +19,11 @@ generate_attribute!(Stanza, "stanza", {
     Message => "message",
 }, Default = Iq);
 
-#[derive(Debug, Clone)]
-pub struct Open {
-    pub block_size: u16,
-    pub sid: String,
-    pub stanza: Stanza,
-}
-
-impl TryFrom<Element> for Open {
-    type Err = Error;
-
-    fn try_from(elem: Element) -> Result<Open, Error> {
-        if !elem.is("open", ns::IBB) {
-            return Err(Error::ParseError("This is not an open element."));
-        }
-        for _ in elem.children() {
-            return Err(Error::ParseError("Unknown child in open element."));
-        }
-        Ok(Open {
-            block_size: get_attr!(elem, "block-size", required),
-            sid: get_attr!(elem, "sid", required),
-            stanza: get_attr!(elem, "stanza", default),
-        })
-    }
-}
-
-impl From<Open> for Element {
-    fn from(open: Open) -> Element {
-        Element::builder("open")
-                .ns(ns::IBB)
-                .attr("block-size", open.block_size)
-                .attr("sid", open.sid)
-                .attr("stanza", open.stanza)
-                .build()
-    }
-}
+generate_element_with_only_attributes!(Open, "open", ns::IBB, [
+    block_size: u16 = "block-size" => required,
+    sid: String = "sid" => required,
+    stanza: Stanza = "stanza" => default,
+]);
 
 #[derive(Debug, Clone)]
 pub struct Data {
@@ -91,35 +61,9 @@ impl From<Data> for Element {
     }
 }
 
-#[derive(Debug, Clone)]
-pub struct Close {
-    pub sid: String,
-}
-
-impl TryFrom<Element> for Close {
-    type Err = Error;
-
-    fn try_from(elem: Element) -> Result<Close, Error> {
-        if !elem.is("close", ns::IBB) {
-            return Err(Error::ParseError("This is not a close element."));
-        }
-        for _ in elem.children() {
-            return Err(Error::ParseError("Unknown child in close element."));
-        }
-        Ok(Close {
-            sid: get_attr!(elem, "sid", required),
-        })
-    }
-}
-
-impl From<Close> for Element {
-    fn from(close: Close) -> Element {
-        Element::builder("close")
-                .ns(ns::IBB)
-                .attr("sid", close.sid)
-                .build()
-    }
-}
+generate_element_with_only_attributes!(Close, "close", ns::IBB, [
+    sid: String = "sid" => required,
+]);
 
 #[cfg(test)]
 mod tests {

src/jingle_ft.rs 🔗

@@ -256,35 +256,10 @@ impl From<Checksum> for Element {
     }
 }
 
-#[derive(Debug, Clone)]
-pub struct Received {
-    pub name: ContentId,
-    pub creator: Creator,
-}
-
-impl TryFrom<Element> for Received {
-    type Err = Error;
-
-    fn try_from(elem: Element) -> Result<Received, Error> {
-        check_self!(elem, "received", ns::JINGLE_FT);
-        check_no_children!(elem, "received");
-        check_no_unknown_attributes!(elem, "received", ["name", "creator"]);
-        Ok(Received {
-            name: get_attr!(elem, "name", required),
-            creator: get_attr!(elem, "creator", required),
-        })
-    }
-}
-
-impl From<Received> for Element {
-    fn from(received: Received) -> Element {
-        Element::builder("received")
-                .ns(ns::JINGLE_FT)
-                .attr("name", received.name)
-                .attr("creator", received.creator)
-                .build()
-    }
-}
+generate_element_with_only_attributes!(Received, "received", ns::JINGLE_FT, [
+    name: ContentId = "name" => required,
+    creator: Creator = "creator" => required,
+]);
 
 #[cfg(test)]
 mod tests {

src/jingle_ibb.rs 🔗

@@ -17,41 +17,11 @@ use ibb::Stanza;
 
 generate_id!(StreamId);
 
-#[derive(Debug, Clone)]
-pub struct Transport {
-    pub block_size: u16,
-    pub sid: StreamId,
-    pub stanza: Stanza,
-}
-
-impl TryFrom<Element> for Transport {
-    type Err = Error;
-
-    fn try_from(elem: Element) -> Result<Transport, Error> {
-        if !elem.is("transport", ns::JINGLE_IBB) {
-            return Err(Error::ParseError("This is not an JingleIBB element."))
-        }
-        for _ in elem.children() {
-            return Err(Error::ParseError("Unknown child in JingleIBB element."));
-        }
-        Ok(Transport {
-            block_size: get_attr!(elem, "block-size", required),
-            sid: get_attr!(elem, "sid", required),
-            stanza: get_attr!(elem, "stanza", default),
-        })
-    }
-}
-
-impl From<Transport> for Element {
-    fn from(transport: Transport) -> Element {
-        Element::builder("transport")
-                .ns(ns::JINGLE_IBB)
-                .attr("block-size", transport.block_size)
-                .attr("sid", transport.sid)
-                .attr("stanza", transport.stanza)
-                .build()
-    }
-}
+generate_element_with_only_attributes!(Transport, "transport", ns::JINGLE_IBB, [
+    block_size: u16 = "block-size" => required,
+    sid: StreamId = "sid" => required,
+    stanza: Stanza = "stanza" => default,
+]);
 
 #[cfg(test)]
 mod tests {

src/jingle_s5b.rs 🔗

@@ -30,29 +30,14 @@ generate_id!(CandidateId);
 
 generate_id!(StreamId);
 
-#[derive(Debug, Clone)]
-pub struct Candidate {
-    pub cid: CandidateId,
-    pub host: String,
-    pub jid: Jid,
-    pub port: Option<u16>,
-    pub priority: u32,
-    pub type_: Type,
-}
-
-impl From<Candidate> for Element {
-    fn from(candidate: Candidate) -> Element {
-        Element::builder("candidate")
-                .ns(ns::JINGLE_S5B)
-                .attr("cid", candidate.cid)
-                .attr("host", candidate.host)
-                .attr("jid", String::from(candidate.jid))
-                .attr("port", candidate.port)
-                .attr("priority", candidate.priority)
-                .attr("type", candidate.type_)
-                .build()
-    }
-}
+generate_element_with_only_attributes!(Candidate, "candidate", ns::JINGLE_S5B, [
+    cid: CandidateId = "cid" => required,
+    host: String = "host" => required,
+    jid: Jid = "jid" => required,
+    port: Option<u16> = "port" => optional,
+    priority: u32 = "priority" => required,
+    type_: Type = "type" => default,
+]);
 
 #[derive(Debug, Clone)]
 pub enum TransportPayload {

src/lib.rs 🔗

@@ -203,6 +203,46 @@ macro_rules! generate_empty_element {
     );
 }
 
+macro_rules! generate_element_with_only_attributes {
+    ($elem:ident, $name:tt, $ns:expr, [$($attr:ident: $attr_type:ty = $attr_name:tt => $attr_action:tt),+,]) => (
+        generate_element_with_only_attributes!($elem, $name, $ns, [$($attr: $attr_type = $attr_name => $attr_action),*]);
+    );
+    ($elem:ident, $name:tt, $ns:expr, [$($attr:ident: $attr_type:ty = $attr_name:tt => $attr_action:tt),+]) => (
+        #[derive(Debug, Clone)]
+        pub struct $elem {
+            $(
+            pub $attr: $attr_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)
+                    ),*
+                })
+            }
+        }
+
+        impl From<$elem> for Element {
+            fn from(elem: $elem) -> Element {
+                Element::builder($name)
+                        .ns($ns)
+                        $(
+                        .attr($attr_name, elem.$attr)
+                        )*
+                        .build()
+            }
+        }
+    );
+}
+
 macro_rules! generate_id {
     ($elem:ident) => (
         #[derive(Debug, Clone, PartialEq, Eq, Hash)]

src/message_correct.rs 🔗

@@ -12,31 +12,9 @@ use error::Error;
 
 use ns;
 
-#[derive(Debug, Clone)]
-pub struct Replace {
-    pub id: String,
-}
-
-impl TryFrom<Element> for Replace {
-    type Err = Error;
-
-    fn try_from(elem: Element) -> Result<Replace, Error> {
-        check_self!(elem, "replace", ns::MESSAGE_CORRECT);
-        check_no_children!(elem, "replace");
-        check_no_unknown_attributes!(elem, "replace", ["id"]);
-        let id = get_attr!(elem, "id", required);
-        Ok(Replace { id })
-    }
-}
-
-impl From<Replace> for Element {
-    fn from(replace: Replace) -> Element {
-        Element::builder("replace")
-                .ns(ns::MESSAGE_CORRECT)
-                .attr("id", replace.id)
-                .build()
-    }
-}
+generate_element_with_only_attributes!(Replace, "replace", ns::MESSAGE_CORRECT, [
+    id: String = "id" => required,
+]);
 
 #[cfg(test)]
 mod tests {

src/muc/user.rs 🔗

@@ -192,38 +192,9 @@ impl From<Actor> for Element {
     }
 }
 
-#[derive(Debug, Clone, PartialEq)]
-pub struct Continue {
-    thread: Option<String>,
-}
-
-impl TryFrom<Element> for Continue {
-    type Err = Error;
-
-    fn try_from(elem: Element) -> Result<Continue, Error> {
-        if !elem.is("continue", ns::MUC_USER) {
-            return Err(Error::ParseError("This is not a continue element."));
-        }
-        for _ in elem.children() {
-            return Err(Error::ParseError("Unknown child in continue element."));
-        }
-        for (attr, _) in elem.attrs() {
-            if attr != "thread" {
-                return Err(Error::ParseError("Unknown attribute in continue element."));
-            }
-        }
-        Ok(Continue { thread: get_attr!(elem, "thread", optional) })
-    }
-}
-
-impl From<Continue> for Element {
-    fn from(cont: Continue) -> Element {
-        Element::builder("continue")
-                .ns(ns::MUC_USER)
-                .attr("thread", cont.thread)
-                .build()
-    }
-}
+generate_element_with_only_attributes!(Continue, "continue", ns::MUC_USER, [
+    thread: Option<String> = "thread" => optional,
+]);
 
 #[derive(Debug, Clone, PartialEq)]
 pub struct Reason(String);
@@ -576,7 +547,7 @@ mod tests {
                       thread='foo'/>
         ".parse().unwrap();
         let continue_ = Continue::try_from(elem).unwrap();
-        assert_eq!(continue_, Continue { thread: Some("foo".to_owned()) });
+        assert_eq!(continue_.thread, Some("foo".to_owned()));
     }
 
     #[test]
@@ -725,7 +696,7 @@ mod tests {
         let item = Item::try_from(elem).unwrap();
         let continue_1 = Continue { thread: Some("foobar".to_owned()) };
         match item {
-            Item { continue_: Some(continue_2), .. } => assert_eq!(continue_2, continue_1),
+            Item { continue_: Some(continue_2), .. } => assert_eq!(continue_2.thread, continue_1.thread),
             _ => panic!(),
         }
     }

src/receipts.rs 🔗

@@ -12,68 +12,11 @@ use error::Error;
 
 use ns;
 
-#[derive(Debug, Clone)]
-pub struct Request;
+generate_empty_element!(Request, "request", ns::RECEIPTS);
 
-impl TryFrom<Element> for Request {
-    type Err = Error;
-
-    fn try_from(elem: Element) -> Result<Request, Error> {
-        if !elem.is("request", ns::RECEIPTS) {
-            return Err(Error::ParseError("This is not a request element."));
-        }
-        for _ in elem.children() {
-            return Err(Error::ParseError("Unknown child in request element."));
-        }
-        for _ in elem.attrs() {
-            return Err(Error::ParseError("Unknown attribute in request element."));
-        }
-        Ok(Request)
-    }
-}
-
-impl From<Request> for Element {
-    fn from(_: Request) -> Element {
-        Element::builder("request")
-                .ns(ns::RECEIPTS)
-                .build()
-    }
-}
-
-#[derive(Debug, Clone)]
-pub struct Received {
-    pub id: Option<String>,
-}
-
-impl TryFrom<Element> for Received {
-    type Err = Error;
-
-    fn try_from(elem: Element) -> Result<Received, Error> {
-        if !elem.is("received", ns::RECEIPTS) {
-            return Err(Error::ParseError("This is not a received element."));
-        }
-        for _ in elem.children() {
-            return Err(Error::ParseError("Unknown child in received element."));
-        }
-        for (attr, _) in elem.attrs() {
-            if attr != "id" {
-                return Err(Error::ParseError("Unknown attribute in received element."));
-            }
-        }
-        Ok(Received {
-            id: get_attr!(elem, "id", optional),
-        })
-    }
-}
-
-impl From<Received> for Element {
-    fn from(received: Received) -> Element {
-        Element::builder("received")
-               .ns(ns::RECEIPTS)
-               .attr("id", received.id)
-               .build()
-    }
-}
+generate_element_with_only_attributes!(Received, "received", ns::RECEIPTS, [
+    id: Option<String> = "id" => optional,
+]);
 
 #[cfg(test)]
 mod tests {

src/stanza_id.rs 🔗

@@ -13,68 +13,14 @@ use error::Error;
 
 use ns;
 
-#[derive(Debug, Clone)]
-pub struct StanzaId {
-    pub id: String,
-    pub by: Jid,
-}
-
-impl TryFrom<Element> for StanzaId {
-    type Err = Error;
-
-    fn try_from(elem: Element) -> Result<StanzaId, Error> {
-        if !elem.is("stanza-id", ns::SID) {
-            return Err(Error::ParseError("This is not a stanza-id element."));
-        }
-        for _ in elem.children() {
-            return Err(Error::ParseError("Unknown child in stanza-id element."));
-        }
-        Ok(StanzaId {
-            id: get_attr!(elem, "id", required),
-            by: get_attr!(elem, "by", required),
-        })
-    }
-}
-
-impl From<StanzaId> for Element {
-    fn from(stanza_id: StanzaId) -> Element {
-        Element::builder("stanza-id")
-                .ns(ns::SID)
-                .attr("id", stanza_id.id)
-                .attr("by", stanza_id.by)
-                .build()
-    }
-}
-
-#[derive(Debug, Clone)]
-pub struct OriginId {
-    pub id: String,
-}
-
-impl TryFrom<Element> for OriginId {
-    type Err = Error;
-
-    fn try_from(elem: Element) -> Result<OriginId, Error> {
-        if !elem.is("origin-id", ns::SID) {
-            return Err(Error::ParseError("This is not an origin-id element."));
-        }
-        for _ in elem.children() {
-            return Err(Error::ParseError("Unknown child in origin-id element."));
-        }
-        Ok(OriginId {
-            id: get_attr!(elem, "id", required),
-        })
-    }
-}
-
-impl From<OriginId> for Element {
-    fn from(origin_id: OriginId) -> Element {
-        Element::builder("origin-id")
-                .ns(ns::SID)
-                .attr("id", origin_id.id)
-                .build()
-    }
-}
+generate_element_with_only_attributes!(StanzaId, "stanza-id", ns::SID, [
+    id: String = "id" => required,
+    by: Jid = "by" => required,
+]);
+
+generate_element_with_only_attributes!(OriginId, "origin-id", ns::SID, [
+    id: String = "id" => required,
+]);
 
 #[cfg(test)]
 mod tests {