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