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