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}