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}