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}