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