event.rs

  1// Copyright (c) 2017 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::date::DateTime;
  9use crate::util::error::Error;
 10use crate::ns;
 11use crate::pubsub::{ItemId, NodeName, Subscription, SubscriptionId, Item as PubSubItem};
 12use jid::Jid;
 13use minidom::Element;
 14use std::convert::TryFrom;
 15
 16/// Event wrapper for a PubSub `<item/>`.
 17#[derive(Debug, Clone)]
 18pub struct Item(pub PubSubItem);
 19
 20impl_pubsub_item!(Item, PUBSUB_EVENT);
 21
 22/// Represents an event happening to a PubSub node.
 23#[derive(Debug, Clone)]
 24pub enum PubSubEvent {
 25    /*
 26    Collection {
 27    },
 28    */
 29    /// This node’s configuration changed.
 30    Configuration {
 31        /// The node affected.
 32        node: NodeName,
 33
 34        /// The new configuration of this node.
 35        form: Option<DataForm>,
 36    },
 37
 38    /// This node has been deleted, with an optional redirect to another node.
 39    Delete {
 40        /// The node affected.
 41        node: NodeName,
 42
 43        /// The xmpp: URI of another node replacing this one.
 44        redirect: Option<String>,
 45    },
 46
 47    /// Some items have been published on this node.
 48    PublishedItems {
 49        /// The node affected.
 50        node: NodeName,
 51
 52        /// The list of published items.
 53        items: Vec<Item>,
 54    },
 55
 56    /// Some items have been removed from this node.
 57    RetractedItems {
 58        /// The node affected.
 59        node: NodeName,
 60
 61        /// The list of retracted items.
 62        items: Vec<ItemId>,
 63    },
 64
 65    /// All items of this node just got removed at once.
 66    Purge {
 67        /// The node affected.
 68        node: NodeName,
 69    },
 70
 71    /// The user’s subscription to this node has changed.
 72    Subscription {
 73        /// The node affected.
 74        node: NodeName,
 75
 76        /// The time at which this subscription will expire.
 77        expiry: Option<DateTime>,
 78
 79        /// The JID of the user affected.
 80        jid: Option<Jid>,
 81
 82        /// An identifier for this subscription.
 83        subid: Option<SubscriptionId>,
 84
 85        /// The state of this subscription.
 86        subscription: Option<Subscription>,
 87    },
 88}
 89
 90fn parse_items(elem: Element, node: NodeName) -> Result<PubSubEvent, Error> {
 91    let mut is_retract = None;
 92    let mut items = vec![];
 93    let mut retracts = vec![];
 94    for child in elem.children() {
 95        if child.is("item", ns::PUBSUB_EVENT) {
 96            match is_retract {
 97                None => is_retract = Some(false),
 98                Some(false) => (),
 99                Some(true) => {
100                    return Err(Error::ParseError(
101                        "Mix of item and retract in items element.",
102                    ));
103                }
104            }
105            items.push(Item::try_from(child.clone())?);
106        } else if child.is("retract", ns::PUBSUB_EVENT) {
107            match is_retract {
108                None => is_retract = Some(true),
109                Some(true) => (),
110                Some(false) => {
111                    return Err(Error::ParseError(
112                        "Mix of item and retract in items element.",
113                    ));
114                }
115            }
116            check_no_children!(child, "retract");
117            check_no_unknown_attributes!(child, "retract", ["id"]);
118            let id = get_attr!(child, "id", Required);
119            retracts.push(id);
120        } else {
121            return Err(Error::ParseError("Invalid child in items element."));
122        }
123    }
124    Ok(match is_retract {
125        Some(false) => PubSubEvent::PublishedItems { node, items },
126        Some(true) => PubSubEvent::RetractedItems {
127            node,
128            items: retracts,
129        },
130        None => return Err(Error::ParseError("Missing children in items element.")),
131    })
132}
133
134impl TryFrom<Element> for PubSubEvent {
135    type Error = Error;
136
137    fn try_from(elem: Element) -> Result<PubSubEvent, Error> {
138        check_self!(elem, "event", PUBSUB_EVENT);
139        check_no_attributes!(elem, "event");
140
141        let mut payload = None;
142        for child in elem.children() {
143            let node = get_attr!(child, "node", Required);
144            if child.is("configuration", ns::PUBSUB_EVENT) {
145                let mut payloads = child.children().cloned().collect::<Vec<_>>();
146                let item = payloads.pop();
147                if !payloads.is_empty() {
148                    return Err(Error::ParseError(
149                        "More than a single payload in configuration element.",
150                    ));
151                }
152                let form = match item {
153                    None => None,
154                    Some(payload) => Some(DataForm::try_from(payload)?),
155                };
156                payload = Some(PubSubEvent::Configuration { node, form });
157            } else if child.is("delete", ns::PUBSUB_EVENT) {
158                let mut redirect = None;
159                for item in child.children() {
160                    if item.is("redirect", ns::PUBSUB_EVENT) {
161                        if redirect.is_some() {
162                            return Err(Error::ParseError(
163                                "More than one redirect in delete element.",
164                            ));
165                        }
166                        let uri = get_attr!(item, "uri", Required);
167                        redirect = Some(uri);
168                    } else {
169                        return Err(Error::ParseError("Unknown child in delete element."));
170                    }
171                }
172                payload = Some(PubSubEvent::Delete { node, redirect });
173            } else if child.is("items", ns::PUBSUB_EVENT) {
174                payload = Some(parse_items(child.clone(), node)?);
175            } else if child.is("purge", ns::PUBSUB_EVENT) {
176                check_no_children!(child, "purge");
177                payload = Some(PubSubEvent::Purge { node });
178            } else if child.is("subscription", ns::PUBSUB_EVENT) {
179                check_no_children!(child, "subscription");
180                payload = Some(PubSubEvent::Subscription {
181                    node,
182                    expiry: get_attr!(child, "expiry", Option),
183                    jid: get_attr!(child, "jid", Option),
184                    subid: get_attr!(child, "subid", Option),
185                    subscription: get_attr!(child, "subscription", Option),
186                });
187            } else {
188                return Err(Error::ParseError("Unknown child in event element."));
189            }
190        }
191        Ok(payload.ok_or(Error::ParseError("No payload in event element."))?)
192    }
193}
194
195impl From<PubSubEvent> for Element {
196    fn from(event: PubSubEvent) -> Element {
197        let payload = match event {
198            PubSubEvent::Configuration { node, form } => Element::builder("configuration")
199                .ns(ns::PUBSUB_EVENT)
200                .attr("node", node)
201                .append(form)
202                .build(),
203            PubSubEvent::Delete { node, redirect } => Element::builder("purge")
204                .ns(ns::PUBSUB_EVENT)
205                .attr("node", node)
206                .append(redirect.map(|redirect| {
207                    Element::builder("redirect")
208                        .ns(ns::PUBSUB_EVENT)
209                        .attr("uri", redirect)
210                        .build()
211                }))
212                .build(),
213            PubSubEvent::PublishedItems { node, items } => Element::builder("items")
214                .ns(ns::PUBSUB_EVENT)
215                .attr("node", node)
216                .append(items)
217                .build(),
218            PubSubEvent::RetractedItems { node, items } => Element::builder("items")
219                .ns(ns::PUBSUB_EVENT)
220                .attr("node", node)
221                .append(
222                    items
223                        .into_iter()
224                        .map(|id| {
225                            Element::builder("retract")
226                                .ns(ns::PUBSUB_EVENT)
227                                .attr("id", id)
228                                .build()
229                        })
230                        .collect::<Vec<_>>(),
231                )
232                .build(),
233            PubSubEvent::Purge { node } => Element::builder("purge")
234                .ns(ns::PUBSUB_EVENT)
235                .attr("node", node)
236                .build(),
237            PubSubEvent::Subscription {
238                node,
239                expiry,
240                jid,
241                subid,
242                subscription,
243            } => Element::builder("subscription")
244                .ns(ns::PUBSUB_EVENT)
245                .attr("node", node)
246                .attr("expiry", expiry)
247                .attr("jid", jid)
248                .attr("subid", subid)
249                .attr("subscription", subscription)
250                .build(),
251        };
252        Element::builder("event")
253            .ns(ns::PUBSUB_EVENT)
254            .append(payload)
255            .build()
256    }
257}
258
259#[cfg(test)]
260mod tests {
261    use super::*;
262    use crate::util::compare_elements::NamespaceAwareCompare;
263    use std::str::FromStr;
264
265    #[test]
266    fn missing_items() {
267        let elem: Element =
268            "<event xmlns='http://jabber.org/protocol/pubsub#event'><items node='coucou'/></event>"
269                .parse()
270                .unwrap();
271        let error = PubSubEvent::try_from(elem).unwrap_err();
272        let message = match error {
273            Error::ParseError(string) => string,
274            _ => panic!(),
275        };
276        assert_eq!(message, "Missing children in items element.");
277    }
278
279    #[test]
280    fn test_simple_items() {
281        let elem: Element = "<event xmlns='http://jabber.org/protocol/pubsub#event'><items node='coucou'><item id='test' publisher='test@coucou'/></items></event>".parse().unwrap();
282        let event = PubSubEvent::try_from(elem).unwrap();
283        match event {
284            PubSubEvent::PublishedItems { node, items } => {
285                assert_eq!(node, NodeName(String::from("coucou")));
286                assert_eq!(items[0].id, Some(ItemId(String::from("test"))));
287                assert_eq!(
288                    items[0].publisher,
289                    Some(Jid::from_str("test@coucou").unwrap())
290                );
291                assert_eq!(items[0].payload, None);
292            }
293            _ => panic!(),
294        }
295    }
296
297    #[test]
298    fn test_simple_pep() {
299        let elem: Element = "<event xmlns='http://jabber.org/protocol/pubsub#event'><items node='something'><item><foreign xmlns='example:namespace'/></item></items></event>".parse().unwrap();
300        let event = PubSubEvent::try_from(elem).unwrap();
301        match event {
302            PubSubEvent::PublishedItems { node, items } => {
303                assert_eq!(node, NodeName(String::from("something")));
304                assert_eq!(items[0].id, None);
305                assert_eq!(items[0].publisher, None);
306                match items[0].payload {
307                    Some(ref elem) => assert!(elem.is("foreign", "example:namespace")),
308                    _ => panic!(),
309                }
310            }
311            _ => panic!(),
312        }
313    }
314
315    #[test]
316    fn test_simple_retract() {
317        let elem: Element = "<event xmlns='http://jabber.org/protocol/pubsub#event'><items node='something'><retract id='coucou'/><retract id='test'/></items></event>".parse().unwrap();
318        let event = PubSubEvent::try_from(elem).unwrap();
319        match event {
320            PubSubEvent::RetractedItems { node, items } => {
321                assert_eq!(node, NodeName(String::from("something")));
322                assert_eq!(items[0], ItemId(String::from("coucou")));
323                assert_eq!(items[1], ItemId(String::from("test")));
324            }
325            _ => panic!(),
326        }
327    }
328
329    #[test]
330    fn test_simple_delete() {
331        let elem: Element = "<event xmlns='http://jabber.org/protocol/pubsub#event'><delete node='coucou'><redirect uri='hello'/></delete></event>".parse().unwrap();
332        let event = PubSubEvent::try_from(elem).unwrap();
333        match event {
334            PubSubEvent::Delete { node, redirect } => {
335                assert_eq!(node, NodeName(String::from("coucou")));
336                assert_eq!(redirect, Some(String::from("hello")));
337            }
338            _ => panic!(),
339        }
340    }
341
342    #[test]
343    fn test_simple_purge() {
344        let elem: Element =
345            "<event xmlns='http://jabber.org/protocol/pubsub#event'><purge node='coucou'/></event>"
346                .parse()
347                .unwrap();
348        let event = PubSubEvent::try_from(elem).unwrap();
349        match event {
350            PubSubEvent::Purge { node } => {
351                assert_eq!(node, NodeName(String::from("coucou")));
352            }
353            _ => panic!(),
354        }
355    }
356
357    #[test]
358    fn test_simple_configure() {
359        let elem: Element = "<event xmlns='http://jabber.org/protocol/pubsub#event'><configuration node='coucou'><x xmlns='jabber:x:data' type='result'><field var='FORM_TYPE' type='hidden'><value>http://jabber.org/protocol/pubsub#node_config</value></field></x></configuration></event>".parse().unwrap();
360        let event = PubSubEvent::try_from(elem).unwrap();
361        match event {
362            PubSubEvent::Configuration { node, form: _ } => {
363                assert_eq!(node, NodeName(String::from("coucou")));
364                //assert_eq!(form.type_, Result_);
365            }
366            _ => panic!(),
367        }
368    }
369
370    #[test]
371    fn test_invalid() {
372        let elem: Element =
373            "<event xmlns='http://jabber.org/protocol/pubsub#event'><coucou node='test'/></event>"
374                .parse()
375                .unwrap();
376        let error = PubSubEvent::try_from(elem).unwrap_err();
377        let message = match error {
378            Error::ParseError(string) => string,
379            _ => panic!(),
380        };
381        assert_eq!(message, "Unknown child in event element.");
382    }
383
384    #[cfg(not(feature = "disable-validation"))]
385    #[test]
386    fn test_invalid_attribute() {
387        let elem: Element = "<event xmlns='http://jabber.org/protocol/pubsub#event' coucou=''/>"
388            .parse()
389            .unwrap();
390        let error = PubSubEvent::try_from(elem).unwrap_err();
391        let message = match error {
392            Error::ParseError(string) => string,
393            _ => panic!(),
394        };
395        assert_eq!(message, "Unknown attribute in event element.");
396    }
397
398    #[test]
399    fn test_ex221_subscription() {
400        let elem: Element = r#"
401<event xmlns='http://jabber.org/protocol/pubsub#event'>
402  <subscription
403      expiry='2006-02-28T23:59:59+00:00'
404      jid='francisco@denmark.lit'
405      node='princely_musings'
406      subid='ba49252aaa4f5d320c24d3766f0bdcade78c78d3'
407      subscription='subscribed'/>
408</event>
409"#
410        .parse()
411        .unwrap();
412        let event = PubSubEvent::try_from(elem.clone()).unwrap();
413        match event.clone() {
414            PubSubEvent::Subscription {
415                node,
416                expiry,
417                jid,
418                subid,
419                subscription,
420            } => {
421                assert_eq!(node, NodeName(String::from("princely_musings")));
422                assert_eq!(
423                    subid,
424                    Some(SubscriptionId(String::from(
425                        "ba49252aaa4f5d320c24d3766f0bdcade78c78d3"
426                    )))
427                );
428                assert_eq!(subscription, Some(Subscription::Subscribed));
429                assert_eq!(jid, Some(Jid::from_str("francisco@denmark.lit").unwrap()));
430                assert_eq!(expiry, Some("2006-02-28T23:59:59Z".parse().unwrap()));
431            }
432            _ => panic!(),
433        }
434
435        let elem2: Element = event.into();
436        assert!(elem.compare_to(&elem2));
437    }
438}