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