parsers: port data_forms to use derive macros

Jonas SchΓ€fer created

Change summary

parsers/ChangeLog            |   6 
parsers/src/caps.rs          |   2 
parsers/src/data_forms.rs    | 333 ++++++++++++++-----------------------
parsers/src/disco.rs         |   2 
parsers/src/ecaps2.rs        |  39 ++-
parsers/src/ibr.rs           |   4 
parsers/src/mam.rs           |   4 
parsers/src/media_element.rs |   2 
parsers/src/pubsub/pubsub.rs |   2 
parsers/src/server_info.rs   |  12 
10 files changed, 176 insertions(+), 230 deletions(-)

Detailed changes

parsers/ChangeLog πŸ”—

@@ -68,6 +68,12 @@ XXXX-YY-ZZ RELEASER <admin@example.com>
         it to/from chrono::DateTime values. The numbered member `0` does not
         exist anymore (!551).
       - stanza_error::StanzaError has been ported to use xso (!552).
+      - data_forms has been ported to use xso. This introduces a couple subtle
+        breaking changes for data_forms::DataForm, in particular:
+
+          - the `from_type` member was replaced by a pair of functions.
+          - if the form contains a FORM_TYPE field, it will be part of the
+            `fields` member (unlike before, where that field was removed).
     * New parsers/serialisers:
       - Stream Features (RFC 6120) (!400)
       - Spam Reporting (XEP-0377) (!506)

parsers/src/caps.rs πŸ”—

@@ -97,7 +97,7 @@ fn compute_identities(identities: &[Identity]) -> Vec<u8> {
 fn compute_extensions(extensions: &[DataForm]) -> Vec<u8> {
     compute_items(extensions, |extension| {
         // TODO: maybe handle the error case?
-        let mut bytes = if let Some(ref form_type) = extension.form_type {
+        let mut bytes = if let Some(ref form_type) = extension.form_type() {
             form_type.as_bytes().to_vec()
         } else {
             vec![]

parsers/src/data_forms.rs πŸ”—

@@ -4,16 +4,11 @@
 // License, v. 2.0. If a copy of the MPL was not distributed with this
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-use xso::{
-    error::{Error, FromElementError, FromEventsError},
-    exports::rxml,
-    minidom_compat, AsXml, FromXml,
-};
+use xso::{error::Error, AsXml, FromXml};
 
 use crate::data_forms_validate::Validate;
 use crate::media_element::MediaElement;
 use crate::ns;
-use minidom::Element;
 
 /// Represents one of the possible values for a list- field.
 #[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
@@ -73,34 +68,56 @@ generate_attribute!(
     }, Default = TextSingle
 );
 
+fn validate_field(field: &mut Field) -> Result<(), Error> {
+    if field.type_ != FieldType::Fixed && field.var.is_none() {
+        return Err(Error::Other("Required attribute 'var' missing.").into());
+    }
+
+    if !field.is_list() && field.options.len() > 0 {
+        return Err(Error::Other("Option element found in non-list field.").into());
+    }
+
+    Ok(())
+}
+
 /// Represents a field in a [data form](struct.DataForm.html).
-#[derive(Debug, Clone, PartialEq)]
+#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
+#[xml(namespace = ns::DATA_FORMS, name = "field", deserialize_callback = validate_field)]
 pub struct Field {
     /// The unique identifier for this field, in the form.
+    #[xml(attribute(default))]
     pub var: Option<String>,
 
     /// The type of this field.
+    #[xml(attribute(name = "type", default))]
     pub type_: FieldType,
 
     /// The label to be possibly displayed to the user for this field.
+    #[xml(attribute(default))]
     pub label: Option<String>,
 
     /// The form will be rejected if this field isn’t present.
+    #[xml(flag)]
     pub required: bool,
 
     /// The natural-language description of the field, intended for presentation in a user-agent
+    #[xml(extract(default, fields(text(type_ = String))))]
     pub desc: Option<String>,
 
     /// A list of allowed values.
+    #[xml(child(n = ..))]
     pub options: Vec<Option_>,
 
     /// The values provided for this field.
+    #[xml(extract(n = .., name = "value", fields(text(type_ = String))))]
     pub values: Vec<String>,
 
     /// A list of media related to this field.
+    #[xml(child(n = ..))]
     pub media: Vec<MediaElement>,
 
     /// Validation rules for this field.
+    #[xml(child(default))]
     pub validate: Option<Validate>,
 }
 
@@ -173,92 +190,6 @@ impl Field {
     }
 }
 
-impl TryFrom<Element> for Field {
-    type Error = FromElementError;
-
-    fn try_from(elem: Element) -> Result<Field, FromElementError> {
-        check_self!(elem, "field", DATA_FORMS);
-        check_no_unknown_attributes!(elem, "field", ["label", "type", "var"]);
-        let mut field = Field {
-            var: get_attr!(elem, "var", Option),
-            type_: get_attr!(elem, "type", Default),
-            label: get_attr!(elem, "label", Option),
-            required: false,
-            desc: None,
-            options: vec![],
-            values: vec![],
-            media: vec![],
-            validate: None,
-        };
-
-        if field.type_ != FieldType::Fixed && field.var.is_none() {
-            return Err(Error::Other("Required attribute 'var' missing.").into());
-        }
-
-        for element in elem.children() {
-            if element.is("value", ns::DATA_FORMS) {
-                check_no_children!(element, "value");
-                check_no_attributes!(element, "value");
-                field.values.push(element.text());
-            } else if element.is("required", ns::DATA_FORMS) {
-                if field.required {
-                    return Err(Error::Other("More than one required element.").into());
-                }
-                check_no_children!(element, "required");
-                check_no_attributes!(element, "required");
-                field.required = true;
-            } else if element.is("option", ns::DATA_FORMS) {
-                if !field.is_list() {
-                    return Err(Error::Other("Option element found in non-list field.").into());
-                }
-                let option = Option_::try_from(element.clone())?;
-                field.options.push(option);
-            } else if element.is("media", ns::MEDIA_ELEMENT) {
-                let media_element = MediaElement::try_from(element.clone())?;
-                field.media.push(media_element);
-            } else if element.is("desc", ns::DATA_FORMS) {
-                check_no_children!(element, "desc");
-                check_no_attributes!(element, "desc");
-                field.desc = Some(element.text());
-            } else if element.is("validate", ns::XDATA_VALIDATE) {
-                if field.validate.is_some() {
-                    return Err(Error::Other("More than one validate element in field.").into());
-                }
-                field.validate = Some(Validate::try_from(element.clone())?);
-            } else {
-                return Err(
-                    Error::Other("Field child isn’t a value, option or media element.").into(),
-                );
-            }
-        }
-        Ok(field)
-    }
-}
-
-impl From<Field> for Element {
-    fn from(field: Field) -> Element {
-        Element::builder("field", ns::DATA_FORMS)
-            .attr("var", field.var)
-            .attr("type", field.type_)
-            .attr("label", field.label)
-            .append_all(if field.required {
-                Some(Element::builder("required", ns::DATA_FORMS))
-            } else {
-                None
-            })
-            .append_all(field.options.iter().cloned().map(Element::from))
-            .append_all(
-                field
-                    .values
-                    .into_iter()
-                    .map(|value| Element::builder("value", ns::DATA_FORMS).append(value)),
-            )
-            .append_all(field.media.iter().cloned().map(Element::from))
-            .append_all(field.validate)
-            .build()
-    }
-}
-
 generate_attribute!(
     /// Represents the type of a [data form](struct.DataForm.html).
     DataFormType, "type", {
@@ -278,132 +209,112 @@ generate_attribute!(
 );
 
 /// This is a form to be sent to another entity for filling.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
+#[xml(namespace = ns::DATA_FORMS, name = "x", deserialize_callback = patch_form)]
 pub struct DataForm {
     /// The type of this form, telling the other party which action to execute.
+    #[xml(attribute = "type")]
     pub type_: DataFormType,
 
-    /// An easy accessor for the FORM_TYPE of this form, see
-    /// [XEP-0068](https://xmpp.org/extensions/xep-0068.html) for more
-    /// information.
-    pub form_type: Option<String>,
-
     /// The title of this form.
+    #[xml(extract(fields(text(type_ = String)), default))]
     pub title: Option<String>,
 
     /// The instructions given with this form.
+    #[xml(extract(fields(text(type_ = String)), default))]
     pub instructions: Option<String>,
 
     /// A list of fields comprising this form.
+    #[xml(child(n = ..))]
     pub fields: Vec<Field>,
 }
 
-impl DataForm {
-    /// Create a new DataForm.
-    pub fn new(type_: DataFormType, form_type: &str, fields: Vec<Field>) -> DataForm {
-        DataForm {
-            type_,
-            form_type: Some(String::from(form_type)),
-            title: None,
-            instructions: None,
-            fields,
+fn patch_form(form: &mut DataForm) -> Result<(), Error> {
+    // Sort the FORM_TYPE field, if any, to the first position.
+    let mut form_type_index = None;
+    for (i, field) in form.fields.iter().enumerate() {
+        if field.is_form_type(&form.type_) {
+            if form_type_index.is_some() {
+                return Err(Error::Other("More than one FORM_TYPE in a data form."));
+            }
+
+            if field.values.len() != 1 {
+                return Err(Error::Other("Wrong number of values in FORM_TYPE.").into());
+            }
+
+            form_type_index = Some(i);
         }
     }
-}
 
-impl TryFrom<Element> for DataForm {
-    type Error = FromElementError;
+    if let Some(index) = form_type_index {
+        let field = form.fields.remove(index);
+        form.fields.insert(0, field);
+    }
+    Ok(())
+}
 
-    fn try_from(elem: Element) -> Result<DataForm, FromElementError> {
-        check_self!(elem, "x", DATA_FORMS);
-        check_no_unknown_attributes!(elem, "x", ["type"]);
-        let type_ = get_attr!(elem, "type", Required);
+impl DataForm {
+    /// Create a new DataForm.
+    pub fn new(type_: DataFormType, form_type: &str, fields: Vec<Field>) -> DataForm {
         let mut form = DataForm {
             type_,
-            form_type: None,
             title: None,
             instructions: None,
-            fields: vec![],
+            fields,
         };
-        for child in elem.children() {
-            if child.is("title", ns::DATA_FORMS) {
-                if form.title.is_some() {
-                    return Err(Error::Other("More than one title in form element.").into());
-                }
-                check_no_children!(child, "title");
-                check_no_attributes!(child, "title");
-                form.title = Some(child.text());
-            } else if child.is("instructions", ns::DATA_FORMS) {
-                if form.instructions.is_some() {
-                    return Err(Error::Other("More than one instructions in form element.").into());
-                }
-                check_no_children!(child, "instructions");
-                check_no_attributes!(child, "instructions");
-                form.instructions = Some(child.text());
-            } else if child.is("field", ns::DATA_FORMS) {
-                let field = Field::try_from(child.clone())?;
-                if field.is_form_type(&form.type_) {
-                    let mut field = field;
-                    if form.form_type.is_some() {
-                        return Err(Error::Other("More than one FORM_TYPE in a data form.").into());
-                    }
-                    if field.values.len() != 1 {
-                        return Err(Error::Other("Wrong number of values in FORM_TYPE.").into());
-                    }
-                    form.form_type = field.values.pop();
-                } else {
-                    form.fields.push(field);
-                }
-            } else {
-                return Err(Error::Other("Unknown child in data form element.").into());
-            }
-        }
-        Ok(form)
+        form.set_form_type(form_type.to_owned());
+        form
     }
-}
-
-impl FromXml for DataForm {
-    type Builder = minidom_compat::FromEventsViaElement<DataForm>;
 
-    fn from_events(
-        qname: rxml::QName,
-        attrs: rxml::AttrMap,
-    ) -> Result<Self::Builder, FromEventsError> {
-        if qname.0 != crate::ns::DATA_FORMS || qname.1 != "x" {
-            return Err(FromEventsError::Mismatch { name: qname, attrs });
+    /// Return the value of the `FORM_TYPE` field, if any.
+    ///
+    /// An easy accessor for the FORM_TYPE of this form, see
+    /// [XEP-0068](https://xmpp.org/extensions/xep-0068.html) for more
+    /// information.
+    pub fn form_type(&self) -> Option<&str> {
+        for field in self.fields.iter() {
+            if field.is_form_type(&self.type_) {
+                return field.values.get(0).map(|x| x.as_str());
+            }
         }
-        Self::Builder::new(qname, attrs)
+        None
     }
-}
 
-impl From<DataForm> for Element {
-    fn from(form: DataForm) -> Element {
-        Element::builder("x", ns::DATA_FORMS)
-            .attr("type", form.type_)
-            .append_all(
-                form.title
-                    .map(|title| Element::builder("title", ns::DATA_FORMS).append(title)),
-            )
-            .append_all(
-                form.instructions
-                    .map(|text| Element::builder("instructions", ns::DATA_FORMS).append(text)),
-            )
-            .append_all(form.form_type.map(|form_type| {
-                Element::builder("field", ns::DATA_FORMS)
-                    .attr("var", "FORM_TYPE")
-                    .attr("type", "hidden")
-                    .append(Element::builder("value", ns::DATA_FORMS).append(form_type))
-            }))
-            .append_all(form.fields.iter().cloned().map(Element::from))
-            .build()
-    }
-}
+    /// Create or modify the `FORM_TYPE` field.
+    ///
+    /// If the form has no `FORM_TYPE` field, this function creates a new
+    /// field and inserts it at the beginning of the field list. Otherwise, it
+    /// returns a mutable reference to the existing field.
+    ///
+    /// # Panics
+    ///
+    /// If the type of the form is [`DataFormType::Cancel`], this function
+    /// panics. Such forms should not have any fields and thus no form type.
+    pub fn set_form_type(&mut self, ty: String) -> &mut Field {
+        if self.type_ == DataFormType::Cancel {
+            panic!("cannot add FORM_TYPE field to type='cancel' form");
+        }
 
-impl AsXml for DataForm {
-    type ItemIter<'x> = minidom_compat::AsItemsViaElement<'x>;
+        // Awkward index enum to work around borrowck limitations.
+        let mut index = None;
+        for (i, field) in self.fields.iter().enumerate() {
+            if field.is_form_type(&self.type_) {
+                index = Some(i);
+                break;
+            }
+        }
 
-    fn as_xml_iter(&self) -> Result<Self::ItemIter<'_>, Error> {
-        minidom_compat::AsItemsViaElement::new(self.clone())
+        let field = if let Some(index) = index {
+            &mut self.fields[index]
+        } else {
+            let field = Field::new("FORM_TYPE", FieldType::Hidden);
+            assert!(field.is_form_type(&self.type_));
+            self.fields.insert(0, field);
+            &mut self.fields[0]
+        };
+        field.values.clear();
+        field.values.push(ty);
+        field
     }
 }
 
@@ -411,6 +322,8 @@ impl AsXml for DataForm {
 mod tests {
     use super::*;
     use crate::data_forms_validate::{Datatype, Validate};
+    use minidom::Element;
+    use xso::error::{Error, FromElementError};
 
     #[cfg(target_pointer_width = "32")]
     #[test]
@@ -419,7 +332,7 @@ mod tests {
         assert_size!(FieldType, 1);
         assert_size!(Field, 140);
         assert_size!(DataFormType, 1);
-        assert_size!(DataForm, 52);
+        assert_size!(DataForm, 40);
     }
 
     #[cfg(target_pointer_width = "64")]
@@ -429,7 +342,7 @@ mod tests {
         assert_size!(FieldType, 1);
         assert_size!(Field, 264);
         assert_size!(DataFormType, 1);
-        assert_size!(DataForm, 104);
+        assert_size!(DataForm, 80);
     }
 
     #[test]
@@ -437,7 +350,7 @@ mod tests {
         let elem: Element = "<x xmlns='jabber:x:data' type='result'/>".parse().unwrap();
         let form = DataForm::try_from(elem).unwrap();
         assert_eq!(form.type_, DataFormType::Result_);
-        assert!(form.form_type.is_none());
+        assert!(form.form_type().is_none());
         assert!(form.fields.is_empty());
     }
 
@@ -463,7 +376,7 @@ mod tests {
                 .unwrap();
         let form = DataForm::try_from(elem).unwrap();
         assert_eq!(form.type_, DataFormType::Form);
-        assert!(form.form_type.is_none());
+        assert!(form.form_type().is_none());
         assert_eq!(
             form.fields,
             vec![Field {
@@ -488,7 +401,7 @@ mod tests {
                 .unwrap();
         let form = DataForm::try_from(elem).unwrap();
         assert_eq!(form.type_, DataFormType::Form);
-        assert!(form.form_type.is_none());
+        assert!(form.form_type().is_none());
         assert_eq!(
             form.fields,
             vec![Field {
@@ -517,7 +430,7 @@ mod tests {
             .unwrap();
         let form = DataForm::try_from(elem).unwrap();
         assert_eq!(form.type_, DataFormType::Form);
-        assert!(form.form_type.is_none());
+        assert!(form.form_type().is_none());
         assert_eq!(
             form.fields,
             vec![Field {
@@ -538,6 +451,17 @@ mod tests {
         );
     }
 
+    #[test]
+    fn test_invalid_field() {
+        let elem: Element = "<field xmlns='jabber:x:data' type='text-single' var='foo'><option><value>foo</value></option></field>".parse().unwrap();
+        let error = Field::try_from(elem).unwrap_err();
+        let message = match error {
+            FromElementError::Invalid(Error::Other(string)) => string,
+            _ => panic!(),
+        };
+        assert_eq!(message, "Option element found in non-list field.");
+    }
+
     #[test]
     fn test_invalid() {
         let elem: Element = "<x xmlns='jabber:x:data'/>".parse().unwrap();
@@ -546,7 +470,10 @@ mod tests {
             FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
-        assert_eq!(message, "Required attribute 'type' missing.");
+        assert_eq!(
+            message,
+            "Required attribute field 'type_' on DataForm element missing."
+        );
 
         let elem: Element = "<x xmlns='jabber:x:data' type='coucou'/>".parse().unwrap();
         let error = DataForm::try_from(elem).unwrap_err();
@@ -567,7 +494,7 @@ mod tests {
             FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
-        assert_eq!(message, "Unknown child in data form element.");
+        assert_eq!(message, "Unknown child in DataForm element.");
     }
 
     #[test]
@@ -609,7 +536,7 @@ mod tests {
         let elem: Element = "<x xmlns='jabber:x:data' type='form'><field var='FORM_TYPE' type='text-single'><value>foo</value></field></x>".parse().unwrap();
         match DataForm::try_from(elem) {
             Ok(form) => {
-                match form.form_type {
+                match form.form_type() {
                     None => (),
                     other => panic!("unexpected extracted form type: {:?}", other),
                 };
@@ -625,7 +552,7 @@ mod tests {
         let elem: Element = "<x xmlns='jabber:x:data' type='result'><field var='FORM_TYPE' type='text-single'><value>foo</value></field></x>".parse().unwrap();
         match DataForm::try_from(elem) {
             Ok(form) => {
-                match form.form_type {
+                match form.form_type() {
                     None => (),
                     other => panic!("unexpected extracted form type: {:?}", other),
                 };
@@ -639,7 +566,7 @@ mod tests {
         let elem: Element = "<x xmlns='jabber:x:data' type='submit'><field var='FORM_TYPE'><value>foo</value></field></x>".parse().unwrap();
         match DataForm::try_from(elem) {
             Ok(form) => {
-                match form.form_type {
+                match form.form_type() {
                     Some(ty) => assert_eq!(ty, "foo"),
                     other => panic!("unexpected extracted form type: {:?}", other),
                 };
@@ -653,7 +580,7 @@ mod tests {
         let elem: Element = "<x xmlns='jabber:x:data' type='submit'><field var='FORM_TYPE' type='hidden'><value>foo</value></field></x>".parse().unwrap();
         match DataForm::try_from(elem) {
             Ok(form) => {
-                match form.form_type {
+                match form.form_type() {
                     Some(ty) => assert_eq!(ty, "foo"),
                     other => panic!("unexpected extracted form type: {:?}", other),
                 };
@@ -667,7 +594,7 @@ mod tests {
         let elem: Element = "<x xmlns='jabber:x:data' type='result'><field var='FORM_TYPE' type='hidden'><value>foo</value></field></x>".parse().unwrap();
         match DataForm::try_from(elem) {
             Ok(form) => {
-                match form.form_type {
+                match form.form_type() {
                     Some(ty) => assert_eq!(ty, "foo"),
                     other => panic!("unexpected extracted form type: {:?}", other),
                 };
@@ -681,7 +608,7 @@ mod tests {
         let elem: Element = "<x xmlns='jabber:x:data' type='form'><field var='FORM_TYPE' type='hidden'><value>foo</value></field></x>".parse().unwrap();
         match DataForm::try_from(elem) {
             Ok(form) => {
-                match form.form_type {
+                match form.form_type() {
                     Some(ty) => assert_eq!(ty, "foo"),
                     other => panic!("unexpected extracted form type: {:?}", other),
                 };

parsers/src/disco.rs πŸ”—

@@ -251,7 +251,7 @@ mod tests {
         assert_eq!(query.identities.len(), 1);
         assert_eq!(query.features.len(), 1);
         assert_eq!(query.extensions.len(), 1);
-        assert_eq!(query.extensions[0].form_type, Some(String::from("example")));
+        assert_eq!(query.extensions[0].form_type(), Some("example"));
 
         let elem2 = query.into();
         assert_eq!(elem1, elem2);

parsers/src/ecaps2.rs πŸ”—

@@ -44,10 +44,14 @@ fn compute_item(field: &str) -> Vec<u8> {
     bytes
 }
 
-fn compute_items<T, F: Fn(&T) -> Vec<u8>>(things: &[T], separator: u8, encode: F) -> Vec<u8> {
+fn compute_items<'x, T: 'x, I: IntoIterator<Item = &'x T>, F: Fn(&'x T) -> Vec<u8>>(
+    things: I,
+    separator: u8,
+    encode: F,
+) -> Vec<u8> {
     let mut string: Vec<u8> = vec![];
     let mut accumulator: Vec<Vec<u8>> = vec![];
-    for thing in things {
+    for thing in things.into_iter() {
         let bytes = encode(thing);
         accumulator.push(bytes);
     }
@@ -81,30 +85,37 @@ fn compute_identities(identities: &[Identity]) -> Vec<u8> {
 
 fn compute_extensions(extensions: &[DataForm]) -> Result<Vec<u8>, Error> {
     for extension in extensions {
-        if extension.form_type.is_none() {
+        if extension.form_type().is_none() {
             return Err(Error::Other("Missing FORM_TYPE in extension."));
         }
     }
     Ok(compute_items(extensions, 0x1c, |extension| {
         let mut bytes = compute_item("FORM_TYPE");
         bytes.append(&mut compute_item(
-            if let Some(ref form_type) = extension.form_type {
+            if let Some(ref form_type) = extension.form_type() {
                 form_type
             } else {
                 unreachable!()
             },
         ));
         bytes.push(0x1e);
-        bytes.append(&mut compute_items(&extension.fields, 0x1d, |field| {
-            let mut bytes = vec![];
-            if let Some(var) = &field.var {
-                bytes.append(&mut compute_item(var));
-            }
-            bytes.append(&mut compute_items(&field.values, 0x1e, |value| {
-                compute_item(value)
-            }));
-            bytes
-        }));
+        bytes.append(&mut compute_items(
+            extension
+                .fields
+                .iter()
+                .filter(|field| field.var.as_deref().unwrap_or("") != "FORM_TYPE"),
+            0x1d,
+            |field| {
+                let mut bytes = vec![];
+                if let Some(var) = &field.var {
+                    bytes.append(&mut compute_item(var));
+                }
+                bytes.append(&mut compute_items(&field.values, 0x1e, |value| {
+                    compute_item(value)
+                }));
+                bytes
+            },
+        ));
         bytes
     }))
 }

parsers/src/ibr.rs πŸ”—

@@ -119,13 +119,13 @@ mod tests {
     #[cfg(target_pointer_width = "32")]
     #[test]
     fn test_size() {
-        assert_size!(Query, 68);
+        assert_size!(Query, 56);
     }
 
     #[cfg(target_pointer_width = "64")]
     #[test]
     fn test_size() {
-        assert_size!(Query, 136);
+        assert_size!(Query, 112);
     }
 
     #[test]

parsers/src/mam.rs πŸ”—

@@ -149,7 +149,7 @@ mod tests {
     #[test]
     fn test_size() {
         assert_size!(QueryId, 12);
-        assert_size!(Query, 120);
+        assert_size!(Query, 108);
         assert_size!(Result_, 164);
         assert_size!(Fin, 44);
         assert_size!(Start, 28);
@@ -162,7 +162,7 @@ mod tests {
     #[test]
     fn test_size() {
         assert_size!(QueryId, 24);
-        assert_size!(Query, 240);
+        assert_size!(Query, 216);
         assert_size!(Result_, 312);
         assert_size!(Fin, 88);
         assert_size!(Start, 40);

parsers/src/media_element.rs πŸ”—

@@ -227,7 +227,6 @@ mod tests {
     #[test]
     fn test_xep_ex2() {
         let elem: Element = r#"<x xmlns='jabber:x:data' type='form'>
-  [ ... ]
   <field var='ocr'>
     <media xmlns='urn:xmpp:media-element'
            height='80'
@@ -236,7 +235,6 @@ mod tests {
       <uri type='image/jpeg'>cid:sha1+f24030b8d91d233bac14777be5ab531ca3b9f102@bob.xmpp.org</uri>
     </media>
   </field>
-  [ ... ]
 </x>"#
             .parse()
             .unwrap();

parsers/src/pubsub/pubsub.rs πŸ”—

@@ -691,7 +691,7 @@ mod tests {
         let elem: Element = "<publish-options xmlns='http://jabber.org/protocol/pubsub'><x xmlns='jabber:x:data' type='submit'><field var='FORM_TYPE' type='hidden'><value>http://jabber.org/protocol/pubsub#publish-options</value></field></x></publish-options>".parse().unwrap();
         let publish_options = PublishOptions::try_from(elem).unwrap();
         assert_eq!(
-            &publish_options.form.unwrap().form_type.unwrap(),
+            publish_options.form.unwrap().form_type().unwrap(),
             "http://jabber.org/protocol/pubsub#publish-options"
         );
     }

parsers/src/server_info.rs πŸ”—

@@ -36,11 +36,14 @@ impl TryFrom<DataForm> for ServerInfo {
         if form.type_ != DataFormType::Result_ {
             return Err(Error::Other("Wrong type of form."));
         }
-        if form.form_type != Some(String::from(ns::SERVER_INFO)) {
+        if form.form_type() != Some(ns::SERVER_INFO) {
             return Err(Error::Other("Wrong FORM_TYPE for form."));
         }
         let mut server_info = ServerInfo::default();
         for field in form.fields {
+            if field.var.as_deref().unwrap_or("") == "FORM_TYPE" {
+                continue;
+            }
             if field.type_ != FieldType::ListMulti {
                 return Err(Error::Other("Field is not of the required type."));
             }
@@ -67,9 +70,8 @@ impl TryFrom<DataForm> for ServerInfo {
 
 impl From<ServerInfo> for DataForm {
     fn from(server_info: ServerInfo) -> DataForm {
-        DataForm {
+        let mut form = DataForm {
             type_: DataFormType::Result_,
-            form_type: Some(String::from(ns::SERVER_INFO)),
             title: None,
             instructions: None,
             fields: vec![
@@ -80,7 +82,9 @@ impl From<ServerInfo> for DataForm {
                 generate_address_field("security-addresses", server_info.security),
                 generate_address_field("support-addresses", server_info.support),
             ],
-        }
+        };
+        form.set_form_type(ns::SERVER_INFO.to_owned());
+        form
     }
 }