roster.rs

  1// Copyright (c) 2017 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
  2//
  3// This Source Code Form is subject to the terms of the Mozilla Public
  4// License, v. 2.0. If a copy of the MPL was not distributed with this
  5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6
  7use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload};
  8use jid::BareJid;
  9
 10generate_elem_id!(
 11    /// Represents a group a contact is part of.
 12    Group,
 13    "group",
 14    ROSTER
 15);
 16
 17generate_attribute!(
 18    /// The state of your mutual subscription with a contact.
 19    Subscription, "subscription", {
 20        /// The user doesn’t have any subscription to this contact’s presence,
 21        /// and neither does this contact.
 22        None => "none",
 23
 24        /// Only this contact has a subscription with you, not the opposite.
 25        From => "from",
 26
 27        /// Only you have a subscription with this contact, not the opposite.
 28        To => "to",
 29
 30        /// Both you and your contact are subscribed to each other’s presence.
 31        Both => "both",
 32
 33        /// In a roster set, this asks the server to remove this contact item
 34        /// from your roster.
 35        Remove => "remove",
 36    }, Default = None
 37);
 38
 39generate_attribute!(
 40    /// The sub-state of subscription with a contact.
 41    Ask, "ask", (
 42        /// Pending sub-state of the 'none' subscription state.
 43        Subscribe => "subscribe"
 44    )
 45);
 46
 47generate_element!(
 48    /// Contact from the user’s contact list.
 49    #[derive(PartialEq)]
 50    Item, "item", ROSTER,
 51    attributes: [
 52        /// JID of this contact.
 53        jid: Required<BareJid> = "jid",
 54
 55        /// Name of this contact.
 56        name: OptionEmpty<String> = "name",
 57
 58        /// Subscription status of this contact.
 59        subscription: Default<Subscription> = "subscription",
 60
 61        /// Indicates “Pending Out” sub-states for this contact.
 62        ask: Default<Ask> = "ask",
 63    ],
 64
 65    children: [
 66        /// Groups this contact is part of.
 67        groups: Vec<Group> = ("group", ROSTER) => Group
 68    ]
 69);
 70
 71generate_element!(
 72    /// The contact list of the user.
 73    Roster, "query", ROSTER,
 74    attributes: [
 75        /// Version of the contact list.
 76        ///
 77        /// This is an opaque string that should only be sent back to the server on
 78        /// a new connection, if this client is storing the contact list between
 79        /// connections.
 80        ver: Option<String> = "ver"
 81    ],
 82    children: [
 83        /// List of the contacts of the user.
 84        items: Vec<Item> = ("item", ROSTER) => Item
 85    ]
 86);
 87
 88impl IqGetPayload for Roster {}
 89impl IqSetPayload for Roster {}
 90impl IqResultPayload for Roster {}
 91
 92#[cfg(test)]
 93mod tests {
 94    use super::*;
 95    use crate::util::compare_elements::NamespaceAwareCompare;
 96    use crate::util::error::Error;
 97    use minidom::Element;
 98    use std::str::FromStr;
 99    use std::convert::TryFrom;
100
101    #[cfg(target_pointer_width = "32")]
102    #[test]
103    fn test_size() {
104        assert_size!(Group, 12);
105        assert_size!(Subscription, 1);
106        assert_size!(Ask, 1);
107        assert_size!(Item, 52);
108        assert_size!(Roster, 24);
109    }
110
111    #[cfg(target_pointer_width = "64")]
112    #[test]
113    fn test_size() {
114        assert_size!(Group, 24);
115        assert_size!(Subscription, 1);
116        assert_size!(Ask, 1);
117        assert_size!(Item, 104);
118        assert_size!(Roster, 48);
119    }
120
121    #[test]
122    fn test_get() {
123        let elem: Element = "<query xmlns='jabber:iq:roster'/>".parse().unwrap();
124        let roster = Roster::try_from(elem).unwrap();
125        assert!(roster.ver.is_none());
126        assert!(roster.items.is_empty());
127    }
128
129    #[test]
130    fn test_result() {
131        let elem: Element = "<query xmlns='jabber:iq:roster' ver='ver7'><item jid='nurse@example.com'/><item jid='romeo@example.net'/></query>".parse().unwrap();
132        let roster = Roster::try_from(elem).unwrap();
133        assert_eq!(roster.ver, Some(String::from("ver7")));
134        assert_eq!(roster.items.len(), 2);
135
136        let elem2: Element = "<query xmlns='jabber:iq:roster' ver='ver7'><item jid='nurse@example.com'/><item jid='romeo@example.net' name=''/></query>".parse().unwrap();
137        let roster2 = Roster::try_from(elem2).unwrap();
138        assert_eq!(roster.items, roster2.items);
139
140        let elem: Element = "<query xmlns='jabber:iq:roster' ver='ver9'/>"
141            .parse()
142            .unwrap();
143        let roster = Roster::try_from(elem).unwrap();
144        assert_eq!(roster.ver, Some(String::from("ver9")));
145        assert!(roster.items.is_empty());
146
147        let elem: Element = r#"
148<query xmlns='jabber:iq:roster' ver='ver11'>
149  <item jid='romeo@example.net'
150        name='Romeo'
151        subscription='both'>
152    <group>Friends</group>
153  </item>
154  <item jid='mercutio@example.com'
155        name='Mercutio'
156        subscription='from'/>
157  <item jid='benvolio@example.net'
158        name='Benvolio'
159        subscription='both'/>
160  <item jid='contact@example.org'
161        subscription='none'
162        ask='subscribe'
163        name='MyContact'>
164      <group>MyBuddies</group>
165  </item>
166</query>
167"#
168        .parse()
169        .unwrap();
170        let roster = Roster::try_from(elem).unwrap();
171        assert_eq!(roster.ver, Some(String::from("ver11")));
172        assert_eq!(roster.items.len(), 4);
173        assert_eq!(
174            roster.items[0].jid,
175            BareJid::from_str("romeo@example.net").unwrap()
176        );
177        assert_eq!(roster.items[0].name, Some(String::from("Romeo")));
178        assert_eq!(roster.items[0].subscription, Subscription::Both);
179        assert_eq!(roster.items[0].ask, Ask::None);
180        assert_eq!(
181            roster.items[0].groups,
182            vec!(Group::from_str("Friends").unwrap())
183        );
184
185        assert_eq!(
186            roster.items[3].jid,
187            BareJid::from_str("contact@example.org").unwrap()
188        );
189        assert_eq!(roster.items[3].name, Some(String::from("MyContact")));
190        assert_eq!(roster.items[3].subscription, Subscription::None);
191        assert_eq!(roster.items[3].ask, Ask::Subscribe);
192        assert_eq!(
193            roster.items[3].groups,
194            vec!(Group::from_str("MyBuddies").unwrap())
195        );
196    }
197
198    #[test]
199    fn test_multiple_groups() {
200        let elem: Element = r#"
201<query xmlns='jabber:iq:roster'>
202  <item jid='test@example.org'>
203    <group>A</group>
204    <group>B</group>
205  </item>
206</query>
207"#
208        .parse()
209        .unwrap();
210        let elem1 = elem.clone();
211        let roster = Roster::try_from(elem).unwrap();
212        assert!(roster.ver.is_none());
213        assert_eq!(roster.items.len(), 1);
214        assert_eq!(
215            roster.items[0].jid,
216            BareJid::from_str("test@example.org").unwrap()
217        );
218        assert_eq!(roster.items[0].name, None);
219        assert_eq!(roster.items[0].groups.len(), 2);
220        assert_eq!(roster.items[0].groups[0], Group::from_str("A").unwrap());
221        assert_eq!(roster.items[0].groups[1], Group::from_str("B").unwrap());
222        let elem2 = roster.into();
223        assert!(elem1.compare_to(&elem2));
224    }
225
226    #[test]
227    fn test_set() {
228        let elem: Element =
229            "<query xmlns='jabber:iq:roster'><item jid='nurse@example.com'/></query>"
230                .parse()
231                .unwrap();
232        let roster = Roster::try_from(elem).unwrap();
233        assert!(roster.ver.is_none());
234        assert_eq!(roster.items.len(), 1);
235
236        let elem: Element = r#"
237<query xmlns='jabber:iq:roster'>
238  <item jid='nurse@example.com'
239        name='Nurse'>
240    <group>Servants</group>
241  </item>
242</query>
243"#
244        .parse()
245        .unwrap();
246        let roster = Roster::try_from(elem).unwrap();
247        assert!(roster.ver.is_none());
248        assert_eq!(roster.items.len(), 1);
249        assert_eq!(
250            roster.items[0].jid,
251            BareJid::from_str("nurse@example.com").unwrap()
252        );
253        assert_eq!(roster.items[0].name, Some(String::from("Nurse")));
254        assert_eq!(roster.items[0].groups.len(), 1);
255        assert_eq!(
256            roster.items[0].groups[0],
257            Group::from_str("Servants").unwrap()
258        );
259
260        let elem: Element = r#"
261<query xmlns='jabber:iq:roster'>
262  <item jid='nurse@example.com'
263        subscription='remove'/>
264</query>
265"#
266        .parse()
267        .unwrap();
268        let roster = Roster::try_from(elem).unwrap();
269        assert!(roster.ver.is_none());
270        assert_eq!(roster.items.len(), 1);
271        assert_eq!(
272            roster.items[0].jid,
273            BareJid::from_str("nurse@example.com").unwrap()
274        );
275        assert!(roster.items[0].name.is_none());
276        assert!(roster.items[0].groups.is_empty());
277        assert_eq!(roster.items[0].subscription, Subscription::Remove);
278    }
279
280    #[cfg(not(feature = "disable-validation"))]
281    #[test]
282    fn test_invalid() {
283        let elem: Element = "<query xmlns='jabber:iq:roster'><coucou/></query>"
284            .parse()
285            .unwrap();
286        let error = Roster::try_from(elem).unwrap_err();
287        let message = match error {
288            Error::ParseError(string) => string,
289            _ => panic!(),
290        };
291        assert_eq!(message, "Unknown child in query element.");
292
293        let elem: Element = "<query xmlns='jabber:iq:roster' coucou=''/>"
294            .parse()
295            .unwrap();
296        let error = Roster::try_from(elem).unwrap_err();
297        let message = match error {
298            Error::ParseError(string) => string,
299            _ => panic!(),
300        };
301        assert_eq!(message, "Unknown attribute in query element.");
302    }
303
304    #[test]
305    fn test_invalid_item() {
306        let elem: Element = "<query xmlns='jabber:iq:roster'><item/></query>"
307            .parse()
308            .unwrap();
309        let error = Roster::try_from(elem).unwrap_err();
310        let message = match error {
311            Error::ParseError(string) => string,
312            _ => panic!(),
313        };
314        assert_eq!(message, "Required attribute 'jid' missing.");
315
316        /*
317        let elem: Element = "<query xmlns='jabber:iq:roster'><item jid=''/></query>".parse().unwrap();
318        let error = Roster::try_from(elem).unwrap_err();
319        let error = match error {
320            Error::JidParseError(error) => error,
321            _ => panic!(),
322        };
323        assert_eq!(error.description(), "Invalid JID, I guess?");
324        */
325
326        let elem: Element =
327            "<query xmlns='jabber:iq:roster'><item jid='coucou'><coucou/></item></query>"
328                .parse()
329                .unwrap();
330        let error = Roster::try_from(elem).unwrap_err();
331        let message = match error {
332            Error::ParseError(string) => string,
333            _ => panic!(),
334        };
335        assert_eq!(message, "Unknown child in item element.");
336    }
337}