xmpp-parsers: Split ibr::Query into multiple structs
This introduces FieldsQuery, RemoveQuery, LegacyQuery and FormsQuery, making this module reflect more accurately how it gets used on XMPP.
This commit is contained in:
parent
f6d4ab9e91
commit
d0062d9931
2 changed files with 158 additions and 113 deletions
|
|
@ -4,6 +4,9 @@ XXXX-YY-ZZ RELEASER <admin@example.com>
|
||||||
- The type of jingle_rtp::Description::ssrc has been changed from
|
- The type of jingle_rtp::Description::ssrc has been changed from
|
||||||
Option<String> to Option<u32>, in accordance with XEP-0167 version
|
Option<String> to Option<u32>, in accordance with XEP-0167 version
|
||||||
1.2.3 and RFC 3550. (!602)
|
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:
|
* Improvements:
|
||||||
- Make Priority’s inner i8 pub, which had been broken since the
|
- Make Priority’s inner i8 pub, which had been broken since the
|
||||||
conversion to xso. (!632)
|
conversion to xso. (!632)
|
||||||
|
|
|
||||||
|
|
@ -4,134 +4,177 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
// 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/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
use xso::{AsXml, FromXml};
|
||||||
|
|
||||||
use crate::data_forms::DataForm;
|
use crate::data_forms::DataForm;
|
||||||
use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload};
|
use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload};
|
||||||
use crate::ns;
|
use crate::ns;
|
||||||
use alloc::collections::BTreeMap;
|
|
||||||
use minidom::Element;
|
|
||||||
use xso::error::{Error, FromElementError};
|
|
||||||
|
|
||||||
/// Query for registering against a service.
|
/// Entity requests registration fields from host.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(FromXml, AsXml, Debug, Clone)]
|
||||||
pub struct Query {
|
#[xml(namespace = ns::REGISTER, name = "query")]
|
||||||
/// Deprecated fixed list of possible fields to fill before the user can
|
pub struct FieldsQuery;
|
||||||
/// register.
|
|
||||||
pub fields: BTreeMap<String, String>,
|
|
||||||
|
|
||||||
/// Whether this account is already registered.
|
impl IqGetPayload for FieldsQuery {}
|
||||||
|
|
||||||
|
/// 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,
|
pub registered: bool,
|
||||||
|
|
||||||
/// Whether to remove this account.
|
/// Instructions to be presented to entities implementing this legacy element.
|
||||||
pub remove: bool,
|
#[xml(extract(default, fields(text(type_ = String))))]
|
||||||
|
pub instructions: Option<String>,
|
||||||
|
|
||||||
|
/// Account name associated with the user
|
||||||
|
#[xml(extract(default, fields(text(type_ = String))))]
|
||||||
|
pub username: Option<String>,
|
||||||
|
|
||||||
|
/// Familiar name of the user
|
||||||
|
#[xml(extract(default, fields(text(type_ = String))))]
|
||||||
|
pub nick: Option<String>,
|
||||||
|
|
||||||
|
/// Password or secret for the user
|
||||||
|
#[xml(extract(default, fields(text(type_ = String))))]
|
||||||
|
pub password: Option<String>,
|
||||||
|
|
||||||
|
/// Full name of the user
|
||||||
|
#[xml(extract(default, fields(text(type_ = String))))]
|
||||||
|
pub name: Option<String>,
|
||||||
|
|
||||||
|
/// Given name of the user
|
||||||
|
#[xml(extract(default, fields(text(type_ = String))))]
|
||||||
|
pub first: Option<String>,
|
||||||
|
|
||||||
|
/// Family name of the user
|
||||||
|
#[xml(extract(default, fields(text(type_ = String))))]
|
||||||
|
pub last: Option<String>,
|
||||||
|
|
||||||
|
/// Email address of the user
|
||||||
|
#[xml(extract(default, fields(text(type_ = String))))]
|
||||||
|
pub email: Option<String>,
|
||||||
|
|
||||||
|
/// Street portion of a physical or mailing address
|
||||||
|
#[xml(extract(default, fields(text(type_ = String))))]
|
||||||
|
pub address: Option<String>,
|
||||||
|
|
||||||
|
/// Locality portion of a physical or mailing address
|
||||||
|
#[xml(extract(default, fields(text(type_ = String))))]
|
||||||
|
pub city: Option<String>,
|
||||||
|
|
||||||
|
/// Region portion of a physical or mailing address
|
||||||
|
#[xml(extract(default, fields(text(type_ = String))))]
|
||||||
|
pub state: Option<String>,
|
||||||
|
|
||||||
|
/// Postal code portion of a physical or mailing address
|
||||||
|
#[xml(extract(default, fields(text(type_ = String))))]
|
||||||
|
pub zip: Option<String>,
|
||||||
|
|
||||||
|
/// Telephone number of the user
|
||||||
|
#[xml(extract(default, fields(text(type_ = String))))]
|
||||||
|
pub phone: Option<String>,
|
||||||
|
|
||||||
|
/// URL to web page describing the user
|
||||||
|
#[xml(extract(default, fields(text(type_ = String))))]
|
||||||
|
pub url: Option<String>,
|
||||||
|
|
||||||
|
/// Some date (e.g., birth date, hire date, sign-up date)
|
||||||
|
#[xml(extract(default, fields(text(type_ = String))))]
|
||||||
|
pub date: Option<String>,
|
||||||
|
|
||||||
|
/// Free-form text field (obsolete)
|
||||||
|
#[xml(extract(default, fields(text(type_ = String))))]
|
||||||
|
pub misc: Option<String>,
|
||||||
|
|
||||||
|
/// Free-form text field (obsolete)
|
||||||
|
#[xml(extract(default, fields(text(type_ = String))))]
|
||||||
|
pub text: Option<String>,
|
||||||
|
|
||||||
|
/// Session key for transaction (obsolete)
|
||||||
|
#[xml(extract(default, fields(text(type_ = String))))]
|
||||||
|
pub key: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<String>,
|
||||||
|
|
||||||
/// A data form the user must fill before being allowed to register.
|
/// A data form the user must fill before being allowed to register.
|
||||||
pub form: Option<DataForm>,
|
#[xml(child)]
|
||||||
|
pub form: DataForm,
|
||||||
// Not yet implemented.
|
// Not yet implemented.
|
||||||
//pub oob: Option<Oob>,
|
//pub oob: Option<Oob>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IqGetPayload for Query {}
|
impl IqSetPayload for FormQuery {}
|
||||||
impl IqSetPayload for Query {}
|
impl IqResultPayload for FormQuery {}
|
||||||
impl IqResultPayload for Query {}
|
|
||||||
|
|
||||||
impl TryFrom<Element> for Query {
|
|
||||||
type Error = FromElementError;
|
|
||||||
|
|
||||||
fn try_from(elem: Element) -> Result<Query, FromElementError> {
|
|
||||||
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<Query> 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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::data_forms::DataFormType;
|
||||||
|
use minidom::Element;
|
||||||
|
|
||||||
#[cfg(target_pointer_width = "32")]
|
#[cfg(target_pointer_width = "32")]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_size() {
|
fn test_size() {
|
||||||
assert_size!(Query, 56);
|
assert_size!(RemoveQuery, 1);
|
||||||
|
assert_size!(LegacyQuery, 220);
|
||||||
|
assert_size!(FormQuery, 52);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_pointer_width = "64")]
|
#[cfg(target_pointer_width = "64")]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_size() {
|
fn test_size() {
|
||||||
assert_size!(Query, 112);
|
assert_size!(RemoveQuery, 1);
|
||||||
|
assert_size!(LegacyQuery, 440);
|
||||||
|
assert_size!(FormQuery, 104);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_simple() {
|
fn test_simple() {
|
||||||
let elem: Element = "<query xmlns='jabber:iq:register'/>".parse().unwrap();
|
let elem: Element = "<query xmlns='jabber:iq:register'/>".parse().unwrap();
|
||||||
Query::try_from(elem).unwrap();
|
FieldsQuery::try_from(elem).unwrap();
|
||||||
|
|
||||||
|
let elem: Element = "<query xmlns='jabber:iq:register'><remove/></query>"
|
||||||
|
.parse()
|
||||||
|
.unwrap();
|
||||||
|
let query = RemoveQuery::try_from(elem).unwrap();
|
||||||
|
assert_eq!(query.remove, true);
|
||||||
|
|
||||||
|
let elem: Element = "<query xmlns='jabber:iq:register'/>".parse().unwrap();
|
||||||
|
let query = LegacyQuery::try_from(elem).unwrap();
|
||||||
|
assert_eq!(query.first, None);
|
||||||
|
|
||||||
|
let elem: Element =
|
||||||
|
"<query xmlns='jabber:iq:register'><x xmlns='jabber:x:data' type='submit'/></query>"
|
||||||
|
.parse()
|
||||||
|
.unwrap();
|
||||||
|
let query = FormQuery::try_from(elem).unwrap();
|
||||||
|
assert!(query.instructions.is_none());
|
||||||
|
assert!(query.form.fields.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -148,13 +191,13 @@ mod tests {
|
||||||
"#
|
"#
|
||||||
.parse()
|
.parse()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let query = Query::try_from(elem).unwrap();
|
let query = LegacyQuery::try_from(elem).unwrap();
|
||||||
assert_eq!(query.registered, false);
|
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.instructions.unwrap(), "\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.username.unwrap(), "");
|
||||||
assert_eq!(query.fields["password"], "");
|
assert_eq!(query.password.unwrap(), "");
|
||||||
assert_eq!(query.fields["email"], "");
|
assert_eq!(query.email.unwrap(), "");
|
||||||
assert_eq!(query.fields.contains_key("name"), false);
|
assert_eq!(query.name, None);
|
||||||
|
|
||||||
// FIXME: HashMap doesn’t keep the order right.
|
// FIXME: HashMap doesn’t keep the order right.
|
||||||
//let elem2 = query.into();
|
//let elem2 = query.into();
|
||||||
|
|
@ -167,11 +210,14 @@ mod tests {
|
||||||
.parse()
|
.parse()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let elem1 = elem.clone();
|
let elem1 = elem.clone();
|
||||||
let query = Query::try_from(elem).unwrap();
|
let query = FormQuery::try_from(elem).unwrap();
|
||||||
assert_eq!(query.registered, false);
|
assert!(query.instructions.is_some());
|
||||||
assert!(!query.fields["instructions"].is_empty());
|
let form = query.form.clone();
|
||||||
let form = query.form.clone().unwrap();
|
assert_eq!(
|
||||||
assert!(!form.instructions.unwrap().is_empty());
|
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();
|
let elem2 = query.into();
|
||||||
assert_eq!(elem1, elem2);
|
assert_eq!(elem1, elem2);
|
||||||
}
|
}
|
||||||
|
|
@ -182,11 +228,7 @@ mod tests {
|
||||||
.parse()
|
.parse()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let elem1 = elem.clone();
|
let elem1 = elem.clone();
|
||||||
let query = Query::try_from(elem).unwrap();
|
let query = FormQuery::try_from(elem).unwrap();
|
||||||
assert_eq!(query.registered, false);
|
|
||||||
for _ in &query.fields {
|
|
||||||
panic!();
|
|
||||||
}
|
|
||||||
let elem2 = query.into();
|
let elem2 = query.into();
|
||||||
assert_eq!(elem1, elem2);
|
assert_eq!(elem1, elem2);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue