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;
10use jid::Jid;
11
12use error::Error;
13use ns;
14
15use data_forms::{DataForm, DataFormType};
16
17#[derive(Debug, Clone)]
18pub struct DiscoInfoQuery {
19 pub node: Option<String>,
20}
21
22impl TryFrom<Element> for DiscoInfoQuery {
23 type Err = Error;
24
25 fn try_from(elem: Element) -> Result<DiscoInfoQuery, Error> {
26 if !elem.is("query", ns::DISCO_INFO) {
27 return Err(Error::ParseError("This is not a disco#info element."));
28 }
29 for _ in elem.children() {
30 return Err(Error::ParseError("Unknown child in disco#info."));
31 }
32 for (attr, _) in elem.attrs() {
33 if attr != "node" {
34 return Err(Error::ParseError("Unknown attribute in disco#info."));
35 }
36 }
37 Ok(DiscoInfoQuery {
38 node: get_attr!(elem, "node", optional),
39 })
40 }
41}
42
43impl From<DiscoInfoQuery> for Element {
44 fn from(disco: DiscoInfoQuery) -> Element {
45 Element::builder("query")
46 .ns(ns::DISCO_INFO)
47 .attr("node", disco.node)
48 .build()
49 }
50}
51
52#[derive(Debug, Clone, PartialEq)]
53pub struct Feature {
54 pub var: String,
55}
56
57impl From<Feature> for Element {
58 fn from(feature: Feature) -> Element {
59 Element::builder("feature")
60 .ns(ns::DISCO_INFO)
61 .attr("var", feature.var)
62 .build()
63 }
64}
65
66#[derive(Debug, Clone)]
67pub struct Identity {
68 pub category: String, // TODO: use an enum here.
69 pub type_: String, // TODO: use an enum here.
70 pub lang: Option<String>,
71 pub name: Option<String>,
72}
73
74impl From<Identity> for Element {
75 fn from(identity: Identity) -> Element {
76 Element::builder("identity")
77 .ns(ns::DISCO_INFO)
78 .attr("category", identity.category)
79 .attr("type", identity.type_)
80 .attr("xml:lang", identity.lang)
81 .attr("name", identity.name)
82 .build()
83 }
84}
85
86#[derive(Debug, Clone)]
87pub struct DiscoInfoResult {
88 pub node: Option<String>,
89 pub identities: Vec<Identity>,
90 pub features: Vec<Feature>,
91 pub extensions: Vec<DataForm>,
92}
93
94impl TryFrom<Element> for DiscoInfoResult {
95 type Err = Error;
96
97 fn try_from(elem: Element) -> Result<DiscoInfoResult, Error> {
98 if !elem.is("query", ns::DISCO_INFO) {
99 return Err(Error::ParseError("This is not a disco#info element."));
100 }
101
102 let mut identities: Vec<Identity> = vec!();
103 let mut features: Vec<Feature> = vec!();
104 let mut extensions: Vec<DataForm> = vec!();
105
106 let node = get_attr!(elem, "node", optional);
107
108 for child in elem.children() {
109 if child.is("feature", ns::DISCO_INFO) {
110 let feature = get_attr!(child, "var", required);
111 features.push(Feature {
112 var: feature,
113 });
114 } else if child.is("identity", ns::DISCO_INFO) {
115 let category = get_attr!(child, "category", required);
116 if category == "" {
117 return Err(Error::ParseError("Identity must have a non-empty 'category' attribute."))
118 }
119
120 let type_ = get_attr!(child, "type", required);
121 if type_ == "" {
122 return Err(Error::ParseError("Identity must have a non-empty 'type' attribute."))
123 }
124
125 let lang = get_attr!(child, "xml:lang", optional);
126 let name = get_attr!(child, "name", optional);
127 identities.push(Identity {
128 category: category,
129 type_: type_,
130 lang: lang,
131 name: name,
132 });
133 } else if child.is("x", ns::DATA_FORMS) {
134 let data_form = DataForm::try_from(child.clone())?;
135 if data_form.type_ != DataFormType::Result_ {
136 return Err(Error::ParseError("Data form must have a 'result' type in disco#info."));
137 }
138 match data_form.form_type {
139 Some(_) => extensions.push(data_form),
140 None => return Err(Error::ParseError("Data form found without a FORM_TYPE.")),
141 }
142 } else {
143 return Err(Error::ParseError("Unknown element in disco#info."));
144 }
145 }
146
147 if identities.is_empty() {
148 return Err(Error::ParseError("There must be at least one identity in disco#info."));
149 }
150 if features.is_empty() {
151 return Err(Error::ParseError("There must be at least one feature in disco#info."));
152 }
153 if !features.contains(&Feature { var: ns::DISCO_INFO.to_owned() }) {
154 return Err(Error::ParseError("disco#info feature not present in disco#info."));
155 }
156
157 Ok(DiscoInfoResult {
158 node: node,
159 identities: identities,
160 features: features,
161 extensions: extensions
162 })
163 }
164}
165
166impl From<DiscoInfoResult> for Element {
167 fn from(disco: DiscoInfoResult) -> Element {
168 for _ in disco.extensions {
169 panic!("Not yet implemented!");
170 }
171 Element::builder("query")
172 .ns(ns::DISCO_INFO)
173 .attr("node", disco.node)
174 .append(disco.identities)
175 .append(disco.features)
176 .build()
177 }
178}
179
180#[derive(Debug, Clone)]
181pub struct DiscoItemsQuery {
182 pub node: Option<String>,
183}
184
185impl TryFrom<Element> for DiscoItemsQuery {
186 type Err = Error;
187
188 fn try_from(elem: Element) -> Result<DiscoItemsQuery, Error> {
189 if !elem.is("query", ns::DISCO_ITEMS) {
190 return Err(Error::ParseError("This is not a disco#items element."));
191 }
192 for _ in elem.children() {
193 return Err(Error::ParseError("Unknown child in disco#items."));
194 }
195 for (attr, _) in elem.attrs() {
196 if attr != "node" {
197 return Err(Error::ParseError("Unknown attribute in disco#items."));
198 }
199 }
200 Ok(DiscoItemsQuery {
201 node: get_attr!(elem, "node", optional),
202 })
203 }
204}
205
206impl From<DiscoItemsQuery> for Element {
207 fn from(disco: DiscoItemsQuery) -> Element {
208 Element::builder("query")
209 .ns(ns::DISCO_ITEMS)
210 .attr("node", disco.node)
211 .build()
212 }
213}
214
215#[derive(Debug, Clone)]
216pub struct Item {
217 pub jid: Jid,
218 pub node: Option<String>,
219 pub name: Option<String>,
220}
221
222impl TryFrom<Element> for Item {
223 type Err = Error;
224
225 fn try_from(elem: Element) -> Result<Item, Error> {
226 if !elem.is("item", ns::DISCO_ITEMS) {
227 return Err(Error::ParseError("This is not an item element."));
228 }
229 for _ in elem.children() {
230 return Err(Error::ParseError("Unknown child in item element."));
231 }
232 for (attr, _) in elem.attrs() {
233 if attr != "jid" && attr != "node" && attr != "name" {
234 return Err(Error::ParseError("Unknown attribute in item element."));
235 }
236 }
237 Ok(Item {
238 jid: get_attr!(elem, "jid", required),
239 node: get_attr!(elem, "node", optional),
240 name: get_attr!(elem, "name", optional),
241 })
242 }
243}
244
245impl From<Item> for Element {
246 fn from(item: Item) -> Element {
247 Element::builder("item")
248 .ns(ns::DISCO_ITEMS)
249 .attr("jid", String::from(item.jid))
250 .attr("node", item.node)
251 .attr("name", item.name)
252 .build()
253 }
254}
255
256#[derive(Debug, Clone)]
257pub struct DiscoItemsResult {
258 pub node: Option<String>,
259 pub items: Vec<Item>,
260}
261
262impl TryFrom<Element> for DiscoItemsResult {
263 type Err = Error;
264
265 fn try_from(elem: Element) -> Result<DiscoItemsResult, Error> {
266 if !elem.is("query", ns::DISCO_ITEMS) {
267 return Err(Error::ParseError("This is not a disco#items element."));
268 }
269 for (attr, _) in elem.attrs() {
270 if attr != "node" {
271 return Err(Error::ParseError("Unknown attribute in disco#items."));
272 }
273 }
274
275 let mut items: Vec<Item> = vec!();
276 for child in elem.children() {
277 if child.is("item", ns::DISCO_ITEMS) {
278 items.push(Item::try_from(child.clone())?);
279 } else {
280 return Err(Error::ParseError("Unknown element in disco#items."));
281 }
282 }
283
284 Ok(DiscoItemsResult {
285 node: get_attr!(elem, "node", optional),
286 items: items,
287 })
288 }
289}
290
291impl From<DiscoItemsResult> for Element {
292 fn from(disco: DiscoItemsResult) -> Element {
293 Element::builder("query")
294 .ns(ns::DISCO_ITEMS)
295 .attr("node", disco.node)
296 .append(disco.items)
297 .build()
298 }
299}
300
301#[cfg(test)]
302mod tests {
303 use super::*;
304 use std::str::FromStr;
305
306 #[test]
307 fn test_simple() {
308 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();
309 let query = DiscoInfoResult::try_from(elem).unwrap();
310 assert!(query.node.is_none());
311 assert_eq!(query.identities.len(), 1);
312 assert_eq!(query.features.len(), 1);
313 assert!(query.extensions.is_empty());
314 }
315
316 #[test]
317 fn test_invalid() {
318 let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><coucou/></query>".parse().unwrap();
319 let error = DiscoInfoResult::try_from(elem).unwrap_err();
320 let message = match error {
321 Error::ParseError(string) => string,
322 _ => panic!(),
323 };
324 assert_eq!(message, "Unknown element in disco#info.");
325 }
326
327 #[test]
328 fn test_invalid_identity() {
329 let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><identity/></query>".parse().unwrap();
330 let error = DiscoInfoResult::try_from(elem).unwrap_err();
331 let message = match error {
332 Error::ParseError(string) => string,
333 _ => panic!(),
334 };
335 assert_eq!(message, "Required attribute 'category' missing.");
336
337 let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><identity category=''/></query>".parse().unwrap();
338 let error = DiscoInfoResult::try_from(elem).unwrap_err();
339 let message = match error {
340 Error::ParseError(string) => string,
341 _ => panic!(),
342 };
343 assert_eq!(message, "Identity must have a non-empty 'category' attribute.");
344
345 let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><identity category='coucou'/></query>".parse().unwrap();
346 let error = DiscoInfoResult::try_from(elem).unwrap_err();
347 let message = match error {
348 Error::ParseError(string) => string,
349 _ => panic!(),
350 };
351 assert_eq!(message, "Required attribute 'type' missing.");
352
353 let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><identity category='coucou' type=''/></query>".parse().unwrap();
354 let error = DiscoInfoResult::try_from(elem).unwrap_err();
355 let message = match error {
356 Error::ParseError(string) => string,
357 _ => panic!(),
358 };
359 assert_eq!(message, "Identity must have a non-empty 'type' attribute.");
360 }
361
362 #[test]
363 fn test_invalid_feature() {
364 let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><feature/></query>".parse().unwrap();
365 let error = DiscoInfoResult::try_from(elem).unwrap_err();
366 let message = match error {
367 Error::ParseError(string) => string,
368 _ => panic!(),
369 };
370 assert_eq!(message, "Required attribute 'var' missing.");
371 }
372
373 #[test]
374 fn test_invalid_result() {
375 let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'/>".parse().unwrap();
376 let error = DiscoInfoResult::try_from(elem).unwrap_err();
377 let message = match error {
378 Error::ParseError(string) => string,
379 _ => panic!(),
380 };
381 assert_eq!(message, "There must be at least one identity in disco#info.");
382
383 let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><identity category='client' type='pc'/></query>".parse().unwrap();
384 let error = DiscoInfoResult::try_from(elem).unwrap_err();
385 let message = match error {
386 Error::ParseError(string) => string,
387 _ => panic!(),
388 };
389 assert_eq!(message, "There must be at least one feature in disco#info.");
390
391 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();
392 let error = DiscoInfoResult::try_from(elem).unwrap_err();
393 let message = match error {
394 Error::ParseError(string) => string,
395 _ => panic!(),
396 };
397 assert_eq!(message, "disco#info feature not present in disco#info.");
398 }
399
400 #[test]
401 fn test_simple_items() {
402 let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#items'/>".parse().unwrap();
403 let query = DiscoItemsQuery::try_from(elem).unwrap();
404 assert!(query.node.is_none());
405
406 let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#items' node='coucou'/>".parse().unwrap();
407 let query = DiscoItemsQuery::try_from(elem).unwrap();
408 assert_eq!(query.node, Some(String::from("coucou")));
409 }
410
411 #[test]
412 fn test_simple_items_result() {
413 let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#items'/>".parse().unwrap();
414 let query = DiscoItemsResult::try_from(elem).unwrap();
415 assert!(query.node.is_none());
416 assert!(query.items.is_empty());
417
418 let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#items' node='coucou'/>".parse().unwrap();
419 let query = DiscoItemsResult::try_from(elem).unwrap();
420 assert_eq!(query.node, Some(String::from("coucou")));
421 assert!(query.items.is_empty());
422 }
423
424 #[test]
425 fn test_answers_items_result() {
426 let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#items'><item jid='component'/><item jid='component2' node='test' name='A component'/></query>".parse().unwrap();
427 let query = DiscoItemsResult::try_from(elem).unwrap();
428 assert_eq!(query.items.len(), 2);
429 assert_eq!(query.items[0].jid, Jid::from_str("component").unwrap());
430 assert_eq!(query.items[0].node, None);
431 assert_eq!(query.items[0].name, None);
432 assert_eq!(query.items[1].jid, Jid::from_str("component2").unwrap());
433 assert_eq!(query.items[1].node, Some(String::from("test")));
434 assert_eq!(query.items[1].name, Some(String::from("A component")));
435 }
436}