owner.rs

  1// Copyright (c) 2020 Paul Fariello <paul@fariello.eu>
  2// Copyright (c) 2018 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
  3//
  4// This Source Code Form is subject to the terms of the Mozilla Public
  5// License, v. 2.0. If a copy of the MPL was not distributed with this
  6// file, You can obtain one at http://mozilla.org/MPL/2.0/.
  7
  8use crate::data_forms::DataForm;
  9use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload};
 10use crate::ns;
 11use crate::pubsub::{AffiliationAttribute, NodeName, Subscription};
 12use crate::util::error::Error;
 13use crate::Element;
 14use jid::Jid;
 15use std::convert::TryFrom;
 16
 17generate_element!(
 18    /// A list of affiliations you have on a service, or on a node.
 19    Affiliations, "affiliations", PUBSUB_OWNER,
 20    attributes: [
 21        /// The node name this request pertains to.
 22        node: Required<NodeName> = "node",
 23    ],
 24    children: [
 25        /// The actual list of affiliation elements.
 26        affiliations: Vec<Affiliation> = ("affiliation", PUBSUB_OWNER) => Affiliation
 27    ]
 28);
 29
 30generate_element!(
 31    /// An affiliation element.
 32    Affiliation, "affiliation", PUBSUB_OWNER,
 33    attributes: [
 34        /// The node this affiliation pertains to.
 35        jid: Required<Jid> = "jid",
 36
 37        /// The affiliation you currently have on this node.
 38        affiliation: Required<AffiliationAttribute> = "affiliation",
 39    ]
 40);
 41
 42generate_element!(
 43    /// Request to configure a node.
 44    Configure, "configure", PUBSUB_OWNER,
 45    attributes: [
 46        /// The node to be configured.
 47        node: Option<NodeName> = "node",
 48    ],
 49    children: [
 50        /// The form to configure it.
 51        form: Option<DataForm> = ("x", DATA_FORMS) => DataForm
 52    ]
 53);
 54
 55generate_element!(
 56    /// Request to change default configuration.
 57    Default, "default", PUBSUB_OWNER,
 58    children: [
 59        /// The form to configure it.
 60        form: Option<DataForm> = ("x", DATA_FORMS) => DataForm
 61    ]
 62);
 63
 64generate_element!(
 65    /// Request to delete a node.
 66    Delete, "delete", PUBSUB_OWNER,
 67    attributes: [
 68        /// The node to be configured.
 69        node: Required<NodeName> = "node",
 70    ],
 71    children: [
 72        /// Redirection to replace the deleted node.
 73        redirect: Option<Redirect> = ("redirect", PUBSUB_OWNER) => Redirect
 74    ]
 75);
 76
 77generate_element!(
 78    /// A redirect element.
 79    Redirect, "redirect", PUBSUB_OWNER,
 80    attributes: [
 81        /// The node this node will be redirected to.
 82        uri: Required<String> = "uri",
 83    ]
 84);
 85
 86generate_element!(
 87    /// Request to delete a node.
 88    Purge, "purge", PUBSUB_OWNER,
 89    attributes: [
 90        /// The node to be configured.
 91        node: Required<NodeName> = "node",
 92    ]
 93);
 94
 95generate_element!(
 96    /// A request for current subscriptions.
 97    Subscriptions, "subscriptions", PUBSUB_OWNER,
 98    attributes: [
 99        /// The node to query.
100        node: Required<NodeName> = "node",
101    ],
102    children: [
103        /// The list of subscription elements returned.
104        subscriptions: Vec<SubscriptionElem> = ("subscription", PUBSUB_OWNER) => SubscriptionElem
105    ]
106);
107
108generate_element!(
109    /// A subscription element, describing the state of a subscription.
110    SubscriptionElem, "subscription", PUBSUB_OWNER,
111    attributes: [
112        /// The JID affected by this subscription.
113        jid: Required<Jid> = "jid",
114
115        /// The state of the subscription.
116        subscription: Required<Subscription> = "subscription",
117
118        /// Subscription unique id.
119        subid: Option<String> = "subid",
120    ]
121);
122
123/// Main payload used to communicate with a PubSubOwner service.
124///
125/// `<pubsub xmlns="http://jabber.org/protocol/pubsub#owner"/>`
126#[derive(Debug, Clone)]
127pub enum PubSubOwner {
128    /// Manage the affiliations of a node.
129    Affiliations(Affiliations),
130    /// Request to configure a node, with optional suggested name and suggested configuration.
131    Configure(Configure),
132    /// Request the default node configuration.
133    Default(Default),
134    /// Delete a node.
135    Delete(Delete),
136    /// Purge all items from node.
137    Purge(Purge),
138    /// Request subscriptions of a node.
139    Subscriptions(Subscriptions),
140}
141
142impl IqGetPayload for PubSubOwner {}
143impl IqSetPayload for PubSubOwner {}
144impl IqResultPayload for PubSubOwner {}
145
146impl TryFrom<Element> for PubSubOwner {
147    type Error = Error;
148
149    fn try_from(elem: Element) -> Result<PubSubOwner, Error> {
150        check_self!(elem, "pubsub", PUBSUB_OWNER);
151        check_no_attributes!(elem, "pubsub");
152
153        let mut payload = None;
154        for child in elem.children() {
155            if child.is("configure", ns::PUBSUB_OWNER) {
156                if payload.is_some() {
157                    return Err(Error::ParseError(
158                        "Payload is already defined in pubsub owner element.",
159                    ));
160                }
161                let configure = Configure::try_from(child.clone())?;
162                payload = Some(PubSubOwner::Configure(configure));
163            } else {
164                return Err(Error::ParseError("Unknown child in pubsub element."));
165            }
166        }
167        payload.ok_or(Error::ParseError("No payload in pubsub element."))
168    }
169}
170
171impl From<PubSubOwner> for Element {
172    fn from(pubsub: PubSubOwner) -> Element {
173        Element::builder("pubsub", ns::PUBSUB_OWNER)
174            .append_all(match pubsub {
175                PubSubOwner::Affiliations(affiliations) => vec![Element::from(affiliations)],
176                PubSubOwner::Configure(configure) => vec![Element::from(configure)],
177                PubSubOwner::Default(default) => vec![Element::from(default)],
178                PubSubOwner::Delete(delete) => vec![Element::from(delete)],
179                PubSubOwner::Purge(purge) => vec![Element::from(purge)],
180                PubSubOwner::Subscriptions(subscriptions) => vec![Element::from(subscriptions)],
181            })
182            .build()
183    }
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189    use crate::data_forms::{DataForm, DataFormType, Field, FieldType};
190    use jid::BareJid;
191    use std::str::FromStr;
192
193    #[test]
194    fn affiliations() {
195        let elem: Element = "<pubsub xmlns='http://jabber.org/protocol/pubsub#owner'><affiliations node='foo'><affiliation jid='hamlet@denmark.lit' affiliation='owner'/><affiliation jid='polonius@denmark.lit' affiliation='outcast'/></affiliations></pubsub>"
196        .parse()
197        .unwrap();
198        let elem1 = elem.clone();
199
200        let pubsub = PubSubOwner::Affiliations(Affiliations {
201            node: NodeName(String::from("foo")),
202            affiliations: vec![
203                Affiliation {
204                    jid: Jid::Bare(BareJid::from_str("hamlet@denmark.lit").unwrap()),
205                    affiliation: AffiliationAttribute::Owner,
206                },
207                Affiliation {
208                    jid: Jid::Bare(BareJid::from_str("polonius@denmark.lit").unwrap()),
209                    affiliation: AffiliationAttribute::Outcast,
210                },
211            ],
212        });
213
214        let elem2 = Element::from(pubsub);
215        assert_eq!(elem1, elem2);
216    }
217
218    #[test]
219    fn configure() {
220        let elem: Element = "<pubsub xmlns='http://jabber.org/protocol/pubsub#owner'><configure node='foo'><x xmlns='jabber:x:data' type='submit'><field var='FORM_TYPE' type='hidden'><value>http://jabber.org/protocol/pubsub#node_config</value></field><field var='pubsub#access_model' type='list-single'><value>whitelist</value></field></x></configure></pubsub>"
221        .parse()
222        .unwrap();
223        let elem1 = elem.clone();
224
225        let pubsub = PubSubOwner::Configure(Configure {
226            node: Some(NodeName(String::from("foo"))),
227            form: Some(DataForm {
228                type_: DataFormType::Submit,
229                form_type: Some(String::from(ns::PUBSUB_CONFIGURE)),
230                title: None,
231                instructions: None,
232                fields: vec![Field {
233                    var: String::from("pubsub#access_model"),
234                    type_: FieldType::ListSingle,
235                    label: None,
236                    required: false,
237                    options: vec![],
238                    values: vec![String::from("whitelist")],
239                    media: vec![],
240                }],
241            }),
242        });
243
244        let elem2 = Element::from(pubsub);
245        assert_eq!(elem1, elem2);
246    }
247
248    #[test]
249    fn test_serialize_configure() {
250        let reference: Element = "<pubsub xmlns='http://jabber.org/protocol/pubsub#owner'><configure node='foo'><x xmlns='jabber:x:data' type='submit'/></configure></pubsub>"
251        .parse()
252        .unwrap();
253
254        let elem: Element = "<x xmlns='jabber:x:data' type='submit'/>".parse().unwrap();
255
256        let form = DataForm::try_from(elem).unwrap();
257
258        let configure = PubSubOwner::Configure(Configure {
259            node: Some(NodeName(String::from("foo"))),
260            form: Some(form),
261        });
262        let serialized: Element = configure.into();
263        assert_eq!(serialized, reference);
264    }
265
266    #[test]
267    fn default() {
268        let elem: Element = "<pubsub xmlns='http://jabber.org/protocol/pubsub#owner'><default><x xmlns='jabber:x:data' type='submit'><field var='FORM_TYPE' type='hidden'><value>http://jabber.org/protocol/pubsub#node_config</value></field><field var='pubsub#access_model' type='list-single'><value>whitelist</value></field></x></default></pubsub>"
269        .parse()
270        .unwrap();
271        let elem1 = elem.clone();
272
273        let pubsub = PubSubOwner::Default(Default {
274            form: Some(DataForm {
275                type_: DataFormType::Submit,
276                form_type: Some(String::from(ns::PUBSUB_CONFIGURE)),
277                title: None,
278                instructions: None,
279                fields: vec![Field {
280                    var: String::from("pubsub#access_model"),
281                    type_: FieldType::ListSingle,
282                    label: None,
283                    required: false,
284                    options: vec![],
285                    values: vec![String::from("whitelist")],
286                    media: vec![],
287                }],
288            }),
289        });
290
291        let elem2 = Element::from(pubsub);
292        assert_eq!(elem1, elem2);
293    }
294
295    #[test]
296    fn delete() {
297        let elem: Element = "<pubsub xmlns='http://jabber.org/protocol/pubsub#owner'><delete node='foo'><redirect uri='xmpp:hamlet@denmark.lit?;node=blog'/></delete></pubsub>"
298        .parse()
299        .unwrap();
300        let elem1 = elem.clone();
301
302        let pubsub = PubSubOwner::Delete(Delete {
303            node: NodeName(String::from("foo")),
304            redirect: Some(Redirect {
305                uri: String::from("xmpp:hamlet@denmark.lit?;node=blog"),
306            }),
307        });
308
309        let elem2 = Element::from(pubsub);
310        assert_eq!(elem1, elem2);
311    }
312
313    #[test]
314    fn purge() {
315        let elem: Element = "<pubsub xmlns='http://jabber.org/protocol/pubsub#owner'><purge node='foo'></purge></pubsub>"
316        .parse()
317        .unwrap();
318        let elem1 = elem.clone();
319
320        let pubsub = PubSubOwner::Purge(Purge {
321            node: NodeName(String::from("foo")),
322        });
323
324        let elem2 = Element::from(pubsub);
325        assert_eq!(elem1, elem2);
326    }
327
328    #[test]
329    fn subscriptions() {
330        let elem: Element = "<pubsub xmlns='http://jabber.org/protocol/pubsub#owner'><subscriptions node='foo'><subscription jid='hamlet@denmark.lit' subscription='subscribed'/><subscription jid='polonius@denmark.lit' subscription='unconfigured'/><subscription jid='bernardo@denmark.lit' subscription='subscribed' subid='123-abc'/><subscription jid='bernardo@denmark.lit' subscription='subscribed' subid='004-yyy'/></subscriptions></pubsub>"
331        .parse()
332        .unwrap();
333        let elem1 = elem.clone();
334
335        let pubsub = PubSubOwner::Subscriptions(Subscriptions {
336            node: NodeName(String::from("foo")),
337            subscriptions: vec![
338                SubscriptionElem {
339                    jid: Jid::Bare(BareJid::from_str("hamlet@denmark.lit").unwrap()),
340                    subscription: Subscription::Subscribed,
341                    subid: None,
342                },
343                SubscriptionElem {
344                    jid: Jid::Bare(BareJid::from_str("polonius@denmark.lit").unwrap()),
345                    subscription: Subscription::Unconfigured,
346                    subid: None,
347                },
348                SubscriptionElem {
349                    jid: Jid::Bare(BareJid::from_str("bernardo@denmark.lit").unwrap()),
350                    subscription: Subscription::Subscribed,
351                    subid: Some(String::from("123-abc")),
352                },
353                SubscriptionElem {
354                    jid: Jid::Bare(BareJid::from_str("bernardo@denmark.lit").unwrap()),
355                    subscription: Subscription::Subscribed,
356                    subid: Some(String::from("004-yyy")),
357                },
358            ],
359        });
360
361        let elem2 = Element::from(pubsub);
362        assert_eq!(elem1, elem2);
363    }
364}