xmpp-parsers: Use xso for pubsub owner

Also merge its direct children into its enum fields.
This commit is contained in:
Emmanuel Gil Peyrot 2025-01-27 15:14:57 +01:00
commit 5cbe5936a6
3 changed files with 102 additions and 154 deletions

View file

@ -62,6 +62,8 @@ XXXX-YY-ZZ RELEASER <admin@example.com>
having been exchanged, and the stream_id is now a String instead of having been exchanged, and the stream_id is now a String instead of
&str. &str.
- Add a lang property in Presence, to avoid parser errors. - Add a lang property in Presence, to avoid parser errors.
- pubsub::Owner is now a wrapper for the pubsub::owner::Paylolad enum,
and all its direct children have been merged into this enum (!532)
* New parsers/serialisers: * New parsers/serialisers:
- Stream Features (RFC 6120) (!400) - Stream Features (RFC 6120) (!400)
- Spam Reporting (XEP-0377) (!506) - Spam Reporting (XEP-0377) (!506)

View file

@ -15,7 +15,7 @@ pub mod owner;
pub mod pubsub; pub mod pubsub;
pub use self::event::Event; pub use self::event::Event;
pub use self::owner::PubSubOwner; pub use self::owner::Owner;
pub use self::pubsub::PubSub; pub use self::pubsub::PubSub;
use minidom::Element; use minidom::Element;

View file

@ -12,21 +12,6 @@ use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload};
use crate::ns; use crate::ns;
use crate::pubsub::{AffiliationAttribute, NodeName, Subscription}; use crate::pubsub::{AffiliationAttribute, NodeName, Subscription};
use jid::Jid; use jid::Jid;
use minidom::Element;
use xso::error::{Error, FromElementError};
/// 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. /// An affiliation element.
#[derive(FromXml, AsXml, PartialEq, Debug, Clone)] #[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
@ -41,72 +26,6 @@ pub struct Affiliation {
affiliation: AffiliationAttribute, affiliation: AffiliationAttribute,
} }
/// Request to configure a node.
#[derive(FromXml, AsXml, Debug, PartialEq, Clone)]
#[xml(namespace = ns::PUBSUB_OWNER, name = "configure")]
pub struct Configure {
/// The node to be configured.
#[xml(attribute(default))]
pub node: Option<NodeName>,
/// The form to configure it.
#[xml(child(default))]
pub form: Option<DataForm>,
}
/// Request to retrieve default configuration.
#[derive(FromXml, AsXml, Debug, PartialEq, Clone)]
#[xml(namespace = ns::PUBSUB_OWNER, name = "default")]
pub struct Default {
/// The form to configure it.
#[xml(child(default))]
pub form: Option<DataForm>,
}
/// Request to delete a node.
#[derive(FromXml, AsXml, Debug, PartialEq, Clone)]
#[xml(namespace = ns::PUBSUB_OWNER, name = "delete")]
pub struct Delete {
/// The node to be deleted.
#[xml(attribute)]
pub node: NodeName,
/// Redirection to replace the deleted node.
#[xml(child(default))]
pub redirect: Option<Redirect>,
}
/// A redirect element.
#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
#[xml(namespace = ns::PUBSUB_OWNER, name = "redirect")]
pub struct Redirect {
/// The node this node will be redirected to.
#[xml(attribute)]
pub uri: String,
}
/// Request to clear a node.
#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
#[xml(namespace = ns::PUBSUB_OWNER, name = "purge")]
pub struct Purge {
/// The node to be cleared.
#[xml(attribute)]
pub node: NodeName,
}
/// 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. /// A subscription element, describing the state of a subscription.
#[derive(FromXml, AsXml, PartialEq, Debug, Clone)] #[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
#[xml(namespace = ns::PUBSUB_OWNER, name = "subscription")] #[xml(namespace = ns::PUBSUB_OWNER, name = "subscription")]
@ -124,68 +43,89 @@ pub struct SubscriptionElem {
pub subid: Option<String>, pub subid: Option<String>,
} }
/// Main payload used to communicate with a PubSubOwner service. /// Represents an owner request to a PubSub service.
#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
#[xml(namespace = ns::PUBSUB_OWNER, name = "pubsub")]
pub struct Owner {
/// The inner child of this request.
#[xml(child)]
pub payload: Payload,
}
// TODO: Differentiate each payload per type someday, to simplify our types.
impl IqGetPayload for Owner {}
impl IqSetPayload for Owner {}
impl IqResultPayload for Owner {}
/// Main payload used to communicate with a PubSub service.
/// ///
/// `<pubsub xmlns="http://jabber.org/protocol/pubsub#owner"/>` /// `<pubsub xmlns="http://jabber.org/protocol/pubsub#owner"/>`
#[derive(Debug, Clone, PartialEq)] #[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
pub enum PubSubOwner { #[xml(namespace = ns::PUBSUB_OWNER, exhaustive)]
pub enum Payload {
/// Manage the affiliations of a node. /// Manage the affiliations of a node.
Affiliations(Affiliations), #[xml(name = "affiliations")]
/// Request to configure a node, with optional suggested name and suggested configuration. Affiliations {
Configure(Configure), /// The node name this request pertains to.
#[xml(attribute)]
node: NodeName,
/// The actual list of affiliation elements.
#[xml(child(n = ..))]
affiliations: Vec<Affiliation>,
},
/// Request to configure a node.
#[xml(name = "configure")]
Configure {
/// The node to be configured.
#[xml(attribute(default))]
node: Option<NodeName>,
/// The form to configure it.
#[xml(child(default))]
form: Option<DataForm>,
},
/// Request the default node configuration. /// Request the default node configuration.
Default(Default), #[xml(name = "default")]
Default {
/// The form to configure it.
#[xml(child(default))]
form: Option<DataForm>,
},
/// Delete a node. /// Delete a node.
Delete(Delete), #[xml(name = "delete")]
Delete {
/// The node to be deleted.
#[xml(attribute)]
node: NodeName,
/// Redirection to replace the deleted node.
#[xml(extract(default, name = "redirect", fields(attribute(name = "uri", type_ = String))))]
redirect_uri: Option<String>,
},
/// Purge all items from node. /// Purge all items from node.
Purge(Purge), #[xml(name = "purge")]
/// Request subscriptions of a node. Purge {
Subscriptions(Subscriptions), /// The node to be cleared.
} #[xml(attribute)]
node: NodeName,
},
impl IqGetPayload for PubSubOwner {} /// Request the current subscriptions to a node.
impl IqSetPayload for PubSubOwner {} #[xml(name = "subscriptions")]
impl IqResultPayload for PubSubOwner {} Subscriptions {
/// The node to query.
#[xml(attribute)]
node: NodeName,
impl TryFrom<Element> for PubSubOwner { /// The list of subscription elements returned.
type Error = FromElementError; #[xml(child(n = ..))]
subscriptions: Vec<SubscriptionElem>,
fn try_from(elem: Element) -> Result<PubSubOwner, FromElementError> { },
check_self!(elem, "pubsub", PUBSUB_OWNER);
check_no_attributes!(elem, "pubsub");
let mut payload = None;
for child in elem.children() {
if child.is("configure", ns::PUBSUB_OWNER) {
if payload.is_some() {
return Err(Error::Other(
"Payload is already defined in pubsub owner element.",
)
.into());
}
let configure = Configure::try_from(child.clone())?;
payload = Some(PubSubOwner::Configure(configure));
} else {
return Err(Error::Other("Unknown child in pubsub element.").into());
}
}
payload.ok_or(Error::Other("No payload in pubsub element.").into())
}
}
impl From<PubSubOwner> for Element {
fn from(pubsub: PubSubOwner) -> Element {
Element::builder("pubsub", ns::PUBSUB_OWNER)
.append_all(match pubsub {
PubSubOwner::Affiliations(affiliations) => vec![Element::from(affiliations)],
PubSubOwner::Configure(configure) => vec![Element::from(configure)],
PubSubOwner::Default(default) => vec![Element::from(default)],
PubSubOwner::Delete(delete) => vec![Element::from(delete)],
PubSubOwner::Purge(purge) => vec![Element::from(purge)],
PubSubOwner::Subscriptions(subscriptions) => vec![Element::from(subscriptions)],
})
.build()
}
} }
#[cfg(test)] #[cfg(test)]
@ -194,6 +134,7 @@ mod tests {
use crate::data_forms::{DataFormType, Field, FieldType}; use crate::data_forms::{DataFormType, Field, FieldType};
use core::str::FromStr; use core::str::FromStr;
use jid::BareJid; use jid::BareJid;
use minidom::Element;
#[test] #[test]
fn affiliations() { fn affiliations() {
@ -202,7 +143,7 @@ mod tests {
.unwrap(); .unwrap();
let elem1 = elem.clone(); let elem1 = elem.clone();
let pubsub = PubSubOwner::Affiliations(Affiliations { let payload = Payload::Affiliations {
node: NodeName(String::from("foo")), node: NodeName(String::from("foo")),
affiliations: vec![ affiliations: vec![
Affiliation { Affiliation {
@ -214,7 +155,8 @@ mod tests {
affiliation: AffiliationAttribute::Outcast, affiliation: AffiliationAttribute::Outcast,
}, },
], ],
}); };
let pubsub = Owner { payload };
let elem2 = Element::from(pubsub); let elem2 = Element::from(pubsub);
assert_eq!(elem1, elem2); assert_eq!(elem1, elem2);
@ -227,7 +169,7 @@ mod tests {
.unwrap(); .unwrap();
let elem1 = elem.clone(); let elem1 = elem.clone();
let pubsub = PubSubOwner::Configure(Configure { let payload = Payload::Configure {
node: Some(NodeName(String::from("foo"))), node: Some(NodeName(String::from("foo"))),
form: Some(DataForm::new( form: Some(DataForm::new(
DataFormType::Submit, DataFormType::Submit,
@ -235,7 +177,8 @@ mod tests {
vec![Field::new("pubsub#access_model", FieldType::ListSingle) vec![Field::new("pubsub#access_model", FieldType::ListSingle)
.with_value("whitelist")], .with_value("whitelist")],
)), )),
}); };
let pubsub = Owner { payload };
let elem2 = Element::from(pubsub); let elem2 = Element::from(pubsub);
assert_eq!(elem1, elem2); assert_eq!(elem1, elem2);
@ -251,10 +194,11 @@ mod tests {
let form = DataForm::try_from(elem).unwrap(); let form = DataForm::try_from(elem).unwrap();
let configure = PubSubOwner::Configure(Configure { let payload = Payload::Configure {
node: Some(NodeName(String::from("foo"))), node: Some(NodeName(String::from("foo"))),
form: Some(form), form: Some(form),
}); };
let configure = Owner { payload };
let serialized: Element = configure.into(); let serialized: Element = configure.into();
assert_eq!(serialized, reference); assert_eq!(serialized, reference);
} }
@ -266,14 +210,15 @@ mod tests {
.unwrap(); .unwrap();
let elem1 = elem.clone(); let elem1 = elem.clone();
let pubsub = PubSubOwner::Default(Default { let payload = Payload::Default {
form: Some(DataForm::new( form: Some(DataForm::new(
DataFormType::Submit, DataFormType::Submit,
ns::PUBSUB_CONFIGURE, ns::PUBSUB_CONFIGURE,
vec![Field::new("pubsub#access_model", FieldType::ListSingle) vec![Field::new("pubsub#access_model", FieldType::ListSingle)
.with_value("whitelist")], .with_value("whitelist")],
)), )),
}); };
let pubsub = Owner { payload };
let elem2 = Element::from(pubsub); let elem2 = Element::from(pubsub);
assert_eq!(elem1, elem2); assert_eq!(elem1, elem2);
@ -286,12 +231,11 @@ mod tests {
.unwrap(); .unwrap();
let elem1 = elem.clone(); let elem1 = elem.clone();
let pubsub = PubSubOwner::Delete(Delete { let payload = Payload::Delete {
node: NodeName(String::from("foo")), node: NodeName(String::from("foo")),
redirect: Some(Redirect { redirect_uri: Some(String::from("xmpp:hamlet@denmark.lit?;node=blog")),
uri: String::from("xmpp:hamlet@denmark.lit?;node=blog"), };
}), let pubsub = Owner { payload };
});
let elem2 = Element::from(pubsub); let elem2 = Element::from(pubsub);
assert_eq!(elem1, elem2); assert_eq!(elem1, elem2);
@ -304,9 +248,10 @@ mod tests {
.unwrap(); .unwrap();
let elem1 = elem.clone(); let elem1 = elem.clone();
let pubsub = PubSubOwner::Purge(Purge { let payload = Payload::Purge {
node: NodeName(String::from("foo")), node: NodeName(String::from("foo")),
}); };
let pubsub = Owner { payload };
let elem2 = Element::from(pubsub); let elem2 = Element::from(pubsub);
assert_eq!(elem1, elem2); assert_eq!(elem1, elem2);
@ -319,7 +264,7 @@ mod tests {
.unwrap(); .unwrap();
let elem1 = elem.clone(); let elem1 = elem.clone();
let pubsub = PubSubOwner::Subscriptions(Subscriptions { let payload = Payload::Subscriptions {
node: NodeName(String::from("foo")), node: NodeName(String::from("foo")),
subscriptions: vec![ subscriptions: vec![
SubscriptionElem { SubscriptionElem {
@ -343,7 +288,8 @@ mod tests {
subid: Some(String::from("004-yyy")), subid: Some(String::from("004-yyy")),
}, },
], ],
}); };
let pubsub = Owner { payload };
let elem2 = Element::from(pubsub); let elem2 = Element::from(pubsub);
assert_eq!(elem1, elem2); assert_eq!(elem1, elem2);