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
 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 Error = 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 Into<Element> for Item {
 62    fn into(self) -> Element {
 63        Element::builder("item")
 64                .ns(ns::ROSTER)
 65                .attr("jid", String::from(self.jid))
 66                .attr("name", self.name)
 67                .attr("subscription", self.subscription)
 68                .append(self.groups)
 69                .build()
 70    }
 71}
 72
 73impl IntoElements for Item {
 74    fn into_elements(self, emitter: &mut ElementEmitter) {
 75        emitter.append_child(self.into());
 76    }
 77}
 78
 79#[derive(Debug, Clone)]
 80pub struct Roster {
 81    pub ver: Option<String>,
 82    pub items: Vec<Item>,
 83}
 84
 85impl TryFrom<Element> for Roster {
 86    type Error = Error;
 87
 88    fn try_from(elem: Element) -> Result<Roster, Error> {
 89        if !elem.is("query", ns::ROSTER) {
 90            return Err(Error::ParseError("This is not a roster element."));
 91        }
 92        for (attr, _) in elem.attrs() {
 93            if attr != "ver" {
 94                return Err(Error::ParseError("Unknown attribute in roster element."));
 95            }
 96        }
 97
 98        let mut roster = Roster {
 99            ver: get_attr!(elem, "ver", optional),
100            items: vec!(),
101        };
102        for child in elem.children() {
103            if !child.is("item", ns::ROSTER) {
104                return Err(Error::ParseError("Unknown element in roster element."));
105            }
106            let item = Item::try_from(child.clone())?;
107            roster.items.push(item);
108        }
109        Ok(roster)
110    }
111}
112
113impl Into<Element> for Roster {
114    fn into(self) -> Element {
115        Element::builder("query")
116                .ns(ns::ROSTER)
117                .attr("ver", self.ver)
118                .append(self.items)
119                .build()
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126
127    #[test]
128    fn test_get() {
129        let elem: Element = "<query xmlns='jabber:iq:roster'/>".parse().unwrap();
130        let roster = Roster::try_from(elem).unwrap();
131        assert!(roster.ver.is_none());
132        assert!(roster.items.is_empty());
133    }
134
135    #[test]
136    fn test_result() {
137        let elem: Element = "<query xmlns='jabber:iq:roster' ver='ver7'><item jid='nurse@example.com'/><item jid='romeo@example.net'/></query>".parse().unwrap();
138        let roster = Roster::try_from(elem).unwrap();
139        assert_eq!(roster.ver, Some(String::from("ver7")));
140        assert_eq!(roster.items.len(), 2);
141
142        let elem2: Element = "<query xmlns='jabber:iq:roster' ver='ver7'><item jid='nurse@example.com'/><item jid='romeo@example.net' name=''/></query>".parse().unwrap();
143        let roster2 = Roster::try_from(elem2).unwrap();
144        assert_eq!(roster.items, roster2.items);
145
146        let elem: Element = "<query xmlns='jabber:iq:roster' ver='ver9'/>".parse().unwrap();
147        let roster = Roster::try_from(elem).unwrap();
148        assert_eq!(roster.ver, Some(String::from("ver9")));
149        assert!(roster.items.is_empty());
150
151        let elem: Element = r#"
152<query xmlns='jabber:iq:roster' ver='ver11'>
153  <item jid='romeo@example.net'
154        name='Romeo'
155        subscription='both'>
156    <group>Friends</group>
157  </item>
158  <item jid='mercutio@example.com'
159        name='Mercutio'
160        subscription='from'/>
161  <item jid='benvolio@example.net'
162        name='Benvolio'
163        subscription='both'/>
164</query>
165"#.parse().unwrap();
166        let roster = Roster::try_from(elem).unwrap();
167        assert_eq!(roster.ver, Some(String::from("ver11")));
168        assert_eq!(roster.items.len(), 3);
169        assert_eq!(roster.items[0].jid, Jid::from_str("romeo@example.net").unwrap());
170        assert_eq!(roster.items[0].name, Some(String::from("Romeo")));
171        assert_eq!(roster.items[0].subscription, Some(Subscription::Both));
172        assert_eq!(roster.items[0].groups, vec!(String::from("Friends")));
173    }
174
175    #[test]
176    fn test_set() {
177        let elem: Element = "<query xmlns='jabber:iq:roster'><item jid='nurse@example.com'/></query>".parse().unwrap();
178        let roster = Roster::try_from(elem).unwrap();
179        assert!(roster.ver.is_none());
180        assert_eq!(roster.items.len(), 1);
181
182        let elem: Element = r#"
183<query xmlns='jabber:iq:roster'>
184  <item jid='nurse@example.com'
185        name='Nurse'>
186    <group>Servants</group>
187  </item>
188</query>
189</query>
190"#.parse().unwrap();
191        let roster = Roster::try_from(elem).unwrap();
192        assert!(roster.ver.is_none());
193        assert_eq!(roster.items.len(), 1);
194        assert_eq!(roster.items[0].jid, Jid::from_str("nurse@example.com").unwrap());
195        assert_eq!(roster.items[0].name, Some(String::from("Nurse")));
196        assert_eq!(roster.items[0].groups.len(), 1);
197        assert_eq!(roster.items[0].groups[0], String::from("Servants"));
198
199        let elem: Element = r#"
200<query xmlns='jabber:iq:roster'>
201  <item jid='nurse@example.com'
202        subscription='remove'/>
203</query>
204"#.parse().unwrap();
205        let roster = Roster::try_from(elem).unwrap();
206        assert!(roster.ver.is_none());
207        assert_eq!(roster.items.len(), 1);
208        assert_eq!(roster.items[0].jid, Jid::from_str("nurse@example.com").unwrap());
209        assert!(roster.items[0].name.is_none());
210        assert!(roster.items[0].groups.is_empty());
211        assert_eq!(roster.items[0].subscription, Some(Subscription::Remove));
212    }
213
214    #[test]
215    fn test_invalid() {
216        let elem: Element = "<query xmlns='jabber:iq:roster'><coucou/></query>".parse().unwrap();
217        let error = Roster::try_from(elem).unwrap_err();
218        let message = match error {
219            Error::ParseError(string) => string,
220            _ => panic!(),
221        };
222        assert_eq!(message, "Unknown element in roster element.");
223
224        let elem: Element = "<query xmlns='jabber:iq:roster' coucou=''/>".parse().unwrap();
225        let error = Roster::try_from(elem).unwrap_err();
226        let message = match error {
227            Error::ParseError(string) => string,
228            _ => panic!(),
229        };
230        assert_eq!(message, "Unknown attribute in roster element.");
231    }
232
233    #[test]
234    fn test_invalid_item() {
235        let elem: Element = "<query xmlns='jabber:iq:roster'><item/></query>".parse().unwrap();
236        let error = Roster::try_from(elem).unwrap_err();
237        let message = match error {
238            Error::ParseError(string) => string,
239            _ => panic!(),
240        };
241        assert_eq!(message, "Required attribute 'jid' missing.");
242
243        /*
244        let elem: Element = "<query xmlns='jabber:iq:roster'><item jid=''/></query>".parse().unwrap();
245        let error = Roster::try_from(elem).unwrap_err();
246        let error = match error {
247            Error::JidParseError(error) => error,
248            _ => panic!(),
249        };
250        assert_eq!(error.description(), "Invalid JID, I guess?");
251        */
252
253        let elem: Element = "<query xmlns='jabber:iq:roster'><item jid='coucou'><coucou/></item></query>".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 element in roster item element.");
260    }
261}