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}