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