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