diff --git a/parsers/ChangeLog b/parsers/ChangeLog index 4b02c7fb24fbccf1a2638a3747bd468aa0294c03..51d039255809c72b1cd267cceb1b4f6269936b64 100644 --- a/parsers/ChangeLog +++ b/parsers/ChangeLog @@ -4,6 +4,9 @@ XXXX-YY-ZZ RELEASER - The type of jingle_rtp::Description::ssrc has been changed from Option to Option, in accordance with XEP-0167 version 1.2.3 and RFC 3550. (!602) + - The ibr::Query struct has been split into FieldsQuery, RemoveQuery, + LegacyQuery and FormsQuery, to make it reflect more how it gets + used in XMPP. * Improvements: - Make Priority’s inner i8 pub, which had been broken since the conversion to xso. (!632) diff --git a/parsers/src/ibr.rs b/parsers/src/ibr.rs index e475b60f3ed242cc523206a1da976066265ef30d..fa0ac5f772aada9cf6658364f6fe9bc639b5685d 100644 --- a/parsers/src/ibr.rs +++ b/parsers/src/ibr.rs @@ -4,134 +4,177 @@ // 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::{AsXml, FromXml}; + use crate::data_forms::DataForm; use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload}; use crate::ns; -use alloc::collections::BTreeMap; -use minidom::Element; -use xso::error::{Error, FromElementError}; -/// Query for registering against a service. -#[derive(Debug, Clone)] -pub struct Query { - /// Deprecated fixed list of possible fields to fill before the user can - /// register. - pub fields: BTreeMap, +/// Entity requests registration fields from host. +#[derive(FromXml, AsXml, Debug, Clone)] +#[xml(namespace = ns::REGISTER, name = "query")] +pub struct FieldsQuery; - /// Whether this account is already registered. - pub registered: bool, +impl IqGetPayload for FieldsQuery {} - /// Whether to remove this account. +/// Entity requests cancellation of existing registration. +#[derive(FromXml, AsXml, Debug, Clone)] +#[xml(namespace = ns::REGISTER, name = "query")] +pub struct RemoveQuery { + /// Whether to remove that registration. + // TODO: we should be able to remove that flag and make this type a zero-sized type. + #[xml(flag)] pub remove: bool, +} + +impl IqSetPayload for RemoveQuery {} + +/// Query for registering against a service, the legacy way. +#[derive(FromXml, AsXml, Debug, Clone)] +#[xml(namespace = ns::REGISTER, name = "query")] +pub struct LegacyQuery { + /// Whether this account is already registered + #[xml(flag)] + pub registered: bool, + + /// Instructions to be presented to entities implementing this legacy element. + #[xml(extract(default, fields(text(type_ = String))))] + pub instructions: Option, + + /// Account name associated with the user + #[xml(extract(default, fields(text(type_ = String))))] + pub username: Option, + + /// Familiar name of the user + #[xml(extract(default, fields(text(type_ = String))))] + pub nick: Option, + + /// Password or secret for the user + #[xml(extract(default, fields(text(type_ = String))))] + pub password: Option, + + /// Full name of the user + #[xml(extract(default, fields(text(type_ = String))))] + pub name: Option, + + /// Given name of the user + #[xml(extract(default, fields(text(type_ = String))))] + pub first: Option, + + /// Family name of the user + #[xml(extract(default, fields(text(type_ = String))))] + pub last: Option, + + /// Email address of the user + #[xml(extract(default, fields(text(type_ = String))))] + pub email: Option, + + /// Street portion of a physical or mailing address + #[xml(extract(default, fields(text(type_ = String))))] + pub address: Option, + + /// Locality portion of a physical or mailing address + #[xml(extract(default, fields(text(type_ = String))))] + pub city: Option, + + /// Region portion of a physical or mailing address + #[xml(extract(default, fields(text(type_ = String))))] + pub state: Option, + + /// Postal code portion of a physical or mailing address + #[xml(extract(default, fields(text(type_ = String))))] + pub zip: Option, + + /// Telephone number of the user + #[xml(extract(default, fields(text(type_ = String))))] + pub phone: Option, + + /// URL to web page describing the user + #[xml(extract(default, fields(text(type_ = String))))] + pub url: Option, + + /// Some date (e.g., birth date, hire date, sign-up date) + #[xml(extract(default, fields(text(type_ = String))))] + pub date: Option, + + /// Free-form text field (obsolete) + #[xml(extract(default, fields(text(type_ = String))))] + pub misc: Option, + + /// Free-form text field (obsolete) + #[xml(extract(default, fields(text(type_ = String))))] + pub text: Option, + + /// Session key for transaction (obsolete) + #[xml(extract(default, fields(text(type_ = String))))] + pub key: Option, +} + +impl IqSetPayload for LegacyQuery {} +impl IqResultPayload for LegacyQuery {} + +/// Query for registering against a service. +#[derive(FromXml, AsXml, Debug, Clone)] +#[xml(namespace = ns::REGISTER, name = "query")] +pub struct FormQuery { + /// Legacy instructions fallback for entities which don’t understand the form. + #[xml(extract(default, fields(text(type_ = String))))] + pub instructions: Option, /// A data form the user must fill before being allowed to register. - pub form: Option, + #[xml(child)] + pub form: DataForm, // Not yet implemented. //pub oob: Option, } -impl IqGetPayload for Query {} -impl IqSetPayload for Query {} -impl IqResultPayload for Query {} - -impl TryFrom for Query { - type Error = FromElementError; - - fn try_from(elem: Element) -> Result { - check_self!(elem, "query", REGISTER, "IBR query"); - let mut query = Query { - registered: false, - fields: BTreeMap::new(), - remove: false, - form: None, - }; - for child in elem.children() { - let namespace = child.ns(); - if namespace == ns::REGISTER { - let name = child.name(); - let fields = vec![ - "address", - "city", - "date", - "email", - "first", - "instructions", - "key", - "last", - "misc", - "name", - "nick", - "password", - "phone", - "state", - "text", - "url", - "username", - "zip", - ]; - if fields.binary_search(&name).is_ok() { - query.fields.insert(name.to_owned(), child.text()); - } else if name == "registered" { - query.registered = true; - } else if name == "remove" { - query.remove = true; - } else { - return Err(Error::Other("Wrong field in ibr element.").into()); - } - } else if child.is("x", ns::DATA_FORMS) { - query.form = Some(DataForm::try_from(child.clone())?); - } else { - return Err(Error::Other("Unknown child in ibr element.").into()); - } - } - Ok(query) - } -} - -impl From for Element { - fn from(query: Query) -> Element { - Element::builder("query", ns::REGISTER) - .append_all(if query.registered { - Some(Element::builder("registered", ns::REGISTER)) - } else { - None - }) - .append_all( - query - .fields - .into_iter() - .map(|(name, value)| Element::builder(name, ns::REGISTER).append(value)), - ) - .append_all(if query.remove { - Some(Element::builder("remove", ns::REGISTER)) - } else { - None - }) - .append_all(query.form.map(Element::from)) - .build() - } -} +impl IqSetPayload for FormQuery {} +impl IqResultPayload for FormQuery {} #[cfg(test)] mod tests { use super::*; + use crate::data_forms::DataFormType; + use minidom::Element; #[cfg(target_pointer_width = "32")] #[test] fn test_size() { - assert_size!(Query, 56); + assert_size!(RemoveQuery, 1); + assert_size!(LegacyQuery, 220); + assert_size!(FormQuery, 52); } #[cfg(target_pointer_width = "64")] #[test] fn test_size() { - assert_size!(Query, 112); + assert_size!(RemoveQuery, 1); + assert_size!(LegacyQuery, 440); + assert_size!(FormQuery, 104); } #[test] fn test_simple() { let elem: Element = "".parse().unwrap(); - Query::try_from(elem).unwrap(); + FieldsQuery::try_from(elem).unwrap(); + + let elem: Element = "" + .parse() + .unwrap(); + let query = RemoveQuery::try_from(elem).unwrap(); + assert_eq!(query.remove, true); + + let elem: Element = "".parse().unwrap(); + let query = LegacyQuery::try_from(elem).unwrap(); + assert_eq!(query.first, None); + + let elem: Element = + "" + .parse() + .unwrap(); + let query = FormQuery::try_from(elem).unwrap(); + assert!(query.instructions.is_none()); + assert!(query.form.fields.is_empty()); } #[test] @@ -148,13 +191,13 @@ mod tests { "# .parse() .unwrap(); - let query = Query::try_from(elem).unwrap(); + let query = LegacyQuery::try_from(elem).unwrap(); assert_eq!(query.registered, false); - assert_eq!(query.fields["instructions"], "\n Choose a username and password for use with this service.\n Please also provide your email address.\n "); - assert_eq!(query.fields["username"], ""); - assert_eq!(query.fields["password"], ""); - assert_eq!(query.fields["email"], ""); - assert_eq!(query.fields.contains_key("name"), false); + assert_eq!(query.instructions.unwrap(), "\n Choose a username and password for use with this service.\n Please also provide your email address.\n "); + assert_eq!(query.username.unwrap(), ""); + assert_eq!(query.password.unwrap(), ""); + assert_eq!(query.email.unwrap(), ""); + assert_eq!(query.name, None); // FIXME: HashMap doesn’t keep the order right. //let elem2 = query.into(); @@ -167,11 +210,14 @@ mod tests { .parse() .unwrap(); let elem1 = elem.clone(); - let query = Query::try_from(elem).unwrap(); - assert_eq!(query.registered, false); - assert!(!query.fields["instructions"].is_empty()); - let form = query.form.clone().unwrap(); - assert!(!form.instructions.unwrap().is_empty()); + let query = FormQuery::try_from(elem).unwrap(); + assert!(query.instructions.is_some()); + let form = query.form.clone(); + assert_eq!( + form.instructions.unwrap(), + "Please provide the following information to sign up for our special contests!" + ); + assert_eq!(form.type_, DataFormType::Form); let elem2 = query.into(); assert_eq!(elem1, elem2); } @@ -182,11 +228,7 @@ mod tests { .parse() .unwrap(); let elem1 = elem.clone(); - let query = Query::try_from(elem).unwrap(); - assert_eq!(query.registered, false); - for _ in &query.fields { - panic!(); - } + let query = FormQuery::try_from(elem).unwrap(); let elem2 = query.into(); assert_eq!(elem1, elem2); }