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