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