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