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, Default, 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 MucUser {
313 pub fn new() -> MucUser {
315 MucUser::default()
316 }
317
318 pub fn with_statuses(mut self, status: Vec<Status>) -> MucUser {
320 self.status = status;
321 self
322 }
323
324 pub fn with_items(mut self, items: Vec<Item>) -> MucUser {
326 self.items = items;
327 self
328 }
329}
330
331impl MessagePayload for MucUser {}
332impl PresencePayload for MucUser {}
333
334#[cfg(test)]
335mod tests {
336 use super::*;
337 use crate::message::Message;
338 use crate::presence::{Presence, Type as PresenceType};
339 use jid::Jid;
340 use minidom::Element;
341 use xso::error::{Error, FromElementError};
342
343 #[cfg(target_pointer_width = "32")]
344 #[test]
345 fn test_size() {
346 assert_size!(Status, 1);
347 assert_size!(Actor, 28);
348 assert_size!(Continue, 12);
349 assert_size!(Reason, 12);
350 assert_size!(Affiliation, 1);
351 assert_size!(Role, 1);
352 assert_size!(Item, 84);
353 assert_size!(Invite, 44);
354 assert_size!(Decline, 44);
355 assert_size!(MucUser, 136);
356 }
357
358 #[cfg(target_pointer_width = "64")]
359 #[test]
360 fn test_size() {
361 assert_size!(Status, 1);
362 assert_size!(Actor, 56);
363 assert_size!(Continue, 24);
364 assert_size!(Reason, 24);
365 assert_size!(Affiliation, 1);
366 assert_size!(Role, 1);
367 assert_size!(Item, 168);
368 assert_size!(Invite, 88);
369 assert_size!(Decline, 88);
370 assert_size!(MucUser, 272);
371 }
372
373 #[test]
374 fn test_simple() {
375 let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user'/>"
376 .parse()
377 .unwrap();
378 MucUser::try_from(elem).unwrap();
379 }
380
381 #[test]
382 fn statuses_and_items() {
383 let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user'>
384 <status code='101'/>
385 <status code='102'/>
386 <item affiliation='member' role='moderator'/>
387 </x>"
388 .parse()
389 .unwrap();
390 let muc_user = MucUser::try_from(elem).unwrap();
391 assert_eq!(muc_user.status.len(), 2);
392 assert_eq!(muc_user.status[0], Status::AffiliationChange);
393 assert_eq!(muc_user.status[1], Status::ConfigShowsUnavailableMembers);
394 assert_eq!(muc_user.items.len(), 1);
395 assert_eq!(muc_user.items[0].affiliation, Affiliation::Member);
396 assert_eq!(muc_user.items[0].role, Role::Moderator);
397 }
398
399 #[test]
400 #[cfg_attr(not(feature = "pedantic"), should_panic = "Result::unwrap_err")]
401 fn test_invalid_child() {
402 let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user'>
403 <coucou/>
404 </x>"
405 .parse()
406 .unwrap();
407 let error = MucUser::try_from(elem).unwrap_err();
408 let message = match error {
409 FromElementError::Invalid(Error::Other(string)) => string,
410 _ => panic!(),
411 };
412 assert_eq!(message, "Unknown child in MucUser element.");
413 }
414
415 #[test]
416 fn test_serialise() {
417 let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user'/>"
418 .parse()
419 .unwrap();
420 let muc = MucUser::new();
421 let elem2 = muc.into();
422 assert_eq!(elem, elem2);
423 }
424
425 #[cfg(feature = "pedantic")]
426 #[test]
427 fn test_invalid_attribute() {
428 let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user' coucou=''/>"
429 .parse()
430 .unwrap();
431 let error = MucUser::try_from(elem).unwrap_err();
432 let message = match error {
433 FromElementError::Invalid(Error::Other(string)) => string,
434 _ => panic!(),
435 };
436 assert_eq!(message, "Unknown attribute in MucUser element.");
437 }
438
439 #[test]
440 fn test_status_simple() {
441 let elem: Element = "<status xmlns='http://jabber.org/protocol/muc#user' code='110'/>"
442 .parse()
443 .unwrap();
444 Status::try_from(elem).unwrap();
445 }
446
447 #[test]
448 fn test_status_invalid() {
449 let elem: Element = "<status xmlns='http://jabber.org/protocol/muc#user'/>"
450 .parse()
451 .unwrap();
452 let error = Status::try_from(elem).unwrap_err();
453 let message = match error {
454 FromElementError::Invalid(Error::Other(string)) => string,
455 _ => panic!(),
456 };
457 assert_eq!(message, "Required attribute 'code' missing.");
458 }
459
460 #[cfg(feature = "pedantic")]
461 #[test]
462 fn test_status_invalid_child() {
463 let elem: Element = "<status xmlns='http://jabber.org/protocol/muc#user' code='110'>
464 <foo/>
465 </status>"
466 .parse()
467 .unwrap();
468 let error = Status::try_from(elem).unwrap_err();
469 let message = match error {
470 FromElementError::Invalid(Error::Other(string)) => string,
471 _ => panic!(),
472 };
473 assert_eq!(message, "Unknown child in status element.");
474 }
475
476 #[test]
477 fn test_status_simple_code() {
478 let elem: Element = "<status xmlns='http://jabber.org/protocol/muc#user' code='307'/>"
479 .parse()
480 .unwrap();
481 let status = Status::try_from(elem).unwrap();
482 assert_eq!(status, Status::Kicked);
483 }
484
485 #[test]
486 fn test_status_invalid_code() {
487 let elem: Element = "<status xmlns='http://jabber.org/protocol/muc#user' code='666'/>"
488 .parse()
489 .unwrap();
490 let error = Status::try_from(elem).unwrap_err();
491 let message = match error {
492 FromElementError::Invalid(Error::Other(string)) => string,
493 _ => panic!(),
494 };
495 assert_eq!(message, "Invalid status code value.");
496 }
497
498 #[test]
499 fn test_status_invalid_code2() {
500 let elem: Element = "<status xmlns='http://jabber.org/protocol/muc#user' code='coucou'/>"
501 .parse()
502 .unwrap();
503 let error = Status::try_from(elem).unwrap_err();
504 let error = match error {
505 FromElementError::Invalid(Error::TextParseError(error))
506 if error.is::<core::num::ParseIntError>() =>
507 {
508 error
509 }
510 _ => panic!(),
511 };
512 assert_eq!(error.to_string(), "invalid digit found in string");
513 }
514
515 #[test]
518 #[ignore]
519 fn test_actor_required_attributes() {
520 let elem: Element = "<actor xmlns='http://jabber.org/protocol/muc#user'/>"
521 .parse()
522 .unwrap();
523 let error = Actor::try_from(elem).unwrap_err();
524 let message = match error {
525 FromElementError::Invalid(Error::Other(string)) => string,
526 _ => panic!(),
527 };
528 assert_eq!(message, "Either 'jid' or 'nick' attribute is required.");
529 }
530
531 #[test]
532 fn test_actor_jid() {
533 let elem: Element = "<actor xmlns='http://jabber.org/protocol/muc#user'
534 jid='foo@bar/baz'/>"
535 .parse()
536 .unwrap();
537 let actor = Actor::try_from(elem).unwrap();
538 assert_eq!(actor.jid, Some("foo@bar/baz".parse::<FullJid>().unwrap()));
539 assert_eq!(actor.nick, None);
540 }
541
542 #[test]
543 fn test_actor_nick() {
544 let elem: Element = "<actor xmlns='http://jabber.org/protocol/muc#user' nick='baz'/>"
545 .parse()
546 .unwrap();
547 let actor = Actor::try_from(elem).unwrap();
548 assert_eq!(actor.nick, Some("baz".to_owned()));
549 assert_eq!(actor.jid, None);
550 }
551
552 #[test]
553 fn test_continue_simple() {
554 let elem: Element = "<continue xmlns='http://jabber.org/protocol/muc#user'/>"
555 .parse()
556 .unwrap();
557 Continue::try_from(elem).unwrap();
558 }
559
560 #[test]
561 fn test_continue_thread_attribute() {
562 let elem: Element = "<continue xmlns='http://jabber.org/protocol/muc#user'
563 thread='foo'/>"
564 .parse()
565 .unwrap();
566 let continue_ = Continue::try_from(elem).unwrap();
567 assert_eq!(continue_.thread, Some("foo".to_owned()));
568 }
569
570 #[test]
571 #[cfg_attr(not(feature = "pedantic"), should_panic = "Result::unwrap_err")]
572 fn test_continue_invalid() {
573 let elem: Element =
574 "<continue xmlns='http://jabber.org/protocol/muc#user'><foobar/></continue>"
575 .parse()
576 .unwrap();
577 let continue_ = Continue::try_from(elem).unwrap_err();
578 let message = match continue_ {
579 FromElementError::Invalid(Error::Other(string)) => string,
580 _ => panic!(),
581 };
582 assert_eq!(message, "Unknown child in Continue element.".to_owned());
583 }
584
585 #[test]
586 fn test_reason_simple() {
587 let elem: Element = "<reason xmlns='http://jabber.org/protocol/muc#user'>Reason</reason>"
588 .parse()
589 .unwrap();
590 let elem2 = elem.clone();
591 let reason = Reason::try_from(elem).unwrap();
592 assert_eq!(reason.0, "Reason".to_owned());
593
594 let elem3 = reason.into();
595 assert_eq!(elem2, elem3);
596 }
597
598 #[cfg(feature = "pedantic")]
599 #[test]
600 fn test_reason_invalid_attribute() {
601 let elem: Element = "<reason xmlns='http://jabber.org/protocol/muc#user' foo='bar'/>"
602 .parse()
603 .unwrap();
604 let error = Reason::try_from(elem).unwrap_err();
605 let message = match error {
606 FromElementError::Invalid(Error::Other(string)) => string,
607 _ => panic!(),
608 };
609 assert_eq!(message, "Unknown attribute in Reason element.".to_owned());
610 }
611
612 #[cfg(feature = "pedantic")]
613 #[test]
614 fn test_reason_invalid() {
615 let elem: Element = "<reason xmlns='http://jabber.org/protocol/muc#user'>
616 <foobar/>
617 </reason>"
618 .parse()
619 .unwrap();
620 let error = Reason::try_from(elem).unwrap_err();
621 let message = match error {
622 FromElementError::Invalid(Error::Other(string)) => string,
623 _ => panic!(),
624 };
625 assert_eq!(message, "Unknown child in Reason element.".to_owned());
626 }
627
628 #[cfg(feature = "pedantic")]
629 #[test]
630 fn test_item_invalid_attr() {
631 let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
632 affiliation='member'
633 role='moderator'
634 foo='bar'/>"
635 .parse()
636 .unwrap();
637 let error = Item::try_from(elem).unwrap_err();
638 let message = match error {
639 FromElementError::Invalid(Error::Other(string)) => string,
640 _ => panic!(),
641 };
642 assert_eq!(message, "Unknown attribute in Item element.".to_owned());
643 }
644
645 #[test]
646 fn test_item_affiliation_role_attr() {
647 let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
648 affiliation='member'
649 role='moderator'/>"
650 .parse()
651 .unwrap();
652 Item::try_from(elem).unwrap();
653 }
654
655 #[test]
656 fn test_item_affiliation_role_invalid_attr() {
657 let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
658 affiliation='member'/>"
659 .parse()
660 .unwrap();
661 let error = Item::try_from(elem).unwrap_err();
662 let message = match error {
663 FromElementError::Invalid(Error::Other(string)) => string,
664 _ => panic!(),
665 };
666 assert_eq!(
667 message,
668 "Required attribute field 'role' on Item element missing.".to_owned()
669 );
670 }
671
672 #[test]
673 fn test_item_nick_attr() {
674 let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
675 affiliation='member'
676 role='moderator'
677 nick='foobar'/>"
678 .parse()
679 .unwrap();
680 let item = Item::try_from(elem).unwrap();
681 match item {
682 Item { nick, .. } => assert_eq!(nick, Some("foobar".to_owned())),
683 }
684 }
685
686 #[test]
687 fn test_item_affiliation_role_invalid_attr2() {
688 let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
689 role='moderator'/>"
690 .parse()
691 .unwrap();
692 let error = Item::try_from(elem).unwrap_err();
693 let message = match error {
694 FromElementError::Invalid(Error::Other(string)) => string,
695 _ => panic!(),
696 };
697 assert_eq!(
698 message,
699 "Required attribute field 'affiliation' on Item element missing.".to_owned()
700 );
701 }
702
703 #[test]
704 fn test_item_role_actor_child() {
705 let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
706 affiliation='member'
707 role='moderator'>
708 <actor nick='foobar'/>
709 </item>"
710 .parse()
711 .unwrap();
712 let item = Item::try_from(elem).unwrap();
713 let Item { actor, .. } = item;
714 let actor = actor.unwrap();
715 assert_eq!(actor.nick, Some("foobar".to_owned()));
716 assert_eq!(actor.jid, None);
717 }
718
719 #[test]
720 fn test_item_role_continue_child() {
721 let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
722 affiliation='member'
723 role='moderator'>
724 <continue thread='foobar'/>
725 </item>"
726 .parse()
727 .unwrap();
728 let item = Item::try_from(elem).unwrap();
729 let continue_1 = Continue {
730 thread: Some("foobar".to_owned()),
731 };
732 match item {
733 Item {
734 continue_: Some(continue_2),
735 ..
736 } => assert_eq!(continue_2.thread, continue_1.thread),
737 _ => panic!(),
738 }
739 }
740
741 #[test]
742 fn test_item_role_reason_child() {
743 let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
744 affiliation='member'
745 role='moderator'>
746 <reason>foobar</reason>
747 </item>"
748 .parse()
749 .unwrap();
750 let item = Item::try_from(elem).unwrap();
751 match item {
752 Item { reason, .. } => assert_eq!(reason, Some(Reason("foobar".to_owned()))),
753 }
754 }
755
756 #[test]
757 fn test_serialize_item() {
758 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>"
759 .parse()
760 .unwrap();
761
762 let elem: Element = "<actor xmlns='http://jabber.org/protocol/muc#user' nick='foobar'/>"
763 .parse()
764 .unwrap();
765 let actor = Actor::try_from(elem).unwrap();
766
767 let elem: Element =
768 "<continue xmlns='http://jabber.org/protocol/muc#user' thread='foobar'/>"
769 .parse()
770 .unwrap();
771 let continue_ = Continue::try_from(elem).unwrap();
772
773 let elem: Element = "<reason xmlns='http://jabber.org/protocol/muc#user'>foobar</reason>"
774 .parse()
775 .unwrap();
776 let reason = Reason::try_from(elem).unwrap();
777
778 let item = Item {
779 affiliation: Affiliation::Member,
780 role: Role::Moderator,
781 jid: None,
782 nick: None,
783 actor: Some(actor),
784 reason: Some(reason),
785 continue_: Some(continue_),
786 };
787
788 let serialized: Element = item.into();
789 assert_eq!(serialized, reference);
790 }
791
792 #[test]
793 fn presence_payload() {
794 let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user'/>"
795 .parse()
796 .unwrap();
797 let presence = Presence::new(PresenceType::None).with_payloads(vec![elem]);
798 assert_eq!(presence.payloads.len(), 1);
799 }
800
801 #[test]
802 fn message_payload() {
803 let jid: Jid = Jid::new("louise@example.com").unwrap();
804 let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user'/>"
805 .parse()
806 .unwrap();
807 let message = Message::new(jid).with_payloads(vec![elem]);
808 assert_eq!(message.payloads.len(), 1);
809 }
810}