disco.rs

  1extern crate minidom;
  2
  3use minidom::Element;
  4
  5use error::Error;
  6use 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", ns::DISCO_INFO) {
 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", ns::DISCO_INFO) {
 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", ns::DISCO_INFO) {
 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", ns::DATA_FORMS) {
 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    /*
 89    // TODO: encode these restrictions only for result disco#info, not get ones.
 90    if identities.is_empty() {
 91        return Err(Error::ParseError("There must be at least one identity in disco#info."));
 92    }
 93    if features.is_empty() {
 94        return Err(Error::ParseError("There must be at least one feature in disco#info."));
 95    }
 96    if !features.contains(&Feature { var: ns::DISCO_INFO.to_owned() }) {
 97        return Err(Error::ParseError("disco#info feature not present in disco#info."));
 98    }
 99    */
100
101    return Ok(Disco {
102        node: node,
103        identities: identities,
104        features: features,
105        extensions: extensions
106    });
107}
108
109pub fn serialise_disco(disco: &Disco) -> Element {
110    let mut root = Element::builder("query")
111                           .ns(ns::DISCO_INFO)
112                           .attr("node", disco.node.clone())
113                           .build();
114    for identity in &disco.identities {
115        let identity_element = Element::builder("identity")
116                                       .ns(ns::DISCO_INFO)
117                                       .attr("category", identity.category.clone())
118                                       .attr("type", identity.type_.clone())
119                                       .attr("xml:lang", identity.xml_lang.clone())
120                                       .attr("name", identity.name.clone())
121                                       .build();
122        root.append_child(identity_element);
123    }
124    for feature in &disco.features {
125        let feature_element = Element::builder("feature")
126                                      .ns(ns::DISCO_INFO)
127                                      .attr("var", feature.var.clone())
128                                      .build();
129        root.append_child(feature_element);
130    }
131    for _ in &disco.extensions {
132        panic!("Not yet implemented!");
133    }
134    root
135}
136
137#[cfg(test)]
138mod tests {
139    use minidom::Element;
140    use error::Error;
141    use disco;
142
143    #[test]
144    fn test_simple() {
145        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();
146        let query = disco::parse_disco(&elem).unwrap();
147        assert!(query.node.is_none());
148        assert_eq!(query.identities.len(), 1);
149        assert_eq!(query.features.len(), 1);
150        assert!(query.extensions.is_empty());
151    }
152
153    #[test]
154    fn test_invalid() {
155        let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><coucou/></query>".parse().unwrap();
156        let error = disco::parse_disco(&elem).unwrap_err();
157        let message = match error {
158            Error::ParseError(string) => string,
159            _ => panic!(),
160        };
161        assert_eq!(message, "Unknown element in disco#info.");
162    }
163
164    #[test]
165    fn test_invalid_identity() {
166        let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><identity/></query>".parse().unwrap();
167        let error = disco::parse_disco(&elem).unwrap_err();
168        let message = match error {
169            Error::ParseError(string) => string,
170            _ => panic!(),
171        };
172        assert_eq!(message, "Identity must have a 'category' attribute.");
173
174        let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><identity category=''/></query>".parse().unwrap();
175        let error = disco::parse_disco(&elem).unwrap_err();
176        let message = match error {
177            Error::ParseError(string) => string,
178            _ => panic!(),
179        };
180        assert_eq!(message, "Identity must have a non-empty 'category' attribute.");
181
182        let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><identity category='coucou'/></query>".parse().unwrap();
183        let error = disco::parse_disco(&elem).unwrap_err();
184        let message = match error {
185            Error::ParseError(string) => string,
186            _ => panic!(),
187        };
188        assert_eq!(message, "Identity must have a 'type' attribute.");
189
190        let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><identity category='coucou' type=''/></query>".parse().unwrap();
191        let error = disco::parse_disco(&elem).unwrap_err();
192        let message = match error {
193            Error::ParseError(string) => string,
194            _ => panic!(),
195        };
196        assert_eq!(message, "Identity must have a non-empty 'type' attribute.");
197    }
198
199    #[test]
200    fn test_invalid_feature() {
201        let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><feature/></query>".parse().unwrap();
202        let error = disco::parse_disco(&elem).unwrap_err();
203        let message = match error {
204            Error::ParseError(string) => string,
205            _ => panic!(),
206        };
207        assert_eq!(message, "Feature must have a 'var' attribute.");
208    }
209
210    #[test]
211    #[ignore]
212    fn test_invalid_result() {
213        let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'/>".parse().unwrap();
214        let error = disco::parse_disco(&elem).unwrap_err();
215        let message = match error {
216            Error::ParseError(string) => string,
217            _ => panic!(),
218        };
219        assert_eq!(message, "There must be at least one identity in disco#info.");
220
221        let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><identity category='client' type='pc'/></query>".parse().unwrap();
222        let error = disco::parse_disco(&elem).unwrap_err();
223        let message = match error {
224            Error::ParseError(string) => string,
225            _ => panic!(),
226        };
227        assert_eq!(message, "There must be at least one feature in disco#info.");
228
229        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();
230        let error = disco::parse_disco(&elem).unwrap_err();
231        let message = match error {
232            Error::ParseError(string) => string,
233            _ => panic!(),
234        };
235        assert_eq!(message, "disco#info feature not present in disco#info.");
236    }
237}