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}