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