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 crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload};
8use jid::Jid;
9
10generate_elem_id!(
11 /// Represents a group a contact is part of.
12 Group,
13 "group",
14 ROSTER
15);
16
17generate_attribute!(
18 /// The state of your mutual subscription with a contact.
19 Subscription, "subscription", {
20 /// The user doesn’t have any subscription to this contact’s presence,
21 /// and neither does this contact.
22 None => "none",
23
24 /// Only this contact has a subscription with you, not the opposite.
25 From => "from",
26
27 /// Only you have a subscription with this contact, not the opposite.
28 To => "to",
29
30 /// Both you and your contact are subscribed to each other’s presence.
31 Both => "both",
32
33 /// In a roster set, this asks the server to remove this contact item
34 /// from your roster.
35 Remove => "remove",
36 }, Default = None
37);
38
39generate_element!(
40 /// Contact from the user’s contact list.
41 #[derive(PartialEq)]
42 Item, "item", ROSTER,
43 attributes: [
44 /// JID of this contact.
45 jid: Jid = "jid" => required,
46
47 /// Name of this contact.
48 name: Option<String> = "name" => optional_empty,
49
50 /// Subscription status of this contact.
51 subscription: Subscription = "subscription" => default
52 ],
53
54 children: [
55 /// Groups this contact is part of.
56 groups: Vec<Group> = ("group", ROSTER) => Group
57 ]
58);
59
60generate_element!(
61 /// The contact list of the user.
62 Roster, "query", ROSTER,
63 attributes: [
64 /// Version of the contact list.
65 ///
66 /// This is an opaque string that should only be sent back to the server on
67 /// a new connection, if this client is storing the contact list between
68 /// connections.
69 ver: Option<String> = "ver" => optional
70 ],
71 children: [
72 /// List of the contacts of the user.
73 items: Vec<Item> = ("item", ROSTER) => Item
74 ]
75);
76
77impl IqGetPayload for Roster {}
78impl IqSetPayload for Roster {}
79impl IqResultPayload for Roster {}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84 use crate::compare_elements::NamespaceAwareCompare;
85 use crate::error::Error;
86 use minidom::Element;
87 use std::str::FromStr;
88 use try_from::TryFrom;
89
90 #[cfg(target_pointer_width = "32")]
91 #[test]
92 fn test_size() {
93 assert_size!(Group, 12);
94 assert_size!(Subscription, 1);
95 assert_size!(Item, 64);
96 assert_size!(Roster, 24);
97 }
98
99 #[cfg(target_pointer_width = "64")]
100 #[test]
101 fn test_size() {
102 assert_size!(Group, 24);
103 assert_size!(Subscription, 1);
104 assert_size!(Item, 128);
105 assert_size!(Roster, 48);
106 }
107
108 #[test]
109 fn test_get() {
110 let elem: Element = "<query xmlns='jabber:iq:roster'/>".parse().unwrap();
111 let roster = Roster::try_from(elem).unwrap();
112 assert!(roster.ver.is_none());
113 assert!(roster.items.is_empty());
114 }
115
116 #[test]
117 fn test_result() {
118 let elem: Element = "<query xmlns='jabber:iq:roster' ver='ver7'><item jid='nurse@example.com'/><item jid='romeo@example.net'/></query>".parse().unwrap();
119 let roster = Roster::try_from(elem).unwrap();
120 assert_eq!(roster.ver, Some(String::from("ver7")));
121 assert_eq!(roster.items.len(), 2);
122
123 let elem2: Element = "<query xmlns='jabber:iq:roster' ver='ver7'><item jid='nurse@example.com'/><item jid='romeo@example.net' name=''/></query>".parse().unwrap();
124 let roster2 = Roster::try_from(elem2).unwrap();
125 assert_eq!(roster.items, roster2.items);
126
127 let elem: Element = "<query xmlns='jabber:iq:roster' ver='ver9'/>"
128 .parse()
129 .unwrap();
130 let roster = Roster::try_from(elem).unwrap();
131 assert_eq!(roster.ver, Some(String::from("ver9")));
132 assert!(roster.items.is_empty());
133
134 let elem: Element = r#"
135<query xmlns='jabber:iq:roster' ver='ver11'>
136 <item jid='romeo@example.net'
137 name='Romeo'
138 subscription='both'>
139 <group>Friends</group>
140 </item>
141 <item jid='mercutio@example.com'
142 name='Mercutio'
143 subscription='from'/>
144 <item jid='benvolio@example.net'
145 name='Benvolio'
146 subscription='both'/>
147</query>
148"#
149 .parse()
150 .unwrap();
151 let roster = Roster::try_from(elem).unwrap();
152 assert_eq!(roster.ver, Some(String::from("ver11")));
153 assert_eq!(roster.items.len(), 3);
154 assert_eq!(
155 roster.items[0].jid,
156 Jid::from_str("romeo@example.net").unwrap()
157 );
158 assert_eq!(roster.items[0].name, Some(String::from("Romeo")));
159 assert_eq!(roster.items[0].subscription, Subscription::Both);
160 assert_eq!(
161 roster.items[0].groups,
162 vec!(Group::from_str("Friends").unwrap())
163 );
164 }
165
166 #[test]
167 fn test_multiple_groups() {
168 let elem: Element = r#"
169<query xmlns='jabber:iq:roster'>
170 <item jid='test@example.org'>
171 <group>A</group>
172 <group>B</group>
173 </item>
174</query>
175"#
176 .parse()
177 .unwrap();
178 let elem1 = elem.clone();
179 let roster = Roster::try_from(elem).unwrap();
180 assert!(roster.ver.is_none());
181 assert_eq!(roster.items.len(), 1);
182 assert_eq!(
183 roster.items[0].jid,
184 Jid::from_str("test@example.org").unwrap()
185 );
186 assert_eq!(roster.items[0].name, None);
187 assert_eq!(roster.items[0].groups.len(), 2);
188 assert_eq!(roster.items[0].groups[0], Group::from_str("A").unwrap());
189 assert_eq!(roster.items[0].groups[1], Group::from_str("B").unwrap());
190 let elem2 = roster.into();
191 assert!(elem1.compare_to(&elem2));
192 }
193
194 #[test]
195 fn test_set() {
196 let elem: Element =
197 "<query xmlns='jabber:iq:roster'><item jid='nurse@example.com'/></query>"
198 .parse()
199 .unwrap();
200 let roster = Roster::try_from(elem).unwrap();
201 assert!(roster.ver.is_none());
202 assert_eq!(roster.items.len(), 1);
203
204 let elem: Element = r#"
205<query xmlns='jabber:iq:roster'>
206 <item jid='nurse@example.com'
207 name='Nurse'>
208 <group>Servants</group>
209 </item>
210</query>
211"#
212 .parse()
213 .unwrap();
214 let roster = Roster::try_from(elem).unwrap();
215 assert!(roster.ver.is_none());
216 assert_eq!(roster.items.len(), 1);
217 assert_eq!(
218 roster.items[0].jid,
219 Jid::from_str("nurse@example.com").unwrap()
220 );
221 assert_eq!(roster.items[0].name, Some(String::from("Nurse")));
222 assert_eq!(roster.items[0].groups.len(), 1);
223 assert_eq!(
224 roster.items[0].groups[0],
225 Group::from_str("Servants").unwrap()
226 );
227
228 let elem: Element = r#"
229<query xmlns='jabber:iq:roster'>
230 <item jid='nurse@example.com'
231 subscription='remove'/>
232</query>
233"#
234 .parse()
235 .unwrap();
236 let roster = Roster::try_from(elem).unwrap();
237 assert!(roster.ver.is_none());
238 assert_eq!(roster.items.len(), 1);
239 assert_eq!(
240 roster.items[0].jid,
241 Jid::from_str("nurse@example.com").unwrap()
242 );
243 assert!(roster.items[0].name.is_none());
244 assert!(roster.items[0].groups.is_empty());
245 assert_eq!(roster.items[0].subscription, Subscription::Remove);
246 }
247
248 #[cfg(not(feature = "compat"))]
249 #[test]
250 fn test_invalid() {
251 let elem: Element = "<query xmlns='jabber:iq:roster'><coucou/></query>"
252 .parse()
253 .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 child in query element.");
260
261 let elem: Element = "<query xmlns='jabber:iq:roster' coucou=''/>"
262 .parse()
263 .unwrap();
264 let error = Roster::try_from(elem).unwrap_err();
265 let message = match error {
266 Error::ParseError(string) => string,
267 _ => panic!(),
268 };
269 assert_eq!(message, "Unknown attribute in query element.");
270 }
271
272 #[test]
273 fn test_invalid_item() {
274 let elem: Element = "<query xmlns='jabber:iq:roster'><item/></query>"
275 .parse()
276 .unwrap();
277 let error = Roster::try_from(elem).unwrap_err();
278 let message = match error {
279 Error::ParseError(string) => string,
280 _ => panic!(),
281 };
282 assert_eq!(message, "Required attribute 'jid' missing.");
283
284 /*
285 let elem: Element = "<query xmlns='jabber:iq:roster'><item jid=''/></query>".parse().unwrap();
286 let error = Roster::try_from(elem).unwrap_err();
287 let error = match error {
288 Error::JidParseError(error) => error,
289 _ => panic!(),
290 };
291 assert_eq!(error.description(), "Invalid JID, I guess?");
292 */
293
294 let elem: Element =
295 "<query xmlns='jabber:iq:roster'><item jid='coucou'><coucou/></item></query>"
296 .parse()
297 .unwrap();
298 let error = Roster::try_from(elem).unwrap_err();
299 let message = match error {
300 Error::ParseError(string) => string,
301 _ => panic!(),
302 };
303 assert_eq!(message, "Unknown child in item element.");
304 }
305}