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