From 134255d1de9180b792da6afc869785e3a15945d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Sch=C3=A4fer?= Date: Sat, 5 Apr 2025 13:46:03 +0200 Subject: [PATCH] parsers: port data_forms to use derive macros --- 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(-) diff --git a/parsers/ChangeLog b/parsers/ChangeLog index 47d79f2aec002a20b51bb6339b632edc596ef8c9..fb142a2efb7b610000b11a9f7d344e26e40beff1 100644 --- a/parsers/ChangeLog +++ b/parsers/ChangeLog @@ -68,6 +68,12 @@ XXXX-YY-ZZ RELEASER 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) diff --git a/parsers/src/caps.rs b/parsers/src/caps.rs index 7182b5a491ccb5137837a8e32fc3e35008f54f23..ecb4853001cca3b59220136c64d1dba0d0c137b4 100644 --- a/parsers/src/caps.rs +++ b/parsers/src/caps.rs @@ -97,7 +97,7 @@ fn compute_identities(identities: &[Identity]) -> Vec { fn compute_extensions(extensions: &[DataForm]) -> Vec { 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![] diff --git a/parsers/src/data_forms.rs b/parsers/src/data_forms.rs index d2b0a6a1097b2614e4ac86777240af6b08fae129..93533f1e4dd2a74fed4113769ca59b6a5832ee05 100644 --- a/parsers/src/data_forms.rs +++ b/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, /// 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, /// 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, /// A list of allowed values. + #[xml(child(n = ..))] pub options: Vec, /// The values provided for this field. + #[xml(extract(n = .., name = "value", fields(text(type_ = String))))] pub values: Vec, /// A list of media related to this field. + #[xml(child(n = ..))] pub media: Vec, /// Validation rules for this field. + #[xml(child(default))] pub validate: Option, } @@ -173,92 +190,6 @@ impl Field { } } -impl TryFrom for Field { - type Error = FromElementError; - - fn try_from(elem: Element) -> Result { - 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 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, - /// The title of this form. + #[xml(extract(fields(text(type_ = String)), default))] pub title: Option, /// The instructions given with this form. + #[xml(extract(fields(text(type_ = String)), default))] pub instructions: Option, /// A list of fields comprising this form. + #[xml(child(n = ..))] pub fields: Vec, } -impl DataForm { - /// Create a new DataForm. - pub fn new(type_: DataFormType, form_type: &str, fields: Vec) -> 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 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 { - 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) -> 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; - fn from_events( - qname: rxml::QName, - attrs: rxml::AttrMap, - ) -> Result { - 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 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, 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 = "".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 = "".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 = "".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 = "".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 = "foo".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 = "foo".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 = "foo".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 = "foo".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 = "foo".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 = "foo".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), }; diff --git a/parsers/src/disco.rs b/parsers/src/disco.rs index 1cf51063e3f30f6cd03c6ebd0b6b331c8a8a5083..9e4957a18171a55e98d24b218dfc76c964e25246 100644 --- a/parsers/src/disco.rs +++ b/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); diff --git a/parsers/src/ecaps2.rs b/parsers/src/ecaps2.rs index 04f4c0998be98019511c3d5b934f46c246f538e4..f6882af01b2bcf95e03d5b174794367740a11e6f 100644 --- a/parsers/src/ecaps2.rs +++ b/parsers/src/ecaps2.rs @@ -44,10 +44,14 @@ fn compute_item(field: &str) -> Vec { bytes } -fn compute_items Vec>(things: &[T], separator: u8, encode: F) -> Vec { +fn compute_items<'x, T: 'x, I: IntoIterator, F: Fn(&'x T) -> Vec>( + things: I, + separator: u8, + encode: F, +) -> Vec { let mut string: Vec = vec![]; let mut accumulator: Vec> = 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 { fn compute_extensions(extensions: &[DataForm]) -> Result, 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 })) } diff --git a/parsers/src/ibr.rs b/parsers/src/ibr.rs index 820762769706e009b11572a1e72c42297328e48e..e475b60f3ed242cc523206a1da976066265ef30d 100644 --- a/parsers/src/ibr.rs +++ b/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] diff --git a/parsers/src/mam.rs b/parsers/src/mam.rs index 731cc422b3b2c1198eb456e2112b054c307cc3d5..d1435c1fd0467b0a832c2adb93350c09fc8b9bb7 100644 --- a/parsers/src/mam.rs +++ b/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); diff --git a/parsers/src/media_element.rs b/parsers/src/media_element.rs index d17b0da190aa9a7dc0c6c2ec090a6e067f3df71c..1a1b364e0c101410738d88b726ed6eba118a0677 100644 --- a/parsers/src/media_element.rs +++ b/parsers/src/media_element.rs @@ -227,7 +227,6 @@ mod tests { #[test] fn test_xep_ex2() { let elem: Element = r#" - [ ... ] cid:sha1+f24030b8d91d233bac14777be5ab531ca3b9f102@bob.xmpp.org - [ ... ] "# .parse() .unwrap(); diff --git a/parsers/src/pubsub/pubsub.rs b/parsers/src/pubsub/pubsub.rs index 406fe98980792b4ebe16174a80288de9c78585c9..aa31c9122d234c3309ff7dc66dd2700b14484d08 100644 --- a/parsers/src/pubsub/pubsub.rs +++ b/parsers/src/pubsub/pubsub.rs @@ -691,7 +691,7 @@ mod tests { let elem: Element = "http://jabber.org/protocol/pubsub#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" ); } diff --git a/parsers/src/server_info.rs b/parsers/src/server_info.rs index 3595155fe006d74e727bf4fab511d3abfcd0157e..fa59e4f3eddf5a480085b119fe0b8be1f3c291d0 100644 --- a/parsers/src/server_info.rs +++ b/parsers/src/server_info.rs @@ -36,11 +36,14 @@ impl TryFrom 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 for ServerInfo { impl From 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 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 } }