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