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