1use xso::{AsXml, FromXml};
9
10use crate::message::MessagePayload;
11use crate::muc::mav::AffiliationsVersioning;
12use crate::ns;
13use crate::presence::PresencePayload;
14
15use jid::{FullJid, Jid};
16
17generate_attribute_enum!(
18Status, "status", MUC_USER, "code", {
20 NonAnonymousRoom => 100,
22
23 AffiliationChange => 101,
25
26 ConfigShowsUnavailableMembers => 102,
28
29 ConfigHidesUnavailableMembers => 103,
31
32 ConfigNonPrivacyRelated => 104,
34
35 SelfPresence => 110,
37
38 ConfigRoomLoggingEnabled => 170,
40
41 ConfigRoomLoggingDisabled => 171,
43
44 ConfigRoomNonAnonymous => 172,
46
47 ConfigRoomSemiAnonymous => 173,
49
50 RoomHasBeenCreated => 201,
52
53 AssignedNick => 210,
55
56 Banned => 301,
58
59 NewNick => 303,
61
62 Kicked => 307,
64
65 RemovalFromRoom => 321,
68
69 ConfigMembersOnly => 322,
73
74 ServiceShutdown => 332,
77
78 ServiceErrorKick => 333,
80});
81
82#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
89#[xml(namespace = ns::MUC_USER, name = "actor")]
90pub struct Actor {
91 #[xml(attribute(default))]
93 jid: Option<FullJid>,
94
95 #[xml(attribute(default))]
97 nick: Option<String>,
98}
99
100#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
103#[xml(namespace = ns::MUC_USER, name = "continue")]
104pub struct Continue {
105 #[xml(attribute(default))]
107 pub thread: Option<String>,
108}
109
110generate_elem_id!(
111 Reason,
113 "reason",
114 MUC_USER
115);
116
117generate_attribute!(
118 Affiliation, "affiliation", {
121 Owner => "owner",
124
125 Admin => "admin",
128
129 Member => "member",
132
133 Outcast => "outcast",
135
136 None => "none",
138 }, Default = None
139);
140
141generate_attribute!(
142 Role, "role", {
145 Moderator => "moderator",
148
149 Participant => "participant",
151
152 Visitor => "visitor",
155
156 None => "none",
158 }, Default = None
159);
160
161#[derive(FromXml, AsXml, Debug, PartialEq, Clone)]
163#[xml(namespace = ns::MUC_USER, name = "item")]
164pub struct Item {
165 #[xml(attribute)]
167 pub affiliation: Affiliation,
168
169 #[xml(attribute(default))]
171 pub jid: Option<FullJid>,
172
173 #[xml(attribute(default))]
175 pub nick: Option<String>,
176
177 #[xml(attribute)]
179 pub role: Role,
180
181 #[xml(child(default))]
183 pub actor: Option<Actor>,
184
185 #[xml(child(default))]
187 pub continue_: Option<Continue>,
188
189 #[xml(child(default))]
191 pub reason: Option<Reason>,
192}
193
194impl Item {
195 pub fn new(affiliation: Affiliation, role: Role) -> Item {
197 Item {
198 affiliation,
199 role,
200 jid: None,
201 nick: None,
202 actor: None,
203 continue_: None,
204 reason: None,
205 }
206 }
207
208 pub fn with_jid(mut self, jid: FullJid) -> Item {
210 self.jid = Some(jid);
211 self
212 }
213
214 pub fn with_nick<S: Into<String>>(mut self, nick: S) -> Item {
216 self.nick = Some(nick.into());
217 self
218 }
219
220 pub fn with_actor(mut self, actor: Actor) -> Item {
222 self.actor = Some(actor);
223 self
224 }
225
226 pub fn with_continue<S: Into<String>>(mut self, continue_: S) -> Item {
228 self.continue_ = Some(Continue {
229 thread: Some(continue_.into()),
230 });
231 self
232 }
233
234 pub fn with_reason<S: Into<String>>(mut self, reason: S) -> Item {
236 self.reason = Some(Reason(reason.into()));
237 self
238 }
239}
240
241#[derive(FromXml, AsXml, Debug, PartialEq, Clone)]
243#[xml(namespace = ns::MUC_USER, name = "invite")]
244pub struct Invite {
245 #[xml(attribute(default))]
250 pub from: Option<Jid>,
251
252 #[xml(attribute(default))]
257 pub to: Option<Jid>,
258
259 #[xml(extract(name = "reason", default, fields(text(type_ = String))))]
261 pub reason: Option<String>,
262}
263
264#[derive(FromXml, AsXml, Debug, PartialEq, Clone)]
266#[xml(namespace = ns::MUC_USER, name = "decline")]
267pub struct Decline {
268 #[xml(attribute(default))]
273 pub from: Option<Jid>,
274
275 #[xml(attribute(default))]
280 pub to: Option<Jid>,
281
282 #[xml(extract(name = "reason", default, fields(text(type_ = String))))]
284 pub reason: Option<String>,
285}
286
287#[derive(FromXml, AsXml, Debug, PartialEq, Clone)]
289#[xml(namespace = ns::MUC_USER, name = "x")]
290pub struct MucUser {
291 #[xml(child(n = ..))]
293 pub status: Vec<Status>,
294
295 #[xml(child(n = ..))]
297 pub items: Vec<Item>,
298
299 #[xml(child(default))]
301 pub invite: Option<Invite>,
302
303 #[xml(child(default))]
305 pub decline: Option<Decline>,
306
307 #[xml(child(default))]
309 pub affiliations: Option<AffiliationsVersioning>,
310}
311
312impl Default for MucUser {
313 fn default() -> Self {
314 Self::new()
315 }
316}
317
318impl MucUser {
319 pub fn new() -> MucUser {
321 MucUser {
322 status: vec![],
323 items: vec![],
324 invite: None,
325 decline: None,
326 affiliations: None,
327 }
328 }
329
330 pub fn with_statuses(mut self, status: Vec<Status>) -> MucUser {
332 self.status = status;
333 self
334 }
335
336 pub fn with_items(mut self, items: Vec<Item>) -> MucUser {
338 self.items = items;
339 self
340 }
341}
342
343impl MessagePayload for MucUser {}
344impl PresencePayload for MucUser {}
345
346#[cfg(test)]
347mod tests {
348 use super::*;
349 use crate::message::Message;
350 use crate::presence::{Presence, Type as PresenceType};
351 use jid::Jid;
352 use minidom::Element;
353 use xso::error::{Error, FromElementError};
354
355 #[cfg(target_pointer_width = "32")]
356 #[test]
357 fn test_size() {
358 assert_size!(Status, 1);
359 assert_size!(Actor, 28);
360 assert_size!(Continue, 12);
361 assert_size!(Reason, 12);
362 assert_size!(Affiliation, 1);
363 assert_size!(Role, 1);
364 assert_size!(Item, 84);
365 assert_size!(Invite, 44);
366 assert_size!(Decline, 44);
367 assert_size!(MucUser, 136);
368 }
369
370 #[cfg(target_pointer_width = "64")]
371 #[test]
372 fn test_size() {
373 assert_size!(Status, 1);
374 assert_size!(Actor, 56);
375 assert_size!(Continue, 24);
376 assert_size!(Reason, 24);
377 assert_size!(Affiliation, 1);
378 assert_size!(Role, 1);
379 assert_size!(Item, 168);
380 assert_size!(Invite, 88);
381 assert_size!(Decline, 88);
382 assert_size!(MucUser, 272);
383 }
384
385 #[test]
386 fn test_simple() {
387 let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user'/>"
388 .parse()
389 .unwrap();
390 MucUser::try_from(elem).unwrap();
391 }
392
393 #[test]
394 fn statuses_and_items() {
395 let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user'>
396 <status code='101'/>
397 <status code='102'/>
398 <item affiliation='member' role='moderator'/>
399 </x>"
400 .parse()
401 .unwrap();
402 let muc_user = MucUser::try_from(elem).unwrap();
403 assert_eq!(muc_user.status.len(), 2);
404 assert_eq!(muc_user.status[0], Status::AffiliationChange);
405 assert_eq!(muc_user.status[1], Status::ConfigShowsUnavailableMembers);
406 assert_eq!(muc_user.items.len(), 1);
407 assert_eq!(muc_user.items[0].affiliation, Affiliation::Member);
408 assert_eq!(muc_user.items[0].role, Role::Moderator);
409 }
410
411 #[test]
412 #[cfg_attr(not(feature = "pedantic"), should_panic = "Result::unwrap_err")]
413 fn test_invalid_child() {
414 let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user'>
415 <coucou/>
416 </x>"
417 .parse()
418 .unwrap();
419 let error = MucUser::try_from(elem).unwrap_err();
420 let message = match error {
421 FromElementError::Invalid(Error::Other(string)) => string,
422 _ => panic!(),
423 };
424 assert_eq!(message, "Unknown child in MucUser element.");
425 }
426
427 #[test]
428 fn test_serialise() {
429 let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user'/>"
430 .parse()
431 .unwrap();
432 let muc = MucUser {
433 status: vec![],
434 items: vec![],
435 invite: None,
436 decline: None,
437 affiliations: None,
438 };
439 let elem2 = muc.into();
440 assert_eq!(elem, elem2);
441 }
442
443 #[cfg(feature = "pedantic")]
444 #[test]
445 fn test_invalid_attribute() {
446 let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user' coucou=''/>"
447 .parse()
448 .unwrap();
449 let error = MucUser::try_from(elem).unwrap_err();
450 let message = match error {
451 FromElementError::Invalid(Error::Other(string)) => string,
452 _ => panic!(),
453 };
454 assert_eq!(message, "Unknown attribute in MucUser element.");
455 }
456
457 #[test]
458 fn test_status_simple() {
459 let elem: Element = "<status xmlns='http://jabber.org/protocol/muc#user' code='110'/>"
460 .parse()
461 .unwrap();
462 Status::try_from(elem).unwrap();
463 }
464
465 #[test]
466 fn test_status_invalid() {
467 let elem: Element = "<status xmlns='http://jabber.org/protocol/muc#user'/>"
468 .parse()
469 .unwrap();
470 let error = Status::try_from(elem).unwrap_err();
471 let message = match error {
472 FromElementError::Invalid(Error::Other(string)) => string,
473 _ => panic!(),
474 };
475 assert_eq!(message, "Required attribute 'code' missing.");
476 }
477
478 #[cfg(feature = "pedantic")]
479 #[test]
480 fn test_status_invalid_child() {
481 let elem: Element = "<status xmlns='http://jabber.org/protocol/muc#user' code='110'>
482 <foo/>
483 </status>"
484 .parse()
485 .unwrap();
486 let error = Status::try_from(elem).unwrap_err();
487 let message = match error {
488 FromElementError::Invalid(Error::Other(string)) => string,
489 _ => panic!(),
490 };
491 assert_eq!(message, "Unknown child in status element.");
492 }
493
494 #[test]
495 fn test_status_simple_code() {
496 let elem: Element = "<status xmlns='http://jabber.org/protocol/muc#user' code='307'/>"
497 .parse()
498 .unwrap();
499 let status = Status::try_from(elem).unwrap();
500 assert_eq!(status, Status::Kicked);
501 }
502
503 #[test]
504 fn test_status_invalid_code() {
505 let elem: Element = "<status xmlns='http://jabber.org/protocol/muc#user' code='666'/>"
506 .parse()
507 .unwrap();
508 let error = Status::try_from(elem).unwrap_err();
509 let message = match error {
510 FromElementError::Invalid(Error::Other(string)) => string,
511 _ => panic!(),
512 };
513 assert_eq!(message, "Invalid status code value.");
514 }
515
516 #[test]
517 fn test_status_invalid_code2() {
518 let elem: Element = "<status xmlns='http://jabber.org/protocol/muc#user' code='coucou'/>"
519 .parse()
520 .unwrap();
521 let error = Status::try_from(elem).unwrap_err();
522 let error = match error {
523 FromElementError::Invalid(Error::TextParseError(error))
524 if error.is::<core::num::ParseIntError>() =>
525 {
526 error
527 }
528 _ => panic!(),
529 };
530 assert_eq!(error.to_string(), "invalid digit found in string");
531 }
532
533 #[test]
536 #[ignore]
537 fn test_actor_required_attributes() {
538 let elem: Element = "<actor xmlns='http://jabber.org/protocol/muc#user'/>"
539 .parse()
540 .unwrap();
541 let error = Actor::try_from(elem).unwrap_err();
542 let message = match error {
543 FromElementError::Invalid(Error::Other(string)) => string,
544 _ => panic!(),
545 };
546 assert_eq!(message, "Either 'jid' or 'nick' attribute is required.");
547 }
548
549 #[test]
550 fn test_actor_jid() {
551 let elem: Element = "<actor xmlns='http://jabber.org/protocol/muc#user'
552 jid='foo@bar/baz'/>"
553 .parse()
554 .unwrap();
555 let actor = Actor::try_from(elem).unwrap();
556 assert_eq!(actor.jid, Some("foo@bar/baz".parse::<FullJid>().unwrap()));
557 assert_eq!(actor.nick, None);
558 }
559
560 #[test]
561 fn test_actor_nick() {
562 let elem: Element = "<actor xmlns='http://jabber.org/protocol/muc#user' nick='baz'/>"
563 .parse()
564 .unwrap();
565 let actor = Actor::try_from(elem).unwrap();
566 assert_eq!(actor.nick, Some("baz".to_owned()));
567 assert_eq!(actor.jid, None);
568 }
569
570 #[test]
571 fn test_continue_simple() {
572 let elem: Element = "<continue xmlns='http://jabber.org/protocol/muc#user'/>"
573 .parse()
574 .unwrap();
575 Continue::try_from(elem).unwrap();
576 }
577
578 #[test]
579 fn test_continue_thread_attribute() {
580 let elem: Element = "<continue xmlns='http://jabber.org/protocol/muc#user'
581 thread='foo'/>"
582 .parse()
583 .unwrap();
584 let continue_ = Continue::try_from(elem).unwrap();
585 assert_eq!(continue_.thread, Some("foo".to_owned()));
586 }
587
588 #[test]
589 #[cfg_attr(not(feature = "pedantic"), should_panic = "Result::unwrap_err")]
590 fn test_continue_invalid() {
591 let elem: Element =
592 "<continue xmlns='http://jabber.org/protocol/muc#user'><foobar/></continue>"
593 .parse()
594 .unwrap();
595 let continue_ = Continue::try_from(elem).unwrap_err();
596 let message = match continue_ {
597 FromElementError::Invalid(Error::Other(string)) => string,
598 _ => panic!(),
599 };
600 assert_eq!(message, "Unknown child in Continue element.".to_owned());
601 }
602
603 #[test]
604 fn test_reason_simple() {
605 let elem: Element = "<reason xmlns='http://jabber.org/protocol/muc#user'>Reason</reason>"
606 .parse()
607 .unwrap();
608 let elem2 = elem.clone();
609 let reason = Reason::try_from(elem).unwrap();
610 assert_eq!(reason.0, "Reason".to_owned());
611
612 let elem3 = reason.into();
613 assert_eq!(elem2, elem3);
614 }
615
616 #[cfg(feature = "pedantic")]
617 #[test]
618 fn test_reason_invalid_attribute() {
619 let elem: Element = "<reason xmlns='http://jabber.org/protocol/muc#user' foo='bar'/>"
620 .parse()
621 .unwrap();
622 let error = Reason::try_from(elem).unwrap_err();
623 let message = match error {
624 FromElementError::Invalid(Error::Other(string)) => string,
625 _ => panic!(),
626 };
627 assert_eq!(message, "Unknown attribute in Reason element.".to_owned());
628 }
629
630 #[cfg(feature = "pedantic")]
631 #[test]
632 fn test_reason_invalid() {
633 let elem: Element = "<reason xmlns='http://jabber.org/protocol/muc#user'>
634 <foobar/>
635 </reason>"
636 .parse()
637 .unwrap();
638 let error = Reason::try_from(elem).unwrap_err();
639 let message = match error {
640 FromElementError::Invalid(Error::Other(string)) => string,
641 _ => panic!(),
642 };
643 assert_eq!(message, "Unknown child in Reason element.".to_owned());
644 }
645
646 #[cfg(feature = "pedantic")]
647 #[test]
648 fn test_item_invalid_attr() {
649 let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
650 affiliation='member'
651 role='moderator'
652 foo='bar'/>"
653 .parse()
654 .unwrap();
655 let error = Item::try_from(elem).unwrap_err();
656 let message = match error {
657 FromElementError::Invalid(Error::Other(string)) => string,
658 _ => panic!(),
659 };
660 assert_eq!(message, "Unknown attribute in Item element.".to_owned());
661 }
662
663 #[test]
664 fn test_item_affiliation_role_attr() {
665 let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
666 affiliation='member'
667 role='moderator'/>"
668 .parse()
669 .unwrap();
670 Item::try_from(elem).unwrap();
671 }
672
673 #[test]
674 fn test_item_affiliation_role_invalid_attr() {
675 let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
676 affiliation='member'/>"
677 .parse()
678 .unwrap();
679 let error = Item::try_from(elem).unwrap_err();
680 let message = match error {
681 FromElementError::Invalid(Error::Other(string)) => string,
682 _ => panic!(),
683 };
684 assert_eq!(
685 message,
686 "Required attribute field 'role' on Item element missing.".to_owned()
687 );
688 }
689
690 #[test]
691 fn test_item_nick_attr() {
692 let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
693 affiliation='member'
694 role='moderator'
695 nick='foobar'/>"
696 .parse()
697 .unwrap();
698 let item = Item::try_from(elem).unwrap();
699 match item {
700 Item { nick, .. } => assert_eq!(nick, Some("foobar".to_owned())),
701 }
702 }
703
704 #[test]
705 fn test_item_affiliation_role_invalid_attr2() {
706 let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
707 role='moderator'/>"
708 .parse()
709 .unwrap();
710 let error = Item::try_from(elem).unwrap_err();
711 let message = match error {
712 FromElementError::Invalid(Error::Other(string)) => string,
713 _ => panic!(),
714 };
715 assert_eq!(
716 message,
717 "Required attribute field 'affiliation' on Item element missing.".to_owned()
718 );
719 }
720
721 #[test]
722 fn test_item_role_actor_child() {
723 let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
724 affiliation='member'
725 role='moderator'>
726 <actor nick='foobar'/>
727 </item>"
728 .parse()
729 .unwrap();
730 let item = Item::try_from(elem).unwrap();
731 let Item { actor, .. } = item;
732 let actor = actor.unwrap();
733 assert_eq!(actor.nick, Some("foobar".to_owned()));
734 assert_eq!(actor.jid, None);
735 }
736
737 #[test]
738 fn test_item_role_continue_child() {
739 let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
740 affiliation='member'
741 role='moderator'>
742 <continue thread='foobar'/>
743 </item>"
744 .parse()
745 .unwrap();
746 let item = Item::try_from(elem).unwrap();
747 let continue_1 = Continue {
748 thread: Some("foobar".to_owned()),
749 };
750 match item {
751 Item {
752 continue_: Some(continue_2),
753 ..
754 } => assert_eq!(continue_2.thread, continue_1.thread),
755 _ => panic!(),
756 }
757 }
758
759 #[test]
760 fn test_item_role_reason_child() {
761 let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
762 affiliation='member'
763 role='moderator'>
764 <reason>foobar</reason>
765 </item>"
766 .parse()
767 .unwrap();
768 let item = Item::try_from(elem).unwrap();
769 match item {
770 Item { reason, .. } => assert_eq!(reason, Some(Reason("foobar".to_owned()))),
771 }
772 }
773
774 #[test]
775 fn test_serialize_item() {
776 let reference: Element = "<item xmlns='http://jabber.org/protocol/muc#user' affiliation='member' role='moderator'><actor nick='foobar'/><continue thread='foobar'/><reason>foobar</reason></item>"
777 .parse()
778 .unwrap();
779
780 let elem: Element = "<actor xmlns='http://jabber.org/protocol/muc#user' nick='foobar'/>"
781 .parse()
782 .unwrap();
783 let actor = Actor::try_from(elem).unwrap();
784
785 let elem: Element =
786 "<continue xmlns='http://jabber.org/protocol/muc#user' thread='foobar'/>"
787 .parse()
788 .unwrap();
789 let continue_ = Continue::try_from(elem).unwrap();
790
791 let elem: Element = "<reason xmlns='http://jabber.org/protocol/muc#user'>foobar</reason>"
792 .parse()
793 .unwrap();
794 let reason = Reason::try_from(elem).unwrap();
795
796 let item = Item {
797 affiliation: Affiliation::Member,
798 role: Role::Moderator,
799 jid: None,
800 nick: None,
801 actor: Some(actor),
802 reason: Some(reason),
803 continue_: Some(continue_),
804 };
805
806 let serialized: Element = item.into();
807 assert_eq!(serialized, reference);
808 }
809
810 #[test]
811 fn presence_payload() {
812 let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user'/>"
813 .parse()
814 .unwrap();
815 let presence = Presence::new(PresenceType::None).with_payloads(vec![elem]);
816 assert_eq!(presence.payloads.len(), 1);
817 }
818
819 #[test]
820 fn message_payload() {
821 let jid: Jid = Jid::new("louise@example.com").unwrap();
822 let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user'/>"
823 .parse()
824 .unwrap();
825 let message = Message::new(jid).with_payloads(vec![elem]);
826 assert_eq!(message.payloads.len(), 1);
827 }
828}