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