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