use xso::{error::Error, AsXml, FromXml};
use crate::data_forms_validate::Validate;
use crate::media_element::MediaElement;
use crate::ns;
#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
#[xml(namespace = ns::DATA_FORMS, name = "option")]
pub struct Option_ {
#[xml(attribute(default))]
pub label: Option<String>,
#[xml(extract(fields(text)))]
pub value: String,
}
generate_attribute!(
FieldType, "type", {
Boolean => "boolean",
Fixed => "fixed",
Hidden => "hidden",
JidMulti => "jid-multi",
JidSingle => "jid-single",
ListMulti => "list-multi",
ListSingle => "list-single",
TextMulti => "text-multi",
TextPrivate => "text-private",
TextSingle => "text-single",
}, 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."));
}
if !field.is_list() && field.options.len() > 0 {
return Err(Error::Other("Option element found in non-list field."));
}
Ok(())
}
#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
#[xml(namespace = ns::DATA_FORMS, name = "field", deserialize_callback = validate_field)]
pub struct Field {
#[xml(attribute(default))]
pub var: Option<String>,
#[xml(attribute(name = "type", default))]
pub type_: FieldType,
#[xml(attribute(default))]
pub label: Option<String>,
#[xml(flag)]
pub required: bool,
#[xml(extract(default, fields(text(type_ = String))))]
pub desc: Option<String>,
#[xml(child(n = ..))]
pub options: Vec<Option_>,
#[xml(extract(n = .., name = "value", fields(text(type_ = String))))]
pub values: Vec<String>,
#[xml(child(n = ..))]
pub media: Vec<MediaElement>,
#[xml(child(default))]
pub validate: Option<Validate>,
}
impl Field {
pub fn new(var: &str, type_: FieldType) -> Field {
Field {
var: Some(String::from(var)),
type_,
label: None,
required: false,
desc: None,
options: Vec::new(),
media: Vec::new(),
values: Vec::new(),
validate: None,
}
}
pub fn with_value(mut self, value: &str) -> Field {
self.values.push(String::from(value));
self
}
pub fn text_single(var: &str, value: &str) -> Field {
Field::new(var, FieldType::TextSingle).with_value(value)
}
fn is_list(&self) -> bool {
self.type_ == FieldType::ListSingle || self.type_ == FieldType::ListMulti
}
pub fn is_form_type(&self, ty: &DataFormType) -> bool {
if self.var.as_deref() != Some("FORM_TYPE") {
return false;
}
match ty {
DataFormType::Form | DataFormType::Result_ => self.type_ == FieldType::Hidden,
DataFormType::Submit => matches!(self.type_, FieldType::Hidden | FieldType::TextSingle),
DataFormType::Cancel => false,
}
}
}
generate_attribute!(
DataFormType, "type", {
Cancel => "cancel",
Form => "form",
Result_ => "result",
Submit => "submit",
}
);
#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
#[xml(namespace = ns::DATA_FORMS, name = "x", deserialize_callback = patch_form)]
pub struct DataForm {
#[xml(attribute = "type")]
pub type_: DataFormType,
#[xml(extract(fields(text(type_ = String)), default))]
pub title: Option<String>,
#[xml(extract(fields(text(type_ = String)), default))]
pub instructions: Option<String>,
#[xml(child(n = ..))]
pub fields: Vec<Field>,
}
fn patch_form(form: &mut DataForm) -> Result<(), Error> {
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."));
}
form_type_index = Some(i);
}
}
if let Some(index) = form_type_index {
let field = form.fields.remove(index);
form.fields.insert(0, field);
}
Ok(())
}
impl DataForm {
pub fn new(type_: DataFormType, form_type: &str, fields: Vec<Field>) -> DataForm {
let mut form = DataForm {
type_,
title: None,
instructions: None,
fields,
};
form.set_form_type(form_type.to_owned());
form
}
pub fn form_type(&self) -> Option<&str> {
for field in self.fields.iter() {
if field.is_form_type(&self.type_) {
return field.values.first().map(|x| x.as_str());
}
}
None
}
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");
}
let mut index = None;
for (i, field) in self.fields.iter().enumerate() {
if field.is_form_type(&self.type_) {
index = Some(i);
break;
}
}
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
}
}
#[cfg(test)]
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]
fn test_size() {
assert_size!(Option_, 24);
assert_size!(FieldType, 1);
assert_size!(Field, 140);
assert_size!(DataFormType, 1);
assert_size!(DataForm, 40);
}
#[cfg(target_pointer_width = "64")]
#[test]
fn test_size() {
assert_size!(Option_, 48);
assert_size!(FieldType, 1);
assert_size!(Field, 264);
assert_size!(DataFormType, 1);
assert_size!(DataForm, 80);
}
#[test]
fn test_simple() {
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.fields.is_empty());
}
#[test]
fn test_missing_var() {
let elem: Element =
"<x xmlns='jabber:x:data' type='form'><field type='text-single' label='The name of your bot'/></x>"
.parse()
.unwrap();
let error = DataForm::try_from(elem).unwrap_err();
let message = match error {
FromElementError::Invalid(Error::Other(string)) => string,
_ => panic!(),
};
assert_eq!(message, "Required attribute 'var' missing.");
}
#[test]
fn test_fixed_field() {
let elem: Element =
"<x xmlns='jabber:x:data' type='form'><field type='fixed'><value>Section 1: Bot Info</value></field></x>"
.parse()
.unwrap();
let form = DataForm::try_from(elem).unwrap();
assert_eq!(form.type_, DataFormType::Form);
assert!(form.form_type().is_none());
assert_eq!(
form.fields,
vec![Field {
var: None,
type_: FieldType::Fixed,
label: None,
required: false,
desc: None,
options: vec![],
values: vec!["Section 1: Bot Info".to_string()],
media: vec![],
validate: None,
}]
);
}
#[test]
fn test_desc() {
let elem: Element =
"<x xmlns='jabber:x:data' type='form'><field type='jid-multi' label='People to invite' var='invitelist'><desc>Tell all your friends about your new bot!</desc></field></x>"
.parse()
.unwrap();
let form = DataForm::try_from(elem).unwrap();
assert_eq!(form.type_, DataFormType::Form);
assert!(form.form_type().is_none());
assert_eq!(
form.fields,
vec![Field {
var: Some("invitelist".to_string()),
type_: FieldType::JidMulti,
label: Some("People to invite".to_string()),
required: false,
desc: Some("Tell all your friends about your new bot!".to_string()),
options: vec![],
values: vec![],
media: vec![],
validate: None,
}]
);
}
#[test]
fn test_validate() {
let elem: Element = r#"<x xmlns='jabber:x:data' type='form'>
<field var='evt.date' type='text-single' label='Event Date/Time'>
<validate xmlns='http://jabber.org/protocol/xdata-validate' datatype='xs:dateTime'/>
<value>2003-10-06T11:22:00-07:00</value>
</field>
</x>"#
.parse()
.unwrap();
let form = DataForm::try_from(elem).unwrap();
assert_eq!(form.type_, DataFormType::Form);
assert!(form.form_type().is_none());
assert_eq!(
form.fields,
vec![Field {
var: Some("evt.date".to_string()),
type_: FieldType::TextSingle,
label: Some("Event Date/Time".to_string()),
required: false,
desc: None,
options: vec![],
values: vec!["2003-10-06T11:22:00-07:00".to_string()],
media: vec![],
validate: Some(Validate {
datatype: Some(Datatype::DateTime),
method: None,
list_range: None,
}),
}]
);
}
#[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();
let error = DataForm::try_from(elem).unwrap_err();
let message = match error {
FromElementError::Invalid(Error::Other(string)) => string,
_ => panic!(),
};
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();
let message = match error {
FromElementError::Invalid(Error::TextParseError(string)) => string,
other => panic!("unexpected result: {:?}", other),
};
assert_eq!(message.to_string(), "Unknown value for 'type' attribute.");
}
#[test]
fn test_wrong_child() {
let elem: Element = "<x xmlns='jabber:x:data' type='cancel'><coucou/></x>"
.parse()
.unwrap();
let error = DataForm::try_from(elem).unwrap_err();
let message = match error {
FromElementError::Invalid(Error::Other(string)) => string,
_ => panic!(),
};
assert_eq!(message, "Unknown child in DataForm element.");
}
#[test]
fn option() {
let elem: Element =
"<option xmlns='jabber:x:data' label='Coucou !'><value>coucou</value></option>"
.parse()
.unwrap();
let option = Option_::try_from(elem).unwrap();
assert_eq!(&option.label.unwrap(), "Coucou !");
assert_eq!(&option.value, "coucou");
let elem: Element = "<option xmlns='jabber:x:data' label='Coucou !'/>"
.parse()
.unwrap();
let error = Option_::try_from(elem).unwrap_err();
let message = match error {
FromElementError::Invalid(Error::Other(string)) => string,
_ => panic!(),
};
assert_eq!(message, "Missing child field 'value' in Option_ element.");
let elem: Element = "<option xmlns='jabber:x:data' label='Coucou !'><value>coucou</value><value>error</value></option>".parse().unwrap();
let error = Option_::try_from(elem).unwrap_err();
let message = match error {
FromElementError::Invalid(Error::Other(string)) => string,
_ => panic!(),
};
assert_eq!(
message,
"Option_ element must not have more than one child in field 'value'."
);
}
#[test]
fn test_ignore_form_type_field_if_field_type_mismatches_in_form_typed_forms() {
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() {
None => (),
other => panic!("unexpected extracted form type: {:?}", other),
};
}
other => panic!("unexpected result: {:?}", other),
}
}
#[test]
fn test_ignore_form_type_field_if_field_type_mismatches_in_result_typed_forms() {
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() {
None => (),
other => panic!("unexpected extracted form type: {:?}", other),
};
}
other => panic!("unexpected result: {:?}", other),
}
}
#[test]
fn test_accept_form_type_field_without_type_attribute_in_submit_typed_forms() {
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() {
Some(ty) => assert_eq!(ty, "foo"),
other => panic!("unexpected extracted form type: {:?}", other),
};
}
other => panic!("unexpected result: {:?}", other),
}
}
#[test]
fn test_accept_form_type_field_with_type_hidden_in_submit_typed_forms() {
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() {
Some(ty) => assert_eq!(ty, "foo"),
other => panic!("unexpected extracted form type: {:?}", other),
};
}
other => panic!("unexpected result: {:?}", other),
}
}
#[test]
fn test_accept_form_type_field_with_type_hidden_in_result_typed_forms() {
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() {
Some(ty) => assert_eq!(ty, "foo"),
other => panic!("unexpected extracted form type: {:?}", other),
};
}
other => panic!("unexpected result: {:?}", other),
}
}
#[test]
fn test_accept_form_type_field_with_type_hidden_in_form_typed_forms() {
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() {
Some(ty) => assert_eq!(ty, "foo"),
other => panic!("unexpected extracted form type: {:?}", other),
};
}
other => panic!("unexpected result: {:?}", other),
}
}
}