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, 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 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.iter().map(|group| Element::builder("group").ns(ns::ROSTER).append(group)).collect::<Vec<_>>())
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 Err = 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 From<Roster> for Element {
114 fn from(roster: Roster) -> Element {
115 Element::builder("query")
116 .ns(ns::ROSTER)
117 .attr("ver", roster.ver)
118 .append(roster.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_multiple_groups() {
177 let elem: Element = r#"
178<query xmlns='jabber:iq:roster'>
179 <item jid='test@example.org'>
180 <group>A</group>
181 <group>B</group>
182 </item>
183</query>
184"#.parse().unwrap();
185 let elem1 = elem.clone();
186 let roster = Roster::try_from(elem).unwrap();
187 assert!(roster.ver.is_none());
188 assert_eq!(roster.items.len(), 1);
189 assert_eq!(roster.items[0].jid, Jid::from_str("test@example.org").unwrap());
190 assert_eq!(roster.items[0].name, None);
191 assert_eq!(roster.items[0].groups.len(), 2);
192 assert_eq!(roster.items[0].groups[0], String::from("A"));
193 assert_eq!(roster.items[0].groups[1], String::from("B"));
194 let elem2 = roster.into();
195 assert_eq!(elem1, elem2);
196 }
197
198 #[test]
199 fn test_set() {
200 let elem: Element = "<query xmlns='jabber:iq:roster'><item jid='nurse@example.com'/></query>".parse().unwrap();
201 let roster = Roster::try_from(elem).unwrap();
202 assert!(roster.ver.is_none());
203 assert_eq!(roster.items.len(), 1);
204
205 let elem: Element = r#"
206<query xmlns='jabber:iq:roster'>
207 <item jid='nurse@example.com'
208 name='Nurse'>
209 <group>Servants</group>
210 </item>
211</query>
212"#.parse().unwrap();
213 let roster = Roster::try_from(elem).unwrap();
214 assert!(roster.ver.is_none());
215 assert_eq!(roster.items.len(), 1);
216 assert_eq!(roster.items[0].jid, Jid::from_str("nurse@example.com").unwrap());
217 assert_eq!(roster.items[0].name, Some(String::from("Nurse")));
218 assert_eq!(roster.items[0].groups.len(), 1);
219 assert_eq!(roster.items[0].groups[0], String::from("Servants"));
220
221 let elem: Element = r#"
222<query xmlns='jabber:iq:roster'>
223 <item jid='nurse@example.com'
224 subscription='remove'/>
225</query>
226"#.parse().unwrap();
227 let roster = Roster::try_from(elem).unwrap();
228 assert!(roster.ver.is_none());
229 assert_eq!(roster.items.len(), 1);
230 assert_eq!(roster.items[0].jid, Jid::from_str("nurse@example.com").unwrap());
231 assert!(roster.items[0].name.is_none());
232 assert!(roster.items[0].groups.is_empty());
233 assert_eq!(roster.items[0].subscription, Some(Subscription::Remove));
234 }
235
236 #[test]
237 fn test_invalid() {
238 let elem: Element = "<query xmlns='jabber:iq:roster'><coucou/></query>".parse().unwrap();
239 let error = Roster::try_from(elem).unwrap_err();
240 let message = match error {
241 Error::ParseError(string) => string,
242 _ => panic!(),
243 };
244 assert_eq!(message, "Unknown element in roster element.");
245
246 let elem: Element = "<query xmlns='jabber:iq:roster' coucou=''/>".parse().unwrap();
247 let error = Roster::try_from(elem).unwrap_err();
248 let message = match error {
249 Error::ParseError(string) => string,
250 _ => panic!(),
251 };
252 assert_eq!(message, "Unknown attribute in roster element.");
253 }
254
255 #[test]
256 fn test_invalid_item() {
257 let elem: Element = "<query xmlns='jabber:iq:roster'><item/></query>".parse().unwrap();
258 let error = Roster::try_from(elem).unwrap_err();
259 let message = match error {
260 Error::ParseError(string) => string,
261 _ => panic!(),
262 };
263 assert_eq!(message, "Required attribute 'jid' missing.");
264
265 /*
266 let elem: Element = "<query xmlns='jabber:iq:roster'><item jid=''/></query>".parse().unwrap();
267 let error = Roster::try_from(elem).unwrap_err();
268 let error = match error {
269 Error::JidParseError(error) => error,
270 _ => panic!(),
271 };
272 assert_eq!(error.description(), "Invalid JID, I guess?");
273 */
274
275 let elem: Element = "<query xmlns='jabber:iq:roster'><item jid='coucou'><coucou/></item></query>".parse().unwrap();
276 let error = Roster::try_from(elem).unwrap_err();
277 let message = match error {
278 Error::ParseError(string) => string,
279 _ => panic!(),
280 };
281 assert_eq!(message, "Unknown element in roster item element.");
282 }
283}