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    Item, "item", ROSTER,
 50    attributes: [
 51        /// JID of this contact.
 52        jid: Required<BareJid> = "jid",
 53
 54        /// Name of this contact.
 55        name: OptionEmpty<String> = "name",
 56
 57        /// Subscription status of this contact.
 58        subscription: Default<Subscription> = "subscription",
 59
 60        /// Indicates “Pending Out” sub-states for this contact.
 61        ask: Default<Ask> = "ask",
 62    ],
 63
 64    children: [
 65        /// Groups this contact is part of.
 66        groups: Vec<Group> = ("group", ROSTER) => Group
 67    ]
 68);
 69
 70generate_element!(
 71    /// The contact list of the user.
 72    Roster, "query", ROSTER,
 73    attributes: [
 74        /// Version of the contact list.
 75        ///
 76        /// This is an opaque string that should only be sent back to the server on
 77        /// a new connection, if this client is storing the contact list between
 78        /// connections.
 79        ver: Option<String> = "ver"
 80    ],
 81    children: [
 82        /// List of the contacts of the user.
 83        items: Vec<Item> = ("item", ROSTER) => Item
 84    ]
 85);
 86
 87impl IqGetPayload for Roster {}
 88impl IqSetPayload for Roster {}
 89impl IqResultPayload for Roster {}
 90
 91#[cfg(test)]
 92mod tests {
 93    use super::*;
 94    use crate::util::error::Error;
 95    use crate::Element;
 96    use std::convert::TryFrom;
 97    use std::str::FromStr;
 98
 99    #[cfg(target_pointer_width = "32")]
100    #[test]
101    fn test_size() {
102        assert_size!(Group, 12);
103        assert_size!(Subscription, 1);
104        assert_size!(Ask, 1);
105        assert_size!(Item, 52);
106        assert_size!(Roster, 24);
107    }
108
109    #[cfg(target_pointer_width = "64")]
110    #[test]
111    fn test_size() {
112        assert_size!(Group, 24);
113        assert_size!(Subscription, 1);
114        assert_size!(Ask, 1);
115        assert_size!(Item, 104);
116        assert_size!(Roster, 48);
117    }
118
119    #[test]
120    fn test_get() {
121        let elem: Element = "<query xmlns='jabber:iq:roster'/>".parse().unwrap();
122        let roster = Roster::try_from(elem).unwrap();
123        assert!(roster.ver.is_none());
124        assert!(roster.items.is_empty());
125    }
126
127    #[test]
128    fn test_result() {
129        let elem: Element = "<query xmlns='jabber:iq:roster' ver='ver7'><item jid='nurse@example.com'/><item jid='romeo@example.net'/></query>".parse().unwrap();
130        let roster = Roster::try_from(elem).unwrap();
131        assert_eq!(roster.ver, Some(String::from("ver7")));
132        assert_eq!(roster.items.len(), 2);
133
134        let elem2: Element = "<query xmlns='jabber:iq:roster' ver='ver7'><item jid='nurse@example.com'/><item jid='romeo@example.net' name=''/></query>".parse().unwrap();
135        let roster2 = Roster::try_from(elem2).unwrap();
136        assert_eq!(roster.items, roster2.items);
137
138        let elem: Element = "<query xmlns='jabber:iq:roster' ver='ver9'/>"
139            .parse()
140            .unwrap();
141        let roster = Roster::try_from(elem).unwrap();
142        assert_eq!(roster.ver, Some(String::from("ver9")));
143        assert!(roster.items.is_empty());
144
145        let elem: Element = r#"<query xmlns='jabber:iq:roster' ver='ver11'>
146  <item jid='romeo@example.net'
147        name='Romeo'
148        subscription='both'>
149    <group>Friends</group>
150  </item>
151  <item jid='mercutio@example.com'
152        name='Mercutio'
153        subscription='from'/>
154  <item jid='benvolio@example.net'
155        name='Benvolio'
156        subscription='both'/>
157  <item jid='contact@example.org'
158        subscription='none'
159        ask='subscribe'
160        name='MyContact'>
161      <group>MyBuddies</group>
162  </item>
163</query>
164"#
165        .parse()
166        .unwrap();
167        let roster = Roster::try_from(elem).unwrap();
168        assert_eq!(roster.ver, Some(String::from("ver11")));
169        assert_eq!(roster.items.len(), 4);
170        assert_eq!(roster.items[0].jid, BareJid::new("romeo", "example.net"));
171        assert_eq!(roster.items[0].name, Some(String::from("Romeo")));
172        assert_eq!(roster.items[0].subscription, Subscription::Both);
173        assert_eq!(roster.items[0].ask, Ask::None);
174        assert_eq!(
175            roster.items[0].groups,
176            vec!(Group::from_str("Friends").unwrap())
177        );
178
179        assert_eq!(roster.items[3].jid, BareJid::new("contact", "example.org"));
180        assert_eq!(roster.items[3].name, Some(String::from("MyContact")));
181        assert_eq!(roster.items[3].subscription, Subscription::None);
182        assert_eq!(roster.items[3].ask, Ask::Subscribe);
183        assert_eq!(
184            roster.items[3].groups,
185            vec!(Group::from_str("MyBuddies").unwrap())
186        );
187    }
188
189    #[test]
190    fn test_multiple_groups() {
191        let elem: Element = "<query xmlns='jabber:iq:roster'><item jid='test@example.org'><group>A</group><group>B</group></item></query>"
192        .parse()
193        .unwrap();
194        let elem1 = elem.clone();
195        let roster = Roster::try_from(elem).unwrap();
196        assert!(roster.ver.is_none());
197        assert_eq!(roster.items.len(), 1);
198        assert_eq!(roster.items[0].jid, BareJid::new("test", "example.org"));
199        assert_eq!(roster.items[0].name, None);
200        assert_eq!(roster.items[0].groups.len(), 2);
201        assert_eq!(roster.items[0].groups[0], Group::from_str("A").unwrap());
202        assert_eq!(roster.items[0].groups[1], Group::from_str("B").unwrap());
203        let elem2 = roster.into();
204        assert_eq!(elem1, elem2);
205    }
206
207    #[test]
208    fn test_set() {
209        let elem: Element =
210            "<query xmlns='jabber:iq:roster'><item jid='nurse@example.com'/></query>"
211                .parse()
212                .unwrap();
213        let roster = Roster::try_from(elem).unwrap();
214        assert!(roster.ver.is_none());
215        assert_eq!(roster.items.len(), 1);
216
217        let elem: Element = r#"<query xmlns='jabber:iq:roster'>
218  <item jid='nurse@example.com'
219        name='Nurse'>
220    <group>Servants</group>
221  </item>
222</query>"#
223            .parse()
224            .unwrap();
225        let roster = Roster::try_from(elem).unwrap();
226        assert!(roster.ver.is_none());
227        assert_eq!(roster.items.len(), 1);
228        assert_eq!(roster.items[0].jid, BareJid::new("nurse", "example.com"));
229        assert_eq!(roster.items[0].name, Some(String::from("Nurse")));
230        assert_eq!(roster.items[0].groups.len(), 1);
231        assert_eq!(
232            roster.items[0].groups[0],
233            Group::from_str("Servants").unwrap()
234        );
235
236        let elem: Element = r#"<query xmlns='jabber:iq:roster'>
237  <item jid='nurse@example.com'
238        subscription='remove'/>
239</query>"#
240            .parse()
241            .unwrap();
242        let roster = Roster::try_from(elem).unwrap();
243        assert!(roster.ver.is_none());
244        assert_eq!(roster.items.len(), 1);
245        assert_eq!(roster.items[0].jid, BareJid::new("nurse", "example.com"));
246        assert!(roster.items[0].name.is_none());
247        assert!(roster.items[0].groups.is_empty());
248        assert_eq!(roster.items[0].subscription, Subscription::Remove);
249    }
250
251    #[cfg(not(feature = "disable-validation"))]
252    #[test]
253    fn test_invalid() {
254        let elem: Element = "<query xmlns='jabber:iq:roster'><coucou/></query>"
255            .parse()
256            .unwrap();
257        let error = Roster::try_from(elem).unwrap_err();
258        let message = match error {
259            Error::ParseError(string) => string,
260            _ => panic!(),
261        };
262        assert_eq!(message, "Unknown child in query element.");
263
264        let elem: Element = "<query xmlns='jabber:iq:roster' coucou=''/>"
265            .parse()
266            .unwrap();
267        let error = Roster::try_from(elem).unwrap_err();
268        let message = match error {
269            Error::ParseError(string) => string,
270            _ => panic!(),
271        };
272        assert_eq!(message, "Unknown attribute in query element.");
273    }
274
275    #[test]
276    fn test_invalid_item() {
277        let elem: Element = "<query xmlns='jabber:iq:roster'><item/></query>"
278            .parse()
279            .unwrap();
280        let error = Roster::try_from(elem).unwrap_err();
281        let message = match error {
282            Error::ParseError(string) => string,
283            _ => panic!(),
284        };
285        assert_eq!(message, "Required attribute 'jid' missing.");
286
287        /*
288        let elem: Element = "<query xmlns='jabber:iq:roster'><item jid=''/></query>".parse().unwrap();
289        let error = Roster::try_from(elem).unwrap_err();
290        let error = match error {
291            Error::JidParseError(error) => error,
292            _ => panic!(),
293        };
294        assert_eq!(error.description(), "Invalid JID, I guess?");
295        */
296
297        let elem: Element =
298            "<query xmlns='jabber:iq:roster'><item jid='coucou'><coucou/></item></query>"
299                .parse()
300                .unwrap();
301        let error = Roster::try_from(elem).unwrap_err();
302        let message = match error {
303            Error::ParseError(string) => string,
304            _ => panic!(),
305        };
306        assert_eq!(message, "Unknown child in item element.");
307    }
308}