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 std::convert::TryFrom;
  8
  9use minidom::Element;
 10
 11use error::Error;
 12use ns;
 13
 14use data_forms::{DataForm, DataFormType};
 15
 16#[derive(Debug, Clone, PartialEq)]
 17pub struct Feature {
 18    pub var: String,
 19}
 20
 21#[derive(Debug, Clone)]
 22pub struct Identity {
 23    pub category: String, // TODO: use an enum here.
 24    pub type_: String, // TODO: use an enum here.
 25    pub xml_lang: String,
 26    pub name: Option<String>,
 27}
 28
 29#[derive(Debug, Clone)]
 30pub struct Disco {
 31    pub node: Option<String>,
 32    pub identities: Vec<Identity>,
 33    pub features: Vec<Feature>,
 34    pub extensions: Vec<DataForm>,
 35}
 36
 37pub fn parse_disco(root: &Element) -> Result<Disco, Error> {
 38    if !root.is("query", ns::DISCO_INFO) {
 39        return Err(Error::ParseError("This is not a disco#info element."));
 40    }
 41
 42    let mut identities: Vec<Identity> = vec!();
 43    let mut features: Vec<Feature> = vec!();
 44    let mut extensions: Vec<DataForm> = vec!();
 45
 46    let node = root.attr("node")
 47                   .and_then(|node| node.parse().ok());
 48
 49    for child in root.children() {
 50        if child.is("feature", ns::DISCO_INFO) {
 51            let feature = child.attr("var")
 52                               .ok_or(Error::ParseError("Feature must have a 'var' attribute."))?;
 53            features.push(Feature {
 54                var: feature.to_owned(),
 55            });
 56        } else if child.is("identity", ns::DISCO_INFO) {
 57            let category = child.attr("category")
 58                                .ok_or(Error::ParseError("Identity must have a 'category' attribute."))?;
 59            if category == "" {
 60                return Err(Error::ParseError("Identity must have a non-empty 'category' attribute."))
 61            }
 62
 63            let type_ = child.attr("type")
 64                             .ok_or(Error::ParseError("Identity must have a 'type' attribute."))?;
 65            if type_ == "" {
 66                return Err(Error::ParseError("Identity must have a non-empty 'type' attribute."))
 67            }
 68
 69            let xml_lang = child.attr("xml:lang").unwrap_or("");
 70            let name = child.attr("name")
 71                            .and_then(|name| name.parse().ok());
 72            identities.push(Identity {
 73                category: category.to_owned(),
 74                type_: type_.to_owned(),
 75                xml_lang: xml_lang.to_owned(),
 76                name: name,
 77            });
 78        } else if child.is("x", ns::DATA_FORMS) {
 79            let data_form = DataForm::try_from(child)?;
 80            match data_form.type_ {
 81                DataFormType::Result_ => (),
 82                _ => return Err(Error::ParseError("Data form must have a 'result' type in disco#info.")),
 83            }
 84            match data_form.form_type {
 85                Some(_) => extensions.push(data_form),
 86                None => return Err(Error::ParseError("Data form found without a FORM_TYPE.")),
 87            }
 88        } else {
 89            return Err(Error::ParseError("Unknown element in disco#info."));
 90        }
 91    }
 92
 93    /*
 94    // TODO: encode these restrictions only for result disco#info, not get ones.
 95    if identities.is_empty() {
 96        return Err(Error::ParseError("There must be at least one identity in disco#info."));
 97    }
 98    if features.is_empty() {
 99        return Err(Error::ParseError("There must be at least one feature in disco#info."));
100    }
101    if !features.contains(&Feature { var: ns::DISCO_INFO.to_owned() }) {
102        return Err(Error::ParseError("disco#info feature not present in disco#info."));
103    }
104    */
105
106    Ok(Disco {
107        node: node,
108        identities: identities,
109        features: features,
110        extensions: extensions
111    })
112}
113
114pub fn serialise_disco(disco: &Disco) -> Element {
115    let mut root = Element::builder("query")
116                           .ns(ns::DISCO_INFO)
117                           .attr("node", disco.node.clone())
118                           .build();
119    for identity in &disco.identities {
120        let identity_element = Element::builder("identity")
121                                       .ns(ns::DISCO_INFO)
122                                       .attr("category", identity.category.clone())
123                                       .attr("type", identity.type_.clone())
124                                       .attr("xml:lang", identity.xml_lang.clone())
125                                       .attr("name", identity.name.clone())
126                                       .build();
127        root.append_child(identity_element);
128    }
129    for feature in &disco.features {
130        let feature_element = Element::builder("feature")
131                                      .ns(ns::DISCO_INFO)
132                                      .attr("var", feature.var.clone())
133                                      .build();
134        root.append_child(feature_element);
135    }
136    for _ in &disco.extensions {
137        panic!("Not yet implemented!");
138    }
139    root
140}
141
142#[cfg(test)]
143mod tests {
144    use minidom::Element;
145    use error::Error;
146    use disco;
147
148    #[test]
149    fn test_simple() {
150        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();
151        let query = disco::parse_disco(&elem).unwrap();
152        assert!(query.node.is_none());
153        assert_eq!(query.identities.len(), 1);
154        assert_eq!(query.features.len(), 1);
155        assert!(query.extensions.is_empty());
156    }
157
158    #[test]
159    fn test_invalid() {
160        let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><coucou/></query>".parse().unwrap();
161        let error = disco::parse_disco(&elem).unwrap_err();
162        let message = match error {
163            Error::ParseError(string) => string,
164            _ => panic!(),
165        };
166        assert_eq!(message, "Unknown element in disco#info.");
167    }
168
169    #[test]
170    fn test_invalid_identity() {
171        let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><identity/></query>".parse().unwrap();
172        let error = disco::parse_disco(&elem).unwrap_err();
173        let message = match error {
174            Error::ParseError(string) => string,
175            _ => panic!(),
176        };
177        assert_eq!(message, "Identity must have a 'category' attribute.");
178
179        let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><identity category=''/></query>".parse().unwrap();
180        let error = disco::parse_disco(&elem).unwrap_err();
181        let message = match error {
182            Error::ParseError(string) => string,
183            _ => panic!(),
184        };
185        assert_eq!(message, "Identity must have a non-empty 'category' attribute.");
186
187        let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><identity category='coucou'/></query>".parse().unwrap();
188        let error = disco::parse_disco(&elem).unwrap_err();
189        let message = match error {
190            Error::ParseError(string) => string,
191            _ => panic!(),
192        };
193        assert_eq!(message, "Identity must have a 'type' attribute.");
194
195        let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><identity category='coucou' type=''/></query>".parse().unwrap();
196        let error = disco::parse_disco(&elem).unwrap_err();
197        let message = match error {
198            Error::ParseError(string) => string,
199            _ => panic!(),
200        };
201        assert_eq!(message, "Identity must have a non-empty 'type' attribute.");
202    }
203
204    #[test]
205    fn test_invalid_feature() {
206        let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><feature/></query>".parse().unwrap();
207        let error = disco::parse_disco(&elem).unwrap_err();
208        let message = match error {
209            Error::ParseError(string) => string,
210            _ => panic!(),
211        };
212        assert_eq!(message, "Feature must have a 'var' attribute.");
213    }
214
215    #[test]
216    #[ignore]
217    fn test_invalid_result() {
218        let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'/>".parse().unwrap();
219        let error = disco::parse_disco(&elem).unwrap_err();
220        let message = match error {
221            Error::ParseError(string) => string,
222            _ => panic!(),
223        };
224        assert_eq!(message, "There must be at least one identity in disco#info.");
225
226        let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><identity category='client' type='pc'/></query>".parse().unwrap();
227        let error = disco::parse_disco(&elem).unwrap_err();
228        let message = match error {
229            Error::ParseError(string) => string,
230            _ => panic!(),
231        };
232        assert_eq!(message, "There must be at least one feature in disco#info.");
233
234        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();
235        let error = disco::parse_disco(&elem).unwrap_err();
236        let message = match error {
237            Error::ParseError(string) => string,
238            _ => panic!(),
239        };
240        assert_eq!(message, "disco#info feature not present in disco#info.");
241    }
242}