1use xso::{AsXml, FromXml};
8
9use jid::BareJid;
10
11use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload};
12use crate::ns;
13
14generate_elem_id!(
15 Group,
17 "group",
18 ROSTER
19);
20
21generate_attribute!(
22 Subscription, "subscription", {
24 None => "none",
27
28 From => "from",
30
31 To => "to",
33
34 Both => "both",
36
37 Remove => "remove",
40 }, Default = None
41);
42
43generate_attribute!(
44 Ask, "ask", (
46 Subscribe => "subscribe"
48 )
49);
50
51#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
53#[xml(namespace = ns::ROSTER, name = "item")]
54pub struct Item {
55 #[xml(attribute)]
57 pub jid: BareJid,
58
59 #[xml(attribute(default))]
61 pub name: Option<String>,
62
63 #[xml(attribute(default))]
65 pub subscription: Subscription,
66
67 #[xml(attribute(default))]
69 pub ask: Ask,
70
71 #[xml(child(n = ..))]
73 pub groups: Vec<Group>,
74
75 #[xml(attribute(default))]
77 pub approved: Option<bool>,
78}
79
80#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
82#[xml(namespace = ns::ROSTER, name = "query")]
83pub struct Roster {
84 #[xml(attribute(default))]
90 pub ver: Option<String>,
91
92 #[xml(child(n = ..))]
94 pub items: Vec<Item>,
95}
96
97impl IqGetPayload for Roster {}
98impl IqSetPayload for Roster {}
99impl IqResultPayload for Roster {}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104 use core::str::FromStr;
105 use minidom::Element;
106 use xso::error::{Error, FromElementError};
107
108 #[cfg(target_pointer_width = "32")]
109 #[test]
110 fn test_size() {
111 assert_size!(Group, 12);
112 assert_size!(Subscription, 1);
113 assert_size!(Ask, 1);
114 assert_size!(Item, 44);
115 assert_size!(Roster, 24);
116 }
117
118 #[cfg(target_pointer_width = "64")]
119 #[test]
120 fn test_size() {
121 assert_size!(Group, 24);
122 assert_size!(Subscription, 1);
123 assert_size!(Ask, 1);
124 assert_size!(Item, 88);
125 assert_size!(Roster, 48);
126 }
127
128 #[test]
129 fn test_get() {
130 let elem: Element = "<query xmlns='jabber:iq:roster'/>".parse().unwrap();
131 let roster = Roster::try_from(elem).unwrap();
132 assert!(roster.ver.is_none());
133 assert!(roster.items.is_empty());
134 }
135
136 #[test]
137 fn test_result() {
138 let elem: Element = "<query xmlns='jabber:iq:roster' ver='ver7'><item jid='nurse@example.com'/><item jid='romeo@example.net'/></query>".parse().unwrap();
139 let roster = Roster::try_from(elem).unwrap();
140 assert_eq!(roster.ver, Some(String::from("ver7")));
141 assert_eq!(roster.items.len(), 2);
142
143 let elem: Element = "<query xmlns='jabber:iq:roster' ver='ver9'/>"
144 .parse()
145 .unwrap();
146 let roster = Roster::try_from(elem).unwrap();
147 assert_eq!(roster.ver, Some(String::from("ver9")));
148 assert!(roster.items.is_empty());
149
150 let elem: Element = r#"<query xmlns='jabber:iq:roster' ver='ver11'>
151 <item jid='romeo@example.net'
152 name='Romeo'
153 subscription='both'>
154 <group>Friends</group>
155 </item>
156 <item jid='mercutio@example.com'
157 name='Mercutio'
158 subscription='from'/>
159 <item jid='benvolio@example.net'
160 name='Benvolio'
161 subscription='both'/>
162 <item jid='contact@example.org'
163 subscription='none'
164 ask='subscribe'
165 approved='true'
166 name='MyContact'>
167 <group>MyBuddies</group>
168 </item>
169</query>
170"#
171 .parse()
172 .unwrap();
173 let roster = Roster::try_from(elem).unwrap();
174 assert_eq!(roster.ver, Some(String::from("ver11")));
175 assert_eq!(roster.items.len(), 4);
176 assert_eq!(
177 roster.items[0].jid,
178 BareJid::new("romeo@example.net").unwrap()
179 );
180 assert_eq!(roster.items[0].name, Some(String::from("Romeo")));
181 assert_eq!(roster.items[0].subscription, Subscription::Both);
182 assert_eq!(roster.items[0].ask, Ask::None);
183 assert_eq!(roster.items[0].approved, None);
184 assert_eq!(
185 roster.items[0].groups,
186 vec!(Group::from_str("Friends").unwrap())
187 );
188
189 assert_eq!(
190 roster.items[3].jid,
191 BareJid::new("contact@example.org").unwrap()
192 );
193 assert_eq!(roster.items[3].name, Some(String::from("MyContact")));
194 assert_eq!(roster.items[3].subscription, Subscription::None);
195 assert_eq!(roster.items[3].ask, Ask::Subscribe);
196 assert_eq!(roster.items[3].approved, Some(true));
197 assert_eq!(
198 roster.items[3].groups,
199 vec!(Group::from_str("MyBuddies").unwrap())
200 );
201 }
202
203 #[test]
204 fn test_multiple_groups() {
205 let elem: Element = "<query xmlns='jabber:iq:roster'><item jid='test@example.org'><group>A</group><group>B</group></item></query>"
206 .parse()
207 .unwrap();
208 let elem1 = elem.clone();
209 let roster = Roster::try_from(elem).unwrap();
210 assert!(roster.ver.is_none());
211 assert_eq!(roster.items.len(), 1);
212 assert_eq!(
213 roster.items[0].jid,
214 BareJid::new("test@example.org").unwrap()
215 );
216 assert_eq!(roster.items[0].name, None);
217 assert_eq!(roster.items[0].groups.len(), 2);
218 assert_eq!(roster.items[0].groups[0], Group::from_str("A").unwrap());
219 assert_eq!(roster.items[0].groups[1], Group::from_str("B").unwrap());
220 let elem2 = roster.into();
221 assert_eq!(elem1, elem2);
222 }
223
224 #[test]
225 fn test_set() {
226 let elem: Element =
227 "<query xmlns='jabber:iq:roster'><item jid='nurse@example.com'/></query>"
228 .parse()
229 .unwrap();
230 let roster = Roster::try_from(elem).unwrap();
231 assert!(roster.ver.is_none());
232 assert_eq!(roster.items.len(), 1);
233
234 let elem: Element = r#"<query xmlns='jabber:iq:roster'>
235 <item jid='nurse@example.com'
236 name='Nurse'>
237 <group>Servants</group>
238 </item>
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!(
246 roster.items[0].jid,
247 BareJid::new("nurse@example.com").unwrap()
248 );
249 assert_eq!(roster.items[0].name, Some(String::from("Nurse")));
250 assert_eq!(roster.items[0].groups.len(), 1);
251 assert_eq!(
252 roster.items[0].groups[0],
253 Group::from_str("Servants").unwrap()
254 );
255
256 let elem: Element = r#"<query xmlns='jabber:iq:roster'>
257 <item jid='nurse@example.com'
258 subscription='remove'/>
259</query>"#
260 .parse()
261 .unwrap();
262 let roster = Roster::try_from(elem).unwrap();
263 assert!(roster.ver.is_none());
264 assert_eq!(roster.items.len(), 1);
265 assert_eq!(
266 roster.items[0].jid,
267 BareJid::new("nurse@example.com").unwrap()
268 );
269 assert!(roster.items[0].name.is_none());
270 assert!(roster.items[0].groups.is_empty());
271 assert_eq!(roster.items[0].subscription, Subscription::Remove);
272 }
273
274 #[cfg(feature = "pedantic")]
275 #[test]
276 fn test_invalid() {
277 let elem: Element = "<query xmlns='jabber:iq:roster'><coucou/></query>"
278 .parse()
279 .unwrap();
280 let error = Roster::try_from(elem).unwrap_err();
281 let message = match error {
282 FromElementError::Invalid(Error::Other(string)) => string,
283 _ => panic!(),
284 };
285 assert_eq!(message, "Unknown child in Roster element.");
286
287 let elem: Element = "<query xmlns='jabber:iq:roster' coucou=''/>"
288 .parse()
289 .unwrap();
290 let error = Roster::try_from(elem).unwrap_err();
291 let message = match error {
292 FromElementError::Invalid(Error::Other(string)) => string,
293 _ => panic!(),
294 };
295 assert_eq!(message, "Unknown attribute in Roster element.");
296 }
297
298 #[test]
299 fn test_item_missing_jid() {
300 let elem: Element = "<query xmlns='jabber:iq:roster'><item/></query>"
301 .parse()
302 .unwrap();
303 let error = Roster::try_from(elem).unwrap_err();
304 let message = match error {
305 FromElementError::Invalid(Error::Other(string)) => string,
306 _ => panic!(),
307 };
308 assert_eq!(
309 message,
310 "Required attribute field 'jid' on Item element missing."
311 );
312 }
313
314 #[test]
315 fn test_item_invalid_jid() {
316 let elem: Element = "<query xmlns='jabber:iq:roster'><item jid=''/></query>"
317 .parse()
318 .unwrap();
319 let error = Roster::try_from(elem).unwrap_err();
320 assert_eq!(
321 format!("{error}"),
322 "text parse error: domain doesn’t pass idna validation"
323 );
324 }
325
326 #[test]
327 #[cfg_attr(not(feature = "pedantic"), should_panic = "Result::unwrap_err")]
328 fn test_item_unknown_child() {
329 let elem: Element =
330 "<query xmlns='jabber:iq:roster'><item jid='coucou'><coucou/></item></query>"
331 .parse()
332 .unwrap();
333 let error = Roster::try_from(elem).unwrap_err();
334 let message = match error {
335 FromElementError::Invalid(Error::Other(string)) => string,
336 _ => panic!(),
337 };
338 assert_eq!(message, "Unknown child in Item element.");
339 }
340}