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