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