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