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