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
 37impl<'a> TryFrom<&'a Element> for Disco {
 38    type Error = Error;
 39
 40    fn try_from(elem: &'a Element) -> Result<Disco, Error> {
 41        if !elem.is("query", ns::DISCO_INFO) {
 42            return Err(Error::ParseError("This is not a disco#info element."));
 43        }
 44
 45        let mut identities: Vec<Identity> = vec!();
 46        let mut features: Vec<Feature> = vec!();
 47        let mut extensions: Vec<DataForm> = vec!();
 48
 49        let node = get_attr!(elem, "node", optional);
 50
 51        for child in elem.children() {
 52            if child.is("feature", ns::DISCO_INFO) {
 53                let feature = get_attr!(child, "var", required);
 54                features.push(Feature {
 55                    var: feature,
 56                });
 57            } else if child.is("identity", ns::DISCO_INFO) {
 58                let category = get_attr!(child, "category", required);
 59                if category == "" {
 60                    return Err(Error::ParseError("Identity must have a non-empty 'category' attribute."))
 61                }
 62
 63                let type_ = get_attr!(child, "type", required);
 64                if type_ == "" {
 65                    return Err(Error::ParseError("Identity must have a non-empty 'type' attribute."))
 66                }
 67
 68                let lang = get_attr!(child, "xml:lang", default);
 69                let name = get_attr!(child, "name", optional);
 70                identities.push(Identity {
 71                    category: category,
 72                    type_: type_,
 73                    xml_lang: lang,
 74                    name: name,
 75                });
 76            } else if child.is("x", ns::DATA_FORMS) {
 77                let data_form = DataForm::try_from(child)?;
 78                if data_form.type_ != DataFormType::Result_ {
 79                    return Err(Error::ParseError("Data form must have a 'result' type in disco#info."));
 80                }
 81                match data_form.form_type {
 82                    Some(_) => extensions.push(data_form),
 83                    None => return Err(Error::ParseError("Data form found without a FORM_TYPE.")),
 84                }
 85            } else {
 86                return Err(Error::ParseError("Unknown element in disco#info."));
 87            }
 88        }
 89
 90        /*
 91        // TODO: encode these restrictions only for result disco#info, not get ones.
 92        if identities.is_empty() {
 93            return Err(Error::ParseError("There must be at least one identity in disco#info."));
 94        }
 95        if features.is_empty() {
 96            return Err(Error::ParseError("There must be at least one feature in disco#info."));
 97        }
 98        if !features.contains(&Feature { var: ns::DISCO_INFO.to_owned() }) {
 99            return Err(Error::ParseError("disco#info feature not present in disco#info."));
100        }
101        */
102
103        Ok(Disco {
104            node: node,
105            identities: identities,
106            features: features,
107            extensions: extensions
108        })
109    }
110}
111
112impl<'a> Into<Element> for &'a Disco {
113    fn into(self) -> Element {
114        let mut root = Element::builder("query")
115                               .ns(ns::DISCO_INFO)
116                               .attr("node", self.node.clone())
117                               .build();
118        for identity in &self.identities {
119            let identity_element = Element::builder("identity")
120                                           .ns(ns::DISCO_INFO)
121                                           .attr("category", identity.category.clone())
122                                           .attr("type", identity.type_.clone())
123                                           .attr("xml:lang", identity.xml_lang.clone())
124                                           .attr("name", identity.name.clone())
125                                           .build();
126            root.append_child(identity_element);
127        }
128        for feature in &self.features {
129            let feature_element = Element::builder("feature")
130                                          .ns(ns::DISCO_INFO)
131                                          .attr("var", feature.var.clone())
132                                          .build();
133            root.append_child(feature_element);
134        }
135        for _ in &self.extensions {
136            panic!("Not yet implemented!");
137        }
138        root
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
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::try_from(&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::try_from(&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::try_from(&elem).unwrap_err();
171        let message = match error {
172            Error::ParseError(string) => string,
173            _ => panic!(),
174        };
175        assert_eq!(message, "Required attribute 'category' missing.");
176
177        let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><identity category=''/></query>".parse().unwrap();
178        let error = Disco::try_from(&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::try_from(&elem).unwrap_err();
187        let message = match error {
188            Error::ParseError(string) => string,
189            _ => panic!(),
190        };
191        assert_eq!(message, "Required attribute 'type' missing.");
192
193        let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><identity category='coucou' type=''/></query>".parse().unwrap();
194        let error = Disco::try_from(&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::try_from(&elem).unwrap_err();
206        let message = match error {
207            Error::ParseError(string) => string,
208            _ => panic!(),
209        };
210        assert_eq!(message, "Required attribute 'var' missing.");
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::try_from(&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::try_from(&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::try_from(&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}