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