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
16generate_elem_id!(Group, "group", ns::ROSTER);
17
18generate_attribute!(Subscription, "subscription", {
19 None => "none",
20 From => "from",
21 To => "to",
22 Both => "both",
23 Remove => "remove",
24}, Default = None);
25
26/// Contact from the user’s contact list.
27#[derive(Debug, Clone, PartialEq)]
28pub struct Item {
29 /// JID of this contact.
30 pub jid: Jid,
31
32 /// Name of this contact.
33 pub name: Option<String>,
34
35 /// Subscription status of this contact.
36 pub subscription: Subscription,
37
38 /// Groups this contact is part of.
39 pub groups: Vec<Group>,
40}
41
42impl TryFrom<Element> for Item {
43 type Err = Error;
44
45 fn try_from(elem: Element) -> Result<Item, Error> {
46 if !elem.is("item", ns::ROSTER) {
47 return Err(Error::ParseError("This is not a roster item element."));
48 }
49
50 let mut item = Item {
51 jid: get_attr!(elem, "jid", required),
52 name: get_attr!(elem, "name", optional).and_then(|name| if name == "" { None } else { Some(name) }),
53 subscription: get_attr!(elem, "subscription", default),
54 groups: vec!(),
55 };
56 for child in elem.children() {
57 if !child.is("group", ns::ROSTER) {
58 return Err(Error::ParseError("Unknown element in roster item element."));
59 }
60 for _ in child.children() {
61 return Err(Error::ParseError("Roster item group can’t have children."));
62 }
63 for _ in child.attrs() {
64 return Err(Error::ParseError("Roster item group can’t have attributes."));
65 }
66 let group = Group(child.text());
67 item.groups.push(group);
68 }
69 Ok(item)
70 }
71}
72
73impl From<Item> for Element {
74 fn from(item: Item) -> Element {
75 Element::builder("item")
76 .ns(ns::ROSTER)
77 .attr("jid", item.jid)
78 .attr("name", item.name)
79 .attr("subscription", item.subscription)
80 .append(item.groups)
81 .build()
82 }
83}
84
85generate_element_with_children!(
86 /// The contact list of the user.
87 Roster, "query", ns::ROSTER,
88 attributes: [
89 /// Version of the contact list.
90 ///
91 /// This is an opaque string that should only be sent back to the server on
92 /// a new connection, if this client is storing the contact list between
93 /// connections.
94 ver: Option<String> = "ver" => optional
95 ],
96 children: [
97 /// List of the contacts of the user.
98 items: Vec<Item> = ("item", ns::ROSTER) => Item
99 ]
100);
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105 use compare_elements::NamespaceAwareCompare;
106
107 #[test]
108 fn test_get() {
109 let elem: Element = "<query xmlns='jabber:iq:roster'/>".parse().unwrap();
110 let roster = Roster::try_from(elem).unwrap();
111 assert!(roster.ver.is_none());
112 assert!(roster.items.is_empty());
113 }
114
115 #[test]
116 fn test_result() {
117 let elem: Element = "<query xmlns='jabber:iq:roster' ver='ver7'><item jid='nurse@example.com'/><item jid='romeo@example.net'/></query>".parse().unwrap();
118 let roster = Roster::try_from(elem).unwrap();
119 assert_eq!(roster.ver, Some(String::from("ver7")));
120 assert_eq!(roster.items.len(), 2);
121
122 let elem2: Element = "<query xmlns='jabber:iq:roster' ver='ver7'><item jid='nurse@example.com'/><item jid='romeo@example.net' name=''/></query>".parse().unwrap();
123 let roster2 = Roster::try_from(elem2).unwrap();
124 assert_eq!(roster.items, roster2.items);
125
126 let elem: Element = "<query xmlns='jabber:iq:roster' ver='ver9'/>".parse().unwrap();
127 let roster = Roster::try_from(elem).unwrap();
128 assert_eq!(roster.ver, Some(String::from("ver9")));
129 assert!(roster.items.is_empty());
130
131 let elem: Element = r#"
132<query xmlns='jabber:iq:roster' ver='ver11'>
133 <item jid='romeo@example.net'
134 name='Romeo'
135 subscription='both'>
136 <group>Friends</group>
137 </item>
138 <item jid='mercutio@example.com'
139 name='Mercutio'
140 subscription='from'/>
141 <item jid='benvolio@example.net'
142 name='Benvolio'
143 subscription='both'/>
144</query>
145"#.parse().unwrap();
146 let roster = Roster::try_from(elem).unwrap();
147 assert_eq!(roster.ver, Some(String::from("ver11")));
148 assert_eq!(roster.items.len(), 3);
149 assert_eq!(roster.items[0].jid, Jid::from_str("romeo@example.net").unwrap());
150 assert_eq!(roster.items[0].name, Some(String::from("Romeo")));
151 assert_eq!(roster.items[0].subscription, Subscription::Both);
152 assert_eq!(roster.items[0].groups, vec!(Group::from_str("Friends").unwrap()));
153 }
154
155 #[test]
156 fn test_multiple_groups() {
157 let elem: Element = r#"
158<query xmlns='jabber:iq:roster'>
159 <item jid='test@example.org'>
160 <group>A</group>
161 <group>B</group>
162 </item>
163</query>
164"#.parse().unwrap();
165 let elem1 = elem.clone();
166 let roster = Roster::try_from(elem).unwrap();
167 assert!(roster.ver.is_none());
168 assert_eq!(roster.items.len(), 1);
169 assert_eq!(roster.items[0].jid, Jid::from_str("test@example.org").unwrap());
170 assert_eq!(roster.items[0].name, None);
171 assert_eq!(roster.items[0].groups.len(), 2);
172 assert_eq!(roster.items[0].groups[0], Group::from_str("A").unwrap());
173 assert_eq!(roster.items[0].groups[1], Group::from_str("B").unwrap());
174 let elem2 = roster.into();
175 assert!(elem1.compare_to(&elem2));
176 }
177
178 #[test]
179 fn test_set() {
180 let elem: Element = "<query xmlns='jabber:iq:roster'><item jid='nurse@example.com'/></query>".parse().unwrap();
181 let roster = Roster::try_from(elem).unwrap();
182 assert!(roster.ver.is_none());
183 assert_eq!(roster.items.len(), 1);
184
185 let elem: Element = r#"
186<query xmlns='jabber:iq:roster'>
187 <item jid='nurse@example.com'
188 name='Nurse'>
189 <group>Servants</group>
190 </item>
191</query>
192"#.parse().unwrap();
193 let roster = Roster::try_from(elem).unwrap();
194 assert!(roster.ver.is_none());
195 assert_eq!(roster.items.len(), 1);
196 assert_eq!(roster.items[0].jid, Jid::from_str("nurse@example.com").unwrap());
197 assert_eq!(roster.items[0].name, Some(String::from("Nurse")));
198 assert_eq!(roster.items[0].groups.len(), 1);
199 assert_eq!(roster.items[0].groups[0], Group::from_str("Servants").unwrap());
200
201 let elem: Element = r#"
202<query xmlns='jabber:iq:roster'>
203 <item jid='nurse@example.com'
204 subscription='remove'/>
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!(roster.items[0].name.is_none());
212 assert!(roster.items[0].groups.is_empty());
213 assert_eq!(roster.items[0].subscription, Subscription::Remove);
214 }
215
216 #[test]
217 fn test_invalid() {
218 let elem: Element = "<query xmlns='jabber:iq:roster'><coucou/></query>".parse().unwrap();
219 let error = Roster::try_from(elem).unwrap_err();
220 let message = match error {
221 Error::ParseError(string) => string,
222 _ => panic!(),
223 };
224 assert_eq!(message, "Unknown child in query element.");
225
226 let elem: Element = "<query xmlns='jabber:iq:roster' coucou=''/>".parse().unwrap();
227 let error = Roster::try_from(elem).unwrap_err();
228 let message = match error {
229 Error::ParseError(string) => string,
230 _ => panic!(),
231 };
232 assert_eq!(message, "Unknown attribute in query element.");
233 }
234
235 #[test]
236 fn test_invalid_item() {
237 let elem: Element = "<query xmlns='jabber:iq:roster'><item/></query>".parse().unwrap();
238 let error = Roster::try_from(elem).unwrap_err();
239 let message = match error {
240 Error::ParseError(string) => string,
241 _ => panic!(),
242 };
243 assert_eq!(message, "Required attribute 'jid' missing.");
244
245 /*
246 let elem: Element = "<query xmlns='jabber:iq:roster'><item jid=''/></query>".parse().unwrap();
247 let error = Roster::try_from(elem).unwrap_err();
248 let error = match error {
249 Error::JidParseError(error) => error,
250 _ => panic!(),
251 };
252 assert_eq!(error.description(), "Invalid JID, I guess?");
253 */
254
255 let elem: Element = "<query xmlns='jabber:iq:roster'><item jid='coucou'><coucou/></item></query>".parse().unwrap();
256 let error = Roster::try_from(elem).unwrap_err();
257 let message = match error {
258 Error::ParseError(string) => string,
259 _ => panic!(),
260 };
261 assert_eq!(message, "Unknown element in roster item element.");
262 }
263}