1use xso::{error::Error, AsXml, FromXml};
8
9use crate::data_forms_validate::Validate;
10use crate::media_element::MediaElement;
11use crate::ns;
12
13#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
15#[xml(namespace = ns::DATA_FORMS, name = "option")]
16pub struct Option_ {
17 #[xml(attribute(default))]
19 pub label: Option<String>,
20
21 #[xml(extract(fields(text)))]
23 pub value: String,
24}
25
26generate_attribute!(
27 FieldType, "type", {
29 Boolean => "boolean",
32
33 Fixed => "fixed",
36
37 Hidden => "hidden",
40
41 JidMulti => "jid-multi",
45
46 JidSingle => "jid-single",
50
51 ListMulti => "list-multi",
54
55 ListSingle => "list-single",
58
59 TextMulti => "text-multi",
61
62 TextPrivate => "text-private",
65
66 TextSingle => "text-single",
68 }, Default = TextSingle
69);
70
71fn validate_field(field: &mut Field) -> Result<(), Error> {
72 if field.type_ != FieldType::Fixed && field.var.is_none() {
73 return Err(Error::Other("Required attribute 'var' missing.").into());
74 }
75
76 if !field.is_list() && field.options.len() > 0 {
77 return Err(Error::Other("Option element found in non-list field.").into());
78 }
79
80 Ok(())
81}
82
83#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
85#[xml(namespace = ns::DATA_FORMS, name = "field", deserialize_callback = validate_field)]
86pub struct Field {
87 #[xml(attribute(default))]
89 pub var: Option<String>,
90
91 #[xml(attribute(name = "type", default))]
93 pub type_: FieldType,
94
95 #[xml(attribute(default))]
97 pub label: Option<String>,
98
99 #[xml(flag)]
101 pub required: bool,
102
103 #[xml(extract(default, fields(text(type_ = String))))]
105 pub desc: Option<String>,
106
107 #[xml(child(n = ..))]
109 pub options: Vec<Option_>,
110
111 #[xml(extract(n = .., name = "value", fields(text(type_ = String))))]
113 pub values: Vec<String>,
114
115 #[xml(child(n = ..))]
117 pub media: Vec<MediaElement>,
118
119 #[xml(child(default))]
121 pub validate: Option<Validate>,
122}
123
124impl Field {
125 pub fn new(var: &str, type_: FieldType) -> Field {
127 Field {
128 var: Some(String::from(var)),
129 type_,
130 label: None,
131 required: false,
132 desc: None,
133 options: Vec::new(),
134 media: Vec::new(),
135 values: Vec::new(),
136 validate: None,
137 }
138 }
139
140 pub fn with_value(mut self, value: &str) -> Field {
142 self.values.push(String::from(value));
143 self
144 }
145
146 pub fn text_single(var: &str, value: &str) -> Field {
148 Field::new(var, FieldType::TextSingle).with_value(value)
149 }
150
151 fn is_list(&self) -> bool {
152 self.type_ == FieldType::ListSingle || self.type_ == FieldType::ListMulti
153 }
154
155 pub fn is_form_type(&self, ty: &DataFormType) -> bool {
161 if self.var.as_deref() != Some("FORM_TYPE") {
163 return false;
164 }
165
166 match ty {
167 DataFormType::Form | DataFormType::Result_ => self.type_ == FieldType::Hidden,
172
173 DataFormType::Submit => matches!(self.type_, FieldType::Hidden | FieldType::TextSingle),
182
183 DataFormType::Cancel => false,
189 }
190 }
191}
192
193generate_attribute!(
194 DataFormType, "type", {
196 Cancel => "cancel",
198
199 Form => "form",
202
203 Result_ => "result",
205
206 Submit => "submit",
208 }
209);
210
211#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
213#[xml(namespace = ns::DATA_FORMS, name = "x", deserialize_callback = patch_form)]
214pub struct DataForm {
215 #[xml(attribute = "type")]
217 pub type_: DataFormType,
218
219 #[xml(extract(fields(text(type_ = String)), default))]
221 pub title: Option<String>,
222
223 #[xml(extract(fields(text(type_ = String)), default))]
225 pub instructions: Option<String>,
226
227 #[xml(child(n = ..))]
229 pub fields: Vec<Field>,
230}
231
232fn patch_form(form: &mut DataForm) -> Result<(), Error> {
233 let mut form_type_index = None;
235 for (i, field) in form.fields.iter().enumerate() {
236 if field.is_form_type(&form.type_) {
237 if form_type_index.is_some() {
238 return Err(Error::Other("More than one FORM_TYPE in a data form."));
239 }
240
241 if field.values.len() != 1 {
242 return Err(Error::Other("Wrong number of values in FORM_TYPE.").into());
243 }
244
245 form_type_index = Some(i);
246 }
247 }
248
249 if let Some(index) = form_type_index {
250 let field = form.fields.remove(index);
251 form.fields.insert(0, field);
252 }
253 Ok(())
254}
255
256impl DataForm {
257 pub fn new(type_: DataFormType, form_type: &str, fields: Vec<Field>) -> DataForm {
259 let mut form = DataForm {
260 type_,
261 title: None,
262 instructions: None,
263 fields,
264 };
265 form.set_form_type(form_type.to_owned());
266 form
267 }
268
269 pub fn form_type(&self) -> Option<&str> {
275 for field in self.fields.iter() {
276 if field.is_form_type(&self.type_) {
277 return field.values.get(0).map(|x| x.as_str());
278 }
279 }
280 None
281 }
282
283 pub fn set_form_type(&mut self, ty: String) -> &mut Field {
294 if self.type_ == DataFormType::Cancel {
295 panic!("cannot add FORM_TYPE field to type='cancel' form");
296 }
297
298 let mut index = None;
300 for (i, field) in self.fields.iter().enumerate() {
301 if field.is_form_type(&self.type_) {
302 index = Some(i);
303 break;
304 }
305 }
306
307 let field = if let Some(index) = index {
308 &mut self.fields[index]
309 } else {
310 let field = Field::new("FORM_TYPE", FieldType::Hidden);
311 assert!(field.is_form_type(&self.type_));
312 self.fields.insert(0, field);
313 &mut self.fields[0]
314 };
315 field.values.clear();
316 field.values.push(ty);
317 field
318 }
319}
320
321#[cfg(test)]
322mod tests {
323 use super::*;
324 use crate::data_forms_validate::{Datatype, Validate};
325 use minidom::Element;
326 use xso::error::{Error, FromElementError};
327
328 #[cfg(target_pointer_width = "32")]
329 #[test]
330 fn test_size() {
331 assert_size!(Option_, 24);
332 assert_size!(FieldType, 1);
333 assert_size!(Field, 140);
334 assert_size!(DataFormType, 1);
335 assert_size!(DataForm, 40);
336 }
337
338 #[cfg(target_pointer_width = "64")]
339 #[test]
340 fn test_size() {
341 assert_size!(Option_, 48);
342 assert_size!(FieldType, 1);
343 assert_size!(Field, 264);
344 assert_size!(DataFormType, 1);
345 assert_size!(DataForm, 80);
346 }
347
348 #[test]
349 fn test_simple() {
350 let elem: Element = "<x xmlns='jabber:x:data' type='result'/>".parse().unwrap();
351 let form = DataForm::try_from(elem).unwrap();
352 assert_eq!(form.type_, DataFormType::Result_);
353 assert!(form.form_type().is_none());
354 assert!(form.fields.is_empty());
355 }
356
357 #[test]
358 fn test_missing_var() {
359 let elem: Element =
360 "<x xmlns='jabber:x:data' type='form'><field type='text-single' label='The name of your bot'/></x>"
361 .parse()
362 .unwrap();
363 let error = DataForm::try_from(elem).unwrap_err();
364 let message = match error {
365 FromElementError::Invalid(Error::Other(string)) => string,
366 _ => panic!(),
367 };
368 assert_eq!(message, "Required attribute 'var' missing.");
369 }
370
371 #[test]
372 fn test_fixed_field() {
373 let elem: Element =
374 "<x xmlns='jabber:x:data' type='form'><field type='fixed'><value>Section 1: Bot Info</value></field></x>"
375 .parse()
376 .unwrap();
377 let form = DataForm::try_from(elem).unwrap();
378 assert_eq!(form.type_, DataFormType::Form);
379 assert!(form.form_type().is_none());
380 assert_eq!(
381 form.fields,
382 vec![Field {
383 var: None,
384 type_: FieldType::Fixed,
385 label: None,
386 required: false,
387 desc: None,
388 options: vec![],
389 values: vec!["Section 1: Bot Info".to_string()],
390 media: vec![],
391 validate: None,
392 }]
393 );
394 }
395
396 #[test]
397 fn test_desc() {
398 let elem: Element =
399 "<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>"
400 .parse()
401 .unwrap();
402 let form = DataForm::try_from(elem).unwrap();
403 assert_eq!(form.type_, DataFormType::Form);
404 assert!(form.form_type().is_none());
405 assert_eq!(
406 form.fields,
407 vec![Field {
408 var: Some("invitelist".to_string()),
409 type_: FieldType::JidMulti,
410 label: Some("People to invite".to_string()),
411 required: false,
412 desc: Some("Tell all your friends about your new bot!".to_string()),
413 options: vec![],
414 values: vec![],
415 media: vec![],
416 validate: None,
417 }]
418 );
419 }
420
421 #[test]
422 fn test_validate() {
423 let elem: Element = r#"<x xmlns='jabber:x:data' type='form'>
424 <field var='evt.date' type='text-single' label='Event Date/Time'>
425 <validate xmlns='http://jabber.org/protocol/xdata-validate' datatype='xs:dateTime'/>
426 <value>2003-10-06T11:22:00-07:00</value>
427 </field>
428 </x>"#
429 .parse()
430 .unwrap();
431 let form = DataForm::try_from(elem).unwrap();
432 assert_eq!(form.type_, DataFormType::Form);
433 assert!(form.form_type().is_none());
434 assert_eq!(
435 form.fields,
436 vec![Field {
437 var: Some("evt.date".to_string()),
438 type_: FieldType::TextSingle,
439 label: Some("Event Date/Time".to_string()),
440 required: false,
441 desc: None,
442 options: vec![],
443 values: vec!["2003-10-06T11:22:00-07:00".to_string()],
444 media: vec![],
445 validate: Some(Validate {
446 datatype: Some(Datatype::DateTime),
447 method: None,
448 list_range: None,
449 }),
450 }]
451 );
452 }
453
454 #[test]
455 fn test_invalid_field() {
456 let elem: Element = "<field xmlns='jabber:x:data' type='text-single' var='foo'><option><value>foo</value></option></field>".parse().unwrap();
457 let error = Field::try_from(elem).unwrap_err();
458 let message = match error {
459 FromElementError::Invalid(Error::Other(string)) => string,
460 _ => panic!(),
461 };
462 assert_eq!(message, "Option element found in non-list field.");
463 }
464
465 #[test]
466 fn test_invalid() {
467 let elem: Element = "<x xmlns='jabber:x:data'/>".parse().unwrap();
468 let error = DataForm::try_from(elem).unwrap_err();
469 let message = match error {
470 FromElementError::Invalid(Error::Other(string)) => string,
471 _ => panic!(),
472 };
473 assert_eq!(
474 message,
475 "Required attribute field 'type_' on DataForm element missing."
476 );
477
478 let elem: Element = "<x xmlns='jabber:x:data' type='coucou'/>".parse().unwrap();
479 let error = DataForm::try_from(elem).unwrap_err();
480 let message = match error {
481 FromElementError::Invalid(Error::TextParseError(string)) => string,
482 other => panic!("unexpected result: {:?}", other),
483 };
484 assert_eq!(message.to_string(), "Unknown value for 'type' attribute.");
485 }
486
487 #[test]
488 fn test_wrong_child() {
489 let elem: Element = "<x xmlns='jabber:x:data' type='cancel'><coucou/></x>"
490 .parse()
491 .unwrap();
492 let error = DataForm::try_from(elem).unwrap_err();
493 let message = match error {
494 FromElementError::Invalid(Error::Other(string)) => string,
495 _ => panic!(),
496 };
497 assert_eq!(message, "Unknown child in DataForm element.");
498 }
499
500 #[test]
501 fn option() {
502 let elem: Element =
503 "<option xmlns='jabber:x:data' label='Coucou !'><value>coucou</value></option>"
504 .parse()
505 .unwrap();
506 let option = Option_::try_from(elem).unwrap();
507 assert_eq!(&option.label.unwrap(), "Coucou !");
508 assert_eq!(&option.value, "coucou");
509
510 let elem: Element = "<option xmlns='jabber:x:data' label='Coucou !'/>"
511 .parse()
512 .unwrap();
513 let error = Option_::try_from(elem).unwrap_err();
514 let message = match error {
515 FromElementError::Invalid(Error::Other(string)) => string,
516 _ => panic!(),
517 };
518 assert_eq!(message, "Missing child field 'value' in Option_ element.");
519
520 let elem: Element = "<option xmlns='jabber:x:data' label='Coucou !'><value>coucou</value><value>error</value></option>".parse().unwrap();
521 let error = Option_::try_from(elem).unwrap_err();
522 let message = match error {
523 FromElementError::Invalid(Error::Other(string)) => string,
524 _ => panic!(),
525 };
526 assert_eq!(
527 message,
528 "Option_ element must not have more than one child in field 'value'."
529 );
530 }
531
532 #[test]
533 fn test_ignore_form_type_field_if_field_type_mismatches_in_form_typed_forms() {
534 let elem: Element = "<x xmlns='jabber:x:data' type='form'><field var='FORM_TYPE' type='text-single'><value>foo</value></field></x>".parse().unwrap();
537 match DataForm::try_from(elem) {
538 Ok(form) => {
539 match form.form_type() {
540 None => (),
541 other => panic!("unexpected extracted form type: {:?}", other),
542 };
543 }
544 other => panic!("unexpected result: {:?}", other),
545 }
546 }
547
548 #[test]
549 fn test_ignore_form_type_field_if_field_type_mismatches_in_result_typed_forms() {
550 let elem: Element = "<x xmlns='jabber:x:data' type='result'><field var='FORM_TYPE' type='text-single'><value>foo</value></field></x>".parse().unwrap();
553 match DataForm::try_from(elem) {
554 Ok(form) => {
555 match form.form_type() {
556 None => (),
557 other => panic!("unexpected extracted form type: {:?}", other),
558 };
559 }
560 other => panic!("unexpected result: {:?}", other),
561 }
562 }
563
564 #[test]
565 fn test_accept_form_type_field_without_type_attribute_in_submit_typed_forms() {
566 let elem: Element = "<x xmlns='jabber:x:data' type='submit'><field var='FORM_TYPE'><value>foo</value></field></x>".parse().unwrap();
567 match DataForm::try_from(elem) {
568 Ok(form) => {
569 match form.form_type() {
570 Some(ty) => assert_eq!(ty, "foo"),
571 other => panic!("unexpected extracted form type: {:?}", other),
572 };
573 }
574 other => panic!("unexpected result: {:?}", other),
575 }
576 }
577
578 #[test]
579 fn test_accept_form_type_field_with_type_hidden_in_submit_typed_forms() {
580 let elem: Element = "<x xmlns='jabber:x:data' type='submit'><field var='FORM_TYPE' type='hidden'><value>foo</value></field></x>".parse().unwrap();
581 match DataForm::try_from(elem) {
582 Ok(form) => {
583 match form.form_type() {
584 Some(ty) => assert_eq!(ty, "foo"),
585 other => panic!("unexpected extracted form type: {:?}", other),
586 };
587 }
588 other => panic!("unexpected result: {:?}", other),
589 }
590 }
591
592 #[test]
593 fn test_accept_form_type_field_with_type_hidden_in_result_typed_forms() {
594 let elem: Element = "<x xmlns='jabber:x:data' type='result'><field var='FORM_TYPE' type='hidden'><value>foo</value></field></x>".parse().unwrap();
595 match DataForm::try_from(elem) {
596 Ok(form) => {
597 match form.form_type() {
598 Some(ty) => assert_eq!(ty, "foo"),
599 other => panic!("unexpected extracted form type: {:?}", other),
600 };
601 }
602 other => panic!("unexpected result: {:?}", other),
603 }
604 }
605
606 #[test]
607 fn test_accept_form_type_field_with_type_hidden_in_form_typed_forms() {
608 let elem: Element = "<x xmlns='jabber:x:data' type='form'><field var='FORM_TYPE' type='hidden'><value>foo</value></field></x>".parse().unwrap();
609 match DataForm::try_from(elem) {
610 Ok(form) => {
611 match form.form_type() {
612 Some(ty) => assert_eq!(ty, "foo"),
613 other => panic!("unexpected extracted form type: {:?}", other),
614 };
615 }
616 other => panic!("unexpected result: {:?}", other),
617 }
618 }
619}