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
 16type Group = String;
 17
 18generate_attribute!(Subscription, "subscription", {
 19    None => "none",
 20    From => "from",
 21    To => "to",
 22    Both => "both",
 23    Remove => "remove",
 24});
 25
 26#[derive(Debug, Clone, PartialEq)]
 27pub struct Item {
 28    pub jid: Jid,
 29    pub name: Option<String>,
 30    pub subscription: Option<Subscription>,
 31    pub groups: Vec<Group>,
 32}
 33
 34impl TryFrom<Element> for Item {
 35    type Err = Error;
 36
 37    fn try_from(elem: Element) -> Result<Item, Error> {
 38        if !elem.is("item", ns::ROSTER) {
 39            return Err(Error::ParseError("This is not a roster item element."));
 40        }
 41
 42        let mut item = Item {
 43            jid: get_attr!(elem, "jid", required),
 44            name: get_attr!(elem, "name", optional).and_then(|name| if name == "" { None } else { Some(name) }),
 45            subscription: get_attr!(elem, "subscription", optional),
 46            groups: vec!(),
 47        };
 48        for child in elem.children() {
 49            if !child.is("group", ns::ROSTER) {
 50                return Err(Error::ParseError("Unknown element in roster item element."));
 51            }
 52            for _ in child.children() {
 53                return Err(Error::ParseError("Roster item group can’t have children."));
 54            }
 55            item.groups.push(child.text());
 56        }
 57        Ok(item)
 58    }
 59}
 60
 61impl From<Item> for Element {
 62    fn from(item: Item) -> Element {
 63        Element::builder("item")
 64                .ns(ns::ROSTER)
 65                .attr("jid", String::from(item.jid))
 66                .attr("name", item.name)
 67                .attr("subscription", item.subscription)
 68                .append(item.groups.into_iter().map(|group| Element::builder("group").ns(ns::ROSTER).append(group)).collect::<Vec<_>>())
 69                .build()
 70    }
 71}
 72
 73#[derive(Debug, Clone)]
 74pub struct Roster {
 75    pub ver: Option<String>,
 76    pub items: Vec<Item>,
 77}
 78
 79impl TryFrom<Element> for Roster {
 80    type Err = Error;
 81
 82    fn try_from(elem: Element) -> Result<Roster, Error> {
 83        if !elem.is("query", ns::ROSTER) {
 84            return Err(Error::ParseError("This is not a roster element."));
 85        }
 86        for (attr, _) in elem.attrs() {
 87            if attr != "ver" {
 88                return Err(Error::ParseError("Unknown attribute in roster element."));
 89            }
 90        }
 91
 92        let mut roster = Roster {
 93            ver: get_attr!(elem, "ver", optional),
 94            items: vec!(),
 95        };
 96        for child in elem.children() {
 97            if !child.is("item", ns::ROSTER) {
 98                return Err(Error::ParseError("Unknown element in roster element."));
 99            }
100            let item = Item::try_from(child.clone())?;
101            roster.items.push(item);
102        }
103        Ok(roster)
104    }
105}
106
107impl From<Roster> for Element {
108    fn from(roster: Roster) -> Element {
109        Element::builder("query")
110                .ns(ns::ROSTER)
111                .attr("ver", roster.ver)
112                .append(roster.items)
113                .build()
114    }
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
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'/>".parse().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#"
146<query xmlns='jabber:iq:roster' ver='ver11'>
147  <item jid='romeo@example.net'
148        name='Romeo'
149        subscription='both'>
150    <group>Friends</group>
151  </item>
152  <item jid='mercutio@example.com'
153        name='Mercutio'
154        subscription='from'/>
155  <item jid='benvolio@example.net'
156        name='Benvolio'
157        subscription='both'/>
158</query>
159"#.parse().unwrap();
160        let roster = Roster::try_from(elem).unwrap();
161        assert_eq!(roster.ver, Some(String::from("ver11")));
162        assert_eq!(roster.items.len(), 3);
163        assert_eq!(roster.items[0].jid, Jid::from_str("romeo@example.net").unwrap());
164        assert_eq!(roster.items[0].name, Some(String::from("Romeo")));
165        assert_eq!(roster.items[0].subscription, Some(Subscription::Both));
166        assert_eq!(roster.items[0].groups, vec!(String::from("Friends")));
167    }
168
169    #[test]
170    fn test_multiple_groups() {
171        let elem: Element = r#"
172<query xmlns='jabber:iq:roster'>
173  <item jid='test@example.org'>
174    <group>A</group>
175    <group>B</group>
176  </item>
177</query>
178"#.parse().unwrap();
179        let elem1 = elem.clone();
180        let roster = Roster::try_from(elem).unwrap();
181        assert!(roster.ver.is_none());
182        assert_eq!(roster.items.len(), 1);
183        assert_eq!(roster.items[0].jid, Jid::from_str("test@example.org").unwrap());
184        assert_eq!(roster.items[0].name, None);
185        assert_eq!(roster.items[0].groups.len(), 2);
186        assert_eq!(roster.items[0].groups[0], String::from("A"));
187        assert_eq!(roster.items[0].groups[1], String::from("B"));
188        let elem2 = roster.into();
189        assert_eq!(elem1, elem2);
190    }
191
192    #[test]
193    fn test_set() {
194        let elem: Element = "<query xmlns='jabber:iq:roster'><item jid='nurse@example.com'/></query>".parse().unwrap();
195        let roster = Roster::try_from(elem).unwrap();
196        assert!(roster.ver.is_none());
197        assert_eq!(roster.items.len(), 1);
198
199        let elem: Element = r#"
200<query xmlns='jabber:iq:roster'>
201  <item jid='nurse@example.com'
202        name='Nurse'>
203    <group>Servants</group>
204  </item>
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_eq!(roster.items[0].name, Some(String::from("Nurse")));
212        assert_eq!(roster.items[0].groups.len(), 1);
213        assert_eq!(roster.items[0].groups[0], String::from("Servants"));
214
215        let elem: Element = r#"
216<query xmlns='jabber:iq:roster'>
217  <item jid='nurse@example.com'
218        subscription='remove'/>
219</query>
220"#.parse().unwrap();
221        let roster = Roster::try_from(elem).unwrap();
222        assert!(roster.ver.is_none());
223        assert_eq!(roster.items.len(), 1);
224        assert_eq!(roster.items[0].jid, Jid::from_str("nurse@example.com").unwrap());
225        assert!(roster.items[0].name.is_none());
226        assert!(roster.items[0].groups.is_empty());
227        assert_eq!(roster.items[0].subscription, Some(Subscription::Remove));
228    }
229
230    #[test]
231    fn test_invalid() {
232        let elem: Element = "<query xmlns='jabber:iq:roster'><coucou/></query>".parse().unwrap();
233        let error = Roster::try_from(elem).unwrap_err();
234        let message = match error {
235            Error::ParseError(string) => string,
236            _ => panic!(),
237        };
238        assert_eq!(message, "Unknown element in roster element.");
239
240        let elem: Element = "<query xmlns='jabber:iq:roster' coucou=''/>".parse().unwrap();
241        let error = Roster::try_from(elem).unwrap_err();
242        let message = match error {
243            Error::ParseError(string) => string,
244            _ => panic!(),
245        };
246        assert_eq!(message, "Unknown attribute in roster element.");
247    }
248
249    #[test]
250    fn test_invalid_item() {
251        let elem: Element = "<query xmlns='jabber:iq:roster'><item/></query>".parse().unwrap();
252        let error = Roster::try_from(elem).unwrap_err();
253        let message = match error {
254            Error::ParseError(string) => string,
255            _ => panic!(),
256        };
257        assert_eq!(message, "Required attribute 'jid' missing.");
258
259        /*
260        let elem: Element = "<query xmlns='jabber:iq:roster'><item jid=''/></query>".parse().unwrap();
261        let error = Roster::try_from(elem).unwrap_err();
262        let error = match error {
263            Error::JidParseError(error) => error,
264            _ => panic!(),
265        };
266        assert_eq!(error.description(), "Invalid JID, I guess?");
267        */
268
269        let elem: Element = "<query xmlns='jabber:iq:roster'><item jid='coucou'><coucou/></item></query>".parse().unwrap();
270        let error = Roster::try_from(elem).unwrap_err();
271        let message = match error {
272            Error::ParseError(string) => string,
273            _ => panic!(),
274        };
275        assert_eq!(message, "Unknown element in roster item element.");
276    }
277}