parsers: port more things to derive macros

Jonas SchΓ€fer created

Change summary

parsers/src/avatar.rs          |  16 ++--
parsers/src/cert_management.rs |  32 +++++-----
parsers/src/disco.rs           |  39 ++++++------
parsers/src/ecaps2.rs          |  24 ++++---
parsers/src/extdisco.rs        |  42 +++++++-------
parsers/src/http_upload.rs     |  51 ++++++++++++----
parsers/src/jingle_grouping.rs |  24 ++++----
parsers/src/jingle_ssma.rs     |  48 ++++++++--------
parsers/src/legacy_omemo.rs    |  65 +++++++++++----------
parsers/src/media_element.rs   |  33 +++++-----
parsers/src/mix.rs             |  30 +++++-----
parsers/src/openpgp.rs         |  16 ++--
parsers/src/pubsub/owner.rs    |  48 ++++++++--------
parsers/src/pubsub/pubsub.rs   | 108 ++++++++++++++++++-----------------
parsers/src/reactions.rs       |  24 ++++----
parsers/src/roster.rs          |  42 +++++++------
parsers/src/rsm.rs             |  22 +++++++
parsers/src/util/macros.rs     |  22 +++++++
18 files changed, 382 insertions(+), 304 deletions(-)

Detailed changes

parsers/src/avatar.rs πŸ”—

@@ -13,14 +13,14 @@ use crate::hashes::Sha1HexAttribute;
 use crate::ns;
 use crate::pubsub::PubSubPayload;
 
-generate_element!(
-    /// Communicates information about an avatar.
-    Metadata, "metadata", AVATAR_METADATA,
-    children: [
-        /// List of information elements describing this avatar.
-        infos: Vec<Info> = ("info", AVATAR_METADATA) => Info
-    ]
-);
+/// Communicates information about an avatar.
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::AVATAR_METADATA, name = "metadata")]
+pub struct Metadata {
+    /// List of information elements describing this avatar.
+    #[xml(child(n = ..))]
+    pub infos: Vec<Info>,
+}
 
 impl PubSubPayload for Metadata {}
 

parsers/src/cert_management.rs πŸ”—

@@ -56,14 +56,14 @@ generate_elem_id!(
     SASL_CERT
 );
 
-generate_element!(
-    /// A list of resources currently using this certificate.
-    Users, "users", SASL_CERT,
-    children: [
-        /// Resources currently using this certificate.
-        resources: Vec<Resource> = ("resource", SASL_CERT) => Resource
-    ]
-);
+/// A list of resources currently using this certificate.
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::SASL_CERT, name = "users")]
+pub struct Users {
+    /// Resources currently using this certificate.
+    #[xml(child(n = ..))]
+    pub resources: Vec<Resource>,
+}
 
 generate_element!(
     /// An X.509 certificate being set for this user.
@@ -83,14 +83,14 @@ generate_element!(
     ]
 );
 
-generate_element!(
-    /// Server answers with the current list of X.509 certificates.
-    ListCertsResponse, "items", SASL_CERT,
-    children: [
-        /// List of certificates.
-        items: Vec<Item> = ("item", SASL_CERT) => Item
-    ]
-);
+/// Server answers with the current list of X.509 certificates.
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::SASL_CERT, name = "items")]
+pub struct ListCertsResponse {
+    /// List of certificates.
+    #[xml(child(n = ..))]
+    pub items: Vec<Item>,
+}
 
 impl IqResultPayload for ListCertsResponse {}
 

parsers/src/disco.rs πŸ”—

@@ -220,25 +220,26 @@ pub struct Item {
     pub name: Option<String>,
 }
 
-generate_element!(
-    /// 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", DISCO_ITEMS,
-    attributes: [
-        /// Node on which we have done this discovery.
-        node: Option<String> = "node"
-    ],
-    children: [
-        /// List of items pointed by this entity.
-        items: Vec<Item> = ("item", DISCO_ITEMS) => Item,
-
-        /// Optional paging via Result Set Management
-        rsm: Option<crate::rsm::SetResult> = ("set", RSM) => SetResult,
-    ]
-);
+/// 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(FromXml, AsXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::DISCO_ITEMS, name = "query")]
+pub struct DiscoItemsResult {
+    /// Node on which we have done this discovery.
+    #[xml(attribute(default))]
+    pub node: Option<String>,
+
+    /// List of items pointed by this entity.
+    #[xml(child(n = ..))]
+    pub items: Vec<Item>,
+
+    /// Optional paging via Result Set Management
+    #[xml(child(default))]
+    pub rsm: Option<SetResult>,
+}
 
 impl IqResultPayload for DiscoItemsResult {}
 

parsers/src/ecaps2.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::{AsXml, FromXml};
+
 use crate::data_forms::DataForm;
 use crate::disco::{DiscoInfoQuery, DiscoInfoResult, Feature, Identity};
 use crate::hashes::{Algo, Hash};
@@ -16,16 +18,16 @@ use sha2::{Sha256, Sha512};
 use sha3::{Sha3_256, Sha3_512};
 use xso::error::Error;
 
-generate_element!(
-    /// Represents a set of capability hashes, all of them must correspond to
-    /// the same input [disco#info](../disco/struct.DiscoInfoResult.html),
-    /// using different [algorithms](../hashes/enum.Algo.html).
-    ECaps2, "c", ECAPS2,
-    children: [
-        /// Hashes of the [disco#info](../disco/struct.DiscoInfoResult.html).
-        hashes: Vec<Hash> = ("hash", HASHES) => Hash
-    ]
-);
+/// Represents a set of capability hashes, all of them must correspond to
+/// the same input [disco#info](../disco/struct.DiscoInfoResult.html),
+/// using different [algorithms](../hashes/enum.Algo.html).
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::ECAPS2, name = "c")]
+pub struct ECaps2 {
+    /// Hashes of the [disco#info](../disco/struct.DiscoInfoResult.html).
+    #[xml(child(n = ..))]
+    pub hashes: Vec<Hash>,
+}
 
 impl PresencePayload for ECaps2 {}
 
@@ -230,7 +232,7 @@ mod tests {
             FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
-        assert_eq!(message, "Unknown child in c element.");
+        assert_eq!(message, "Unknown child in ECaps2 element.");
     }
 
     #[test]

parsers/src/extdisco.rs πŸ”—

@@ -104,37 +104,37 @@ impl IqGetPayload for Service {}
 #[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::EXT_DISCO, name = "services")]
 pub struct ServicesQuery {
-    /// TODO
+    /// The type of service to filter for.
     #[xml(attribute(default, name = "type"))]
     pub type_: Option<Type>,
 }
 
 impl IqGetPayload for ServicesQuery {}
 
-generate_element!(
-    /// Structure representing a `<services xmlns='urn:xmpp:extdisco:2'/>` element.
-    ServicesResult, "services", EXT_DISCO,
-    attributes: [
-        /// TODO
-        type_: Option<Type> = "type",
-    ],
-    children: [
-        /// List of services.
-        services: Vec<Service> = ("service", EXT_DISCO) => Service
-    ]
-);
+/// Structure representing a `<services xmlns='urn:xmpp:extdisco:2'/>` element.
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::EXT_DISCO, name = "services")]
+pub struct ServicesResult {
+    /// The service type which was requested.
+    #[xml(attribute(name = "type", default))]
+    pub type_: Option<Type>,
+
+    /// List of services.
+    #[xml(child(n = ..))]
+    pub services: Vec<Service>,
+}
 
 impl IqResultPayload for ServicesResult {}
 impl IqSetPayload for ServicesResult {}
 
-generate_element!(
-    /// Structure representing a `<credentials xmlns='urn:xmpp:extdisco:2'/>` element.
-    Credentials, "credentials", EXT_DISCO,
-    children: [
-        /// List of services.
-        services: Vec<Service> = ("service", EXT_DISCO) => Service
-    ]
-);
+/// Structure representing a `<credentials xmlns='urn:xmpp:extdisco:2'/>` element.
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::EXT_DISCO, name = "credentials")]
+pub struct Credentials {
+    /// List of services.
+    #[xml(child(n = ..))]
+    pub services: Vec<Service>,
+}
 
 impl IqGetPayload for Credentials {}
 impl IqResultPayload for Credentials {}

parsers/src/http_upload.rs πŸ”—

@@ -5,8 +5,9 @@
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 use xso::{
-    error::{Error, FromElementError},
-    AsXml, FromXml,
+    error::{Error, FromElementError, FromEventsError},
+    exports::rxml,
+    minidom_compat, AsXml, FromXml,
 };
 
 use crate::iq::{IqGetPayload, IqResultPayload};
@@ -68,6 +69,20 @@ impl TryFrom<Element> for Header {
     }
 }
 
+impl FromXml for Header {
+    type Builder = minidom_compat::FromEventsViaElement<Header>;
+
+    fn from_events(
+        qname: rxml::QName,
+        attrs: rxml::AttrMap,
+    ) -> Result<Self::Builder, FromEventsError> {
+        if qname.0 != ns::HTTP_UPLOAD || qname.1 != "header" {
+            return Err(FromEventsError::Mismatch { name: qname, attrs });
+        }
+        Self::Builder::new(qname, attrs)
+    }
+}
+
 impl From<Header> for Element {
     fn from(elem: Header) -> Element {
         let (attr, val) = match elem {
@@ -83,18 +98,26 @@ impl From<Header> for Element {
     }
 }
 
-generate_element!(
-    /// Put URL
-    Put, "put", HTTP_UPLOAD,
-    attributes: [
-        /// URL
-        url: Required<String> = "url",
-    ],
-    children: [
-        /// Header list
-        headers: Vec<Header> = ("header", HTTP_UPLOAD) => Header
-    ]
-);
+impl AsXml for Header {
+    type ItemIter<'x> = minidom_compat::AsItemsViaElement<'x>;
+
+    fn as_xml_iter(&self) -> Result<Self::ItemIter<'_>, Error> {
+        minidom_compat::AsItemsViaElement::new(self.clone())
+    }
+}
+
+/// Put URL
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::HTTP_UPLOAD, name = "put")]
+pub struct Put {
+    /// URL
+    #[xml(attribute)]
+    pub url: String,
+
+    /// Header list
+    #[xml(child(n = ..))]
+    pub headers: Vec<Header>,
+}
 
 /// Get URL
 #[derive(FromXml, AsXml, PartialEq, Debug, Clone)]

parsers/src/jingle_grouping.rs πŸ”—

@@ -38,18 +38,18 @@ impl Content {
     }
 }
 
-generate_element!(
-    /// A semantic group of contents.
-    Group, "group", JINGLE_GROUPING,
-    attributes: [
-        /// Semantics of the grouping.
-        semantics: Required<Semantics> = "semantics",
-    ],
-    children: [
-        /// List of contents that should be grouped with each other.
-        contents: Vec<Content> = ("content", JINGLE_GROUPING) => Content
-    ]
-);
+/// A semantic group of contents.
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::JINGLE_GROUPING, name = "group")]
+pub struct Group {
+    /// Semantics of the grouping.
+    #[xml(attribute)]
+    pub semantics: Semantics,
+
+    /// List of contents that should be grouped with each other.
+    #[xml(child(n = ..))]
+    pub contents: Vec<Content>,
+}
 
 #[cfg(test)]
 mod tests {

parsers/src/jingle_ssma.rs πŸ”—

@@ -8,18 +8,18 @@ use xso::{AsXml, FromXml};
 
 use crate::ns;
 
-generate_element!(
-    /// Source element for the ssrc SDP attribute.
-    Source, "source", JINGLE_SSMA,
-    attributes: [
-        /// Maps to the ssrc-id parameter.
-        id: Required<u32> = "ssrc",
-    ],
-    children: [
-        /// List of attributes for this source.
-        parameters: Vec<Parameter> = ("parameter", JINGLE_SSMA) => Parameter
-    ]
-);
+/// Source element for the ssrc SDP attribute.
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::JINGLE_SSMA, name = "source")]
+pub struct Source {
+    /// Maps to the ssrc-id parameter.
+    #[xml(attribute = "ssrc")]
+    pub id: u32,
+
+    /// List of attributes for this source.
+    #[xml(child(n = ..))]
+    pub parameters: Vec<Parameter>,
+}
 
 impl Source {
     /// Create a new SSMA Source element.
@@ -67,18 +67,18 @@ generate_attribute!(
     }
 );
 
-generate_element!(
-    /// Element grouping multiple ssrc.
-    Group, "ssrc-group", JINGLE_SSMA,
-    attributes: [
-        /// The semantics of this group.
-        semantics: Required<Semantics> = "semantics",
-    ],
-    children: [
-        /// The various ssrc concerned by this group.
-        sources: Vec<Source> = ("source", JINGLE_SSMA) => Source
-    ]
-);
+/// Element grouping multiple ssrc.
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::JINGLE_SSMA, name = "ssrc-group")]
+pub struct Group {
+    /// The semantics of this group.
+    #[xml(attribute)]
+    pub semantics: Semantics,
+
+    /// The various ssrc concerned by this group.
+    #[xml(child(n = ..))]
+    pub sources: Vec<Source>,
+}
 
 #[cfg(test)]
 mod tests {

parsers/src/legacy_omemo.rs πŸ”—

@@ -19,15 +19,15 @@ pub struct Device {
     pub id: u32,
 }
 
-generate_element!(
-    /// A user's device list contains the OMEMO device ids of all the user's
-    /// devicse. These can be used to look up bundles and build a session.
-    DeviceList, "list", LEGACY_OMEMO,
-    children: [
-        /// List of devices
-        devices: Vec<Device> = ("device", LEGACY_OMEMO) => Device
-    ]
-);
+/// A user's device list contains the OMEMO device ids of all the user's
+/// devicse. These can be used to look up bundles and build a session.
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::LEGACY_OMEMO, name = "list")]
+pub struct DeviceList {
+    /// List of devices
+    #[xml(child(n = ..))]
+    pub devices: Vec<Device>,
+}
 
 impl PubSubPayload for DeviceList {}
 
@@ -64,15 +64,15 @@ pub struct IdentityKey {
     pub data: Vec<u8>,
 }
 
-generate_element!(
+/// List of (single use) PreKeys
+/// Part of a device's bundle
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::LEGACY_OMEMO, name = "prekeys")]
+pub struct Prekeys {
     /// List of (single use) PreKeys
-    /// Part of a device's bundle
-    Prekeys, "prekeys", LEGACY_OMEMO,
-    children: [
-        /// List of (single use) PreKeys
-        keys: Vec<PreKeyPublic> = ("preKeyPublic", LEGACY_OMEMO) => PreKeyPublic,
-    ]
-);
+    #[xml(child(n = ..))]
+    pub keys: Vec<PreKeyPublic>,
+}
 
 /// PreKey public key
 /// Part of a device's bundle
@@ -113,22 +113,23 @@ pub struct Bundle {
 
 impl PubSubPayload for Bundle {}
 
-generate_element!(
-    /// The header contains encrypted keys for a message
-    Header, "header", LEGACY_OMEMO,
-    attributes: [
-        /// The device id of the sender
-        sid: Required<u32> = "sid",
-    ],
-    children: [
-        /// The key that the payload message is encrypted with, separately
-        /// encrypted for each recipient device.
-        keys: Vec<Key> = ("key", LEGACY_OMEMO) => Key,
+/// The header contains encrypted keys for a message
+#[derive(FromXml, AsXml, Debug, PartialEq, Clone)]
+#[xml(namespace = ns::LEGACY_OMEMO, name = "header")]
+pub struct Header {
+    /// The device id of the sender
+    #[xml(attribute)]
+    pub sid: u32,
 
-        /// IV used for payload encryption
-        iv: Required<IV> = ("iv", LEGACY_OMEMO) => IV
-    ]
-);
+    /// The key that the payload message is encrypted with, separately
+    /// encrypted for each recipient device.
+    #[xml(child(n = ..))]
+    pub keys: Vec<Key>,
+
+    /// IV used for payload encryption
+    #[xml(child)]
+    pub iv: IV,
+}
 
 /// IV used for payload encryption
 #[derive(FromXml, AsXml, PartialEq, Debug, Clone)]

parsers/src/media_element.rs πŸ”—

@@ -27,22 +27,23 @@ pub struct Uri {
     pub uri: String,
 }
 
-generate_element!(
-    /// References a media element, to be used in [data
-    /// forms](../data_forms/index.html).
-    MediaElement, "media", MEDIA_ELEMENT,
-    attributes: [
-        /// The recommended display width in pixels.
-        width: Option<usize> = "width",
+/// References a media element, to be used in [data
+/// forms](../data_forms/index.html).
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::MEDIA_ELEMENT, name = "media")]
+pub struct MediaElement {
+    /// The recommended display width in pixels.
+    #[xml(attribute(default))]
+    pub width: Option<usize>,
+
+    /// The recommended display height in pixels.
+    #[xml(attribute(default))]
+    pub height: Option<usize>,
 
-        /// The recommended display height in pixels.
-        height: Option<usize> = "height"
-    ],
-    children: [
-        /// A list of URIs referencing this media.
-        uris: Vec<Uri> = ("uri", MEDIA_ELEMENT) => Uri
-    ]
-);
+    /// A list of URIs referencing this media.
+    #[xml(child(n = ..))]
+    pub uris: Vec<Uri>,
+}
 
 #[cfg(test)]
 mod tests {
@@ -162,7 +163,7 @@ mod tests {
             FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
-        assert_eq!(message, "Unknown child in media element.");
+        assert_eq!(message, "Unknown child in MediaElement element.");
     }
 
     #[test]

parsers/src/mix.rs πŸ”—

@@ -113,21 +113,21 @@ impl Join {
     }
 }
 
-generate_element!(
-    /// Update a given subscription.
-    UpdateSubscription, "update-subscription", MIX_CORE,
-    attributes: [
-        /// The JID of the user to be affected.
-        // TODO: why is it not a participant id instead?
-        jid: Option<BareJid> = "jid",
-    ],
-    children: [
-        /// The list of additional nodes to subscribe to.
-        // TODO: what happens when we are already subscribed?  Also, how do we unsubscribe from
-        // just one?
-        subscribes: Vec<Subscribe> = ("subscribe", MIX_CORE) => Subscribe
-    ]
-);
+/// Update a given subscription.
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::MIX_CORE, name = "update-subscription")]
+pub struct UpdateSubscription {
+    /// The JID of the user to be affected.
+    // TODO: why is it not a participant id instead?
+    #[xml(attribute(default))]
+    pub jid: Option<BareJid>,
+
+    /// The list of additional nodes to subscribe to.
+    // TODO: what happens when we are already subscribed?  Also, how do we unsubscribe from
+    // just one?
+    #[xml(child(n = ..))]
+    pub subscribes: Vec<Subscribe>,
+}
 
 impl IqSetPayload for UpdateSubscription {}
 impl IqResultPayload for UpdateSubscription {}

parsers/src/openpgp.rs πŸ”—

@@ -48,14 +48,14 @@ pub struct PubKeyMeta {
     pub date: DateTime,
 }
 
-generate_element!(
-    /// List of public key metadata
-    PubKeysMeta, "public-key-list", OX,
-    children: [
-        /// Public keys
-        pubkeys: Vec<PubKeyMeta> = ("pubkey-metadata", OX) => PubKeyMeta
-    ]
-);
+/// List of public key metadata
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::OX, name = "public-key-list")]
+pub struct PubKeysMeta {
+    /// Public keys
+    #[xml(child(n = ..))]
+    pub pubkeys: Vec<PubKeyMeta>,
+}
 
 impl PubSubPayload for PubKeysMeta {}
 

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

@@ -15,18 +15,18 @@ use jid::Jid;
 use minidom::Element;
 use xso::error::{Error, FromElementError};
 
-generate_element!(
-    /// A list of affiliations you have on a service, or on a node.
-    Affiliations, "affiliations", PUBSUB_OWNER,
-    attributes: [
-        /// The node name this request pertains to.
-        node: Required<NodeName> = "node",
-    ],
-    children: [
-        /// The actual list of affiliation elements.
-        affiliations: Vec<Affiliation> = ("affiliation", PUBSUB_OWNER) => Affiliation
-    ]
-);
+/// A list of affiliations you have on a service, or on a node.
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::PUBSUB_OWNER, name = "affiliations")]
+pub struct Affiliations {
+    /// The node name this request pertains to.
+    #[xml(attribute)]
+    pub node: NodeName,
+
+    /// The actual list of affiliation elements.
+    #[xml(child(n = ..))]
+    pub affiliations: Vec<Affiliation>,
+}
 
 /// An affiliation element.
 #[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
@@ -94,18 +94,18 @@ pub struct Purge {
     pub node: NodeName,
 }
 
-generate_element!(
-    /// A request for current subscriptions.
-    Subscriptions, "subscriptions", PUBSUB_OWNER,
-    attributes: [
-        /// The node to query.
-        node: Required<NodeName> = "node",
-    ],
-    children: [
-        /// The list of subscription elements returned.
-        subscriptions: Vec<SubscriptionElem> = ("subscription", PUBSUB_OWNER) => SubscriptionElem
-    ]
-);
+/// A request for current subscriptions.
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::PUBSUB_OWNER, name = "subscriptions")]
+pub struct Subscriptions {
+    /// The node to query.
+    #[xml(attribute)]
+    pub node: NodeName,
+
+    /// The list of subscription elements returned.
+    #[xml(child(n = ..))]
+    pub subscriptions: Vec<SubscriptionElem>,
+}
 
 /// A subscription element, describing the state of a subscription.
 #[derive(FromXml, AsXml, PartialEq, Debug, Clone)]

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

@@ -21,18 +21,18 @@ use minidom::Element;
 
 // TODO: a better solution would be to split this into a query and a result elements, like for
 // XEP-0030.
-generate_element!(
-    /// A list of affiliations you have on a service, or on a node.
-    Affiliations, "affiliations", PUBSUB,
-    attributes: [
-        /// The optional node name this request pertains to.
-        node: Option<NodeName> = "node",
-    ],
-    children: [
-        /// The actual list of affiliation elements.
-        affiliations: Vec<Affiliation> = ("affiliation", PUBSUB) => Affiliation
-    ]
-);
+/// A list of affiliations you have on a service, or on a node.
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::PUBSUB, name = "affiliations")]
+pub struct Affiliations {
+    /// The optional node name this request pertains to.
+    #[xml(attribute(default))]
+    pub node: Option<NodeName>,
+
+    /// The actual list of affiliation elements.
+    #[xml(child(n = ..))]
+    pub affiliations: Vec<Affiliation>,
+}
 
 /// An affiliation element.
 #[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
@@ -77,25 +77,27 @@ pub struct Default {
     // type_: Option<String>,
 }
 
-generate_element!(
-    /// A request for a list of items.
-    Items, "items", PUBSUB,
-    attributes: [
-        // TODO: should be an xs:positiveInteger, that is, an unbounded int β‰₯ 1.
-        /// Maximum number of items returned.
-        max_items: Option<u32> = "max_items",
+/// A request for a list of items.
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::PUBSUB, name = "items")]
+pub struct Items {
+    // TODO: should be an xs:positiveInteger, that is, an unbounded int β‰₯ 1.
+    /// Maximum number of items returned.
+    #[xml(attribute(name = "max_items" /*sic!*/, default))]
+    pub max_items: Option<u32>,
+
+    /// The node queried by this request.
+    #[xml(attribute)]
+    pub node: NodeName,
 
-        /// The node queried by this request.
-        node: Required<NodeName> = "node",
+    /// The subscription identifier related to this request.
+    #[xml(attribute(default))]
+    pub subid: Option<SubscriptionId>,
 
-        /// The subscription identifier related to this request.
-        subid: Option<SubscriptionId> = "subid",
-    ],
-    children: [
-        /// The actual list of items returned.
-        items: Vec<Item> = ("item", PUBSUB) => Item
-    ]
-);
+    /// The actual list of items returned.
+    #[xml(child(n = ..))]
+    pub items: Vec<Item>,
+}
 
 impl Items {
     /// Create a new items request.
@@ -136,18 +138,18 @@ pub struct Options {
     pub form: Option<DataForm>,
 }
 
-generate_element!(
-    /// Request to publish items to a node.
-    Publish, "publish", PUBSUB,
-    attributes: [
-        /// The target node for this operation.
-        node: Required<NodeName> = "node",
-    ],
-    children: [
-        /// The items you want to publish.
-        items: Vec<Item> = ("item", PUBSUB) => Item
-    ]
-);
+/// Request to publish items to a node.
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::PUBSUB, name = "publish")]
+pub struct Publish {
+    /// The target node for this operation.
+    #[xml(attribute)]
+    pub node: NodeName,
+
+    /// The items you want to publish.
+    #[xml(child(n = ..))]
+    pub items: Vec<Item>,
+}
 
 /// The options associated to a publish request.
 #[derive(FromXml, AsXml, Debug, PartialEq, Clone)]
@@ -259,18 +261,18 @@ pub struct Subscribe {
     pub node: Option<NodeName>,
 }
 
-generate_element!(
-    /// A request for current subscriptions.
-    Subscriptions, "subscriptions", PUBSUB,
-    attributes: [
-        /// The node to query.
-        node: Option<NodeName> = "node",
-    ],
-    children: [
-        /// The list of subscription elements returned.
-        subscription: Vec<SubscriptionElem> = ("subscription", PUBSUB) => SubscriptionElem
-    ]
-);
+/// A request for current subscriptions.
+#[derive(FromXml, AsXml, Debug, PartialEq, Clone)]
+#[xml(namespace = ns::PUBSUB, name = "subscriptions")]
+pub struct Subscriptions {
+    /// The node to query.
+    #[xml(attribute(default))]
+    pub node: Option<NodeName>,
+
+    /// The list of subscription elements returned.
+    #[xml(child(n = ..))]
+    pub subscription: Vec<SubscriptionElem>,
+}
 
 /// A subscription element, describing the state of a subscription.
 #[derive(FromXml, AsXml, Debug, PartialEq, Clone)]

parsers/src/reactions.rs πŸ”—

@@ -9,18 +9,18 @@ use xso::{AsXml, FromXml};
 use crate::message::MessagePayload;
 use crate::ns;
 
-generate_element!(
-    /// Container for a set of reactions.
-    Reactions, "reactions", REACTIONS,
-    attributes: [
-        /// The id of the message these reactions apply to.
-        id: Required<String> = "id",
-    ],
-    children: [
-        /// The list of reactions.
-        reactions: Vec<Reaction> = ("reaction", REACTIONS) => Reaction,
-    ]
-);
+/// Container for a set of reactions.
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::REACTIONS, name = "reactions")]
+pub struct Reactions {
+    /// The id of the message these reactions apply to.
+    #[xml(attribute)]
+    pub id: String,
+
+    /// The list of reactions.
+    #[xml(child(n = ..))]
+    pub reactions: Vec<Reaction>,
+}
 
 impl MessagePayload for Reactions {}
 

parsers/src/roster.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::iq::{IqGetPayload, IqResultPayload, IqSetPayload};
+use xso::{AsXml, FromXml};
+
 use jid::BareJid;
 
+use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload};
+use crate::ns;
+
 generate_elem_id!(
     /// Represents a group a contact is part of.
     Group,
@@ -67,22 +71,22 @@ generate_element!(
     ]
 );
 
-generate_element!(
-    /// The contact list of the user.
-    Roster, "query", ROSTER,
-    attributes: [
-        /// Version of the contact list.
-        ///
-        /// This is an opaque string that should only be sent back to the server on
-        /// a new connection, if this client is storing the contact list between
-        /// connections.
-        ver: Option<String> = "ver"
-    ],
-    children: [
-        /// List of the contacts of the user.
-        items: Vec<Item> = ("item", ROSTER) => Item
-    ]
-);
+/// The contact list of the user.
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::ROSTER, name = "query")]
+pub struct Roster {
+    /// Version of the contact list.
+    ///
+    /// This is an opaque string that should only be sent back to the server on
+    /// a new connection, if this client is storing the contact list between
+    /// connections.
+    #[xml(attribute(default))]
+    pub ver: Option<String>,
+
+    /// List of the contacts of the user.
+    #[xml(child(n = ..))]
+    pub items: Vec<Item>,
+}
 
 impl IqGetPayload for Roster {}
 impl IqSetPayload for Roster {}
@@ -273,7 +277,7 @@ mod tests {
             FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
-        assert_eq!(message, "Unknown child in query element.");
+        assert_eq!(message, "Unknown child in Roster element.");
 
         let elem: Element = "<query xmlns='jabber:iq:roster' coucou=''/>"
             .parse()
@@ -283,7 +287,7 @@ mod tests {
             FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
-        assert_eq!(message, "Unknown attribute in query element.");
+        assert_eq!(message, "Unknown attribute in Roster element.");
     }
 
     #[test]

parsers/src/rsm.rs πŸ”—

@@ -172,6 +172,20 @@ impl TryFrom<Element> for SetResult {
     }
 }
 
+impl FromXml for SetResult {
+    type Builder = minidom_compat::FromEventsViaElement<SetResult>;
+
+    fn from_events(
+        qname: rxml::QName,
+        attrs: rxml::AttrMap,
+    ) -> Result<Self::Builder, FromEventsError> {
+        if qname.0 != crate::ns::RSM || qname.1 != "set" {
+            return Err(FromEventsError::Mismatch { name: qname, attrs });
+        }
+        Self::Builder::new(qname, attrs)
+    }
+}
+
 impl From<SetResult> for Element {
     fn from(set: SetResult) -> Element {
         let first = set.first.clone().map(|first| {
@@ -193,6 +207,14 @@ impl From<SetResult> for Element {
     }
 }
 
+impl AsXml for SetResult {
+    type ItemIter<'x> = minidom_compat::AsItemsViaElement<'x>;
+
+    fn as_xml_iter(&self) -> Result<Self::ItemIter<'_>, Error> {
+        minidom_compat::AsItemsViaElement::new(self.clone())
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;

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

@@ -762,6 +762,20 @@ macro_rules! impl_pubsub_item {
             }
         }
 
+        impl ::xso::FromXml for $item {
+            type Builder = ::xso::minidom_compat::FromEventsViaElement<$item>;
+
+            fn from_events(
+                qname: ::xso::exports::rxml::QName,
+                attrs: ::xso::exports::rxml::AttrMap,
+            ) -> Result<Self::Builder, ::xso::error::FromEventsError> {
+                if qname.0 != crate::ns::$ns || qname.1 != "item" {
+                    return Err(::xso::error::FromEventsError::Mismatch { name: qname, attrs });
+                }
+                Self::Builder::new(qname, attrs)
+            }
+        }
+
         impl From<$item> for minidom::Element {
             fn from(item: $item) -> minidom::Element {
                 minidom::Element::builder("item", ns::$ns)
@@ -772,6 +786,14 @@ macro_rules! impl_pubsub_item {
             }
         }
 
+        impl ::xso::AsXml for $item {
+            type ItemIter<'x> = ::xso::minidom_compat::AsItemsViaElement<'x>;
+
+            fn as_xml_iter(&self) -> Result<Self::ItemIter<'_>, ::xso::error::Error> {
+                ::xso::minidom_compat::AsItemsViaElement::new(self.clone())
+            }
+        }
+
         impl ::std::ops::Deref for $item {
             type Target = crate::pubsub::Item;