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}