disco.rs

  1extern crate minidom;
  2
  3use minidom::Element;
  4
  5use error::Error;
  6use ns::{DISCO_INFO_NS, DATA_FORMS_NS};
  7
  8use data_forms::{DataForm, DataFormType, parse_data_form};
  9
 10#[derive(Debug, PartialEq)]
 11pub struct Feature {
 12    pub var: String,
 13}
 14
 15#[derive(Debug)]
 16pub struct Identity {
 17    pub category: String, // TODO: use an enum here.
 18    pub type_: String, // TODO: use an enum here.
 19    pub xml_lang: String,
 20    pub name: Option<String>,
 21}
 22
 23#[derive(Debug)]
 24pub struct Disco {
 25    pub node: Option<String>,
 26    pub identities: Vec<Identity>,
 27    pub features: Vec<Feature>,
 28    pub extensions: Vec<DataForm>,
 29}
 30
 31pub fn parse_disco(root: &Element) -> Result<Disco, Error> {
 32    if !root.is("query", DISCO_INFO_NS) {
 33        return Err(Error::ParseError("This is not a disco#info element."));
 34    }
 35
 36    let mut identities: Vec<Identity> = vec!();
 37    let mut features: Vec<Feature> = vec!();
 38    let mut extensions: Vec<DataForm> = vec!();
 39
 40    let node = root.attr("node")
 41                   .and_then(|node| node.parse().ok());
 42
 43    for child in root.children() {
 44        if child.is("feature", DISCO_INFO_NS) {
 45            let feature = child.attr("var")
 46                               .ok_or(Error::ParseError("Feature must have a 'var' attribute."))?;
 47            features.push(Feature {
 48                var: feature.to_owned(),
 49            });
 50        } else if child.is("identity", DISCO_INFO_NS) {
 51            let category = child.attr("category")
 52                                .ok_or(Error::ParseError("Identity must have a 'category' attribute."))?;
 53            if category == "" {
 54                return Err(Error::ParseError("Identity must have a non-empty 'category' attribute."))
 55            }
 56
 57            let type_ = child.attr("type")
 58                             .ok_or(Error::ParseError("Identity must have a 'type' attribute."))?;
 59            if type_ == "" {
 60                return Err(Error::ParseError("Identity must have a non-empty 'type' attribute."))
 61            }
 62
 63            // TODO: this must check for the namespace of the attribute, but minidom doesn’t support that yet, see issue #2.
 64            let xml_lang = child.attr("lang").unwrap_or("");
 65            let name = child.attr("name")
 66                            .and_then(|name| name.parse().ok());
 67            identities.push(Identity {
 68                category: category.to_owned(),
 69                type_: type_.to_owned(),
 70                xml_lang: xml_lang.to_owned(),
 71                name: name,
 72            });
 73        } else if child.is("x", DATA_FORMS_NS) {
 74            let data_form = parse_data_form(child)?;
 75            match data_form.type_ {
 76                DataFormType::Result_ => (),
 77                _ => return Err(Error::ParseError("Data form must have a 'result' type in disco#info.")),
 78            }
 79            match data_form.form_type {
 80                Some(_) => extensions.push(data_form),
 81                None => return Err(Error::ParseError("Data form found without a FORM_TYPE.")),
 82            }
 83        } else {
 84            return Err(Error::ParseError("Unknown element in disco#info."));
 85        }
 86    }
 87
 88    if identities.is_empty() {
 89        return Err(Error::ParseError("There must be at least one identity in disco#info."));
 90    }
 91    if features.is_empty() {
 92        return Err(Error::ParseError("There must be at least one feature in disco#info."));
 93    }
 94    if !features.contains(&Feature { var: DISCO_INFO_NS.to_owned() }) {
 95        return Err(Error::ParseError("disco#info feature not present in disco#info."));
 96    }
 97
 98    return Ok(Disco {
 99        node: node,
100        identities: identities,
101        features: features,
102        extensions: extensions
103    });
104}
105
106#[cfg(test)]
107mod tests {
108    use minidom::Element;
109    use error::Error;
110    use disco;
111
112    #[test]
113    fn test_simple() {
114        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();
115        let query = disco::parse_disco(&elem).unwrap();
116        assert!(query.node.is_none());
117        assert_eq!(query.identities.len(), 1);
118        assert_eq!(query.features.len(), 1);
119        assert!(query.extensions.is_empty());
120    }
121
122    #[test]
123    fn test_invalid() {
124        let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><coucou/></query>".parse().unwrap();
125        let error = disco::parse_disco(&elem).unwrap_err();
126        let message = match error {
127            Error::ParseError(string) => string,
128            _ => panic!(),
129        };
130        assert_eq!(message, "Unknown element in disco#info.");
131
132        let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'/>".parse().unwrap();
133        let error = disco::parse_disco(&elem).unwrap_err();
134        let message = match error {
135            Error::ParseError(string) => string,
136            _ => panic!(),
137        };
138        assert_eq!(message, "There must be at least one identity in disco#info.");
139
140        let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><identity/></query>".parse().unwrap();
141        let error = disco::parse_disco(&elem).unwrap_err();
142        let message = match error {
143            Error::ParseError(string) => string,
144            _ => panic!(),
145        };
146        assert_eq!(message, "Identity must have a 'category' attribute.");
147
148        let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><identity category=''/></query>".parse().unwrap();
149        let error = disco::parse_disco(&elem).unwrap_err();
150        let message = match error {
151            Error::ParseError(string) => string,
152            _ => panic!(),
153        };
154        assert_eq!(message, "Identity must have a non-empty 'category' attribute.");
155
156        let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><identity category='coucou'/></query>".parse().unwrap();
157        let error = disco::parse_disco(&elem).unwrap_err();
158        let message = match error {
159            Error::ParseError(string) => string,
160            _ => panic!(),
161        };
162        assert_eq!(message, "Identity must have a 'type' attribute.");
163
164        let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><identity category='coucou' type=''/></query>".parse().unwrap();
165        let error = disco::parse_disco(&elem).unwrap_err();
166        let message = match error {
167            Error::ParseError(string) => string,
168            _ => panic!(),
169        };
170        assert_eq!(message, "Identity must have a non-empty 'type' attribute.");
171
172        let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><feature/></query>".parse().unwrap();
173        let error = disco::parse_disco(&elem).unwrap_err();
174        let message = match error {
175            Error::ParseError(string) => string,
176            _ => panic!(),
177        };
178        assert_eq!(message, "Feature must have a 'var' attribute.");
179
180        let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><identity category='client' type='pc'/></query>".parse().unwrap();
181        let error = disco::parse_disco(&elem).unwrap_err();
182        let message = match error {
183            Error::ParseError(string) => string,
184            _ => panic!(),
185        };
186        assert_eq!(message, "There must be at least one feature in disco#info.");
187
188        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();
189        let error = disco::parse_disco(&elem).unwrap_err();
190        let message = match error {
191            Error::ParseError(string) => string,
192            _ => panic!(),
193        };
194        assert_eq!(message, "disco#info feature not present in disco#info.");
195    }
196}