pubsub.rs

  1// Copyright (c) 2018 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
  2//
  3// This Source Code Form is subject to the terms of the Mozilla Public
  4// License, v. 2.0. If a copy of the MPL was not distributed with this
  5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6
  7use try_from::TryFrom;
  8
  9use minidom::Element;
 10use jid::Jid;
 11
 12use error::Error;
 13
 14use ns;
 15
 16use iq::{IqGetPayload, IqSetPayload, IqResultPayload};
 17use data_forms::DataForm;
 18
 19use pubsub::{NodeName, ItemId, Subscription, SubscriptionId};
 20
 21// TODO: a better solution would be to split this into a query and a result elements, like for
 22// XEP-0030.
 23generate_element!(
 24    /// A list of affiliations you have on a service, or on a node.
 25    Affiliations, "affiliations", PUBSUB,
 26    attributes: [
 27        /// The optional node name this request pertains to.
 28        node: Option<NodeName> = "node" => optional,
 29    ],
 30    children: [
 31        /// The actual list of affiliation elements.
 32        affiliations: Vec<Affiliation> = ("affiliation", PUBSUB) => Affiliation
 33    ]
 34);
 35
 36generate_attribute!(
 37    /// A list of possible affiliations to a node.
 38    AffiliationAttribute, "affiliation", {
 39        /// You are a member of this node, you can subscribe and retrieve items.
 40        Member => "member",
 41
 42        /// You don’t have a specific affiliation with this node, you can only subscribe to it.
 43        None => "none",
 44
 45        /// You are banned from this node.
 46        Outcast => "outcast",
 47
 48        /// You are an owner of this node, and can do anything with it.
 49        Owner => "owner",
 50
 51        /// You are a publisher on this node, you can publish and retract items to it.
 52        Publisher => "publisher",
 53
 54        /// You can publish and retract items on this node, but not subscribe or retrive items.
 55        PublishOnly => "publish-only",
 56    }
 57);
 58
 59generate_element!(
 60    /// An affiliation element.
 61    Affiliation, "affiliation", PUBSUB,
 62    attributes: [
 63        /// The node this affiliation pertains to.
 64        node: NodeName = "node" => required,
 65
 66        /// The affiliation you currently have on this node.
 67        affiliation: AffiliationAttribute = "affiliation" => required,
 68    ]
 69);
 70
 71generate_element!(
 72    /// Request to configure a new node.
 73    Configure, "configure", PUBSUB,
 74    children: [
 75        /// The form to configure it.
 76        form: Option<DataForm> = ("x", DATA_FORMS) => DataForm
 77    ]
 78);
 79
 80generate_element!(
 81    /// Request to create a new node.
 82    Create, "create", PUBSUB,
 83    attributes: [
 84        /// The node name to create, if `None` the service will generate one.
 85        node: Option<NodeName> = "node" => optional,
 86    ]
 87);
 88
 89generate_element!(
 90    /// Request for a default node configuration.
 91    Default, "default", PUBSUB,
 92    attributes: [
 93        /// The node targetted by this request, otherwise the entire service.
 94        node: Option<NodeName> = "node" => optional,
 95
 96        // TODO: do we really want to support collection nodes?
 97        // type: String = "type" => optional,
 98    ]
 99);
100
101generate_element!(
102    /// A request for a list of items.
103    Items, "items", PUBSUB,
104    attributes: [
105        // TODO: should be an xs:positiveInteger, that is, an unbounded int ≥ 1.
106        /// Maximum number of items returned.
107        max_items: Option<u32> = "max_items" => optional,
108
109        /// The node queried by this request.
110        node: NodeName = "node" => required,
111
112        /// The subscription identifier related to this request.
113        subid: Option<SubscriptionId> = "subid" => optional,
114    ],
115    children: [
116        /// The actual list of items returned.
117        items: Vec<Item> = ("item", PUBSUB) => Item
118    ]
119);
120
121/// An item from a PubSub node.
122#[derive(Debug, Clone)]
123pub struct Item {
124    /// The payload of this item, in an arbitrary namespace.
125    pub payload: Option<Element>,
126
127    /// The 'id' attribute of this item.
128    pub id: Option<ItemId>,
129}
130
131impl TryFrom<Element> for Item {
132    type Err = Error;
133
134    fn try_from(elem: Element) -> Result<Item, Error> {
135        check_self!(elem, "item", PUBSUB);
136        check_no_unknown_attributes!(elem, "item", ["id"]);
137        let mut payloads = elem.children().cloned().collect::<Vec<_>>();
138        let payload = payloads.pop();
139        if !payloads.is_empty() {
140            return Err(Error::ParseError("More than a single payload in item element."));
141        }
142        Ok(Item {
143            payload,
144            id: get_attr!(elem, "id", optional),
145        })
146    }
147}
148
149impl From<Item> for Element {
150    fn from(item: Item) -> Element {
151        Element::builder("item")
152                .ns(ns::PUBSUB)
153                .attr("id", item.id)
154                .append(item.payload)
155                .build()
156    }
157}
158
159generate_element!(
160    /// The options associated to a subscription request.
161    Options, "options", PUBSUB,
162    attributes: [
163        /// The JID affected by this request.
164        jid: Jid = "jid" => required,
165
166        /// The node affected by this request.
167        node: Option<NodeName> = "node" => optional,
168
169        /// The subscription identifier affected by this request.
170        subid: Option<SubscriptionId> = "subid" => optional,
171    ],
172    children: [
173        /// The form describing the subscription.
174        form: Option<DataForm> = ("x", DATA_FORMS) => DataForm
175    ]
176);
177
178generate_element!(
179    /// Request to publish items to a node.
180    Publish, "publish", PUBSUB,
181    attributes: [
182        /// The target node for this operation.
183        node: NodeName = "node" => required,
184    ],
185    children: [
186        /// The items you want to publish.
187        items: Vec<Item> = ("item", PUBSUB) => Item
188    ]
189);
190
191generate_element!(
192    /// The options associated to a publish request.
193    PublishOptions, "publish-options", PUBSUB,
194    children: [
195        /// The form describing these options.
196        form: Option<DataForm> = ("x", DATA_FORMS) => DataForm
197    ]
198);
199
200generate_attribute!(
201    /// Whether a retract request should notify subscribers or not.
202    Notify, "notify", bool
203);
204
205generate_element!(
206    /// A request to retract some items from a node.
207    Retract, "retract", PUBSUB,
208    attributes: [
209        /// The node affected by this request.
210        node: NodeName = "node" => required,
211
212        /// Whether a retract request should notify subscribers or not.
213        notify: Notify = "notify" => default,
214    ],
215    children: [
216        /// The items affected by this request.
217        items: Vec<Item> = ("item", PUBSUB) => Item
218    ]
219);
220
221/// Indicate that the subscription can be configured.
222#[derive(Debug, Clone)]
223pub struct SubscribeOptions {
224    /// If `true`, the configuration is actually required.
225    required: bool,
226}
227
228impl TryFrom<Element> for SubscribeOptions {
229    type Err = Error;
230
231    fn try_from(elem: Element) -> Result<Self, Error> {
232        check_self!(elem, "subscribe-options", PUBSUB);
233        check_no_attributes!(elem, "subscribe-options");
234        let mut required = false;
235        for child in elem.children() {
236            if child.is("required", ns::PUBSUB) {
237                if required {
238                    return Err(Error::ParseError("More than one required element in subscribe-options."));
239                }
240                required = true;
241            } else {
242                return Err(Error::ParseError("Unknown child in subscribe-options element."));
243            }
244        }
245        Ok(SubscribeOptions { required })
246    }
247}
248
249impl From<SubscribeOptions> for Element {
250    fn from(subscribe_options: SubscribeOptions) -> Element {
251        Element::builder("subscribe-options")
252            .ns(ns::PUBSUB)
253            .append(if subscribe_options.required {
254                 vec!(Element::builder("required")
255                     .ns(ns::PUBSUB)
256                     .build())
257             } else {
258                 vec!()
259             })
260            .build()
261    }
262}
263
264generate_element!(
265    /// A request to subscribe a JID to a node.
266    Subscribe, "subscribe", PUBSUB,
267    attributes: [
268        /// The JID being subscribed.
269        jid: Jid = "jid" => required,
270
271        /// The node to subscribe to.
272        node: Option<NodeName> = "node" => optional,
273    ]
274);
275
276generate_element!(
277    /// A request for current subscriptions.
278    Subscriptions, "subscriptions", PUBSUB,
279    attributes: [
280        /// The node to query.
281        node: Option<NodeName> = "node" => optional,
282    ],
283    children: [
284        /// The list of subscription elements returned.
285        subscription: Vec<SubscriptionElem> = ("subscription", PUBSUB) => SubscriptionElem
286    ]
287);
288
289generate_element!(
290    /// A subscription element, describing the state of a subscription.
291    SubscriptionElem, "subscription", PUBSUB,
292    attributes: [
293        /// The JID affected by this subscription.
294        jid: Jid = "jid" => required,
295
296        /// The node affected by this subscription.
297        node: Option<NodeName> = "node" => optional,
298
299        /// The subscription identifier for this subscription.
300        subid: Option<SubscriptionId> = "subid" => optional,
301
302        /// The state of the subscription.
303        subscription: Option<Subscription> = "subscription" => optional,
304    ],
305    children: [
306        /// The options related to this subscription.
307        subscribe_options: Option<SubscribeOptions> = ("subscribe-options", PUBSUB) => SubscribeOptions
308    ]
309);
310
311generate_element!(
312    /// An unsubscribe request.
313    Unsubscribe, "unsubscribe", PUBSUB,
314    attributes: [
315        /// The JID affected by this request.
316        jid: Jid = "jid" => required,
317
318        /// The node affected by this request.
319        node: Option<NodeName> = "node" => optional,
320
321        /// The subscription identifier for this subscription.
322        subid: Option<SubscriptionId> = "subid" => optional,
323    ]
324);
325
326/// Main payload used to communicate with a PubSub service.
327///
328/// `<pubsub xmlns="http://jabber.org/protocol/pubsub"/>`
329#[derive(Debug, Clone)]
330pub enum PubSub {
331    /// Request to create a new node, with optional suggested name and suggested configuration.
332    Create {
333        /// The create request.
334        create: Create,
335
336        /// The configure request for the new node.
337        configure: Option<Configure>
338    },
339
340    /// Request to publish items to a node, with optional options.
341    Publish {
342        /// The publish request.
343        publish: Publish,
344
345        /// The options related to this publish request.
346        publish_options: Option<PublishOptions>
347    },
348
349    /// A list of affiliations you have on a service, or on a node.
350    Affiliations(Affiliations),
351
352    /// Request for a default node configuration.
353    Default(Default),
354
355    /// A request for a list of items.
356    Items(Items),
357
358    /// A request to retract some items from a node.
359    Retract(Retract),
360
361    /// A request about a subscription.
362    Subscription(SubscriptionElem),
363
364    /// A request for current subscriptions.
365    Subscriptions(Subscriptions),
366
367    /// An unsubscribe request.
368    Unsubscribe(Unsubscribe),
369}
370
371impl IqGetPayload for PubSub {}
372impl IqSetPayload for PubSub {}
373impl IqResultPayload for PubSub {}
374
375impl TryFrom<Element> for PubSub {
376    type Err = Error;
377
378    fn try_from(elem: Element) -> Result<PubSub, Error> {
379        check_self!(elem, "pubsub", PUBSUB);
380        check_no_attributes!(elem, "pubsub");
381
382        let mut payload = None;
383        for child in elem.children() {
384            if child.is("create", ns::PUBSUB) {
385                if payload.is_some() {
386                    return Err(Error::ParseError("Payload is already defined in pubsub element."));
387                }
388                let create = Create::try_from(child.clone())?;
389                payload = Some(PubSub::Create { create, configure: None });
390            } else if child.is("configure", ns::PUBSUB) {
391                if let Some(PubSub::Create { create, configure }) = payload {
392                    if configure.is_some() {
393                        return Err(Error::ParseError("Configure is already defined in pubsub element."));
394                    }
395                    let configure = Some(Configure::try_from(child.clone())?);
396                    payload = Some(PubSub::Create { create, configure });
397                } else {
398                    return Err(Error::ParseError("Payload is already defined in pubsub element."));
399                }
400            } else if child.is("publish", ns::PUBSUB) {
401                if payload.is_some() {
402                    return Err(Error::ParseError("Payload is already defined in pubsub element."));
403                }
404                let publish = Publish::try_from(child.clone())?;
405                payload = Some(PubSub::Publish { publish, publish_options: None });
406            } else if child.is("publish-options", ns::PUBSUB) {
407                if let Some(PubSub::Publish { publish, publish_options }) = payload {
408                    if publish_options.is_some() {
409                        return Err(Error::ParseError("Publish-options are already defined in pubsub element."));
410                    }
411                    let publish_options = Some(PublishOptions::try_from(child.clone())?);
412                    payload = Some(PubSub::Publish { publish, publish_options });
413                } else {
414                    return Err(Error::ParseError("Payload is already defined in pubsub element."));
415                }
416            } else if child.is("affiliations", ns::PUBSUB) {
417                if payload.is_some() {
418                    return Err(Error::ParseError("Payload is already defined in pubsub element."));
419                }
420                let affiliations = Affiliations::try_from(child.clone())?;
421                payload = Some(PubSub::Affiliations(affiliations));
422            } else if child.is("default", ns::PUBSUB) {
423                if payload.is_some() {
424                    return Err(Error::ParseError("Payload is already defined in pubsub element."));
425                }
426                let default = Default::try_from(child.clone())?;
427                payload = Some(PubSub::Default(default));
428            } else if child.is("items", ns::PUBSUB) {
429                if payload.is_some() {
430                    return Err(Error::ParseError("Payload is already defined in pubsub element."));
431                }
432                let items = Items::try_from(child.clone())?;
433                payload = Some(PubSub::Items(items));
434            } else if child.is("retract", ns::PUBSUB) {
435                if payload.is_some() {
436                    return Err(Error::ParseError("Payload is already defined in pubsub element."));
437                }
438                let retract = Retract::try_from(child.clone())?;
439                payload = Some(PubSub::Retract(retract));
440            } else if child.is("subscription", ns::PUBSUB) {
441                if payload.is_some() {
442                    return Err(Error::ParseError("Payload is already defined in pubsub element."));
443                }
444                let subscription = SubscriptionElem::try_from(child.clone())?;
445                payload = Some(PubSub::Subscription(subscription));
446            } else if child.is("subscriptions", ns::PUBSUB) {
447                if payload.is_some() {
448                    return Err(Error::ParseError("Payload is already defined in pubsub element."));
449                }
450                let subscriptions = Subscriptions::try_from(child.clone())?;
451                payload = Some(PubSub::Subscriptions(subscriptions));
452            } else if child.is("unsubscribe", ns::PUBSUB) {
453                if payload.is_some() {
454                    return Err(Error::ParseError("Payload is already defined in pubsub element."));
455                }
456                let unsubscribe = Unsubscribe::try_from(child.clone())?;
457                payload = Some(PubSub::Unsubscribe(unsubscribe));
458            } else {
459                return Err(Error::ParseError("Unknown child in pubsub element."));
460            }
461        }
462        Ok(payload.ok_or(Error::ParseError("No payload in pubsub element."))?)
463    }
464}
465
466impl From<PubSub> for Element {
467    fn from(pubsub: PubSub) -> Element {
468        Element::builder("pubsub")
469            .ns(ns::PUBSUB)
470            .append(match pubsub {
471                 PubSub::Create { create, configure } => {
472                     let mut elems = vec!(Element::from(create));
473                     if let Some(configure) = configure {
474                         elems.push(Element::from(configure));
475                     }
476                     elems
477                 },
478                 PubSub::Publish { publish, publish_options } => {
479                     let mut elems = vec!(Element::from(publish));
480                     if let Some(publish_options) = publish_options {
481                         elems.push(Element::from(publish_options));
482                     }
483                     elems
484                 },
485                 PubSub::Affiliations(affiliations) => vec!(Element::from(affiliations)),
486                 PubSub::Default(default) => vec!(Element::from(default)),
487                 PubSub::Items(items) => vec!(Element::from(items)),
488                 PubSub::Retract(retract) => vec!(Element::from(retract)),
489                 PubSub::Subscription(subscription) => vec!(Element::from(subscription)),
490                 PubSub::Subscriptions(subscriptions) => vec!(Element::from(subscriptions)),
491                 PubSub::Unsubscribe(unsubscribe) => vec!(Element::from(unsubscribe)),
492             })
493            .build()
494    }
495}
496
497#[cfg(test)]
498mod tests {
499    use super::*;
500    use compare_elements::NamespaceAwareCompare;
501
502    #[test]
503    fn create() {
504        let elem: Element = "<pubsub xmlns='http://jabber.org/protocol/pubsub'><create/></pubsub>".parse().unwrap();
505        let elem1 = elem.clone();
506        let pubsub = PubSub::try_from(elem).unwrap();
507        match pubsub.clone() {
508            PubSub::Create { create, configure } => {
509                assert!(create.node.is_none());
510                assert!(configure.is_none());
511            }
512            _ => panic!(),
513        }
514
515        let elem2 = Element::from(pubsub);
516        assert!(elem1.compare_to(&elem2));
517
518        let elem: Element = "<pubsub xmlns='http://jabber.org/protocol/pubsub'><create node='coucou'/></pubsub>".parse().unwrap();
519        let elem1 = elem.clone();
520        let pubsub = PubSub::try_from(elem).unwrap();
521        match pubsub.clone() {
522            PubSub::Create { create, configure } => {
523                assert_eq!(&create.node.unwrap().0, "coucou");
524                assert!(configure.is_none());
525            }
526            _ => panic!(),
527        }
528
529        let elem2 = Element::from(pubsub);
530        assert!(elem1.compare_to(&elem2));
531    }
532
533    #[test]
534    fn create_and_configure() {
535        let elem: Element = "<pubsub xmlns='http://jabber.org/protocol/pubsub'><create/><configure/></pubsub>".parse().unwrap();
536        let elem1 = elem.clone();
537        let pubsub = PubSub::try_from(elem).unwrap();
538        match pubsub.clone() {
539            PubSub::Create { create, configure } => {
540                assert!(create.node.is_none());
541                assert!(configure.unwrap().form.is_none());
542            }
543            _ => panic!(),
544        }
545
546        let elem2 = Element::from(pubsub);
547        assert!(elem1.compare_to(&elem2));
548    }
549
550    #[test]
551    fn publish() {
552        let elem: Element = "<pubsub xmlns='http://jabber.org/protocol/pubsub'><publish node='coucou'/></pubsub>".parse().unwrap();
553        let elem1 = elem.clone();
554        let pubsub = PubSub::try_from(elem).unwrap();
555        match pubsub.clone() {
556            PubSub::Publish { publish, publish_options } => {
557                assert_eq!(&publish.node.0, "coucou");
558                assert!(publish_options.is_none());
559            }
560            _ => panic!(),
561        }
562
563        let elem2 = Element::from(pubsub);
564        assert!(elem1.compare_to(&elem2));
565    }
566
567    #[test]
568    fn publish_with_publish_options() {
569        let elem: Element = "<pubsub xmlns='http://jabber.org/protocol/pubsub'><publish node='coucou'/><publish-options/></pubsub>".parse().unwrap();
570        let elem1 = elem.clone();
571        let pubsub = PubSub::try_from(elem).unwrap();
572        match pubsub.clone() {
573            PubSub::Publish { publish, publish_options } => {
574                assert_eq!(&publish.node.0, "coucou");
575                assert!(publish_options.unwrap().form.is_none());
576            }
577            _ => panic!(),
578        }
579
580        let elem2 = Element::from(pubsub);
581        assert!(elem1.compare_to(&elem2));
582    }
583
584    #[test]
585    fn invalid_empty_pubsub() {
586        let elem: Element = "<pubsub xmlns='http://jabber.org/protocol/pubsub'/>".parse().unwrap();
587        let error = PubSub::try_from(elem).unwrap_err();
588        let message = match error {
589            Error::ParseError(string) => string,
590            _ => panic!(),
591        };
592        assert_eq!(message, "No payload in pubsub element.");
593    }
594
595    #[test]
596    fn publish_option() {
597        let elem: Element = "<publish-options xmlns='http://jabber.org/protocol/pubsub'><x xmlns='jabber:x:data' type='submit'><field var='FORM_TYPE' type='hidden'><value>http://jabber.org/protocol/pubsub#publish-options</value></field></x></publish-options>".parse().unwrap();
598        let publish_options = PublishOptions::try_from(elem).unwrap();
599        assert_eq!(&publish_options.form.unwrap().form_type.unwrap(), "http://jabber.org/protocol/pubsub#publish-options");
600    }
601
602    #[test]
603    fn subscribe_options() {
604        let elem1: Element = "<subscribe-options xmlns='http://jabber.org/protocol/pubsub'/>".parse().unwrap();
605        let subscribe_options1 = SubscribeOptions::try_from(elem1).unwrap();
606        assert_eq!(subscribe_options1.required, false);
607
608        let elem2: Element = "<subscribe-options xmlns='http://jabber.org/protocol/pubsub'><required/></subscribe-options>".parse().unwrap();
609        let subscribe_options2 = SubscribeOptions::try_from(elem2).unwrap();
610        assert_eq!(subscribe_options2.required, true);
611    }
612}