data_forms.rs

  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;
  8use std::str::FromStr;
  9
 10use minidom::{Element, IntoAttributeValue};
 11
 12use error::Error;
 13use ns;
 14
 15use media_element::MediaElement;
 16
 17#[derive(Debug, Clone)]
 18pub struct Option_ {
 19    pub label: Option<String>,
 20    pub value: String,
 21}
 22
 23impl TryFrom<Element> for Option_ {
 24    type Err = Error;
 25
 26    fn try_from(elem: Element) -> Result<Option_, Error> {
 27        check_self!(elem, "option", ns::DATA_FORMS);
 28        check_no_unknown_attributes!(elem, "option", ["label"]);
 29        let mut value = None;
 30        for child in elem.children() {
 31            if !child.is("value", ns::DATA_FORMS) {
 32                return Err(Error::ParseError("Non-value element in option element"));
 33            }
 34            if value.is_some() {
 35                return Err(Error::ParseError("More than one value element in option element"));
 36            }
 37            value = Some(child.text());
 38        }
 39        Ok(Option_ {
 40            label: get_attr!(elem, "label", optional),
 41            value: value.ok_or(Error::ParseError("No value element in option element"))?,
 42        })
 43    }
 44}
 45
 46impl From<Option_> for Element {
 47    fn from(option: Option_) -> Element {
 48        Element::builder("option")
 49                .ns(ns::DATA_FORMS)
 50                .attr("label", option.label)
 51                .append(Element::builder("value")
 52                                .ns(ns::DATA_FORMS)
 53                                .append(option.value)
 54                                .build())
 55                .build()
 56    }
 57}
 58
 59generate_attribute!(FieldType, "type", {
 60    Boolean => "boolean",
 61    Fixed => "fixed",
 62    Hidden => "hidden",
 63    JidMulti => "jid-multi",
 64    JidSingle => "jid-single",
 65    ListMulti => "list-multi",
 66    ListSingle => "list-single",
 67    TextMulti => "text-multi",
 68    TextPrivate => "text-private",
 69    TextSingle => "text-single",
 70}, Default = TextSingle);
 71
 72#[derive(Debug, Clone)]
 73pub struct Field {
 74    pub var: String,
 75    pub type_: FieldType,
 76    pub label: Option<String>,
 77    pub required: bool,
 78    pub options: Vec<Option_>,
 79    pub values: Vec<String>,
 80    pub media: Vec<MediaElement>,
 81}
 82
 83impl Field {
 84    fn is_list(&self) -> bool {
 85        self.type_ == FieldType::ListSingle ||
 86        self.type_ == FieldType::ListMulti
 87    }
 88}
 89
 90impl TryFrom<Element> for Field {
 91    type Err = Error;
 92
 93    fn try_from(elem: Element) -> Result<Field, Error> {
 94        check_self!(elem, "field", ns::DATA_FORMS);
 95        check_no_unknown_attributes!(elem, "field", ["label", "type", "var"]);
 96        let mut field = Field {
 97            var: get_attr!(elem, "var", required),
 98            type_: get_attr!(elem, "type", default),
 99            label: get_attr!(elem, "label", optional),
100            required: false,
101            options: vec!(),
102            values: vec!(),
103            media: vec!(),
104        };
105        for element in elem.children() {
106            if element.is("value", ns::DATA_FORMS) {
107                check_no_children!(element, "value");
108                check_no_attributes!(element, "value");
109                field.values.push(element.text());
110            } else if element.is("required", ns::DATA_FORMS) {
111                if field.required {
112                    return Err(Error::ParseError("More than one required element."));
113                }
114                check_no_children!(element, "required");
115                check_no_attributes!(element, "required");
116                field.required = true;
117            } else if element.is("option", ns::DATA_FORMS) {
118                if !field.is_list() {
119                    return Err(Error::ParseError("Option element found in non-list field."));
120                }
121                let option = Option_::try_from(element.clone())?;
122                field.options.push(option);
123            } else if element.is("media", ns::MEDIA_ELEMENT) {
124                let media_element = MediaElement::try_from(element.clone())?;
125                field.media.push(media_element);
126            } else {
127                return Err(Error::ParseError("Field child isn’t a value, option or media element."));
128            }
129        }
130        Ok(field)
131    }
132}
133
134impl From<Field> for Element {
135    fn from(field: Field) -> Element {
136        Element::builder("field")
137                .ns(ns::DATA_FORMS)
138                .attr("var", field.var)
139                .attr("type", field.type_)
140                .attr("label", field.label)
141                .append(if field.required { Some(Element::builder("required").ns(ns::DATA_FORMS).build()) } else { None })
142                .append(field.options)
143                .append(field.values.into_iter().map(|value| {
144                     Element::builder("value").ns(ns::DATA_FORMS).append(value).build()
145                 }).collect::<Vec<_>>())
146                .append(field.media)
147                .build()
148    }
149}
150
151generate_attribute!(DataFormType, "type", {
152    Cancel => "cancel",
153    Form => "form",
154    Result_ => "result",
155    Submit => "submit",
156});
157
158#[derive(Debug, Clone)]
159pub struct DataForm {
160    pub type_: DataFormType,
161    pub form_type: Option<String>,
162    pub title: Option<String>,
163    pub instructions: Option<String>,
164    pub fields: Vec<Field>,
165}
166
167impl TryFrom<Element> for DataForm {
168    type Err = Error;
169
170    fn try_from(elem: Element) -> Result<DataForm, Error> {
171        check_self!(elem, "x", ns::DATA_FORMS);
172        check_no_unknown_attributes!(elem, "x", ["type"]);
173        let type_ = get_attr!(elem, "type", required);
174        let mut form = DataForm {
175            type_: type_,
176            form_type: None,
177            title: None,
178            instructions: None,
179            fields: vec!(),
180        };
181        for child in elem.children() {
182            if child.is("title", ns::DATA_FORMS) {
183                if form.title.is_some() {
184                    return Err(Error::ParseError("More than one title in form element."));
185                }
186                check_no_children!(child, "title");
187                check_no_attributes!(child, "title");
188                form.title = Some(child.text());
189            } else if child.is("instructions", ns::DATA_FORMS) {
190                if form.instructions.is_some() {
191                    return Err(Error::ParseError("More than one instructions in form element."));
192                }
193                check_no_children!(child, "instructions");
194                check_no_attributes!(child, "instructions");
195                form.instructions = Some(child.text());
196            } else if child.is("field", ns::DATA_FORMS) {
197                let field = Field::try_from(child.clone())?;
198                if field.var == "FORM_TYPE" && field.type_ == FieldType::Hidden {
199                    if form.form_type.is_some() {
200                        return Err(Error::ParseError("More than one FORM_TYPE in a data form."));
201                    }
202                    if field.values.len() != 1 {
203                        return Err(Error::ParseError("Wrong number of values in FORM_TYPE."));
204                    }
205                    form.form_type = Some(field.values[0].clone());
206                }
207                form.fields.push(field);
208            } else {
209                return Err(Error::ParseError("Unknown child in data form element."));
210            }
211        }
212        Ok(form)
213    }
214}
215
216impl From<DataForm> for Element {
217    fn from(form: DataForm) -> Element {
218        Element::builder("x")
219                .ns(ns::DATA_FORMS)
220                .attr("type", form.type_)
221                .append(form.title.map(|title| Element::builder("title").ns(ns::DATA_FORMS).append(title)))
222                .append(form.instructions.map(|text| Element::builder("instructions").ns(ns::DATA_FORMS).append(text)))
223                .append(form.fields)
224                .build()
225    }
226}
227
228#[cfg(test)]
229mod tests {
230    use super::*;
231
232    #[test]
233    fn test_simple() {
234        let elem: Element = "<x xmlns='jabber:x:data' type='result'/>".parse().unwrap();
235        let form = DataForm::try_from(elem).unwrap();
236        assert_eq!(form.type_, DataFormType::Result_);
237        assert!(form.form_type.is_none());
238        assert!(form.fields.is_empty());
239    }
240
241    #[test]
242    fn test_invalid() {
243        let elem: Element = "<x xmlns='jabber:x:data'/>".parse().unwrap();
244        let error = DataForm::try_from(elem).unwrap_err();
245        let message = match error {
246            Error::ParseError(string) => string,
247            _ => panic!(),
248        };
249        assert_eq!(message, "Required attribute 'type' missing.");
250
251        let elem: Element = "<x xmlns='jabber:x:data' type='coucou'/>".parse().unwrap();
252        let error = DataForm::try_from(elem).unwrap_err();
253        let message = match error {
254            Error::ParseError(string) => string,
255            _ => panic!(),
256        };
257        assert_eq!(message, "Unknown value for 'type' attribute.");
258    }
259
260    #[test]
261    fn test_wrong_child() {
262        let elem: Element = "<x xmlns='jabber:x:data' type='cancel'><coucou/></x>".parse().unwrap();
263        let error = DataForm::try_from(elem).unwrap_err();
264        let message = match error {
265            Error::ParseError(string) => string,
266            _ => panic!(),
267        };
268        assert_eq!(message, "Unknown child in data form element.");
269    }
270}