parsers: port more elements to derive macros

Jonas SchΓ€fer created

Change summary

parsers/src/avatar.rs         |  54 +++++++++++--------
parsers/src/bookmarks.rs      |  26 +++++---
parsers/src/disco.rs          |  36 ++++++++----
parsers/src/eme.rs            |  38 ++++++++-----
parsers/src/hashes.rs         |  14 +++++
parsers/src/http_upload.rs    |  32 ++++++-----
parsers/src/ibb.rs            |  14 +++-
parsers/src/jingle_ice_udp.rs | 103 +++++++++++++++++++++---------------
parsers/src/jingle_raw_udp.rs |  51 ++++++++++-------
parsers/src/jingle_rtcp_fb.rs |  27 +++++---
parsers/src/jingle_rtp.rs     |  25 ++++----
parsers/src/jingle_ssma.rs    |  27 +++++---
parsers/src/pubsub/owner.rs   |  26 +++++----
parsers/src/pubsub/pubsub.rs  |  56 ++++++++++---------
parsers/src/sm.rs             |  46 ++++++++-------
parsers/src/stream.rs         |  42 ++++++++------
parsers/src/util/macros.rs    |  20 +++++++
parsers/src/websocket.rs      |  42 ++++++++------
18 files changed, 405 insertions(+), 274 deletions(-)

Detailed changes

parsers/src/avatar.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::hashes::Sha1HexAttribute;
+use crate::ns;
 use crate::pubsub::PubSubPayload;
 use crate::util::text_node_codecs::{Codec, WhitespaceAwareBase64};
 
@@ -19,29 +22,34 @@ generate_element!(
 
 impl PubSubPayload for Metadata {}
 
-generate_element!(
-    /// Communicates avatar metadata.
-    Info, "info", AVATAR_METADATA,
-    attributes: [
-        /// The size of the image data in bytes.
-        bytes: Required<u32> = "bytes",
-
-        /// The width of the image in pixels.
-        width: Option<u16> = "width",
-
-        /// The height of the image in pixels.
-        height: Option<u16> = "height",
-
-        /// The SHA-1 hash of the image data for the specified content-type.
-        id: Required<Sha1HexAttribute> = "id",
-
-        /// The IANA-registered content type of the image data.
-        type_: Required<String> = "type",
-
-        /// The http: or https: URL at which the image data file is hosted.
-        url: Option<String> = "url",
-    ]
-);
+/// Communicates avatar metadata.
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::AVATAR_METADATA, name = "info")]
+pub struct Info {
+    /// The size of the image data in bytes.
+    #[xml(attribute)]
+    pub bytes: u32,
+
+    /// The width of the image in pixels.
+    #[xml(attribute(default))]
+    pub width: Option<u16>,
+
+    /// The height of the image in pixels.
+    #[xml(attribute(default))]
+    pub height: Option<u16>,
+
+    /// The SHA-1 hash of the image data for the specified content-type.
+    #[xml(attribute)]
+    pub id: Sha1HexAttribute,
+
+    /// The IANA-registered content type of the image data.
+    #[xml(attribute = "type")]
+    pub type_: String,
+
+    /// The http: or https: URL at which the image data file is hosted.
+    #[xml(attribute(default))]
+    pub url: Option<String>,
+}
 
 generate_element!(
     /// The actual avatar data.

parsers/src/bookmarks.rs πŸ”—

@@ -16,9 +16,12 @@
 //!
 //! The [`Conference`][crate::bookmarks::Conference] struct used in [`private::Query`][`crate::private::Query`] is the one from this module. Only the querying mechanism changes from a legacy PubSub implementation here, to a legacy Private XML Query implementation in that other module. The [`Conference`][crate::bookmarks2::Conference] element from the [`bookmarks2`][crate::bookmarks2] module is a different structure, but conversion is possible from [`bookmarks::Conference`][crate::bookmarks::Conference] to [`bookmarks2::Conference`][crate::bookmarks2::Conference] via the [`Conference::into_bookmarks2`][crate::bookmarks::Conference::into_bookmarks2] method.
 
+use xso::{FromXml, IntoXml};
+
 use jid::BareJid;
 
 pub use crate::bookmarks2::Autojoin;
+use crate::ns;
 
 generate_element!(
     /// A conference bookmark.
@@ -59,17 +62,18 @@ impl Conference {
     }
 }
 
-generate_element!(
-    /// An URL bookmark.
-    Url, "url", BOOKMARKS,
-    attributes: [
-        /// A user-defined name for this URL.
-        name: Option<String> = "name",
-
-        /// The URL of this bookmark.
-        url: Required<String> = "url",
-    ]
-);
+/// An URL bookmark.
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::BOOKMARKS, name = "url")]
+pub struct Url {
+    /// A user-defined name for this URL.
+    #[xml(attribute(default))]
+    pub name: Option<String>,
+
+    /// The URL of this bookmark.
+    #[xml(attribute)]
+    pub url: String,
+}
 
 generate_element!(
     /// Container element for multiple bookmarks.

parsers/src/disco.rs πŸ”—

@@ -4,24 +4,29 @@
 // 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::{
+    error::{Error, FromElementError},
+    FromXml, IntoXml,
+};
+
 use crate::data_forms::{DataForm, DataFormType};
 use crate::iq::{IqGetPayload, IqResultPayload};
 use crate::ns;
 use crate::rsm::{SetQuery, SetResult};
 use crate::Element;
 use jid::Jid;
-use xso::error::{Error, FromElementError};
 
-generate_element!(
 /// Structure representing a `<query xmlns='http://jabber.org/protocol/disco#info'/>` element.
 ///
 /// It should only be used in an `<iq type='get'/>`, as it can only represent
 /// the request, and not a result.
-DiscoInfoQuery, "query", DISCO_INFO,
-attributes: [
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::DISCO_INFO, name = "query")]
+pub struct DiscoInfoQuery {
     /// Node on which we are doing the discovery.
-    node: Option<String> = "node",
-]);
+    #[xml(attribute(default))]
+    pub node: Option<String>,
+}
 
 impl IqGetPayload for DiscoInfoQuery {}
 
@@ -197,17 +202,22 @@ children: [
 
 impl IqGetPayload for DiscoItemsQuery {}
 
-generate_element!(
 /// Structure representing an `<item xmlns='http://jabber.org/protocol/disco#items'/>` element.
-Item, "item", DISCO_ITEMS,
-attributes: [
+#[derive(FromXml, IntoXml, Debug, Clone, PartialEq)]
+#[xml(namespace = ns::DISCO_ITEMS, name = "item")]
+pub struct Item {
     /// JID of the entity pointed by this item.
-    jid: Required<Jid> = "jid",
+    #[xml(attribute)]
+    pub jid: Jid,
+
     /// Node of the entity pointed by this item.
-    node: Option<String> = "node",
+    #[xml(attribute(default))]
+    pub node: Option<String>,
+
     /// Name of the entity pointed by this item.
-    name: Option<String> = "name",
-]);
+    #[xml(attribute(default))]
+    pub name: Option<String>,
+}
 
 generate_element!(
     /// Structure representing a `<query

parsers/src/eme.rs πŸ”—

@@ -4,20 +4,24 @@
 // 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;
 
-generate_element!(
-    /// Structure representing an `<encryption xmlns='urn:xmpp:eme:0'/>` element.
-    ExplicitMessageEncryption, "encryption", EME,
-    attributes: [
-        /// Namespace of the encryption scheme used.
-        namespace: Required<String> = "namespace",
+/// Structure representing an `<encryption xmlns='urn:xmpp:eme:0'/>` element.
+#[derive(FromXml, IntoXml, Debug, Clone, PartialEq)]
+#[xml(namespace = ns::EME, name = "encryption")]
+pub struct ExplicitMessageEncryption {
+    /// Namespace of the encryption scheme used.
+    #[xml(attribute)]
+    pub namespace: String,
 
-        /// User-friendly name for the encryption scheme, should be `None` for OTR,
-        /// legacy OpenPGP and OX.
-        name: Option<String> = "name",
-    ]
-);
+    /// User-friendly name for the encryption scheme, should be `None` for OTR,
+    /// legacy OpenPGP and OX.
+    #[xml(attribute(default))]
+    pub name: Option<String>,
+}
 
 impl MessagePayload for ExplicitMessageEncryption {}
 
@@ -69,15 +73,19 @@ mod tests {
 
     #[test]
     fn test_invalid_child() {
-        let elem: Element = "<encryption xmlns='urn:xmpp:eme:0'><coucou/></encryption>"
-            .parse()
-            .unwrap();
+        let elem: Element =
+            "<encryption xmlns='urn:xmpp:eme:0' namespace='urn:xmpp:otr:0'><coucou/></encryption>"
+                .parse()
+                .unwrap();
         let error = ExplicitMessageEncryption::try_from(elem).unwrap_err();
         let message = match error {
             FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
-        assert_eq!(message, "Unknown child in encryption element.");
+        assert_eq!(
+            message,
+            "Unknown child in ExplicitMessageEncryption element."
+        );
     }
 
     #[test]

parsers/src/hashes.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::{FromXmlText, IntoXmlText};
+
 use crate::util::text_node_codecs::{Base64, Codec};
 use base64::{engine::general_purpose::STANDARD as Base64Engine, Engine};
 use minidom::IntoAttributeValue;
@@ -184,6 +186,18 @@ impl FromStr for Sha1HexAttribute {
     }
 }
 
+impl FromXmlText for Sha1HexAttribute {
+    fn from_xml_text(s: String) -> Result<Self, xso::error::Error> {
+        Self::from_str(&s).map_err(xso::error::Error::text_parse_error)
+    }
+}
+
+impl IntoXmlText for Sha1HexAttribute {
+    fn into_xml_text(self) -> Result<String, xso::error::Error> {
+        Ok(self.to_hex())
+    }
+}
+
 impl IntoAttributeValue for Sha1HexAttribute {
     fn into_attribute_value(self) -> Option<String> {
         Some(self.to_hex())

parsers/src/http_upload.rs πŸ”—

@@ -4,27 +4,31 @@
 // 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 xso::{
+    error::{Error, FromElementError},
+    FromXml, IntoXml,
+};
 
 use crate::iq::{IqGetPayload, IqResultPayload};
 use crate::ns;
 use crate::Element;
-use xso::error::{Error, FromElementError};
 
-generate_element!(
-    /// Requesting a slot
-    SlotRequest, "request", HTTP_UPLOAD,
-    attributes: [
-        /// The filename to be uploaded.
-        filename: Required<String> = "filename",
+/// Requesting a slot
+#[derive(FromXml, IntoXml, Debug, Clone, PartialEq)]
+#[xml(namespace = ns::HTTP_UPLOAD, name = "request")]
+pub struct SlotRequest {
+    /// The filename to be uploaded.
+    #[xml(attribute)]
+    pub filename: String,
 
-        /// Size of the file to be uploaded.
-        size: Required<u64> = "size",
+    /// Size of the file to be uploaded.
+    #[xml(attribute)]
+    pub size: u64,
 
-        /// Content-Type of the file.
-        content_type: Option<String> = "content-type",
-    ]
-);
+    /// Content-Type of the file.
+    #[xml(attribute(name = "content-type"))]
+    pub content_type: Option<String>,
+}
 
 impl IqGetPayload for SlotRequest {}
 

parsers/src/ibb.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::iq::IqSetPayload;
+use crate::ns;
 use crate::util::text_node_codecs::{Base64, Codec};
 
 generate_id!(
@@ -59,13 +62,14 @@ Data, "data", IBB,
 
 impl IqSetPayload for Data {}
 
-generate_element!(
 /// Close an open stream.
-Close, "close", IBB,
-attributes: [
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::IBB, name = "close")]
+pub struct Close {
     /// The identifier of the stream to be closed.
-    sid: Required<StreamId> = "sid",
-]);
+    #[xml(attribute)]
+    pub sid: StreamId,
+}
 
 impl IqSetPayload for Close {}
 

parsers/src/jingle_ice_udp.rs πŸ”—

@@ -4,9 +4,13 @@
 // 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 crate::jingle_dtls_srtp::Fingerprint;
 use std::net::IpAddr;
 
+use xso::{FromXml, IntoXml};
+
+use crate::jingle_dtls_srtp::Fingerprint;
+use crate::ns;
+
 generate_element!(
     /// Wrapper element for an ICE-UDP transport.
     #[derive(Default)]
@@ -63,50 +67,61 @@ generate_attribute!(
     }
 );
 
-generate_element!(
-    /// A candidate for an ICE-UDP session.
-    Candidate, "candidate", JINGLE_ICE_UDP,
-    attributes: [
-        /// A Component ID as defined in ICE-CORE.
-        component: Required<u8> = "component",
-
-        /// A Foundation as defined in ICE-CORE.
-        foundation: Required<String> = "foundation",
-
-        /// An index, starting at 0, that enables the parties to keep track of updates to the
-        /// candidate throughout the life of the session.
-        generation: Required<u8> = "generation",
-
-        /// A unique identifier for the candidate.
-        id: Required<String> = "id",
-
-        /// The Internet Protocol (IP) address for the candidate transport mechanism; this can be
-        /// either an IPv4 address or an IPv6 address.
-        ip: Required<IpAddr> = "ip",
+/// A candidate for an ICE-UDP session.
+#[derive(FromXml, IntoXml, Debug, PartialEq, Clone)]
+#[xml(namespace = ns::JINGLE_ICE_UDP, name = "candidate")]
+pub struct Candidate {
+    /// A Component ID as defined in ICE-CORE.
+    #[xml(attribute)]
+    pub component: u8,
+
+    /// A Foundation as defined in ICE-CORE.
+    #[xml(attribute)]
+    pub foundation: String,
+
+    /// An index, starting at 0, that enables the parties to keep track of updates to the
+    /// candidate throughout the life of the session.
+    #[xml(attribute)]
+    pub generation: u8,
+
+    /// A unique identifier for the candidate.
+    #[xml(attribute)]
+    pub id: String,
+
+    /// The Internet Protocol (IP) address for the candidate transport mechanism; this can be
+    /// either an IPv4 address or an IPv6 address.
+    #[xml(attribute)]
+    pub ip: IpAddr,
+
+    /// The port at the candidate IP address.
+    #[xml(attribute)]
+    pub port: u16,
+
+    /// A Priority as defined in ICE-CORE.
+    #[xml(attribute)]
+    pub priority: u32,
+
+    /// The protocol to be used. The only value defined by this specification is "udp".
+    #[xml(attribute)]
+    pub protocol: String,
+
+    /// A related address as defined in ICE-CORE.
+    #[xml(attribute(default, name = "rel-addr"))]
+    pub rel_addr: Option<IpAddr>,
+
+    /// A related port as defined in ICE-CORE.
+    #[xml(attribute(default, name = "rel-port"))]
+    pub rel_port: Option<u16>,
+
+    /// An index, starting at 0, referencing which network this candidate is on for a given
+    /// peer.
+    #[xml(attribute(default))]
+    pub network: Option<u8>,
 
-        /// The port at the candidate IP address.
-        port: Required<u16> = "port",
-
-        /// A Priority as defined in ICE-CORE.
-        priority: Required<u32> = "priority",
-
-        /// The protocol to be used. The only value defined by this specification is "udp".
-        protocol: Required<String> = "protocol",
-
-        /// A related address as defined in ICE-CORE.
-        rel_addr: Option<IpAddr> = "rel-addr",
-
-        /// A related port as defined in ICE-CORE.
-        rel_port: Option<u16> = "rel-port",
-
-        /// An index, starting at 0, referencing which network this candidate is on for a given
-        /// peer.
-        network: Option<u8> = "network",
-
-        /// A Candidate Type as defined in ICE-CORE.
-        type_: Required<Type> = "type",
-    ]
-);
+    /// A Candidate Type as defined in ICE-CORE.
+    #[xml(attribute(name = "type"))]
+    pub type_: Type,
+}
 
 #[cfg(test)]
 mod tests {

parsers/src/jingle_raw_udp.rs πŸ”—

@@ -4,9 +4,13 @@
 // 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 crate::jingle_ice_udp::Type;
 use std::net::IpAddr;
 
+use xso::{FromXml, IntoXml};
+
+use crate::jingle_ice_udp::Type;
+use crate::ns;
+
 generate_element!(
     /// Wrapper element for an raw UDP transport.
     #[derive(Default)]
@@ -30,31 +34,36 @@ impl Transport {
     }
 }
 
-generate_element!(
-    /// A candidate for an ICE-UDP session.
-    Candidate, "candidate", JINGLE_RAW_UDP,
-    attributes: [
-        /// A Component ID as defined in ICE-CORE.
-        component: Required<u8> = "component",
+/// A candidate for an ICE-UDP session.
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::JINGLE_RAW_UDP, name = "candidate")]
+pub struct Candidate {
+    /// A Component ID as defined in ICE-CORE.
+    #[xml(attribute)]
+    pub component: u8,
 
-        /// An index, starting at 0, that enables the parties to keep track of updates to the
-        /// candidate throughout the life of the session.
-        generation: Required<u8> = "generation",
+    /// An index, starting at 0, that enables the parties to keep track of updates to the
+    /// candidate throughout the life of the session.
+    #[xml(attribute)]
+    pub generation: u8,
 
-        /// A unique identifier for the candidate.
-        id: Required<String> = "id",
+    /// A unique identifier for the candidate.
+    #[xml(attribute)]
+    pub id: String,
 
-        /// The Internet Protocol (IP) address for the candidate transport mechanism; this can be
-        /// either an IPv4 address or an IPv6 address.
-        ip: Required<IpAddr> = "ip",
+    /// The Internet Protocol (IP) address for the candidate transport mechanism; this can be
+    /// either an IPv4 address or an IPv6 address.
+    #[xml(attribute)]
+    pub ip: IpAddr,
 
-        /// The port at the candidate IP address.
-        port: Required<u16> = "port",
+    /// The port at the candidate IP address.
+    #[xml(attribute)]
+    pub port: u16,
 
-        /// A Candidate Type as defined in ICE-CORE.
-        type_: Option<Type> = "type",
-    ]
-);
+    /// A Candidate Type as defined in ICE-CORE.
+    #[xml(attribute(default, name = "type"))]
+    pub type_: Option<Type>,
+}
 
 #[cfg(test)]
 mod tests {

parsers/src/jingle_rtcp_fb.rs πŸ”—

@@ -4,17 +4,22 @@
 // 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!(
-    /// Wrapper element for a rtcp-fb.
-    RtcpFb, "rtcp-fb", JINGLE_RTCP_FB,
-    attributes: [
-        /// Type of this rtcp-fb.
-        type_: Required<String> = "type",
-
-        /// Subtype of this rtcp-fb, if relevant.
-        subtype: Option<String> = "subtype",
-    ]
-);
+use xso::{FromXml, IntoXml};
+
+use crate::ns;
+
+/// Wrapper element for a rtcp-fb.
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::JINGLE_RTCP_FB, name = "rtcp-fb")]
+pub struct RtcpFb {
+    /// Type of this rtcp-fb.
+    #[xml(attribute = "type")]
+    pub type_: String,
+
+    /// Subtype of this rtcp-fb, if relevant.
+    #[xml(attribute(default))]
+    pub subtype: Option<String>,
+}
 
 #[cfg(test)]
 mod tests {

parsers/src/jingle_rtp.rs πŸ”—

@@ -137,18 +137,19 @@ impl PayloadType {
     }
 }
 
-generate_element!(
-    /// Parameter related to a payload.
-    Parameter, "parameter", JINGLE_RTP,
-    attributes: [
-        /// The name of the parameter, from the list at
-        /// <https://www.iana.org/assignments/sdp-parameters/sdp-parameters.xhtml>
-        name: Required<String> = "name",
-
-        /// The value of this parameter.
-        value: Required<String> = "value",
-    ]
-);
+/// Parameter related to a payload.
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::JINGLE_RTP, name = "parameter")]
+pub struct Parameter {
+    /// The name of the parameter, from the list at
+    /// <https://www.iana.org/assignments/sdp-parameters/sdp-parameters.xhtml>
+    #[xml(attribute)]
+    pub name: String,
+
+    /// The value of this parameter.
+    #[xml(attribute)]
+    pub value: String,
+}
 
 #[cfg(test)]
 mod tests {

parsers/src/jingle_ssma.rs πŸ”—

@@ -4,6 +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::ns;
+
 generate_element!(
     /// Source element for the ssrc SDP attribute.
     Source, "source", JINGLE_SSMA,
@@ -27,17 +31,18 @@ impl Source {
     }
 }
 
-generate_element!(
-    /// Parameter associated with a ssrc.
-    Parameter, "parameter", JINGLE_SSMA,
-    attributes: [
-        /// The name of the parameter.
-        name: Required<String> = "name",
-
-        /// The optional value of the parameter.
-        value: Option<String> = "value",
-    ]
-);
+/// Parameter associated with a ssrc.
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::JINGLE_SSMA, name = "parameter")]
+pub struct Parameter {
+    /// The name of the parameter.
+    #[xml(attribute)]
+    pub name: String,
+
+    /// The optional value of the parameter.
+    #[xml(attribute(default))]
+    pub value: Option<String>,
+}
 
 generate_attribute!(
     /// From RFC5888, the list of allowed semantics.

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

@@ -107,20 +107,22 @@ generate_element!(
     ]
 );
 
-generate_element!(
-    /// A subscription element, describing the state of a subscription.
-    SubscriptionElem, "subscription", PUBSUB_OWNER,
-    attributes: [
-        /// The JID affected by this subscription.
-        jid: Required<Jid> = "jid",
+/// A subscription element, describing the state of a subscription.
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::PUBSUB_OWNER, name = "subscription")]
+pub struct SubscriptionElem {
+    /// The JID affected by this subscription.
+    #[xml(attribute)]
+    pub jid: Jid,
 
-        /// The state of the subscription.
-        subscription: Required<Subscription> = "subscription",
+    /// The state of the subscription.
+    #[xml(attribute)]
+    pub subscription: Subscription,
 
-        /// Subscription unique id.
-        subid: Option<String> = "subid",
-    ]
-);
+    /// Subscription unique id.
+    #[xml(attribute(default))]
+    pub subid: Option<String>,
+}
 
 /// Main payload used to communicate with a PubSubOwner service.
 ///

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

@@ -4,6 +4,11 @@
 // 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::{
+    error::{Error, FromElementError},
+    FromXml, IntoXml,
+};
+
 use crate::data_forms::DataForm;
 use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload};
 use crate::ns;
@@ -12,10 +17,6 @@ use crate::pubsub::{
 };
 use crate::Element;
 use jid::Jid;
-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.
@@ -220,17 +221,18 @@ impl From<SubscribeOptions> for Element {
     }
 }
 
-generate_element!(
-    /// A request to subscribe a JID to a node.
-    Subscribe, "subscribe", PUBSUB,
-    attributes: [
-        /// The JID being subscribed.
-        jid: Required<Jid> = "jid",
+/// A request to subscribe a JID to a node.
+#[derive(FromXml, IntoXml, Debug, PartialEq, Clone)]
+#[xml(namespace = ns::PUBSUB, name = "subscribe")]
+pub struct Subscribe {
+    /// The JID being subscribed.
+    #[xml(attribute)]
+    pub jid: Jid,
 
-        /// The node to subscribe to.
-        node: Option<NodeName> = "node",
-    ]
-);
+    /// The node to subscribe to.
+    #[xml(attribute)]
+    pub node: Option<NodeName>,
+}
 
 generate_element!(
     /// A request for current subscriptions.
@@ -267,20 +269,22 @@ generate_element!(
     ]
 );
 
-generate_element!(
-    /// An unsubscribe request.
-    Unsubscribe, "unsubscribe", PUBSUB,
-    attributes: [
-        /// The JID affected by this request.
-        jid: Required<Jid> = "jid",
+/// An unsubscribe request.
+#[derive(FromXml, IntoXml, Debug, PartialEq, Clone)]
+#[xml(namespace = ns::PUBSUB, name = "unsubscribe")]
+pub struct Unsubscribe {
+    /// The JID affected by this request.
+    #[xml(attribute)]
+    jid: Jid,
 
-        /// The node affected by this request.
-        node: Option<NodeName> = "node",
+    /// The node affected by this request.
+    #[xml(attribute)]
+    node: Option<NodeName>,
 
-        /// The subscription identifier for this subscription.
-        subid: Option<SubscriptionId> = "subid",
-    ]
-);
+    /// The subscription identifier for this subscription.
+    #[xml(attribute)]
+    subid: Option<SubscriptionId>,
+}
 
 /// Main payload used to communicate with a PubSub service.
 ///

parsers/src/sm.rs πŸ”—

@@ -109,31 +109,33 @@ generate_element!(
 #[xml(namespace = ns::SM, name = "r")]
 pub struct R;
 
-generate_element!(
-    /// Requests a stream resumption.
-    Resume, "resume", SM,
-    attributes: [
-        /// The last handled stanza.
-        h: Required<u32> = "h",
+/// Requests a stream resumption.
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::SM, name = "resume")]
+pub struct Resume {
+    /// The last handled stanza.
+    #[xml(attribute)]
+    pub h: u32,
 
-        /// The previous id given by the server on
-        /// [enabled](struct.Enabled.html).
-        previd: Required<StreamId> = "previd",
-    ]
-);
+    /// The previous id given by the server on
+    /// [enabled](struct.Enabled.html).
+    #[xml(attribute)]
+    pub previd: StreamId,
+}
 
-generate_element!(
-    /// The response by the server for a successfully resumed stream.
-    Resumed, "resumed", SM,
-    attributes: [
-        /// The last handled stanza.
-        h: Required<u32> = "h",
+/// The response by the server for a successfully resumed stream.
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::SM, name = "resumed")]
+pub struct Resumed {
+    /// The last handled stanza.
+    #[xml(attribute)]
+    pub h: u32,
 
-        /// The previous id given by the server on
-        /// [enabled](struct.Enabled.html).
-        previd: Required<StreamId> = "previd",
-    ]
-);
+    /// The previous id given by the server on
+    /// [enabled](struct.Enabled.html).
+    #[xml(attribute)]
+    pub previd: StreamId,
+}
 
 // TODO: add support for optional and required.
 /// Represents availability of Stream Management in `<stream:features/>`.

parsers/src/stream.rs πŸ”—

@@ -4,29 +4,37 @@
 // 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 jid::BareJid;
 
-generate_element!(
-    /// The stream opening for client-server communications.
-    Stream, "stream", STREAM,
-    attributes: [
-        /// The JID of the entity opening this stream.
-        from: Option<BareJid> = "from",
+use crate::ns;
+
+/// The stream opening for client-server communications.
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::STREAM, name = "stream")]
+pub struct Stream {
+    /// The JID of the entity opening this stream.
+    #[xml(attribute(default))]
+    pub from: Option<BareJid>,
 
-        /// The JID of the entity receiving this stream opening.
-        to: Option<BareJid> = "to",
+    /// The JID of the entity receiving this stream opening.
+    #[xml(attribute(default))]
+    to: Option<BareJid>,
 
-        /// The id of the stream, used for authentication challenges.
-        id: Option<String> = "id",
+    /// The id of the stream, used for authentication challenges.
+    #[xml(attribute(default))]
+    id: Option<String>,
 
-        /// The XMPP version used during this stream.
-        version: Option<String> = "version",
+    /// The XMPP version used during this stream.
+    #[xml(attribute(default))]
+    version: Option<String>,
 
-        /// The default human language for all subsequent stanzas, which will
-        /// be transmitted to other entities for better localisation.
-        xml_lang: Option<String> = "xml:lang",
-    ]
-);
+    /// The default human language for all subsequent stanzas, which will
+    /// be transmitted to other entities for better localisation.
+    #[xml(attribute(default, name = "xml:lang"))]
+    xml_lang: Option<String>,
+}
 
 impl Stream {
     /// Creates a simple client→server `<stream:stream>` element.

parsers/src/util/macros.rs πŸ”—

@@ -125,6 +125,26 @@ macro_rules! generate_attribute {
                 })
             }
         }
+        impl ::xso::FromXmlText for $elem {
+            fn from_xml_text(s: String) -> Result<$elem, xso::error::Error> {
+                s.parse().map_err(xso::error::Error::text_parse_error)
+            }
+        }
+        impl ::xso::IntoXmlText for $elem {
+            fn into_xml_text(self) -> Result<String, xso::error::Error> {
+                Ok(String::from(match self {
+                    $($elem::$a => $b),+
+                }))
+            }
+
+            #[allow(unreachable_patterns)]
+            fn into_optional_xml_text(self) -> Result<Option<String>, xso::error::Error> {
+                Ok(Some(String::from(match self {
+                    $elem::$default => return Ok(None),
+                    $($elem::$a => $b),+
+                })))
+            }
+        }
         impl ::minidom::IntoAttributeValue for $elem {
             #[allow(unreachable_patterns)]
             fn into_attribute_value(self) -> Option<String> {

parsers/src/websocket.rs πŸ”—

@@ -4,29 +4,37 @@
 // 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 jid::BareJid;
 
-generate_element!(
-    /// The stream opening for WebSocket.
-    Open, "open", WEBSOCKET,
-    attributes: [
-        /// The JID of the entity opening this stream.
-        from: Option<BareJid> = "from",
+use crate::ns;
+
+/// The stream opening for WebSocket.
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::WEBSOCKET, name = "open")]
+pub struct Open {
+    /// The JID of the entity opening this stream.
+    #[xml(attribute(default))]
+    pub from: Option<BareJid>,
 
-        /// The JID of the entity receiving this stream opening.
-        to: Option<BareJid> = "to",
+    /// The JID of the entity receiving this stream opening.
+    #[xml(attribute(default))]
+    pub to: Option<BareJid>,
 
-        /// The id of the stream, used for authentication challenges.
-        id: Option<String> = "id",
+    /// The id of the stream, used for authentication challenges.
+    #[xml(attribute(default))]
+    pub id: Option<String>,
 
-        /// The XMPP version used during this stream.
-        version: Option<String> = "version",
+    /// The XMPP version used during this stream.
+    #[xml(attribute(default))]
+    pub version: Option<String>,
 
-        /// The default human language for all subsequent stanzas, which will
-        /// be transmitted to other entities for better localisation.
-        xml_lang: Option<String> = "xml:lang",
-    ]
-);
+    /// The default human language for all subsequent stanzas, which will
+    /// be transmitted to other entities for better localisation.
+    #[xml(attribute(default, name = "xml:lang"))]
+    pub xml_lang: Option<String>,
+}
 
 impl Open {
     /// Creates a simple client→server `<open/>` element.