disco.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;
 11
 12use error::Error;
 13use ns;
 14
 15use data_forms::{DataForm, DataFormType};
 16
 17#[derive(Debug, Clone)]
 18pub struct DiscoInfoQuery {
 19    pub node: Option<String>,
 20}
 21
 22impl TryFrom<Element> for DiscoInfoQuery {
 23    type Err = Error;
 24
 25    fn try_from(elem: Element) -> Result<DiscoInfoQuery, Error> {
 26        if !elem.is("query", ns::DISCO_INFO) {
 27            return Err(Error::ParseError("This is not a disco#info element."));
 28        }
 29        for _ in elem.children() {
 30            return Err(Error::ParseError("Unknown child in disco#info."));
 31        }
 32        for (attr, _) in elem.attrs() {
 33            if attr != "node" {
 34                return Err(Error::ParseError("Unknown attribute in disco#info."));
 35            }
 36        }
 37        Ok(DiscoInfoQuery {
 38            node: get_attr!(elem, "node", optional),
 39        })
 40    }
 41}
 42
 43impl From<DiscoInfoQuery> for Element {
 44    fn from(disco: DiscoInfoQuery) -> Element {
 45        Element::builder("query")
 46                .ns(ns::DISCO_INFO)
 47                .attr("node", disco.node)
 48                .build()
 49    }
 50}
 51
 52#[derive(Debug, Clone, PartialEq)]
 53pub struct Feature {
 54    pub var: String,
 55}
 56
 57impl From<Feature> for Element {
 58    fn from(feature: Feature) -> Element {
 59        Element::builder("feature")
 60                .ns(ns::DISCO_INFO)
 61                .attr("var", feature.var)
 62                .build()
 63    }
 64}
 65
 66#[derive(Debug, Clone)]
 67pub struct Identity {
 68    pub category: String, // TODO: use an enum here.
 69    pub type_: String, // TODO: use an enum here.
 70    pub lang: Option<String>,
 71    pub name: Option<String>,
 72}
 73
 74impl From<Identity> for Element {
 75    fn from(identity: Identity) -> Element {
 76        Element::builder("identity")
 77                .ns(ns::DISCO_INFO)
 78                .attr("category", identity.category)
 79                .attr("type", identity.type_)
 80                .attr("xml:lang", identity.lang)
 81                .attr("name", identity.name)
 82                .build()
 83    }
 84}
 85
 86#[derive(Debug, Clone)]
 87pub struct DiscoInfoResult {
 88    pub node: Option<String>,
 89    pub identities: Vec<Identity>,
 90    pub features: Vec<Feature>,
 91    pub extensions: Vec<DataForm>,
 92}
 93
 94impl TryFrom<Element> for DiscoInfoResult {
 95    type Err = Error;
 96
 97    fn try_from(elem: Element) -> Result<DiscoInfoResult, Error> {
 98        if !elem.is("query", ns::DISCO_INFO) {
 99            return Err(Error::ParseError("This is not a disco#info element."));
100        }
101
102        let mut identities: Vec<Identity> = vec!();
103        let mut features: Vec<Feature> = vec!();
104        let mut extensions: Vec<DataForm> = vec!();
105
106        let node = get_attr!(elem, "node", optional);
107
108        for child in elem.children() {
109            if child.is("feature", ns::DISCO_INFO) {
110                let feature = get_attr!(child, "var", required);
111                features.push(Feature {
112                    var: feature,
113                });
114            } else if child.is("identity", ns::DISCO_INFO) {
115                let category = get_attr!(child, "category", required);
116                if category == "" {
117                    return Err(Error::ParseError("Identity must have a non-empty 'category' attribute."))
118                }
119
120                let type_ = get_attr!(child, "type", required);
121                if type_ == "" {
122                    return Err(Error::ParseError("Identity must have a non-empty 'type' attribute."))
123                }
124
125                let lang = get_attr!(child, "xml:lang", optional);
126                let name = get_attr!(child, "name", optional);
127                identities.push(Identity {
128                    category: category,
129                    type_: type_,
130                    lang: lang,
131                    name: name,
132                });
133            } else if child.is("x", ns::DATA_FORMS) {
134                let data_form = DataForm::try_from(child.clone())?;
135                if data_form.type_ != DataFormType::Result_ {
136                    return Err(Error::ParseError("Data form must have a 'result' type in disco#info."));
137                }
138                match data_form.form_type {
139                    Some(_) => extensions.push(data_form),
140                    None => return Err(Error::ParseError("Data form found without a FORM_TYPE.")),
141                }
142            } else {
143                return Err(Error::ParseError("Unknown element in disco#info."));
144            }
145        }
146
147        if identities.is_empty() {
148            return Err(Error::ParseError("There must be at least one identity in disco#info."));
149        }
150        if features.is_empty() {
151            return Err(Error::ParseError("There must be at least one feature in disco#info."));
152        }
153        if !features.contains(&Feature { var: ns::DISCO_INFO.to_owned() }) {
154            return Err(Error::ParseError("disco#info feature not present in disco#info."));
155        }
156
157        Ok(DiscoInfoResult {
158            node: node,
159            identities: identities,
160            features: features,
161            extensions: extensions
162        })
163    }
164}
165
166impl From<DiscoInfoResult> for Element {
167    fn from(disco: DiscoInfoResult) -> Element {
168        for _ in disco.extensions {
169            panic!("Not yet implemented!");
170        }
171        Element::builder("query")
172                .ns(ns::DISCO_INFO)
173                .attr("node", disco.node)
174                .append(disco.identities)
175                .append(disco.features)
176                .build()
177    }
178}
179
180#[derive(Debug, Clone)]
181pub struct DiscoItemsQuery {
182    pub node: Option<String>,
183}
184
185impl TryFrom<Element> for DiscoItemsQuery {
186    type Err = Error;
187
188    fn try_from(elem: Element) -> Result<DiscoItemsQuery, Error> {
189        if !elem.is("query", ns::DISCO_ITEMS) {
190            return Err(Error::ParseError("This is not a disco#items element."));
191        }
192        for _ in elem.children() {
193            return Err(Error::ParseError("Unknown child in disco#items."));
194        }
195        for (attr, _) in elem.attrs() {
196            if attr != "node" {
197                return Err(Error::ParseError("Unknown attribute in disco#items."));
198            }
199        }
200        Ok(DiscoItemsQuery {
201            node: get_attr!(elem, "node", optional),
202        })
203    }
204}
205
206impl From<DiscoItemsQuery> for Element {
207    fn from(disco: DiscoItemsQuery) -> Element {
208        Element::builder("query")
209                .ns(ns::DISCO_ITEMS)
210                .attr("node", disco.node)
211                .build()
212    }
213}
214
215#[derive(Debug, Clone)]
216pub struct Item {
217    pub jid: Jid,
218    pub node: Option<String>,
219    pub name: Option<String>,
220}
221
222impl TryFrom<Element> for Item {
223    type Err = Error;
224
225    fn try_from(elem: Element) -> Result<Item, Error> {
226        if !elem.is("item", ns::DISCO_ITEMS) {
227            return Err(Error::ParseError("This is not an item element."));
228        }
229        for _ in elem.children() {
230            return Err(Error::ParseError("Unknown child in item element."));
231        }
232        for (attr, _) in elem.attrs() {
233            if attr != "jid" && attr != "node" && attr != "name" {
234                return Err(Error::ParseError("Unknown attribute in item element."));
235            }
236        }
237        Ok(Item {
238            jid: get_attr!(elem, "jid", required),
239            node: get_attr!(elem, "node", optional),
240            name: get_attr!(elem, "name", optional),
241        })
242    }
243}
244
245impl From<Item> for Element {
246    fn from(item: Item) -> Element {
247        Element::builder("item")
248                .ns(ns::DISCO_ITEMS)
249                .attr("jid", String::from(item.jid))
250                .attr("node", item.node)
251                .attr("name", item.name)
252                .build()
253    }
254}
255
256#[derive(Debug, Clone)]
257pub struct DiscoItemsResult {
258    pub node: Option<String>,
259    pub items: Vec<Item>,
260}
261
262impl TryFrom<Element> for DiscoItemsResult {
263    type Err = Error;
264
265    fn try_from(elem: Element) -> Result<DiscoItemsResult, Error> {
266        if !elem.is("query", ns::DISCO_ITEMS) {
267            return Err(Error::ParseError("This is not a disco#items element."));
268        }
269        for (attr, _) in elem.attrs() {
270            if attr != "node" {
271                return Err(Error::ParseError("Unknown attribute in disco#items."));
272            }
273        }
274
275        let mut items: Vec<Item> = vec!();
276        for child in elem.children() {
277            if child.is("item", ns::DISCO_ITEMS) {
278                items.push(Item::try_from(child.clone())?);
279            } else {
280                return Err(Error::ParseError("Unknown element in disco#items."));
281            }
282        }
283
284        Ok(DiscoItemsResult {
285            node: get_attr!(elem, "node", optional),
286            items: items,
287        })
288    }
289}
290
291impl From<DiscoItemsResult> for Element {
292    fn from(disco: DiscoItemsResult) -> Element {
293        Element::builder("query")
294                .ns(ns::DISCO_ITEMS)
295                .attr("node", disco.node)
296                .append(disco.items)
297                .build()
298    }
299}
300
301#[cfg(test)]
302mod tests {
303    use super::*;
304    use std::str::FromStr;
305
306    #[test]
307    fn test_simple() {
308        let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><identity category='client' type='pc'/><feature var='http://jabber.org/protocol/disco#info'/></query>".parse().unwrap();
309        let query = DiscoInfoResult::try_from(elem).unwrap();
310        assert!(query.node.is_none());
311        assert_eq!(query.identities.len(), 1);
312        assert_eq!(query.features.len(), 1);
313        assert!(query.extensions.is_empty());
314    }
315
316    #[test]
317    fn test_invalid() {
318        let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><coucou/></query>".parse().unwrap();
319        let error = DiscoInfoResult::try_from(elem).unwrap_err();
320        let message = match error {
321            Error::ParseError(string) => string,
322            _ => panic!(),
323        };
324        assert_eq!(message, "Unknown element in disco#info.");
325    }
326
327    #[test]
328    fn test_invalid_identity() {
329        let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><identity/></query>".parse().unwrap();
330        let error = DiscoInfoResult::try_from(elem).unwrap_err();
331        let message = match error {
332            Error::ParseError(string) => string,
333            _ => panic!(),
334        };
335        assert_eq!(message, "Required attribute 'category' missing.");
336
337        let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><identity category=''/></query>".parse().unwrap();
338        let error = DiscoInfoResult::try_from(elem).unwrap_err();
339        let message = match error {
340            Error::ParseError(string) => string,
341            _ => panic!(),
342        };
343        assert_eq!(message, "Identity must have a non-empty 'category' attribute.");
344
345        let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><identity category='coucou'/></query>".parse().unwrap();
346        let error = DiscoInfoResult::try_from(elem).unwrap_err();
347        let message = match error {
348            Error::ParseError(string) => string,
349            _ => panic!(),
350        };
351        assert_eq!(message, "Required attribute 'type' missing.");
352
353        let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><identity category='coucou' type=''/></query>".parse().unwrap();
354        let error = DiscoInfoResult::try_from(elem).unwrap_err();
355        let message = match error {
356            Error::ParseError(string) => string,
357            _ => panic!(),
358        };
359        assert_eq!(message, "Identity must have a non-empty 'type' attribute.");
360    }
361
362    #[test]
363    fn test_invalid_feature() {
364        let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><feature/></query>".parse().unwrap();
365        let error = DiscoInfoResult::try_from(elem).unwrap_err();
366        let message = match error {
367            Error::ParseError(string) => string,
368            _ => panic!(),
369        };
370        assert_eq!(message, "Required attribute 'var' missing.");
371    }
372
373    #[test]
374    fn test_invalid_result() {
375        let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'/>".parse().unwrap();
376        let error = DiscoInfoResult::try_from(elem).unwrap_err();
377        let message = match error {
378            Error::ParseError(string) => string,
379            _ => panic!(),
380        };
381        assert_eq!(message, "There must be at least one identity in disco#info.");
382
383        let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><identity category='client' type='pc'/></query>".parse().unwrap();
384        let error = DiscoInfoResult::try_from(elem).unwrap_err();
385        let message = match error {
386            Error::ParseError(string) => string,
387            _ => panic!(),
388        };
389        assert_eq!(message, "There must be at least one feature in disco#info.");
390
391        let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><identity category='client' type='pc'/><feature var='http://jabber.org/protocol/disco#items'/></query>".parse().unwrap();
392        let error = DiscoInfoResult::try_from(elem).unwrap_err();
393        let message = match error {
394            Error::ParseError(string) => string,
395            _ => panic!(),
396        };
397        assert_eq!(message, "disco#info feature not present in disco#info.");
398    }
399
400    #[test]
401    fn test_simple_items() {
402        let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#items'/>".parse().unwrap();
403        let query = DiscoItemsQuery::try_from(elem).unwrap();
404        assert!(query.node.is_none());
405
406        let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#items' node='coucou'/>".parse().unwrap();
407        let query = DiscoItemsQuery::try_from(elem).unwrap();
408        assert_eq!(query.node, Some(String::from("coucou")));
409    }
410
411    #[test]
412    fn test_simple_items_result() {
413        let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#items'/>".parse().unwrap();
414        let query = DiscoItemsResult::try_from(elem).unwrap();
415        assert!(query.node.is_none());
416        assert!(query.items.is_empty());
417
418        let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#items' node='coucou'/>".parse().unwrap();
419        let query = DiscoItemsResult::try_from(elem).unwrap();
420        assert_eq!(query.node, Some(String::from("coucou")));
421        assert!(query.items.is_empty());
422    }
423
424    #[test]
425    fn test_answers_items_result() {
426        let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#items'><item jid='component'/><item jid='component2' node='test' name='A component'/></query>".parse().unwrap();
427        let query = DiscoItemsResult::try_from(elem).unwrap();
428        assert_eq!(query.items.len(), 2);
429        assert_eq!(query.items[0].jid, Jid::from_str("component").unwrap());
430        assert_eq!(query.items[0].node, None);
431        assert_eq!(query.items[0].name, None);
432        assert_eq!(query.items[1].jid, Jid::from_str("component2").unwrap());
433        assert_eq!(query.items[1].node, Some(String::from("test")));
434        assert_eq!(query.items[1].name, Some(String::from("A component")));
435    }
436}