Skip to main content

xmpp_parsers/muc/
admin.rs

1// Copyright (c) 2017 Maxime “pep” Buquet <pep@bouah.net>
2// Copyright (c) 2017 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
3//
4// This Source Code Form is subject to the terms of the Mozilla Public
5// License, v. 2.0. If a copy of the MPL was not distributed with this
6// file, You can obtain one at http://mozilla.org/MPL/2.0/.
7
8use xso::{AsXml, FromXml};
9
10use crate::muc::user::{Affiliation, Role};
11use crate::ns;
12
13use jid::BareJid;
14
15/// Actor defined in muc#admin XML schema but unused in XEP-0045 examples
16#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
17#[xml(namespace = ns::MUC_ADMIN, name = "actor")]
18pub struct Actor {
19    /// The full JID associated with this user.
20    #[xml(attribute(default))]
21    jid: Option<BareJid>,
22
23    /// The nickname of this user.
24    #[xml(attribute(default))]
25    nick: Option<String>,
26}
27
28generate_elem_id!(
29    /// A reason
30    Reason,
31    "reason",
32    MUC_ADMIN
33);
34
35/// An item representing a user in a room.
36#[derive(FromXml, AsXml, Debug, PartialEq, Clone, Default)]
37#[xml(namespace = ns::MUC_ADMIN, name = "item")]
38pub struct Item {
39    /// The affiliation of this user with the room.
40    ///
41    /// MUST be set when request/modifying affiliations.
42    #[xml(attribute)]
43    pub affiliation: Option<Affiliation>,
44
45    /// The real JID of this user, if you are allowed to see it.
46    #[xml(attribute(default))]
47    pub jid: Option<BareJid>,
48
49    /// The current nickname of this user.
50    #[xml(attribute(default))]
51    pub nick: Option<String>,
52
53    /// The current role of this user or selector for users with this role
54    #[xml(attribute)]
55    pub role: Option<Role>,
56
57    /// The actor affected by this item.
58    #[xml(child(default))]
59    pub actor: Option<Actor>,
60
61    /// A reason for this item.
62    #[xml(child(default))]
63    pub reason: Option<Reason>,
64}
65
66impl Item {
67    /// Creates a new item with the given affiliation and role.
68    pub fn new() -> Item {
69        Item {
70            affiliation: None,
71            role: None,
72            jid: None,
73            nick: None,
74            actor: None,
75            reason: None,
76        }
77    }
78
79    /// Set a jid for this Item
80    pub fn with_jid(mut self, jid: BareJid) -> Item {
81        self.jid = Some(jid);
82        self
83    }
84
85    /// Set a nick for this Item
86    pub fn with_nick<S: Into<String>>(mut self, nick: S) -> Item {
87        self.nick = Some(nick.into());
88        self
89    }
90
91    /// Set an actor for this Item
92    pub fn with_actor(mut self, actor: Actor) -> Item {
93        self.actor = Some(actor);
94        self
95    }
96
97    /// Set a reason for this Item
98    pub fn with_reason<S: Into<String>>(mut self, reason: S) -> Item {
99        self.reason = Some(Reason(reason.into()));
100        self
101    }
102
103    /// Set affiliation for this Item
104    pub fn with_affiliation(mut self, affiliation: Affiliation) -> Item {
105        self.affiliation = Some(affiliation);
106        self
107    }
108    /// Set role for this Item
109    pub fn with_role(mut self, role: Role) -> Item {
110        self.role = Some(role);
111        self
112    }
113}
114
115/// The main muc#admin element.
116#[derive(FromXml, AsXml, Debug, Default, PartialEq, Clone)]
117#[xml(namespace = ns::MUC_ADMIN, name = "query")]
118pub struct MucUser {
119    /// List of items.
120    #[xml(child(n = ..))]
121    pub items: Vec<Item>,
122}
123
124impl MucUser {
125    /// Creates an empty MucUser
126    pub fn new() -> MucUser {
127        MucUser::default()
128    }
129
130    /// Set items for this MucUser
131    pub fn with_items(mut self, items: Vec<Item>) -> MucUser {
132        self.items = items;
133        self
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140    use crate::message::Message;
141    use crate::presence::{Presence, Type as PresenceType};
142    use jid::Jid;
143    use minidom::Element;
144    use xso::error::{Error, FromElementError};
145
146    #[cfg(target_pointer_width = "32")]
147    #[test]
148    fn test_size() {
149        assert_size!(Actor, 28);
150        assert_size!(Reason, 12);
151        assert_size!(Affiliation, 1);
152        assert_size!(Role, 1);
153        assert_size!(Item, 84);
154        assert_size!(MucUser, 136);
155    }
156
157    #[cfg(target_pointer_width = "64")]
158    #[test]
159    fn test_size() {
160        assert_size!(Actor, 56);
161        assert_size!(Reason, 24);
162        assert_size!(Affiliation, 1);
163        assert_size!(Role, 1);
164        assert_size!(Item, 144);
165        assert_size!(MucUser, 24);
166    }
167
168    #[test]
169    fn test_simple() {
170        let elem: Element = "<query xmlns='http://jabber.org/protocol/muc#admin'/>"
171            .parse()
172            .unwrap();
173        MucUser::try_from(elem).unwrap();
174    }
175
176    #[test]
177    fn items() {
178        let elem: Element = "<query xmlns='http://jabber.org/protocol/muc#admin'>
179                <item affiliation='member' role='moderator'/>
180            </query>"
181            .parse()
182            .unwrap();
183        let muc_user = MucUser::try_from(elem).unwrap();
184        assert_eq!(muc_user.items.len(), 1);
185        assert_eq!(muc_user.items[0].affiliation, Some(Affiliation::Member));
186        assert_eq!(muc_user.items[0].role, Some(Role::Moderator));
187    }
188
189    #[test]
190    #[cfg_attr(not(feature = "pedantic"), should_panic = "Result::unwrap_err")]
191    fn test_invalid_child() {
192        let elem: Element = "<query xmlns='http://jabber.org/protocol/muc#admin'>
193                <coucou/>
194            </query>"
195            .parse()
196            .unwrap();
197        let error = MucUser::try_from(elem).unwrap_err();
198        let message = match error {
199            FromElementError::Invalid(Error::Other(string)) => string,
200            _ => panic!(),
201        };
202        assert_eq!(message, "Unknown child in MucUser element.");
203    }
204
205    #[test]
206    fn test_serialise() {
207        let elem: Element = "<query xmlns='http://jabber.org/protocol/muc#admin'/>"
208            .parse()
209            .unwrap();
210        let muc = MucUser::new();
211        let elem2 = muc.into();
212        assert_eq!(elem, elem2);
213    }
214
215    #[cfg(feature = "pedantic")]
216    #[test]
217    fn test_invalid_attribute() {
218        let elem: Element = "<query xmlns='http://jabber.org/protocol/muc#admin' coucou=''/>"
219            .parse()
220            .unwrap();
221        let error = MucUser::try_from(elem).unwrap_err();
222        let message = match error {
223            FromElementError::Invalid(Error::Other(string)) => string,
224            _ => panic!(),
225        };
226        assert_eq!(message, "Unknown attribute in MucUser element.");
227    }
228
229    // This test is now ignored because we switched to a representation where we can’t currently
230    // validate whether one of the required attributes is present or not.
231    #[test]
232    #[ignore]
233    fn test_actor_required_attributes() {
234        let elem: Element = "<actor xmlns='http://jabber.org/protocol/muc#admin'/>"
235            .parse()
236            .unwrap();
237        let error = Actor::try_from(elem).unwrap_err();
238        let message = match error {
239            FromElementError::Invalid(Error::Other(string)) => string,
240            _ => panic!(),
241        };
242        assert_eq!(message, "Either 'jid' or 'nick' attribute is required.");
243    }
244
245    #[test]
246    fn test_actor_jid() {
247        let elem: Element = "<actor xmlns='http://jabber.org/protocol/muc#admin'
248                   jid='foo@bar'/>"
249            .parse()
250            .unwrap();
251        let actor = Actor::try_from(elem).unwrap();
252        assert_eq!(actor.jid, Some("foo@bar".parse::<BareJid>().unwrap()));
253        assert_eq!(actor.nick, None);
254    }
255
256    #[test]
257    fn test_actor_nick() {
258        let elem: Element = "<actor xmlns='http://jabber.org/protocol/muc#admin' nick='baz'/>"
259            .parse()
260            .unwrap();
261        let actor = Actor::try_from(elem).unwrap();
262        assert_eq!(actor.nick, Some("baz".to_owned()));
263        assert_eq!(actor.jid, None);
264    }
265
266    #[test]
267    fn test_reason_simple() {
268        let elem: Element = "<reason xmlns='http://jabber.org/protocol/muc#admin'>Reason</reason>"
269            .parse()
270            .unwrap();
271        let elem2 = elem.clone();
272        let reason = Reason::try_from(elem).unwrap();
273        assert_eq!(reason.0, "Reason".to_owned());
274
275        let elem3 = reason.into();
276        assert_eq!(elem2, elem3);
277    }
278
279    #[cfg(feature = "pedantic")]
280    #[test]
281    fn test_reason_invalid_attribute() {
282        let elem: Element = "<reason xmlns='http://jabber.org/protocol/muc#admin' foo='bar'/>"
283            .parse()
284            .unwrap();
285        let error = Reason::try_from(elem).unwrap_err();
286        let message = match error {
287            FromElementError::Invalid(Error::Other(string)) => string,
288            _ => panic!(),
289        };
290        assert_eq!(message, "Unknown attribute in Reason element.".to_owned());
291    }
292
293    #[cfg(feature = "pedantic")]
294    #[test]
295    fn test_reason_invalid() {
296        let elem: Element = "<reason xmlns='http://jabber.org/protocol/muc#admin'>
297                <foobar/>
298            </reason>"
299            .parse()
300            .unwrap();
301        let error = Reason::try_from(elem).unwrap_err();
302        let message = match error {
303            FromElementError::Invalid(Error::Other(string)) => string,
304            _ => panic!(),
305        };
306        assert_eq!(message, "Unknown child in Reason element.".to_owned());
307    }
308
309    #[cfg(feature = "pedantic")]
310    #[test]
311    fn test_item_invalid_attr() {
312        let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#admin'
313                  affiliation='member'
314                  role='moderator'
315                  foo='bar'/>"
316            .parse()
317            .unwrap();
318        let error = Item::try_from(elem).unwrap_err();
319        let message = match error {
320            FromElementError::Invalid(Error::Other(string)) => string,
321            _ => panic!(),
322        };
323        assert_eq!(message, "Unknown attribute in Item element.".to_owned());
324    }
325
326    #[test]
327    fn test_item_affiliation_role_attr() {
328        let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#admin'
329                  affiliation='member'
330                  role='moderator'/>"
331            .parse()
332            .unwrap();
333        Item::try_from(elem).unwrap();
334    }
335
336    #[test]
337    fn test_item_affiliation_role_invalid_attr() {
338        let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#admin'
339                  affiliation='member'/>"
340            .parse()
341            .unwrap();
342        let error = Item::try_from(elem).unwrap_err();
343        let message = match error {
344            FromElementError::Invalid(Error::Other(string)) => string,
345            _ => panic!(),
346        };
347        assert_eq!(
348            message,
349            "Required attribute field 'role' on Item element missing.".to_owned()
350        );
351    }
352
353    #[test]
354    fn test_item_nick_attr() {
355        let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#admin'
356                  affiliation='member'
357                  role='moderator'
358                  nick='foobar'/>"
359            .parse()
360            .unwrap();
361        let item = Item::try_from(elem).unwrap();
362        match item {
363            Item { nick, .. } => assert_eq!(nick, Some("foobar".to_owned())),
364        }
365    }
366
367    #[test]
368    fn test_item_affiliation_role_invalid_attr2() {
369        let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#admin'
370                  role='moderator'/>"
371            .parse()
372            .unwrap();
373        let error = Item::try_from(elem).unwrap_err();
374        let message = match error {
375            FromElementError::Invalid(Error::Other(string)) => string,
376            _ => panic!(),
377        };
378        assert_eq!(
379            message,
380            "Required attribute field 'affiliation' on Item element missing.".to_owned()
381        );
382    }
383
384    #[test]
385    fn test_item_role_actor_child() {
386        let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#admin'
387                  affiliation='member'
388                  role='moderator'>
389                <actor nick='foobar'/>
390            </item>"
391            .parse()
392            .unwrap();
393        let item = Item::try_from(elem).unwrap();
394        let Item { actor, .. } = item;
395        let actor = actor.unwrap();
396        assert_eq!(actor.nick, Some("foobar".to_owned()));
397        assert_eq!(actor.jid, None);
398    }
399
400    #[test]
401    fn test_item_role_reason_child() {
402        let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#admin'
403                  affiliation='member'
404                  role='moderator'>
405                <reason>foobar</reason>
406            </item>"
407            .parse()
408            .unwrap();
409        let item = Item::try_from(elem).unwrap();
410        match item {
411            Item { reason, .. } => assert_eq!(reason, Some(Reason("foobar".to_owned()))),
412        }
413    }
414
415    #[test]
416    fn test_serialize_item() {
417        let reference: Element = "<item xmlns='http://jabber.org/protocol/muc#admin' affiliation='member' role='moderator'><actor nick='foobar'/><reason>foobar</reason></item>"
418        .parse()
419        .unwrap();
420
421        let elem: Element = "<actor xmlns='http://jabber.org/protocol/muc#admin' nick='foobar'/>"
422            .parse()
423            .unwrap();
424        let actor = Actor::try_from(elem).unwrap();
425
426        let elem: Element = "<reason xmlns='http://jabber.org/protocol/muc#admin'>foobar</reason>"
427            .parse()
428            .unwrap();
429        let reason = Reason::try_from(elem).unwrap();
430
431        let item = Item {
432            affiliation: Some(Affiliation::Member),
433            role: Some(Role::Moderator),
434            jid: None,
435            nick: None,
436            actor: Some(actor),
437            reason: Some(reason),
438        };
439
440        let serialized: Element = item.into();
441        println!("{reference:#?}");
442        println!("{serialized:#?}");
443        assert_eq!(serialized, reference);
444    }
445
446    #[test]
447    fn presence_payload() {
448        let elem: Element = "<query xmlns='http://jabber.org/protocol/muc#admin'/>"
449            .parse()
450            .unwrap();
451        let presence = Presence::new(PresenceType::None).with_payloads(vec![elem]);
452        assert_eq!(presence.payloads.len(), 1);
453    }
454
455    #[test]
456    fn message_payload() {
457        let jid: Jid = Jid::new("louise@example.com").unwrap();
458        let elem: Element = "<query xmlns='http://jabber.org/protocol/muc#admin'/>"
459            .parse()
460            .unwrap();
461        let message = Message::new(jid).with_payloads(vec![elem]);
462        assert_eq!(message.payloads.len(), 1);
463    }
464
465    #[test]
466    fn muc_item_affiliation_none() {
467        let elem: Element = "<query xmlns='http://jabber.org/protocol/muc#admin'><item affiliation='none' role='participant' /></query>"
468            .parse()
469            .unwrap();
470
471        let user = MucUser::try_from(elem).unwrap();
472        let item = user.items.iter().next().unwrap();
473        assert_eq!(item.affiliation, Some(Affiliation::None),);
474        assert_eq!(item.role, Some(Role::Participant),);
475
476        let item = Item::new()
477            .with_affiliation(Affiliation::None)
478            .with_role(Role::Participant);
479        let user = MucUser::new().with_items(vec![item]);
480        let elem: Element = user.into();
481        let item = elem.children().next().unwrap();
482        assert_eq!(item.attr("role").unwrap(), "participant");
483        assert_eq!(item.attr("affiliation").unwrap(), "none");
484    }
485
486    #[test]
487    #[ignore]
488    fn muc_item_no_affiliation() {
489        let elem: Element =
490            "<query xmlns='http://jabber.org/protocol/muc#admin'><item role='participant' /></x>"
491                .parse()
492                .unwrap();
493
494        let user = MucUser::try_from(elem).unwrap();
495        let item = user.items.iter().next().unwrap();
496        assert_eq!(item.affiliation, Some(Affiliation::None),);
497        assert_eq!(item.role, Some(Role::Participant),);
498
499        let item = Item::new()
500            .with_affiliation(Affiliation::None)
501            .with_role(Role::Participant);
502        let user = MucUser::new().with_items(vec![item]);
503        let elem: Element = user.into();
504        let item = elem.children().next().unwrap();
505        assert_eq!(item.attr("affiliation").unwrap(), "none");
506    }
507}