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 jid::Jid;
  8use iq::{IqGetPayload, IqSetPayload, IqResultPayload};
  9
 10generate_elem_id!(
 11    /// Represents a group a contact is part of.
 12    Group, "group", ROSTER
 13);
 14
 15generate_attribute!(
 16    /// The state of your mutual subscription with a contact.
 17    Subscription, "subscription", {
 18        /// The user doesn’t have any subscription to this contact’s presence,
 19        /// and neither does this contact.
 20        None => "none",
 21
 22        /// Only this contact has a subscription with you, not the opposite.
 23        From => "from",
 24
 25        /// Only you have a subscription with this contact, not the opposite.
 26        To => "to",
 27
 28        /// Both you and your contact are subscribed to each other’s presence.
 29        Both => "both",
 30
 31        /// In a roster set, this asks the server to remove this contact item
 32        /// from your roster.
 33        Remove => "remove",
 34    }, Default = None
 35);
 36
 37generate_element!(
 38    /// Contact from the user’s contact list.
 39    #[derive(PartialEq)]
 40    Item, "item", ROSTER,
 41    attributes: [
 42        /// JID of this contact.
 43        jid: Jid = "jid" => required,
 44
 45        /// Name of this contact.
 46        name: Option<String> = "name" => optional_empty,
 47
 48        /// Subscription status of this contact.
 49        subscription: Subscription = "subscription" => default
 50    ],
 51
 52    children: [
 53        /// Groups this contact is part of.
 54        groups: Vec<Group> = ("group", ROSTER) => Group
 55    ]
 56);
 57
 58generate_element!(
 59    /// The contact list of the user.
 60    Roster, "query", ROSTER,
 61    attributes: [
 62        /// Version of the contact list.
 63        ///
 64        /// This is an opaque string that should only be sent back to the server on
 65        /// a new connection, if this client is storing the contact list between
 66        /// connections.
 67        ver: Option<String> = "ver" => optional
 68    ],
 69    children: [
 70        /// List of the contacts of the user.
 71        items: Vec<Item> = ("item", ROSTER) => Item
 72    ]
 73);
 74
 75impl IqGetPayload for Roster {}
 76impl IqSetPayload for Roster {}
 77impl IqResultPayload for Roster {}
 78
 79#[cfg(test)]
 80mod tests {
 81    use super::*;
 82    use try_from::TryFrom;
 83    use minidom::Element;
 84    use error::Error;
 85    use std::str::FromStr;
 86    use compare_elements::NamespaceAwareCompare;
 87
 88    #[test]
 89    fn test_get() {
 90        let elem: Element = "<query xmlns='jabber:iq:roster'/>".parse().unwrap();
 91        let roster = Roster::try_from(elem).unwrap();
 92        assert!(roster.ver.is_none());
 93        assert!(roster.items.is_empty());
 94    }
 95
 96    #[test]
 97    fn test_result() {
 98        let elem: Element = "<query xmlns='jabber:iq:roster' ver='ver7'><item jid='nurse@example.com'/><item jid='romeo@example.net'/></query>".parse().unwrap();
 99        let roster = Roster::try_from(elem).unwrap();
100        assert_eq!(roster.ver, Some(String::from("ver7")));
101        assert_eq!(roster.items.len(), 2);
102
103        let elem2: Element = "<query xmlns='jabber:iq:roster' ver='ver7'><item jid='nurse@example.com'/><item jid='romeo@example.net' name=''/></query>".parse().unwrap();
104        let roster2 = Roster::try_from(elem2).unwrap();
105        assert_eq!(roster.items, roster2.items);
106
107        let elem: Element = "<query xmlns='jabber:iq:roster' ver='ver9'/>".parse().unwrap();
108        let roster = Roster::try_from(elem).unwrap();
109        assert_eq!(roster.ver, Some(String::from("ver9")));
110        assert!(roster.items.is_empty());
111
112        let elem: Element = r#"
113<query xmlns='jabber:iq:roster' ver='ver11'>
114  <item jid='romeo@example.net'
115        name='Romeo'
116        subscription='both'>
117    <group>Friends</group>
118  </item>
119  <item jid='mercutio@example.com'
120        name='Mercutio'
121        subscription='from'/>
122  <item jid='benvolio@example.net'
123        name='Benvolio'
124        subscription='both'/>
125</query>
126"#.parse().unwrap();
127        let roster = Roster::try_from(elem).unwrap();
128        assert_eq!(roster.ver, Some(String::from("ver11")));
129        assert_eq!(roster.items.len(), 3);
130        assert_eq!(roster.items[0].jid, Jid::from_str("romeo@example.net").unwrap());
131        assert_eq!(roster.items[0].name, Some(String::from("Romeo")));
132        assert_eq!(roster.items[0].subscription, Subscription::Both);
133        assert_eq!(roster.items[0].groups, vec!(Group::from_str("Friends").unwrap()));
134    }
135
136    #[test]
137    fn test_multiple_groups() {
138        let elem: Element = r#"
139<query xmlns='jabber:iq:roster'>
140  <item jid='test@example.org'>
141    <group>A</group>
142    <group>B</group>
143  </item>
144</query>
145"#.parse().unwrap();
146        let elem1 = elem.clone();
147        let roster = Roster::try_from(elem).unwrap();
148        assert!(roster.ver.is_none());
149        assert_eq!(roster.items.len(), 1);
150        assert_eq!(roster.items[0].jid, Jid::from_str("test@example.org").unwrap());
151        assert_eq!(roster.items[0].name, None);
152        assert_eq!(roster.items[0].groups.len(), 2);
153        assert_eq!(roster.items[0].groups[0], Group::from_str("A").unwrap());
154        assert_eq!(roster.items[0].groups[1], Group::from_str("B").unwrap());
155        let elem2 = roster.into();
156        assert!(elem1.compare_to(&elem2));
157    }
158
159    #[test]
160    fn test_set() {
161        let elem: Element = "<query xmlns='jabber:iq:roster'><item jid='nurse@example.com'/></query>".parse().unwrap();
162        let roster = Roster::try_from(elem).unwrap();
163        assert!(roster.ver.is_none());
164        assert_eq!(roster.items.len(), 1);
165
166        let elem: Element = r#"
167<query xmlns='jabber:iq:roster'>
168  <item jid='nurse@example.com'
169        name='Nurse'>
170    <group>Servants</group>
171  </item>
172</query>
173"#.parse().unwrap();
174        let roster = Roster::try_from(elem).unwrap();
175        assert!(roster.ver.is_none());
176        assert_eq!(roster.items.len(), 1);
177        assert_eq!(roster.items[0].jid, Jid::from_str("nurse@example.com").unwrap());
178        assert_eq!(roster.items[0].name, Some(String::from("Nurse")));
179        assert_eq!(roster.items[0].groups.len(), 1);
180        assert_eq!(roster.items[0].groups[0], Group::from_str("Servants").unwrap());
181
182        let elem: Element = r#"
183<query xmlns='jabber:iq:roster'>
184  <item jid='nurse@example.com'
185        subscription='remove'/>
186</query>
187"#.parse().unwrap();
188        let roster = Roster::try_from(elem).unwrap();
189        assert!(roster.ver.is_none());
190        assert_eq!(roster.items.len(), 1);
191        assert_eq!(roster.items[0].jid, Jid::from_str("nurse@example.com").unwrap());
192        assert!(roster.items[0].name.is_none());
193        assert!(roster.items[0].groups.is_empty());
194        assert_eq!(roster.items[0].subscription, Subscription::Remove);
195    }
196
197    #[test]
198    fn test_invalid() {
199        let elem: Element = "<query xmlns='jabber:iq:roster'><coucou/></query>".parse().unwrap();
200        let error = Roster::try_from(elem).unwrap_err();
201        let message = match error {
202            Error::ParseError(string) => string,
203            _ => panic!(),
204        };
205        assert_eq!(message, "Unknown child in query element.");
206
207        let elem: Element = "<query xmlns='jabber:iq:roster' coucou=''/>".parse().unwrap();
208        let error = Roster::try_from(elem).unwrap_err();
209        let message = match error {
210            Error::ParseError(string) => string,
211            _ => panic!(),
212        };
213        assert_eq!(message, "Unknown attribute in query element.");
214    }
215
216    #[test]
217    fn test_invalid_item() {
218        let elem: Element = "<query xmlns='jabber:iq:roster'><item/></query>".parse().unwrap();
219        let error = Roster::try_from(elem).unwrap_err();
220        let message = match error {
221            Error::ParseError(string) => string,
222            _ => panic!(),
223        };
224        assert_eq!(message, "Required attribute 'jid' missing.");
225
226        /*
227        let elem: Element = "<query xmlns='jabber:iq:roster'><item jid=''/></query>".parse().unwrap();
228        let error = Roster::try_from(elem).unwrap_err();
229        let error = match error {
230            Error::JidParseError(error) => error,
231            _ => panic!(),
232        };
233        assert_eq!(error.description(), "Invalid JID, I guess?");
234        */
235
236        let elem: Element = "<query xmlns='jabber:iq:roster'><item jid='coucou'><coucou/></item></query>".parse().unwrap();
237        let error = Roster::try_from(elem).unwrap_err();
238        let message = match error {
239            Error::ParseError(string) => string,
240            _ => panic!(),
241        };
242        assert_eq!(message, "Unknown child in item element.");
243    }
244}