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