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