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;
8use std::str::FromStr;
9
10use minidom::{Element, IntoElements, IntoAttributeValue, ElementEmitter};
11
12use error::Error;
13use ns;
14
15use media_element::MediaElement;
16
17#[derive(Debug, Clone, PartialEq)]
18pub enum FieldType {
19 Boolean,
20 Fixed,
21 Hidden,
22 JidMulti,
23 JidSingle,
24 ListMulti,
25 ListSingle,
26 TextMulti,
27 TextPrivate,
28 TextSingle,
29}
30
31impl Default for FieldType {
32 fn default() -> FieldType {
33 FieldType::TextSingle
34 }
35}
36
37impl FromStr for FieldType {
38 type Err = Error;
39
40 fn from_str(s: &str) -> Result<FieldType, Error> {
41 Ok(match s {
42 "boolean" => FieldType::Boolean,
43 "fixed" => FieldType::Fixed,
44 "hidden" => FieldType::Hidden,
45 "jid-multi" => FieldType::JidMulti,
46 "jid-single" => FieldType::JidSingle,
47 "list-multi" => FieldType::ListMulti,
48 "list-single" => FieldType::ListSingle,
49 "text-multi" => FieldType::TextMulti,
50 "text-private" => FieldType::TextPrivate,
51 "text-single" => FieldType::TextSingle,
52
53 _ => return Err(Error::ParseError("Invalid 'type' attribute in field element.")),
54 })
55 }
56}
57
58impl IntoAttributeValue for FieldType {
59 fn into_attribute_value(self) -> Option<String> {
60 Some(String::from(match self {
61 FieldType::Boolean => "boolean",
62 FieldType::Fixed => "fixed",
63 FieldType::Hidden => "hidden",
64 FieldType::JidMulti => "jid-multi",
65 FieldType::JidSingle => "jid-single",
66 FieldType::ListMulti => "list-multi",
67 FieldType::ListSingle => "list-single",
68 FieldType::TextMulti => "text-multi",
69 FieldType::TextPrivate => "text-private",
70 FieldType::TextSingle => "text-single",
71 }))
72 }
73}
74
75#[derive(Debug, Clone)]
76pub struct Option_ {
77 pub label: Option<String>,
78 pub value: String,
79}
80
81impl From<Option_> for Element {
82 fn from(option: Option_) -> Element {
83 Element::builder("option")
84 .ns(ns::DATA_FORMS)
85 .attr("label", option.label)
86 .append(Element::builder("value")
87 .ns(ns::DATA_FORMS)
88 .append(option.value)
89 .build())
90 .build()
91 }
92}
93
94impl IntoElements for Option_ {
95 fn into_elements(self, emitter: &mut ElementEmitter) {
96 emitter.append_child(self.into());
97 }
98}
99
100#[derive(Debug, Clone)]
101pub struct Field {
102 pub var: String,
103 pub type_: FieldType,
104 pub label: Option<String>,
105 pub required: bool,
106 pub options: Vec<Option_>,
107 pub values: Vec<String>,
108 pub media: Vec<MediaElement>,
109}
110
111impl From<Field> for Element {
112 fn from(field: Field) -> Element {
113 Element::builder("field")
114 .ns(ns::DATA_FORMS)
115 .attr("var", field.var)
116 .attr("type", field.type_)
117 .attr("label", field.label)
118 .append(if field.required { Some(Element::builder("required").ns(ns::DATA_FORMS).build()) } else { None })
119 .append(field.options)
120 .append(field.values.iter().map(|value| {
121 Element::builder("value").ns(ns::DATA_FORMS).append(value).build()
122 }).collect::<Vec<_>>())
123 .append(field.media)
124 .build()
125 }
126}
127
128impl IntoElements for Field {
129 fn into_elements(self, emitter: &mut ElementEmitter) {
130 emitter.append_child(self.into());
131 }
132}
133
134#[derive(Debug, Clone, PartialEq)]
135pub enum DataFormType {
136 Cancel,
137 Form,
138 Result_,
139 Submit,
140}
141
142impl FromStr for DataFormType {
143 type Err = Error;
144
145 fn from_str(s: &str) -> Result<DataFormType, Error> {
146 Ok(match s {
147 "cancel" => DataFormType::Cancel,
148 "form" => DataFormType::Form,
149 "result" => DataFormType::Result_,
150 "submit" => DataFormType::Submit,
151
152 _ => return Err(Error::ParseError("Unknown data form type.")),
153 })
154 }
155}
156
157impl IntoAttributeValue for DataFormType {
158 fn into_attribute_value(self) -> Option<String> {
159 Some(String::from(match self {
160 DataFormType::Cancel => "cancel",
161 DataFormType::Form => "form",
162 DataFormType::Result_ => "result",
163 DataFormType::Submit => "submit",
164 }))
165 }
166}
167
168#[derive(Debug, Clone)]
169pub struct DataForm {
170 pub type_: DataFormType,
171 pub form_type: Option<String>,
172 pub title: Option<String>,
173 pub instructions: Option<String>,
174 pub fields: Vec<Field>,
175}
176
177impl TryFrom<Element> for DataForm {
178 type Error = Error;
179
180 fn try_from(elem: Element) -> Result<DataForm, Error> {
181 if !elem.is("x", ns::DATA_FORMS) {
182 return Err(Error::ParseError("This is not a data form element."));
183 }
184 let type_ = get_attr!(elem, "type", required);
185 let mut form = DataForm {
186 type_: type_,
187 form_type: None,
188 title: None,
189 instructions: None,
190 fields: vec!(),
191 };
192 for child in elem.children() {
193 if child.is("title", ns::DATA_FORMS) {
194 if form.title.is_some() {
195 return Err(Error::ParseError("More than one title in form element."));
196 }
197 for _ in child.children() {
198 return Err(Error::ParseError("Title element must not have any child."));
199 }
200 for _ in child.attrs() {
201 return Err(Error::ParseError("Title element must not have any attribute."));
202 }
203 form.title = Some(child.text());
204 } else if child.is("instructions", ns::DATA_FORMS) {
205 if form.instructions.is_some() {
206 return Err(Error::ParseError("More than one instructions in form element."));
207 }
208 for _ in child.children() {
209 return Err(Error::ParseError("instructions element must not have any child."));
210 }
211 for _ in child.attrs() {
212 return Err(Error::ParseError("instructions element must not have any attribute."));
213 }
214 form.instructions = Some(child.text());
215 } else if child.is("field", ns::DATA_FORMS) {
216 let var: String = get_attr!(child, "var", required);
217 let field_type = get_attr!(child, "type", default);
218 let label = get_attr!(child, "label", optional);
219
220 let is_form_type = var == "FORM_TYPE" && field_type == FieldType::Hidden;
221 let is_list = field_type == FieldType::ListSingle || field_type == FieldType::ListMulti;
222 let mut field = Field {
223 var: var,
224 type_: field_type,
225 label: label,
226 required: false,
227 options: vec!(),
228 values: vec!(),
229 media: vec!(),
230 };
231 for element in child.children() {
232 if element.is("value", ns::DATA_FORMS) {
233 for _ in element.children() {
234 return Err(Error::ParseError("Value element must not have any child."));
235 }
236 for _ in element.attrs() {
237 return Err(Error::ParseError("Value element must not have any attribute."));
238 }
239 field.values.push(element.text());
240 } else if element.is("required", ns::DATA_FORMS) {
241 if field.required {
242 return Err(Error::ParseError("More than one required element."));
243 }
244 for _ in element.children() {
245 return Err(Error::ParseError("Required element must not have any child."));
246 }
247 for _ in element.attrs() {
248 return Err(Error::ParseError("Required element must not have any attribute."));
249 }
250 field.required = true;
251 } else if element.is("option", ns::DATA_FORMS) {
252 if !is_list {
253 return Err(Error::ParseError("Option element found in non-list field."));
254 }
255 let label = get_attr!(element, "label", optional);
256 let mut value = None;
257 for child2 in element.children() {
258 if child2.is("value", ns::DATA_FORMS) {
259 if value.is_some() {
260 return Err(Error::ParseError("More than one value element in option element"));
261 }
262 value = Some(child2.text());
263 } else {
264 return Err(Error::ParseError("Non-value element in option element"));
265 }
266 }
267 let value = value.ok_or(Error::ParseError("No value element in option element"))?;
268 field.options.push(Option_ {
269 label: label,
270 value: value,
271 });
272 } else if element.is("media", ns::MEDIA_ELEMENT) {
273 match MediaElement::try_from(element.clone()) {
274 Ok(media_element) => field.media.push(media_element),
275 Err(_) => (), // TODO: is it really nice to swallow this error?
276 }
277 } else {
278 return Err(Error::ParseError("Field child isn’t a value or media element."));
279 }
280 }
281 if is_form_type {
282 if form.form_type.is_some() {
283 return Err(Error::ParseError("More than one FORM_TYPE in a data form."));
284 }
285 if field.values.len() != 1 {
286 return Err(Error::ParseError("Wrong number of values in FORM_TYPE."));
287 }
288 form.form_type = Some(field.values[0].clone());
289 }
290 form.fields.push(field);
291 } else {
292 return Err(Error::ParseError("Unknown child in data form element."));
293 }
294 }
295 Ok(form)
296 }
297}
298
299impl From<DataForm> for Element {
300 fn from(form: DataForm) -> Element {
301 Element::builder("x")
302 .ns(ns::DATA_FORMS)
303 .attr("type", form.type_)
304 .append(form.form_type)
305 .append(form.title)
306 .append(form.instructions)
307 .append(form.fields)
308 .build()
309 }
310}
311
312impl IntoElements for DataForm {
313 fn into_elements(self, emitter: &mut ElementEmitter) {
314 emitter.append_child(self.into());
315 }
316}
317
318#[cfg(test)]
319mod tests {
320 use super::*;
321
322 #[test]
323 fn test_simple() {
324 let elem: Element = "<x xmlns='jabber:x:data' type='result'/>".parse().unwrap();
325 let form = DataForm::try_from(elem).unwrap();
326 assert_eq!(form.type_, DataFormType::Result_);
327 assert!(form.form_type.is_none());
328 assert!(form.fields.is_empty());
329 }
330
331 #[test]
332 fn test_invalid() {
333 let elem: Element = "<x xmlns='jabber:x:data'/>".parse().unwrap();
334 let error = DataForm::try_from(elem).unwrap_err();
335 let message = match error {
336 Error::ParseError(string) => string,
337 _ => panic!(),
338 };
339 assert_eq!(message, "Required attribute 'type' missing.");
340
341 let elem: Element = "<x xmlns='jabber:x:data' type='coucou'/>".parse().unwrap();
342 let error = DataForm::try_from(elem).unwrap_err();
343 let message = match error {
344 Error::ParseError(string) => string,
345 _ => panic!(),
346 };
347 assert_eq!(message, "Unknown data form type.");
348 }
349
350 #[test]
351 fn test_wrong_child() {
352 let elem: Element = "<x xmlns='jabber:x:data' type='cancel'><coucou/></x>".parse().unwrap();
353 let error = DataForm::try_from(elem).unwrap_err();
354 let message = match error {
355 Error::ParseError(string) => string,
356 _ => panic!(),
357 };
358 assert_eq!(message, "Unknown child in data form element.");
359 }
360}