1use xso::{
8 error::{Error, FromElementError, FromEventsError},
9 exports::rxml,
10 minidom_compat, AsXml, FromXml,
11};
12
13use crate::data_forms_validate::Validate;
14use crate::media_element::MediaElement;
15use crate::ns;
16use minidom::Element;
17
18#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
20#[xml(namespace = ns::DATA_FORMS, name = "option")]
21pub struct Option_ {
22 #[xml(attribute(default))]
24 pub label: Option<String>,
25
26 #[xml(extract(fields(text)))]
28 pub value: String,
29}
30
31generate_attribute!(
32 FieldType, "type", {
34 Boolean => "boolean",
37
38 Fixed => "fixed",
41
42 Hidden => "hidden",
45
46 JidMulti => "jid-multi",
50
51 JidSingle => "jid-single",
55
56 ListMulti => "list-multi",
59
60 ListSingle => "list-single",
63
64 TextMulti => "text-multi",
66
67 TextPrivate => "text-private",
70
71 TextSingle => "text-single",
73 }, Default = TextSingle
74);
75
76#[derive(Debug, Clone, PartialEq)]
78pub struct Field {
79 pub var: Option<String>,
81
82 pub type_: FieldType,
84
85 pub label: Option<String>,
87
88 pub required: bool,
90
91 pub desc: Option<String>,
93
94 pub options: Vec<Option_>,
96
97 pub values: Vec<String>,
99
100 pub media: Vec<MediaElement>,
102
103 pub validate: Option<Validate>,
105}
106
107impl Field {
108 pub fn new(var: &str, type_: FieldType) -> Field {
110 Field {
111 var: Some(String::from(var)),
112 type_,
113 label: None,
114 required: false,
115 desc: None,
116 options: Vec::new(),
117 media: Vec::new(),
118 values: Vec::new(),
119 validate: None,
120 }
121 }
122
123 pub fn with_value(mut self, value: &str) -> Field {
125 self.values.push(String::from(value));
126 self
127 }
128
129 pub fn text_single(var: &str, value: &str) -> Field {
131 Field::new(var, FieldType::TextSingle).with_value(value)
132 }
133
134 fn is_list(&self) -> bool {
135 self.type_ == FieldType::ListSingle || self.type_ == FieldType::ListMulti
136 }
137
138 pub fn is_form_type(&self, ty: &DataFormType) -> bool {
144 if self.var.as_deref() != Some("FORM_TYPE") {
146 return false;
147 }
148
149 match ty {
150 DataFormType::Form | DataFormType::Result_ => self.type_ == FieldType::Hidden,
155
156 DataFormType::Submit => matches!(self.type_, FieldType::Hidden | FieldType::TextSingle),
165
166 DataFormType::Cancel => false,
172 }
173 }
174}
175
176impl TryFrom<Element> for Field {
177 type Error = FromElementError;
178
179 fn try_from(elem: Element) -> Result<Field, FromElementError> {
180 check_self!(elem, "field", DATA_FORMS);
181 check_no_unknown_attributes!(elem, "field", ["label", "type", "var"]);
182 let mut field = Field {
183 var: get_attr!(elem, "var", Option),
184 type_: get_attr!(elem, "type", Default),
185 label: get_attr!(elem, "label", Option),
186 required: false,
187 desc: None,
188 options: vec![],
189 values: vec![],
190 media: vec![],
191 validate: None,
192 };
193
194 if field.type_ != FieldType::Fixed && field.var.is_none() {
195 return Err(Error::Other("Required attribute 'var' missing.").into());
196 }
197
198 for element in elem.children() {
199 if element.is("value", ns::DATA_FORMS) {
200 check_no_children!(element, "value");
201 check_no_attributes!(element, "value");
202 field.values.push(element.text());
203 } else if element.is("required", ns::DATA_FORMS) {
204 if field.required {
205 return Err(Error::Other("More than one required element.").into());
206 }
207 check_no_children!(element, "required");
208 check_no_attributes!(element, "required");
209 field.required = true;
210 } else if element.is("option", ns::DATA_FORMS) {
211 if !field.is_list() {
212 return Err(Error::Other("Option element found in non-list field.").into());
213 }
214 let option = Option_::try_from(element.clone())?;
215 field.options.push(option);
216 } else if element.is("media", ns::MEDIA_ELEMENT) {
217 let media_element = MediaElement::try_from(element.clone())?;
218 field.media.push(media_element);
219 } else if element.is("desc", ns::DATA_FORMS) {
220 check_no_children!(element, "desc");
221 check_no_attributes!(element, "desc");
222 field.desc = Some(element.text());
223 } else if element.is("validate", ns::XDATA_VALIDATE) {
224 if field.validate.is_some() {
225 return Err(Error::Other("More than one validate element in field.").into());
226 }
227 field.validate = Some(Validate::try_from(element.clone())?);
228 } else {
229 return Err(
230 Error::Other("Field child isn’t a value, option or media element.").into(),
231 );
232 }
233 }
234 Ok(field)
235 }
236}
237
238impl From<Field> for Element {
239 fn from(field: Field) -> Element {
240 Element::builder("field", ns::DATA_FORMS)
241 .attr("var", field.var)
242 .attr("type", field.type_)
243 .attr("label", field.label)
244 .append_all(if field.required {
245 Some(Element::builder("required", ns::DATA_FORMS))
246 } else {
247 None
248 })
249 .append_all(field.options.iter().cloned().map(Element::from))
250 .append_all(
251 field
252 .values
253 .into_iter()
254 .map(|value| Element::builder("value", ns::DATA_FORMS).append(value)),
255 )
256 .append_all(field.media.iter().cloned().map(Element::from))
257 .append_all(field.validate)
258 .build()
259 }
260}
261
262generate_attribute!(
263 DataFormType, "type", {
265 Cancel => "cancel",
267
268 Form => "form",
271
272 Result_ => "result",
274
275 Submit => "submit",
277 }
278);
279
280#[derive(Debug, Clone, PartialEq)]
282pub struct DataForm {
283 pub type_: DataFormType,
285
286 pub form_type: Option<String>,
290
291 pub title: Option<String>,
293
294 pub instructions: Option<String>,
296
297 pub fields: Vec<Field>,
299}
300
301impl DataForm {
302 pub fn new(type_: DataFormType, form_type: &str, fields: Vec<Field>) -> DataForm {
304 DataForm {
305 type_,
306 form_type: Some(String::from(form_type)),
307 title: None,
308 instructions: None,
309 fields,
310 }
311 }
312}
313
314impl TryFrom<Element> for DataForm {
315 type Error = FromElementError;
316
317 fn try_from(elem: Element) -> Result<DataForm, FromElementError> {
318 check_self!(elem, "x", DATA_FORMS);
319 check_no_unknown_attributes!(elem, "x", ["type"]);
320 let type_ = get_attr!(elem, "type", Required);
321 let mut form = DataForm {
322 type_,
323 form_type: None,
324 title: None,
325 instructions: None,
326 fields: vec![],
327 };
328 for child in elem.children() {
329 if child.is("title", ns::DATA_FORMS) {
330 if form.title.is_some() {
331 return Err(Error::Other("More than one title in form element.").into());
332 }
333 check_no_children!(child, "title");
334 check_no_attributes!(child, "title");
335 form.title = Some(child.text());
336 } else if child.is("instructions", ns::DATA_FORMS) {
337 if form.instructions.is_some() {
338 return Err(Error::Other("More than one instructions in form element.").into());
339 }
340 check_no_children!(child, "instructions");
341 check_no_attributes!(child, "instructions");
342 form.instructions = Some(child.text());
343 } else if child.is("field", ns::DATA_FORMS) {
344 let field = Field::try_from(child.clone())?;
345 if field.is_form_type(&form.type_) {
346 let mut field = field;
347 if form.form_type.is_some() {
348 return Err(Error::Other("More than one FORM_TYPE in a data form.").into());
349 }
350 if field.values.len() != 1 {
351 return Err(Error::Other("Wrong number of values in FORM_TYPE.").into());
352 }
353 form.form_type = field.values.pop();
354 } else {
355 form.fields.push(field);
356 }
357 } else {
358 return Err(Error::Other("Unknown child in data form element.").into());
359 }
360 }
361 Ok(form)
362 }
363}
364
365impl FromXml for DataForm {
366 type Builder = minidom_compat::FromEventsViaElement<DataForm>;
367
368 fn from_events(
369 qname: rxml::QName,
370 attrs: rxml::AttrMap,
371 ) -> Result<Self::Builder, FromEventsError> {
372 if qname.0 != crate::ns::DATA_FORMS || qname.1 != "x" {
373 return Err(FromEventsError::Mismatch { name: qname, attrs });
374 }
375 Self::Builder::new(qname, attrs)
376 }
377}
378
379impl From<DataForm> for Element {
380 fn from(form: DataForm) -> Element {
381 Element::builder("x", ns::DATA_FORMS)
382 .attr("type", form.type_)
383 .append_all(
384 form.title
385 .map(|title| Element::builder("title", ns::DATA_FORMS).append(title)),
386 )
387 .append_all(
388 form.instructions
389 .map(|text| Element::builder("instructions", ns::DATA_FORMS).append(text)),
390 )
391 .append_all(form.form_type.map(|form_type| {
392 Element::builder("field", ns::DATA_FORMS)
393 .attr("var", "FORM_TYPE")
394 .attr("type", "hidden")
395 .append(Element::builder("value", ns::DATA_FORMS).append(form_type))
396 }))
397 .append_all(form.fields.iter().cloned().map(Element::from))
398 .build()
399 }
400}
401
402impl AsXml for DataForm {
403 type ItemIter<'x> = minidom_compat::AsItemsViaElement<'x>;
404
405 fn as_xml_iter(&self) -> Result<Self::ItemIter<'_>, Error> {
406 minidom_compat::AsItemsViaElement::new(self.clone())
407 }
408}
409
410#[cfg(test)]
411mod tests {
412 use super::*;
413 use crate::data_forms_validate::{Datatype, Validate};
414
415 #[cfg(target_pointer_width = "32")]
416 #[test]
417 fn test_size() {
418 assert_size!(Option_, 24);
419 assert_size!(FieldType, 1);
420 assert_size!(Field, 140);
421 assert_size!(DataFormType, 1);
422 assert_size!(DataForm, 52);
423 }
424
425 #[cfg(target_pointer_width = "64")]
426 #[test]
427 fn test_size() {
428 assert_size!(Option_, 48);
429 assert_size!(FieldType, 1);
430 assert_size!(Field, 264);
431 assert_size!(DataFormType, 1);
432 assert_size!(DataForm, 104);
433 }
434
435 #[test]
436 fn test_simple() {
437 let elem: Element = "<x xmlns='jabber:x:data' type='result'/>".parse().unwrap();
438 let form = DataForm::try_from(elem).unwrap();
439 assert_eq!(form.type_, DataFormType::Result_);
440 assert!(form.form_type.is_none());
441 assert!(form.fields.is_empty());
442 }
443
444 #[test]
445 fn test_missing_var() {
446 let elem: Element =
447 "<x xmlns='jabber:x:data' type='form'><field type='text-single' label='The name of your bot'/></x>"
448 .parse()
449 .unwrap();
450 let error = DataForm::try_from(elem).unwrap_err();
451 let message = match error {
452 FromElementError::Invalid(Error::Other(string)) => string,
453 _ => panic!(),
454 };
455 assert_eq!(message, "Required attribute 'var' missing.");
456 }
457
458 #[test]
459 fn test_fixed_field() {
460 let elem: Element =
461 "<x xmlns='jabber:x:data' type='form'><field type='fixed'><value>Section 1: Bot Info</value></field></x>"
462 .parse()
463 .unwrap();
464 let form = DataForm::try_from(elem).unwrap();
465 assert_eq!(form.type_, DataFormType::Form);
466 assert!(form.form_type.is_none());
467 assert_eq!(
468 form.fields,
469 vec![Field {
470 var: None,
471 type_: FieldType::Fixed,
472 label: None,
473 required: false,
474 desc: None,
475 options: vec![],
476 values: vec!["Section 1: Bot Info".to_string()],
477 media: vec![],
478 validate: None,
479 }]
480 );
481 }
482
483 #[test]
484 fn test_desc() {
485 let elem: Element =
486 "<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>"
487 .parse()
488 .unwrap();
489 let form = DataForm::try_from(elem).unwrap();
490 assert_eq!(form.type_, DataFormType::Form);
491 assert!(form.form_type.is_none());
492 assert_eq!(
493 form.fields,
494 vec![Field {
495 var: Some("invitelist".to_string()),
496 type_: FieldType::JidMulti,
497 label: Some("People to invite".to_string()),
498 required: false,
499 desc: Some("Tell all your friends about your new bot!".to_string()),
500 options: vec![],
501 values: vec![],
502 media: vec![],
503 validate: None,
504 }]
505 );
506 }
507
508 #[test]
509 fn test_validate() {
510 let elem: Element = r#"<x xmlns='jabber:x:data' type='form'>
511 <field var='evt.date' type='text-single' label='Event Date/Time'>
512 <validate xmlns='http://jabber.org/protocol/xdata-validate' datatype='xs:dateTime'/>
513 <value>2003-10-06T11:22:00-07:00</value>
514 </field>
515 </x>"#
516 .parse()
517 .unwrap();
518 let form = DataForm::try_from(elem).unwrap();
519 assert_eq!(form.type_, DataFormType::Form);
520 assert!(form.form_type.is_none());
521 assert_eq!(
522 form.fields,
523 vec![Field {
524 var: Some("evt.date".to_string()),
525 type_: FieldType::TextSingle,
526 label: Some("Event Date/Time".to_string()),
527 required: false,
528 desc: None,
529 options: vec![],
530 values: vec!["2003-10-06T11:22:00-07:00".to_string()],
531 media: vec![],
532 validate: Some(Validate {
533 datatype: Some(Datatype::DateTime),
534 method: None,
535 list_range: None,
536 }),
537 }]
538 );
539 }
540
541 #[test]
542 fn test_invalid() {
543 let elem: Element = "<x xmlns='jabber:x:data'/>".parse().unwrap();
544 let error = DataForm::try_from(elem).unwrap_err();
545 let message = match error {
546 FromElementError::Invalid(Error::Other(string)) => string,
547 _ => panic!(),
548 };
549 assert_eq!(message, "Required attribute 'type' missing.");
550
551 let elem: Element = "<x xmlns='jabber:x:data' type='coucou'/>".parse().unwrap();
552 let error = DataForm::try_from(elem).unwrap_err();
553 let message = match error {
554 FromElementError::Invalid(Error::TextParseError(string)) => string,
555 other => panic!("unexpected result: {:?}", other),
556 };
557 assert_eq!(message.to_string(), "Unknown value for 'type' attribute.");
558 }
559
560 #[test]
561 fn test_wrong_child() {
562 let elem: Element = "<x xmlns='jabber:x:data' type='cancel'><coucou/></x>"
563 .parse()
564 .unwrap();
565 let error = DataForm::try_from(elem).unwrap_err();
566 let message = match error {
567 FromElementError::Invalid(Error::Other(string)) => string,
568 _ => panic!(),
569 };
570 assert_eq!(message, "Unknown child in data form element.");
571 }
572
573 #[test]
574 fn option() {
575 let elem: Element =
576 "<option xmlns='jabber:x:data' label='Coucou !'><value>coucou</value></option>"
577 .parse()
578 .unwrap();
579 let option = Option_::try_from(elem).unwrap();
580 assert_eq!(&option.label.unwrap(), "Coucou !");
581 assert_eq!(&option.value, "coucou");
582
583 let elem: Element = "<option xmlns='jabber:x:data' label='Coucou !'/>"
584 .parse()
585 .unwrap();
586 let error = Option_::try_from(elem).unwrap_err();
587 let message = match error {
588 FromElementError::Invalid(Error::Other(string)) => string,
589 _ => panic!(),
590 };
591 assert_eq!(message, "Missing child field 'value' in Option_ element.");
592
593 let elem: Element = "<option xmlns='jabber:x:data' label='Coucou !'><value>coucou</value><value>error</value></option>".parse().unwrap();
594 let error = Option_::try_from(elem).unwrap_err();
595 let message = match error {
596 FromElementError::Invalid(Error::Other(string)) => string,
597 _ => panic!(),
598 };
599 assert_eq!(
600 message,
601 "Option_ element must not have more than one child in field 'value'."
602 );
603 }
604
605 #[test]
606 fn test_ignore_form_type_field_if_field_type_mismatches_in_form_typed_forms() {
607 let elem: Element = "<x xmlns='jabber:x:data' type='form'><field var='FORM_TYPE' type='text-single'><value>foo</value></field></x>".parse().unwrap();
610 match DataForm::try_from(elem) {
611 Ok(form) => {
612 match form.form_type {
613 None => (),
614 other => panic!("unexpected extracted form type: {:?}", other),
615 };
616 }
617 other => panic!("unexpected result: {:?}", other),
618 }
619 }
620
621 #[test]
622 fn test_ignore_form_type_field_if_field_type_mismatches_in_result_typed_forms() {
623 let elem: Element = "<x xmlns='jabber:x:data' type='result'><field var='FORM_TYPE' type='text-single'><value>foo</value></field></x>".parse().unwrap();
626 match DataForm::try_from(elem) {
627 Ok(form) => {
628 match form.form_type {
629 None => (),
630 other => panic!("unexpected extracted form type: {:?}", other),
631 };
632 }
633 other => panic!("unexpected result: {:?}", other),
634 }
635 }
636
637 #[test]
638 fn test_accept_form_type_field_without_type_attribute_in_submit_typed_forms() {
639 let elem: Element = "<x xmlns='jabber:x:data' type='submit'><field var='FORM_TYPE'><value>foo</value></field></x>".parse().unwrap();
640 match DataForm::try_from(elem) {
641 Ok(form) => {
642 match form.form_type {
643 Some(ty) => assert_eq!(ty, "foo"),
644 other => panic!("unexpected extracted form type: {:?}", other),
645 };
646 }
647 other => panic!("unexpected result: {:?}", other),
648 }
649 }
650
651 #[test]
652 fn test_accept_form_type_field_with_type_hidden_in_submit_typed_forms() {
653 let elem: Element = "<x xmlns='jabber:x:data' type='submit'><field var='FORM_TYPE' type='hidden'><value>foo</value></field></x>".parse().unwrap();
654 match DataForm::try_from(elem) {
655 Ok(form) => {
656 match form.form_type {
657 Some(ty) => assert_eq!(ty, "foo"),
658 other => panic!("unexpected extracted form type: {:?}", other),
659 };
660 }
661 other => panic!("unexpected result: {:?}", other),
662 }
663 }
664
665 #[test]
666 fn test_accept_form_type_field_with_type_hidden_in_result_typed_forms() {
667 let elem: Element = "<x xmlns='jabber:x:data' type='result'><field var='FORM_TYPE' type='hidden'><value>foo</value></field></x>".parse().unwrap();
668 match DataForm::try_from(elem) {
669 Ok(form) => {
670 match form.form_type {
671 Some(ty) => assert_eq!(ty, "foo"),
672 other => panic!("unexpected extracted form type: {:?}", other),
673 };
674 }
675 other => panic!("unexpected result: {:?}", other),
676 }
677 }
678
679 #[test]
680 fn test_accept_form_type_field_with_type_hidden_in_form_typed_forms() {
681 let elem: Element = "<x xmlns='jabber:x:data' type='form'><field var='FORM_TYPE' type='hidden'><value>foo</value></field></x>".parse().unwrap();
682 match DataForm::try_from(elem) {
683 Ok(form) => {
684 match form.form_type {
685 Some(ty) => assert_eq!(ty, "foo"),
686 other => panic!("unexpected extracted form type: {:?}", other),
687 };
688 }
689 other => panic!("unexpected result: {:?}", other),
690 }
691 }
692}