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
7#![deny(missing_docs)]
8
9use try_from::TryFrom;
10
11use minidom::Element;
12use jid::Jid;
13
14use error::Error;
15
16use ns;
17
18use iq::{IqGetPayload, IqSetPayload, IqResultPayload};
19use data_forms::DataForm;
20
21use pubsub::{NodeName, ItemId, Subscription, SubscriptionId};
22
23// TODO: a better solution would be to split this into a query and a result elements, like for
24// XEP-0030.
25generate_element_with_children!(
26 /// A list of affiliations you have on a service, or on a node.
27 Affiliations, "affiliations", PUBSUB,
28 attributes: [
29 /// The optional node name this request pertains to.
30 node: Option<NodeName> = "node" => optional,
31 ],
32 children: [
33 /// The actual list of affiliation elements.
34 affiliations: Vec<Affiliation> = ("affiliation", PUBSUB) => Affiliation
35 ]
36);
37
38generate_attribute!(
39 /// A list of possible affiliations to a node.
40 AffiliationAttribute, "affiliation", {
41 /// You are a member of this node, you can subscribe and retrieve items.
42 Member => "member",
43
44 /// You don’t have a specific affiliation with this node, you can only subscribe to it.
45 None => "none",
46
47 /// You are banned from this node.
48 Outcast => "outcast",
49
50 /// You are an owner of this node, and can do anything with it.
51 Owner => "owner",
52
53 /// You are a publisher on this node, you can publish and retract items to it.
54 Publisher => "publisher",
55
56 /// You can publish and retract items on this node, but not subscribe or retrive items.
57 PublishOnly => "publish-only",
58 }
59);
60
61generate_element_with_only_attributes!(
62 /// An affiliation element.
63 Affiliation, "affiliation", PUBSUB, [
64 /// The node this affiliation pertains to.
65 node: NodeName = "node" => required,
66
67 /// The affiliation you currently have on this node.
68 affiliation: AffiliationAttribute = "affiliation" => required,
69 ]
70);
71
72generate_element_with_children!(
73 /// Request to configure a new node.
74 Configure, "configure", PUBSUB,
75 child: (
76 /// The form to configure it.
77 form: Option<DataForm> = ("x", DATA_FORMS) => DataForm
78 )
79);
80
81generate_element_with_only_attributes!(
82 /// Request to create a new node.
83 Create, "create", PUBSUB, [
84 /// The node name to create, if `None` the service will generate one.
85 node: Option<NodeName> = "node" => optional,
86 ]
87);
88
89generate_element_with_only_attributes!(
90 /// Request for a default node configuration.
91 Default, "default", PUBSUB, [
92 /// The node targetted by this request, otherwise the entire service.
93 node: Option<NodeName> = "node" => optional,
94
95 // TODO: do we really want to support collection nodes?
96 // type: String = "type" => optional,
97 ]
98);
99
100generate_element_with_children!(
101 /// A request for a list of items.
102 Items, "items", PUBSUB,
103 attributes: [
104 // TODO: should be an xs:positiveInteger, that is, an unbounded int ≥ 1.
105 /// Maximum number of items returned.
106 max_items: Option<u32> = "max_items" => optional,
107
108 /// The node queried by this request.
109 node: NodeName = "node" => required,
110
111 /// The subscription identifier related to this request.
112 subid: Option<SubscriptionId> = "subid" => optional,
113 ],
114 children: [
115 /// The actual list of items returned.
116 items: Vec<Item> = ("item", PUBSUB) => Item
117 ]
118);
119
120/// An item from a PubSub node.
121#[derive(Debug, Clone)]
122pub struct Item {
123 /// The payload of this item, in an arbitrary namespace.
124 pub payload: Option<Element>,
125
126 /// The 'id' attribute of this item.
127 pub id: Option<ItemId>,
128}
129
130impl TryFrom<Element> for Item {
131 type Err = Error;
132
133 fn try_from(elem: Element) -> Result<Item, Error> {
134 check_self!(elem, "item", PUBSUB);
135 check_no_unknown_attributes!(elem, "item", ["id"]);
136 let mut payloads = elem.children().cloned().collect::<Vec<_>>();
137 let payload = payloads.pop();
138 if !payloads.is_empty() {
139 return Err(Error::ParseError("More than a single payload in item element."));
140 }
141 Ok(Item {
142 payload,
143 id: get_attr!(elem, "id", optional),
144 })
145 }
146}
147
148impl From<Item> for Element {
149 fn from(item: Item) -> Element {
150 Element::builder("item")
151 .ns(ns::PUBSUB)
152 .attr("id", item.id)
153 .append(item.payload)
154 .build()
155 }
156}
157
158generate_element_with_children!(
159 /// The options associated to a subscription request.
160 Options, "options", PUBSUB,
161 attributes: [
162 /// The JID affected by this request.
163 jid: Jid = "jid" => required,
164
165 /// The node affected by this request.
166 node: Option<NodeName> = "node" => optional,
167
168 /// The subscription identifier affected by this request.
169 subid: Option<SubscriptionId> = "subid" => optional,
170 ],
171 child: (
172 /// The form describing the subscription.
173 form: Option<DataForm> = ("x", DATA_FORMS) => DataForm
174 )
175);
176
177generate_element_with_children!(
178 /// Request to publish items to a node.
179 Publish, "publish", PUBSUB,
180 attributes: [
181 /// The target node for this operation.
182 node: NodeName = "node" => required,
183 ],
184 children: [
185 /// The items you want to publish.
186 items: Vec<Item> = ("item", PUBSUB) => Item
187 ]
188);
189
190generate_element_with_children!(
191 /// The options associated to a publish request.
192 PublishOptions, "publish-options", PUBSUB,
193 child: (
194 /// The form describing these options.
195 form: Option<DataForm> = ("x", DATA_FORMS) => DataForm
196 )
197);
198
199generate_attribute!(
200 /// Whether a retract request should notify subscribers or not.
201 Notify, "notify", bool
202);
203
204generate_element_with_children!(
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("More than one required element in subscribe-options."));
238 }
239 required = true;
240 } else {
241 return Err(Error::ParseError("Unknown child in subscribe-options element."));
242 }
243 }
244 Ok(SubscribeOptions { required })
245 }
246}
247
248impl From<SubscribeOptions> for Element {
249 fn from(subscribe_options: SubscribeOptions) -> Element {
250 Element::builder("subscribe-options")
251 .ns(ns::PUBSUB)
252 .append(if subscribe_options.required {
253 vec!(Element::builder("required")
254 .ns(ns::PUBSUB)
255 .build())
256 } else {
257 vec!()
258 })
259 .build()
260 }
261}
262
263generate_element_with_only_attributes!(
264 /// A request to subscribe a JID to a node.
265 Subscribe, "subscribe", PUBSUB, [
266 /// The JID being subscribed.
267 jid: Jid = "jid" => required,
268
269 /// The node to subscribe to.
270 node: Option<NodeName> = "node" => optional,
271 ]
272);
273
274generate_element_with_children!(
275 /// A request for current subscriptions.
276 Subscriptions, "subscriptions", PUBSUB,
277 attributes: [
278 /// The node to query.
279 node: Option<NodeName> = "node" => optional,
280 ],
281 children: [
282 /// The list of subscription elements returned.
283 subscription: Vec<SubscriptionElem> = ("subscription", PUBSUB) => SubscriptionElem
284 ]
285);
286
287generate_element_with_children!(
288 /// A subscription element, describing the state of a subscription.
289 SubscriptionElem, "subscription", PUBSUB,
290 attributes: [
291 /// The JID affected by this subscription.
292 jid: Jid = "jid" => required,
293
294 /// The node affected by this subscription.
295 node: Option<NodeName> = "node" => optional,
296
297 /// The subscription identifier for this subscription.
298 subid: Option<SubscriptionId> = "subid" => optional,
299
300 /// The state of the subscription.
301 subscription: Option<Subscription> = "subscription" => optional,
302 ],
303 child: (
304 /// The options related to this subscription.
305 subscribe_options: Option<SubscribeOptions> = ("subscribe-options", PUBSUB) => SubscribeOptions
306 )
307);
308
309generate_element_with_only_attributes!(
310 /// An unsubscribe request.
311 Unsubscribe, "unsubscribe", PUBSUB, [
312 /// The JID affected by this request.
313 jid: Jid = "jid" => required,
314
315 /// The node affected by this request.
316 node: Option<NodeName> = "node" => optional,
317
318 /// The subscription identifier for this subscription.
319 subid: Option<SubscriptionId> = "subid" => optional,
320 ]
321);
322
323/// Main payload used to communicate with a PubSub service.
324///
325/// `<pubsub xmlns="http://jabber.org/protocol/pubsub"/>`
326#[derive(Debug, Clone)]
327pub enum PubSub {
328 /// Request to create a new node, with optional suggested name and suggested configuration.
329 Create {
330 /// The create request.
331 create: Create,
332
333 /// The configure request for the new node.
334 configure: Option<Configure>
335 },
336
337 /// Request to publish items to a node, with optional options.
338 Publish {
339 /// The publish request.
340 publish: Publish,
341
342 /// The options related to this publish request.
343 publish_options: Option<PublishOptions>
344 },
345
346 /// A list of affiliations you have on a service, or on a node.
347 Affiliations(Affiliations),
348
349 /// Request for a default node configuration.
350 Default(Default),
351
352 /// A request for a list of items.
353 Items(Items),
354
355 /// A request to retract some items from a node.
356 Retract(Retract),
357
358 /// A request about a subscription.
359 Subscription(SubscriptionElem),
360
361 /// A request for current subscriptions.
362 Subscriptions(Subscriptions),
363
364 /// An unsubscribe request.
365 Unsubscribe(Unsubscribe),
366}
367
368impl IqGetPayload for PubSub {}
369impl IqSetPayload for PubSub {}
370impl IqResultPayload for PubSub {}
371
372impl TryFrom<Element> for PubSub {
373 type Err = Error;
374
375 fn try_from(elem: Element) -> Result<PubSub, Error> {
376 check_self!(elem, "pubsub", PUBSUB);
377 check_no_attributes!(elem, "pubsub");
378
379 let mut payload = None;
380 for child in elem.children() {
381 if child.is("create", ns::PUBSUB) {
382 if payload.is_some() {
383 return Err(Error::ParseError("Payload is already defined in pubsub element."));
384 }
385 let create = Create::try_from(child.clone())?;
386 payload = Some(PubSub::Create { create, configure: None });
387 } else if child.is("configure", ns::PUBSUB) {
388 if let Some(PubSub::Create { create, configure }) = payload {
389 if configure.is_some() {
390 return Err(Error::ParseError("Configure is already defined in pubsub element."));
391 }
392 let configure = Some(Configure::try_from(child.clone())?);
393 payload = Some(PubSub::Create { create, configure });
394 } else {
395 return Err(Error::ParseError("Payload is already defined in pubsub element."));
396 }
397 } else if child.is("publish", ns::PUBSUB) {
398 if payload.is_some() {
399 return Err(Error::ParseError("Payload is already defined in pubsub element."));
400 }
401 let publish = Publish::try_from(child.clone())?;
402 payload = Some(PubSub::Publish { publish, publish_options: None });
403 } else if child.is("publish-options", ns::PUBSUB) {
404 if let Some(PubSub::Publish { publish, publish_options }) = payload {
405 if publish_options.is_some() {
406 return Err(Error::ParseError("Publish-options are already defined in pubsub element."));
407 }
408 let publish_options = Some(PublishOptions::try_from(child.clone())?);
409 payload = Some(PubSub::Publish { publish, publish_options });
410 } else {
411 return Err(Error::ParseError("Payload is already defined in pubsub element."));
412 }
413 } else if child.is("affiliations", ns::PUBSUB) {
414 if payload.is_some() {
415 return Err(Error::ParseError("Payload is already defined in pubsub element."));
416 }
417 let affiliations = Affiliations::try_from(child.clone())?;
418 payload = Some(PubSub::Affiliations(affiliations));
419 } else if child.is("default", ns::PUBSUB) {
420 if payload.is_some() {
421 return Err(Error::ParseError("Payload is already defined in pubsub element."));
422 }
423 let default = Default::try_from(child.clone())?;
424 payload = Some(PubSub::Default(default));
425 } else if child.is("items", ns::PUBSUB) {
426 if payload.is_some() {
427 return Err(Error::ParseError("Payload is already defined in pubsub element."));
428 }
429 let items = Items::try_from(child.clone())?;
430 payload = Some(PubSub::Items(items));
431 } else if child.is("retract", ns::PUBSUB) {
432 if payload.is_some() {
433 return Err(Error::ParseError("Payload is already defined in pubsub element."));
434 }
435 let retract = Retract::try_from(child.clone())?;
436 payload = Some(PubSub::Retract(retract));
437 } else if child.is("subscription", ns::PUBSUB) {
438 if payload.is_some() {
439 return Err(Error::ParseError("Payload is already defined in pubsub element."));
440 }
441 let subscription = SubscriptionElem::try_from(child.clone())?;
442 payload = Some(PubSub::Subscription(subscription));
443 } else if child.is("subscriptions", ns::PUBSUB) {
444 if payload.is_some() {
445 return Err(Error::ParseError("Payload is already defined in pubsub element."));
446 }
447 let subscriptions = Subscriptions::try_from(child.clone())?;
448 payload = Some(PubSub::Subscriptions(subscriptions));
449 } else if child.is("unsubscribe", ns::PUBSUB) {
450 if payload.is_some() {
451 return Err(Error::ParseError("Payload is already defined in pubsub element."));
452 }
453 let unsubscribe = Unsubscribe::try_from(child.clone())?;
454 payload = Some(PubSub::Unsubscribe(unsubscribe));
455 } else {
456 return Err(Error::ParseError("Unknown child in pubsub element."));
457 }
458 }
459 Ok(payload.ok_or(Error::ParseError("No payload in pubsub element."))?)
460 }
461}
462
463impl From<PubSub> for Element {
464 fn from(pubsub: PubSub) -> Element {
465 Element::builder("pubsub")
466 .ns(ns::PUBSUB)
467 .append(match pubsub {
468 PubSub::Create { create, configure } => {
469 let mut elems = vec!(Element::from(create));
470 if let Some(configure) = configure {
471 elems.push(Element::from(configure));
472 }
473 elems
474 },
475 PubSub::Publish { publish, publish_options } => {
476 let mut elems = vec!(Element::from(publish));
477 if let Some(publish_options) = publish_options {
478 elems.push(Element::from(publish_options));
479 }
480 elems
481 },
482 PubSub::Affiliations(affiliations) => vec!(Element::from(affiliations)),
483 PubSub::Default(default) => vec!(Element::from(default)),
484 PubSub::Items(items) => vec!(Element::from(items)),
485 PubSub::Retract(retract) => vec!(Element::from(retract)),
486 PubSub::Subscription(subscription) => vec!(Element::from(subscription)),
487 PubSub::Subscriptions(subscriptions) => vec!(Element::from(subscriptions)),
488 PubSub::Unsubscribe(unsubscribe) => vec!(Element::from(unsubscribe)),
489 })
490 .build()
491 }
492}
493
494#[cfg(test)]
495mod tests {
496 use super::*;
497 use compare_elements::NamespaceAwareCompare;
498
499 #[test]
500 fn create() {
501 let elem: Element = "<pubsub xmlns='http://jabber.org/protocol/pubsub'><create/></pubsub>".parse().unwrap();
502 let elem1 = elem.clone();
503 let pubsub = PubSub::try_from(elem).unwrap();
504 match pubsub.clone() {
505 PubSub::Create { create, configure } => {
506 assert!(create.node.is_none());
507 assert!(configure.is_none());
508 }
509 _ => panic!(),
510 }
511
512 let elem2 = Element::from(pubsub);
513 assert!(elem1.compare_to(&elem2));
514
515 let elem: Element = "<pubsub xmlns='http://jabber.org/protocol/pubsub'><create node='coucou'/></pubsub>".parse().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_eq!(&create.node.unwrap().0, "coucou");
521 assert!(configure.is_none());
522 }
523 _ => panic!(),
524 }
525
526 let elem2 = Element::from(pubsub);
527 assert!(elem1.compare_to(&elem2));
528 }
529
530 #[test]
531 fn create_and_configure() {
532 let elem: Element = "<pubsub xmlns='http://jabber.org/protocol/pubsub'><create/><configure/></pubsub>".parse().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!(create.node.is_none());
538 assert!(configure.unwrap().form.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 publish() {
549 let elem: Element = "<pubsub xmlns='http://jabber.org/protocol/pubsub'><publish node='coucou'/></pubsub>".parse().unwrap();
550 let elem1 = elem.clone();
551 let pubsub = PubSub::try_from(elem).unwrap();
552 match pubsub.clone() {
553 PubSub::Publish { publish, publish_options } => {
554 assert_eq!(&publish.node.0, "coucou");
555 assert!(publish_options.is_none());
556 }
557 _ => panic!(),
558 }
559
560 let elem2 = Element::from(pubsub);
561 assert!(elem1.compare_to(&elem2));
562 }
563
564 #[test]
565 fn publish_with_publish_options() {
566 let elem: Element = "<pubsub xmlns='http://jabber.org/protocol/pubsub'><publish node='coucou'/><publish-options/></pubsub>".parse().unwrap();
567 let elem1 = elem.clone();
568 let pubsub = PubSub::try_from(elem).unwrap();
569 match pubsub.clone() {
570 PubSub::Publish { publish, publish_options } => {
571 assert_eq!(&publish.node.0, "coucou");
572 assert!(publish_options.unwrap().form.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 invalid_empty_pubsub() {
583 let elem: Element = "<pubsub xmlns='http://jabber.org/protocol/pubsub'/>".parse().unwrap();
584 let error = PubSub::try_from(elem).unwrap_err();
585 let message = match error {
586 Error::ParseError(string) => string,
587 _ => panic!(),
588 };
589 assert_eq!(message, "No payload in pubsub element.");
590 }
591
592 #[test]
593 fn publish_option() {
594 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();
595 let publish_options = PublishOptions::try_from(elem).unwrap();
596 assert_eq!(&publish_options.form.unwrap().form_type.unwrap(), "http://jabber.org/protocol/pubsub#publish-options");
597 }
598
599 #[test]
600 fn subscribe_options() {
601 let elem1: Element = "<subscribe-options xmlns='http://jabber.org/protocol/pubsub'/>".parse().unwrap();
602 let subscribe_options1 = SubscribeOptions::try_from(elem1).unwrap();
603 assert_eq!(subscribe_options1.required, false);
604
605 let elem2: Element = "<subscribe-options xmlns='http://jabber.org/protocol/pubsub'><required/></subscribe-options>".parse().unwrap();
606 let subscribe_options2 = SubscribeOptions::try_from(elem2).unwrap();
607 assert_eq!(subscribe_options2.required, true);
608 }
609}