aboutsummaryrefslogtreecommitdiffstats
path: root/stanza/src/xep_0060/pubsub.rs
diff options
context:
space:
mode:
Diffstat (limited to 'stanza/src/xep_0060/pubsub.rs')
-rw-r--r--stanza/src/xep_0060/pubsub.rs722
1 files changed, 722 insertions, 0 deletions
diff --git a/stanza/src/xep_0060/pubsub.rs b/stanza/src/xep_0060/pubsub.rs
new file mode 100644
index 0000000..3a15a59
--- /dev/null
+++ b/stanza/src/xep_0060/pubsub.rs
@@ -0,0 +1,722 @@
+use std::str::FromStr;
+
+use jid::JID;
+use peanuts::{
+ element::{FromElement, IntoElement},
+ DeserializeError, Element,
+};
+
+use crate::xep_0004::X;
+
+pub const XMLNS: &str = "http://jabber.org/protocol/pubsub";
+
+#[derive(Clone, Debug)]
+pub enum Pubsub {
+ Create(Create, Option<Configure>),
+ Subscribe(Option<Subscribe>, Option<Options>),
+ Publish(Publish, Option<PublishOptions>),
+ Affiliations(Affiliations),
+ Default(Default),
+ Items(Items),
+ Retract(Retract),
+ Subscription(Subscription),
+ Subscriptions(Subscriptions),
+ Unsubscribe(Unsubscribe),
+ Pubsub,
+}
+
+impl FromElement for Pubsub {
+ fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> {
+ element.check_name("pubsub")?;
+ element.check_namespace(XMLNS)?;
+
+ let first_child = element.pop_child_opt::<Element>()?;
+
+ if let Some(first_child) = first_child {
+ match first_child.identify() {
+ (Some(XMLNS), "create") => {
+ let create = Create::from_element(first_child)?;
+ let configure = element.pop_child_opt()?;
+ Ok(Self::Create(create, configure))
+ }
+ (Some(XMLNS), "subscribe") => {
+ let subscribe = Subscribe::from_element(first_child)?;
+ let options = element.pop_child_opt()?;
+ Ok(Self::Subscribe(Some(subscribe), options))
+ }
+ (Some(XMLNS), "options") => {
+ let options = Options::from_element(first_child)?;
+ Ok(Self::Subscribe(None, Some(options)))
+ }
+ (Some(XMLNS), "publish") => {
+ let publish = Publish::from_element(first_child)?;
+ let publish_options = element.pop_child_opt()?;
+ Ok(Self::Publish(publish, publish_options))
+ }
+ (Some(XMLNS), "affiliations") => {
+ Ok(Self::Affiliations(Affiliations::from_element(first_child)?))
+ }
+ (Some(XMLNS), "default") => Ok(Self::Default(Default::from_element(first_child)?)),
+ (Some(XMLNS), "items") => Ok(Self::Items(Items::from_element(first_child)?)),
+ (Some(XMLNS), "retract") => Ok(Self::Retract(Retract::from_element(first_child)?)),
+ (Some(XMLNS), "subscription") => {
+ Ok(Self::Subscription(Subscription::from_element(first_child)?))
+ }
+ (Some(XMLNS), "subscriptions") => Ok(Self::Subscriptions(
+ Subscriptions::from_element(first_child)?,
+ )),
+ (Some(XMLNS), "unsubscribe") => {
+ Ok(Self::Unsubscribe(Unsubscribe::from_element(first_child)?))
+ }
+ _ => Err(peanuts::DeserializeError::UnexpectedElement(first_child)),
+ }
+ } else {
+ Ok(Pubsub::Pubsub)
+ }
+ }
+}
+
+impl IntoElement for Pubsub {
+ fn builder(&self) -> peanuts::element::ElementBuilder {
+ let element = Element::builder("pubsub", Some(XMLNS));
+
+ match self {
+ Pubsub::Create(create, configure) => element
+ .push_child(create.clone())
+ .push_child_opt(configure.clone()),
+ Pubsub::Subscribe(subscribe, options) => element
+ .push_child_opt(subscribe.clone())
+ .push_child_opt(options.clone()),
+ Pubsub::Publish(publish, publish_options) => element
+ .push_child(publish.clone())
+ .push_child_opt(publish_options.clone()),
+ Pubsub::Affiliations(affiliations) => element.push_child(affiliations.clone()),
+ Pubsub::Default(default) => element.push_child(default.clone()),
+ Pubsub::Items(items) => element.push_child(items.clone()),
+ Pubsub::Retract(retract) => element.push_child(retract.clone()),
+ Pubsub::Subscription(subscription) => element.push_child(subscription.clone()),
+ Pubsub::Subscriptions(subscriptions) => element.push_child(subscriptions.clone()),
+ Pubsub::Unsubscribe(unsubscribe) => element.push_child(unsubscribe.clone()),
+ Pubsub::Pubsub => element,
+ }
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct Affiliations {
+ node: Option<String>,
+ affiliations: Vec<Affiliation>,
+}
+
+impl FromElement for Affiliations {
+ fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> {
+ element.check_name("affiliations")?;
+ element.check_namespace(XMLNS)?;
+
+ let node = element.attribute_opt("node")?;
+
+ let affiliations = element.pop_children()?;
+
+ Ok(Self { node, affiliations })
+ }
+}
+
+impl IntoElement for Affiliations {
+ fn builder(&self) -> peanuts::element::ElementBuilder {
+ Element::builder("affilations", Some(XMLNS))
+ .push_attribute_opt("node", self.node.clone())
+ .push_children(self.affiliations.clone())
+ }
+}
+
+// empty
+#[derive(Clone, Debug)]
+pub struct Affiliation {
+ affiliation: AffiliationType,
+ node: String,
+}
+
+impl FromElement for Affiliation {
+ fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> {
+ element.check_name("affiliation")?;
+ element.check_namespace(XMLNS)?;
+
+ let affiliation = element.attribute("affiliation")?;
+ let node = element.attribute("node")?;
+
+ Ok(Self { affiliation, node })
+ }
+}
+
+impl IntoElement for Affiliation {
+ fn builder(&self) -> peanuts::element::ElementBuilder {
+ Element::builder("affiliation", Some(XMLNS))
+ .push_attribute("affiliation", self.affiliation.clone())
+ .push_attribute("node", self.node.clone())
+ }
+}
+
+#[derive(Clone, Debug)]
+pub enum AffiliationType {
+ Member,
+ None,
+ Outcast,
+ Owner,
+ Publisher,
+ PublishOnly,
+}
+
+impl FromStr for AffiliationType {
+ type Err = DeserializeError;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ match s {
+ "member" => Ok(AffiliationType::Member),
+ "none" => Ok(AffiliationType::None),
+ "outcast" => Ok(AffiliationType::Outcast),
+ "owner" => Ok(AffiliationType::Owner),
+ "publisher" => Ok(AffiliationType::Publisher),
+ "publish-only" => Ok(AffiliationType::PublishOnly),
+ s => Err(DeserializeError::FromStr(s.to_string())),
+ }
+ }
+}
+
+impl ToString for AffiliationType {
+ fn to_string(&self) -> String {
+ match self {
+ AffiliationType::Member => "member",
+ AffiliationType::None => "none",
+ AffiliationType::Outcast => "outcast",
+ AffiliationType::Owner => "owner",
+ AffiliationType::Publisher => "publisher",
+ AffiliationType::PublishOnly => "publish-only",
+ }
+ .to_owned()
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct Configure(Option<X>);
+
+impl FromElement for Configure {
+ fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> {
+ element.check_name("configure")?;
+ element.check_namespace(XMLNS)?;
+
+ Ok(Configure(element.pop_child_opt()?))
+ }
+}
+
+impl IntoElement for Configure {
+ fn builder(&self) -> peanuts::element::ElementBuilder {
+ Element::builder("configure", Some(XMLNS)).push_child_opt(self.0.clone())
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct Create {
+ node: Option<String>,
+}
+
+impl FromElement for Create {
+ fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> {
+ element.check_name("create")?;
+ element.check_namespace(XMLNS)?;
+
+ let node = element.attribute_opt("node")?;
+
+ Ok(Self { node })
+ }
+}
+
+impl IntoElement for Create {
+ fn builder(&self) -> peanuts::element::ElementBuilder {
+ Element::builder("create", Some(XMLNS)).push_attribute_opt("node", self.node.clone())
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct Default {
+ node: Option<String>,
+ // default leaf
+ r#type: Option<DefaultType>,
+ default: Option<X>,
+}
+
+impl FromElement for Default {
+ fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> {
+ element.check_name("default")?;
+ element.check_namespace(XMLNS)?;
+
+ let node = element.attribute_opt("node")?;
+ let r#type = element.attribute_opt("type")?;
+
+ let default = element.pop_child_opt()?;
+
+ Ok(Self {
+ node,
+ r#type,
+ default,
+ })
+ }
+}
+
+impl IntoElement for Default {
+ fn builder(&self) -> peanuts::element::ElementBuilder {
+ Element::builder("default", Some(XMLNS))
+ .push_attribute_opt("node", self.node.clone())
+ .push_attribute_opt("type", self.r#type.clone())
+ .push_child_opt(self.default.clone())
+ }
+}
+
+#[derive(Clone, Debug)]
+pub enum DefaultType {
+ Collection,
+ Leaf,
+}
+
+impl FromStr for DefaultType {
+ type Err = DeserializeError;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ match s {
+ "collection" => Ok(Self::Collection),
+ "leaf" => Ok(Self::Leaf),
+ s => Err(DeserializeError::FromStr(s.to_string())),
+ }
+ }
+}
+
+impl ToString for DefaultType {
+ fn to_string(&self) -> String {
+ match self {
+ DefaultType::Collection => "collection",
+ DefaultType::Leaf => "leaf",
+ }
+ .to_owned()
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct Items {
+ max_items: Option<usize>,
+ node: String,
+ subid: Option<String>,
+ items: Vec<Item>,
+}
+
+impl FromElement for Items {
+ fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> {
+ element.check_name("items")?;
+ element.check_namespace(XMLNS)?;
+
+ let max_items = element.attribute_opt("max_items")?;
+ let node = element.attribute("node")?;
+ let subid = element.attribute_opt("subid")?;
+
+ let items = element.pop_children()?;
+
+ Ok(Self {
+ max_items,
+ node,
+ subid,
+ items,
+ })
+ }
+}
+
+impl IntoElement for Items {
+ fn builder(&self) -> peanuts::element::ElementBuilder {
+ Element::builder("items", Some(XMLNS))
+ .push_attribute_opt("max_items", self.max_items)
+ .push_attribute("node", self.node.clone())
+ .push_attribute_opt("subid", self.subid.clone())
+ .push_children(self.items.clone())
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct Item {
+ id: Option<String>,
+ publisher: Option<String>,
+ item: Option<Content>,
+}
+
+impl FromElement for Item {
+ fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> {
+ element.check_name("item")?;
+ element.check_namespace(XMLNS)?;
+
+ let id = element.attribute_opt("id")?;
+ let publisher = element.attribute_opt("publisher")?;
+
+ let item = element.pop_child_opt()?;
+
+ Ok(Self {
+ id,
+ publisher,
+ item,
+ })
+ }
+}
+
+impl IntoElement for Item {
+ fn builder(&self) -> peanuts::element::ElementBuilder {
+ Element::builder("item", Some(XMLNS))
+ .push_attribute_opt("id", self.id.clone())
+ .push_attribute_opt("publisher", self.publisher.clone())
+ .push_child_opt(self.item.clone())
+ }
+}
+
+#[derive(Clone, Debug)]
+pub enum Content {
+ Unknown,
+}
+
+impl FromElement for Content {
+ fn from_element(_element: Element) -> peanuts::element::DeserializeResult<Self> {
+ // TODO: types
+ return Ok(Self::Unknown);
+ }
+}
+
+impl IntoElement for Content {
+ fn builder(&self) -> peanuts::element::ElementBuilder {
+ panic!("unknown content cannot be serialized")
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct Options {
+ jid: JID,
+ node: Option<String>,
+ subid: Option<String>,
+ options: Option<X>,
+}
+
+impl FromElement for Options {
+ fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> {
+ element.check_name("options")?;
+ element.check_namespace(XMLNS)?;
+
+ let jid = element.attribute("jid")?;
+ let node = element.attribute_opt("node")?;
+ let subid = element.attribute_opt("subid")?;
+
+ let options = element.pop_child_opt()?;
+
+ Ok(Self {
+ jid,
+ node,
+ subid,
+ options,
+ })
+ }
+}
+
+impl IntoElement for Options {
+ fn builder(&self) -> peanuts::element::ElementBuilder {
+ Element::builder("options", Some(XMLNS))
+ .push_attribute("jid", self.jid.clone())
+ .push_attribute_opt("node", self.node.clone())
+ .push_attribute_opt("subid", self.subid.clone())
+ .push_child_opt(self.options.clone())
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct Publish {
+ node: String,
+ items: Vec<Item>,
+}
+
+impl FromElement for Publish {
+ fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> {
+ element.check_name("publish")?;
+ element.check_namespace(XMLNS)?;
+
+ let node = element.attribute("node")?;
+
+ let items = element.pop_children()?;
+
+ Ok(Self { node, items })
+ }
+}
+
+impl IntoElement for Publish {
+ fn builder(&self) -> peanuts::element::ElementBuilder {
+ Element::builder("publish", Some(XMLNS))
+ .push_attribute("node", self.node.clone())
+ .push_children(self.items.clone())
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct PublishOptions(pub Option<X>);
+
+impl FromElement for PublishOptions {
+ fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> {
+ element.check_name("publish-options")?;
+ element.check_namespace(XMLNS)?;
+
+ Ok(Self(element.pop_child_opt()?))
+ }
+}
+
+impl IntoElement for PublishOptions {
+ fn builder(&self) -> peanuts::element::ElementBuilder {
+ Element::builder("publish-options", Some(XMLNS)).push_child_opt(self.0.clone())
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct Retract {
+ node: String,
+ notify: Option<bool>,
+ /// minOccurs = 1
+ items: Vec<Item>,
+}
+
+impl FromElement for Retract {
+ fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> {
+ element.check_name("retract")?;
+ element.check_namespace(XMLNS)?;
+
+ let node = element.attribute("node")?;
+ let notify = element.attribute_opt("notify")?;
+
+ let items = element.pop_children()?;
+
+ Ok(Self {
+ node,
+ notify,
+ items,
+ })
+ }
+}
+
+impl IntoElement for Retract {
+ fn builder(&self) -> peanuts::element::ElementBuilder {
+ Element::builder("retract", Some(XMLNS))
+ .push_attribute("node", self.node.clone())
+ .push_attribute_opt("notify", self.notify)
+ .push_children(self.items.clone())
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct SubscribeOptions {
+ // <required/> element
+ required: bool,
+}
+
+impl FromElement for SubscribeOptions {
+ fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> {
+ element.check_name("subscribe-options")?;
+ element.check_namespace(XMLNS)?;
+
+ let required;
+ if let Some(_) = element.pop_child_opt::<Required>()? {
+ required = true;
+ } else {
+ required = false;
+ }
+
+ Ok(Self { required })
+ }
+}
+
+impl IntoElement for SubscribeOptions {
+ fn builder(&self) -> peanuts::element::ElementBuilder {
+ let builder = Element::builder("subscribe-options", Some(XMLNS));
+
+ if self.required {
+ builder.push_child(Required)
+ } else {
+ builder
+ }
+ }
+}
+
+pub struct Required;
+
+impl FromElement for Required {
+ fn from_element(element: Element) -> peanuts::element::DeserializeResult<Self> {
+ element.check_name("required")?;
+ element.check_namespace(XMLNS)?;
+
+ Ok(Self)
+ }
+}
+
+impl IntoElement for Required {
+ fn builder(&self) -> peanuts::element::ElementBuilder {
+ Element::builder("required", Some(XMLNS))
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct Subscribe {
+ jid: JID,
+ node: Option<String>,
+}
+
+impl FromElement for Subscribe {
+ fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> {
+ element.check_name("subscribe")?;
+ element.check_namespace(XMLNS)?;
+
+ let jid = element.attribute("jid")?;
+ let node = element.attribute_opt("node")?;
+
+ Ok(Self { jid, node })
+ }
+}
+
+impl IntoElement for Subscribe {
+ fn builder(&self) -> peanuts::element::ElementBuilder {
+ Element::builder("subscribe", Some(XMLNS))
+ .push_attribute("jid", self.jid.clone())
+ .push_attribute_opt("node", self.node.clone())
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct Subscriptions {
+ node: Option<String>,
+ subscriptions: Vec<Subscription>,
+}
+
+impl FromElement for Subscriptions {
+ fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> {
+ element.check_name("subscriptions")?;
+ element.check_namespace(XMLNS)?;
+
+ let node = element.attribute_opt("node")?;
+
+ let subscriptions = element.pop_children()?;
+
+ Ok(Self {
+ node,
+ subscriptions,
+ })
+ }
+}
+
+impl IntoElement for Subscriptions {
+ fn builder(&self) -> peanuts::element::ElementBuilder {
+ Element::builder("subscriptions", Some(XMLNS))
+ .push_attribute_opt("node", self.node.clone())
+ .push_children(self.subscriptions.clone())
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct Subscription {
+ jid: JID,
+ node: Option<String>,
+ subid: Option<String>,
+ subscription: Option<SubscriptionState>,
+ subscribe_options: Option<SubscribeOptions>,
+}
+
+impl FromElement for Subscription {
+ fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> {
+ element.check_name("subscription")?;
+ element.check_namespace(XMLNS)?;
+
+ let jid = element.attribute("jid")?;
+ let node = element.attribute_opt("node")?;
+ let subid = element.attribute_opt("subid")?;
+ let subscription = element.attribute_opt("subscription")?;
+
+ let subscribe_options = element.pop_child_opt()?;
+
+ Ok(Self {
+ jid,
+ node,
+ subid,
+ subscription,
+ subscribe_options,
+ })
+ }
+}
+
+impl IntoElement for Subscription {
+ fn builder(&self) -> peanuts::element::ElementBuilder {
+ Element::builder("subscription", Some(XMLNS))
+ .push_attribute("jid", self.jid.clone())
+ .push_attribute_opt("node", self.node.clone())
+ .push_attribute_opt("subid", self.subid.clone())
+ .push_attribute_opt("subscription", self.subscription.clone())
+ .push_child_opt(self.subscribe_options.clone())
+ }
+}
+
+#[derive(Clone, Debug)]
+pub enum SubscriptionState {
+ None,
+ Pending,
+ Subscribed,
+ Unconfigured,
+}
+
+impl FromStr for SubscriptionState {
+ type Err = DeserializeError;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ match s {
+ "none" => Ok(Self::None),
+ "pending" => Ok(Self::Pending),
+ "subscribed" => Ok(Self::Subscribed),
+ "unconfigured" => Ok(Self::Unconfigured),
+ s => Err(DeserializeError::FromStr(s.to_owned())),
+ }
+ }
+}
+
+impl ToString for SubscriptionState {
+ fn to_string(&self) -> String {
+ match self {
+ SubscriptionState::None => "none",
+ SubscriptionState::Pending => "pending",
+ SubscriptionState::Subscribed => "subscribed",
+ SubscriptionState::Unconfigured => "unconfigured",
+ }
+ .to_string()
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct Unsubscribe {
+ jid: JID,
+ node: Option<String>,
+ subid: Option<String>,
+}
+
+impl FromElement for Unsubscribe {
+ fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> {
+ element.check_name("unsubscribe")?;
+ element.check_namespace(XMLNS)?;
+
+ let jid = element.attribute("jid")?;
+ let node = element.attribute_opt("node")?;
+ let subid = element.attribute_opt("subid")?;
+
+ Ok(Self { jid, node, subid })
+ }
+}
+
+impl IntoElement for Unsubscribe {
+ fn builder(&self) -> peanuts::element::ElementBuilder {
+ Element::builder("unsubscribe", Some(XMLNS))
+ .push_attribute("jid", self.jid.clone())
+ .push_attribute_opt("node", self.node.clone())
+ .push_attribute_opt("subid", self.subid.clone())
+ }
+}
+
+// TODO: enforce 'empty'