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}