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