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}