parsers: port more generate_element! usages to derive macros

Jonas SchΓ€fer created

Change summary

parsers/Cargo.toml              |  2 
parsers/src/idle.rs             | 35 ++++++++++++++++-----------
parsers/src/jingle_ft.rs        | 35 ++++++++++++++++-----------
parsers/src/jingle_grouping.rs  | 19 ++++++++------
parsers/src/jingle_thumnails.rs | 38 +++++++++++++++++++-----------
parsers/src/legacy_omemo.rs     | 19 ++++++++------
parsers/src/mix.rs              | 32 ++++++++++++------------
parsers/src/openpgp.rs          | 25 +++++++++++-------
parsers/src/pubsub/owner.rs     | 37 +++++++++++++++--------------
parsers/src/pubsub/pubsub.rs    | 28 ++++++++++++---------
parsers/src/rtt.rs              | 21 ++++++++-------
parsers/src/sm.rs               | 16 ++++++------
parsers/src/stanza_id.rs        | 44 ++++++++++++++++++++--------------
13 files changed, 200 insertions(+), 151 deletions(-)

Detailed changes

parsers/Cargo.toml πŸ”—

@@ -24,7 +24,7 @@ chrono = { version = "0.4.5", default-features = false, features = ["std"] }
 # same repository dependencies
 jid = { version = "0.10", features = ["minidom"], path = "../jid" }
 minidom = { version = "0.15", path = "../minidom" }
-xso = { version = "0.0.2", features = ["macros", "minidom", "panicking-into-impl"] }
+xso = { version = "0.0.2", features = ["macros", "minidom", "panicking-into-impl", "jid"] }
 
 [features]
 # Build xmpp-parsers to make components instead of clients.

parsers/src/idle.rs πŸ”—

@@ -4,17 +4,20 @@
 // License, v. 2.0. If a copy of the MPL was not distributed with this
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+use xso::{FromXml, IntoXml};
+
 use crate::date::DateTime;
+use crate::ns;
 use crate::presence::PresencePayload;
 
-generate_element!(
-    /// Represents the last time the user interacted with their system.
-    Idle, "idle", IDLE,
-    attributes: [
-        /// The time at which the user stopped interacting.
-        since: Required<DateTime> = "since",
-    ]
-);
+/// Represents the last time the user interacted with their system.
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::IDLE, name = "idle")]
+pub struct Idle {
+    /// The time at which the user stopped interacting.
+    #[xml(attribute)]
+    pub since: DateTime,
+}
 
 impl PresencePayload for Idle {}
 
@@ -40,15 +43,16 @@ mod tests {
 
     #[test]
     fn test_invalid_child() {
-        let elem: Element = "<idle xmlns='urn:xmpp:idle:1'><coucou/></idle>"
-            .parse()
-            .unwrap();
+        let elem: Element =
+            "<idle xmlns='urn:xmpp:idle:1' since='2017-05-21T20:19:55+01:00'><coucou/></idle>"
+                .parse()
+                .unwrap();
         let error = Idle::try_from(elem).unwrap_err();
         let message = match error {
             FromElementError::Invalid(Error::Other(string)) => string,
-            _ => panic!(),
+            other => panic!("unexpected result: {:?}", other),
         };
-        assert_eq!(message, "Unknown child in idle element.");
+        assert_eq!(message, "Unknown child in Idle element.");
     }
 
     #[test]
@@ -59,7 +63,10 @@ mod tests {
             FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
-        assert_eq!(message, "Required attribute 'since' missing.");
+        assert_eq!(
+            message,
+            "Required attribute field 'since' on Idle element missing."
+        );
     }
 
     #[test]

parsers/src/jingle_ft.rs πŸ”—

@@ -11,7 +11,10 @@ use crate::ns;
 use minidom::{Element, Node};
 use std::collections::BTreeMap;
 use std::str::FromStr;
-use xso::error::{Error, FromElementError};
+use xso::{
+    error::{Error, FromElementError},
+    FromXml, IntoXml,
+};
 
 generate_element!(
     /// Represents a range in a file.
@@ -319,17 +322,18 @@ impl From<Checksum> for Element {
     }
 }
 
-generate_element!(
-    /// A notice that the file transfer has been completed.
-    Received, "received", JINGLE_FT,
-    attributes: [
-        /// The content identifier of this Jingle session.
-        name: Required<ContentId> = "name",
+/// A notice that the file transfer has been completed.
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::JINGLE_FT, name = "received")]
+pub struct Received {
+    /// The content identifier of this Jingle session.
+    #[xml(attribute)]
+    pub name: ContentId,
 
-        /// The creator of this file transfer.
-        creator: Required<Creator> = "creator",
-    ]
-);
+    /// The creator of this file transfer.
+    #[xml(attribute)]
+    pub creator: Creator,
+}
 
 #[cfg(test)]
 mod tests {
@@ -479,7 +483,7 @@ mod tests {
             FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
-        assert_eq!(message, "Unknown child in received element.");
+        assert_eq!(message, "Unknown child in Received element.");
 
         let elem: Element =
             "<received xmlns='urn:xmpp:jingle:apps:file-transfer:5' creator='initiator'/>"
@@ -490,7 +494,10 @@ mod tests {
             FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
-        assert_eq!(message, "Required attribute 'name' missing.");
+        assert_eq!(
+            message,
+            "Required attribute field 'name' on Received element missing."
+        );
 
         let elem: Element = "<received xmlns='urn:xmpp:jingle:apps:file-transfer:5' name='coucou' creator='coucou'/>".parse().unwrap();
         let error = Received::try_from(elem).unwrap_err();
@@ -513,7 +520,7 @@ mod tests {
             FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
-        assert_eq!(message, "Unknown attribute in received element.");
+        assert_eq!(message, "Unknown attribute in Received element.");
     }
 
     #[test]

parsers/src/jingle_grouping.rs πŸ”—

@@ -4,7 +4,10 @@
 // License, v. 2.0. If a copy of the MPL was not distributed with this
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+use xso::{FromXml, IntoXml};
+
 use crate::jingle::ContentId;
+use crate::ns;
 
 generate_attribute!(
     /// The semantics of the grouping.
@@ -17,14 +20,14 @@ generate_attribute!(
     }
 );
 
-generate_element!(
-    /// Describes a content that should be grouped with other ones.
-    Content, "content", JINGLE_GROUPING,
-    attributes: [
-        /// The name of the matching [`Content`](crate::jingle::Content).
-        name: Required<ContentId> = "name",
-    ]
-);
+/// Describes a content that should be grouped with other ones.
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::JINGLE_GROUPING, name = "content")]
+pub struct Content {
+    /// The name of the matching [`Content`](crate::jingle::Content).
+    #[xml(attribute)]
+    pub name: ContentId,
+}
 
 impl Content {
     /// Creates a new \<content/\> element.

parsers/src/jingle_thumnails.rs πŸ”—

@@ -6,20 +6,30 @@
 // License, v. 2.0. If a copy of the MPL was not distributed with this
 // file, you can obtain one at http://mozilla.org/MPL/2.0/.
 
-generate_element!(
-    /// A Jingle thumbnail.
-    Thumbnail, "thumbnail", JINGLE_THUMBNAILS,
-    attributes: [
-        /// The URI of the thumbnail.
-        uri: Required<String> = "uri",
-        /// The media type of the thumbnail.
-        media_type: Required<String> = "media-type",
-        /// The width of the thumbnail.
-        width: Required<u32> = "width",
-        /// The height of the thumbnail.
-        height: Required<u32> = "height",
-    ]
-);
+use xso::{FromXml, IntoXml};
+
+use crate::ns;
+
+/// A Jingle thumbnail.
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::JINGLE_THUMBNAILS, name = "thumbnail")]
+pub struct Thumbnail {
+    /// The URI of the thumbnail.
+    #[xml(attribute)]
+    pub uri: String,
+
+    /// The media type of the thumbnail.
+    #[xml(attribute = "media-type")]
+    pub media_type: String,
+
+    /// The width of the thumbnail.
+    #[xml(attribute)]
+    pub width: u32,
+
+    /// The height of the thumbnail.
+    #[xml(attribute)]
+    pub height: u32,
+}
 
 #[cfg(test)]
 mod tests {

parsers/src/legacy_omemo.rs πŸ”—

@@ -4,18 +4,21 @@
 // License, v. 2.0. If a copy of the MPL was not distributed with this
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+use xso::{FromXml, IntoXml};
+
 use crate::message::MessagePayload;
+use crate::ns;
 use crate::pubsub::PubSubPayload;
 use crate::util::text_node_codecs::{Base64, Codec};
 
-generate_element!(
-    /// Element of the device list
-    Device, "device", LEGACY_OMEMO,
-    attributes: [
-        /// Device id
-        id: Required<u32> = "id"
-    ]
-);
+/// Element of the device list
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::LEGACY_OMEMO, name = "device")]
+pub struct Device {
+    /// Device id
+    #[xml(attribute)]
+    pub id: u32,
+}
 
 generate_element!(
     /// A user's device list contains the OMEMO device ids of all the user's

parsers/src/mix.rs πŸ”—

@@ -58,14 +58,14 @@ impl Participant {
     }
 }
 
-generate_element!(
-    /// A node to subscribe to.
-    Subscribe, "subscribe", MIX_CORE,
-    attributes: [
-        /// The PubSub node to subscribe to.
-        node: Required<NodeName> = "node",
-    ]
-);
+/// A node to subscribe to.
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::MIX_CORE, name = "subscribe")]
+pub struct Subscribe {
+    /// The PubSub node to subscribe to.
+    #[xml(attribute)]
+    pub node: NodeName,
+}
 
 impl Subscribe {
     /// Create a new Subscribe element.
@@ -230,14 +230,14 @@ impl Create {
     }
 }
 
-generate_element!(
-    /// Destroy a given MIX channel.
-    Destroy, "destroy", MIX_CORE,
-    attributes: [
-        /// The channel identifier to be destroyed.
-        channel: Required<ChannelId> = "channel",
-    ]
-);
+/// Destroy a given MIX channel.
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::MIX_CORE, name = "destroy")]
+pub struct Destroy {
+    /// The channel identifier to be destroyed.
+    #[xml(attribute)]
+    pub channel: ChannelId,
+}
 
 // TODO: section 7.3.4, example 33, doesn’t mirror the <destroy/> in the iq result unlike every
 // other section so far.

parsers/src/openpgp.rs πŸ”—

@@ -4,7 +4,10 @@
 // License, v. 2.0. If a copy of the MPL was not distributed with this
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+use xso::{FromXml, IntoXml};
+
 use crate::date::DateTime;
+use crate::ns;
 use crate::pubsub::PubSubPayload;
 use crate::util::text_node_codecs::{Base64, Codec};
 
@@ -33,16 +36,18 @@ generate_element!(
 
 impl PubSubPayload for PubKey {}
 
-generate_element!(
-    /// Public key metadata
-    PubKeyMeta, "pubkey-metadata", OX,
-    attributes: [
-        /// OpenPGP v4 fingerprint
-        v4fingerprint: Required<String> = "v4-fingerprint",
-        /// Time the key was published or updated
-        date: Required<DateTime> = "date",
-    ]
-);
+/// Public key metadata
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::OX, name = "pubkey-metadata")]
+pub struct PubKeyMeta {
+    /// OpenPGP v4 fingerprint
+    #[xml(attribute = "v4-fingerprint")]
+    pub v4fingerprint: String,
+
+    /// Time the key was published or updated
+    #[xml(attribute = "date")]
+    pub date: DateTime,
+}
 
 generate_element!(
     /// List of public key metadata

parsers/src/pubsub/owner.rs πŸ”—

@@ -28,17 +28,18 @@ generate_element!(
     ]
 );
 
-generate_element!(
-    /// An affiliation element.
-    Affiliation, "affiliation", PUBSUB_OWNER,
-    attributes: [
-        /// The node this affiliation pertains to.
-        jid: Required<Jid> = "jid",
+/// An affiliation element.
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::PUBSUB_OWNER, name = "affiliation")]
+pub struct Affiliation {
+    /// The node this affiliation pertains to.
+    #[xml(attribute)]
+    jid: Jid,
 
-        /// The affiliation you currently have on this node.
-        affiliation: Required<AffiliationAttribute> = "affiliation",
-    ]
-);
+    /// The affiliation you currently have on this node.
+    #[xml(attribute)]
+    affiliation: AffiliationAttribute,
+}
 
 generate_element!(
     /// Request to configure a node.
@@ -84,14 +85,14 @@ pub struct Redirect {
     pub uri: String,
 }
 
-generate_element!(
-    /// Request to delete a node.
-    Purge, "purge", PUBSUB_OWNER,
-    attributes: [
-        /// The node to be configured.
-        node: Required<NodeName> = "node",
-    ]
-);
+/// Request to clear a node.
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::PUBSUB_OWNER, name = "purge")]
+pub struct Purge {
+    /// The node to be cleared.
+    #[xml(attribute)]
+    pub node: NodeName,
+}
 
 generate_element!(
     /// A request for current subscriptions.

parsers/src/pubsub/pubsub.rs πŸ”—

@@ -12,7 +12,10 @@ use crate::pubsub::{
 };
 use crate::Element;
 use jid::Jid;
-use xso::error::{Error, FromElementError};
+use xso::{
+    error::{Error, FromElementError},
+    FromXml, IntoXml,
+};
 
 // TODO: a better solution would be to split this into a query and a result elements, like for
 // XEP-0030.
@@ -29,17 +32,18 @@ generate_element!(
     ]
 );
 
-generate_element!(
-    /// An affiliation element.
-    Affiliation, "affiliation", PUBSUB,
-    attributes: [
-        /// The node this affiliation pertains to.
-        node: Required<NodeName> = "node",
-
-        /// The affiliation you currently have on this node.
-        affiliation: Required<AffiliationAttribute> = "affiliation",
-    ]
-);
+/// An affiliation element.
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::PUBSUB, name = "affiliation")]
+pub struct Affiliation {
+    /// The node this affiliation pertains to.
+    #[xml(attribute)]
+    pub node: NodeName,
+
+    /// The affiliation you currently have on this node.
+    #[xml(attribute)]
+    pub affiliation: AffiliationAttribute,
+}
 
 generate_element!(
     /// Request to configure a new node.

parsers/src/rtt.rs πŸ”—

@@ -4,6 +4,8 @@
 // License, v. 2.0. If a copy of the MPL was not distributed with this
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+use xso::{FromXml, IntoXml};
+
 use crate::ns;
 use crate::util::text_node_codecs::{Codec, OptionalCodec, Text};
 use crate::Element;
@@ -110,16 +112,15 @@ impl TryFrom<Action> for Erase {
     }
 }
 
-generate_element!(
-    /// Allow for the transmission of intervals, between real-time text actions, to recreate the
-    /// pauses between key presses.
-    Wait, "w", RTT,
-
-    attributes: [
-        /// Amount of milliseconds to wait before the next action.
-        time: Required<u32> = "n",
-    ]
-);
+/// Allow for the transmission of intervals, between real-time text actions, to recreate the
+/// pauses between key presses.
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::RTT, name = "w")]
+pub struct Wait {
+    /// Amount of milliseconds to wait before the next action.
+    #[xml(attribute = "n")]
+    pub time: u32,
+}
 
 impl TryFrom<Action> for Wait {
     type Error = Error;

parsers/src/sm.rs πŸ”—

@@ -9,14 +9,14 @@ use xso::{FromXml, IntoXml};
 use crate::ns;
 use crate::stanza_error::DefinedCondition;
 
-generate_element!(
-    /// Acknowledgement of the currently received stanzas.
-    A, "a", SM,
-    attributes: [
-        /// The last handled stanza.
-        h: Required<u32> = "h",
-    ]
-);
+/// Acknowledgement of the currently received stanzas.
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::SM, name = "a")]
+pub struct A {
+    /// The last handled stanza.
+    #[xml(attribute)]
+    pub h: u32,
+}
 
 impl A {
     /// Generates a new `<a/>` element.

parsers/src/stanza_id.rs πŸ”—

@@ -10,18 +10,19 @@ use crate::message::MessagePayload;
 use crate::ns;
 use jid::Jid;
 
-generate_element!(
-    /// Gives the identifier a service has stamped on this stanza, often in
-    /// order to identify it inside of [an archive](../mam/index.html).
-    StanzaId, "stanza-id", SID,
-    attributes: [
-        /// The id associated to this stanza by another entity.
-        id: Required<String> = "id",
-
-        /// The entity who stamped this stanza-id.
-        by: Required<Jid> = "by",
-    ]
-);
+/// Gives the identifier a service has stamped on this stanza, often in
+/// order to identify it inside of [an archive](../mam/index.html).
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::SID, name = "stanza-id")]
+pub struct StanzaId {
+    /// The id associated to this stanza by another entity.
+    #[xml(attribute)]
+    pub id: String,
+
+    /// The entity who stamped this stanza-id.
+    #[xml(attribute)]
+    pub by: Jid,
+}
 
 impl MessagePayload for StanzaId {}
 
@@ -76,15 +77,16 @@ mod tests {
 
     #[test]
     fn test_invalid_child() {
-        let elem: Element = "<stanza-id xmlns='urn:xmpp:sid:0'><coucou/></stanza-id>"
-            .parse()
-            .unwrap();
+        let elem: Element =
+            "<stanza-id xmlns='urn:xmpp:sid:0' by='a@b' id='x'><coucou/></stanza-id>"
+                .parse()
+                .unwrap();
         let error = StanzaId::try_from(elem).unwrap_err();
         let message = match error {
             FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
-        assert_eq!(message, "Unknown child in stanza-id element.");
+        assert_eq!(message, "Unknown child in StanzaId element.");
     }
 
     #[test]
@@ -95,7 +97,10 @@ mod tests {
             FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
-        assert_eq!(message, "Required attribute 'id' missing.");
+        assert_eq!(
+            message,
+            "Required attribute field 'id' on StanzaId element missing."
+        );
     }
 
     #[test]
@@ -108,7 +113,10 @@ mod tests {
             FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
-        assert_eq!(message, "Required attribute 'by' missing.");
+        assert_eq!(
+            message,
+            "Required attribute field 'by' on StanzaId element missing."
+        );
     }
 
     #[test]