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_get() {
90 let elem: Element = "<query xmlns='jabber:iq:roster'/>".parse().unwrap();
91 let roster = Roster::try_from(elem).unwrap();
92 assert!(roster.ver.is_none());
93 assert!(roster.items.is_empty());
94 }
95
96 #[test]
97 fn test_result() {
98 let elem: Element = "<query xmlns='jabber:iq:roster' ver='ver7'><item jid='nurse@example.com'/><item jid='romeo@example.net'/></query>".parse().unwrap();
99 let roster = Roster::try_from(elem).unwrap();
100 assert_eq!(roster.ver, Some(String::from("ver7")));
101 assert_eq!(roster.items.len(), 2);
102
103 let elem2: Element = "<query xmlns='jabber:iq:roster' ver='ver7'><item jid='nurse@example.com'/><item jid='romeo@example.net' name=''/></query>".parse().unwrap();
104 let roster2 = Roster::try_from(elem2).unwrap();
105 assert_eq!(roster.items, roster2.items);
106
107 let elem: Element = "<query xmlns='jabber:iq:roster' ver='ver9'/>".parse().unwrap();
108 let roster = Roster::try_from(elem).unwrap();
109 assert_eq!(roster.ver, Some(String::from("ver9")));
110 assert!(roster.items.is_empty());
111
112 let elem: Element = r#"
113<query xmlns='jabber:iq:roster' ver='ver11'>
114 <item jid='romeo@example.net'
115 name='Romeo'
116 subscription='both'>
117 <group>Friends</group>
118 </item>
119 <item jid='mercutio@example.com'
120 name='Mercutio'
121 subscription='from'/>
122 <item jid='benvolio@example.net'
123 name='Benvolio'
124 subscription='both'/>
125</query>
126"#.parse().unwrap();
127 let roster = Roster::try_from(elem).unwrap();
128 assert_eq!(roster.ver, Some(String::from("ver11")));
129 assert_eq!(roster.items.len(), 3);
130 assert_eq!(roster.items[0].jid, Jid::from_str("romeo@example.net").unwrap());
131 assert_eq!(roster.items[0].name, Some(String::from("Romeo")));
132 assert_eq!(roster.items[0].subscription, Subscription::Both);
133 assert_eq!(roster.items[0].groups, vec!(Group::from_str("Friends").unwrap()));
134 }
135
136 #[test]
137 fn test_multiple_groups() {
138 let elem: Element = r#"
139<query xmlns='jabber:iq:roster'>
140 <item jid='test@example.org'>
141 <group>A</group>
142 <group>B</group>
143 </item>
144</query>
145"#.parse().unwrap();
146 let elem1 = elem.clone();
147 let roster = Roster::try_from(elem).unwrap();
148 assert!(roster.ver.is_none());
149 assert_eq!(roster.items.len(), 1);
150 assert_eq!(roster.items[0].jid, Jid::from_str("test@example.org").unwrap());
151 assert_eq!(roster.items[0].name, None);
152 assert_eq!(roster.items[0].groups.len(), 2);
153 assert_eq!(roster.items[0].groups[0], Group::from_str("A").unwrap());
154 assert_eq!(roster.items[0].groups[1], Group::from_str("B").unwrap());
155 let elem2 = roster.into();
156 assert!(elem1.compare_to(&elem2));
157 }
158
159 #[test]
160 fn test_set() {
161 let elem: Element = "<query xmlns='jabber:iq:roster'><item jid='nurse@example.com'/></query>".parse().unwrap();
162 let roster = Roster::try_from(elem).unwrap();
163 assert!(roster.ver.is_none());
164 assert_eq!(roster.items.len(), 1);
165
166 let elem: Element = r#"
167<query xmlns='jabber:iq:roster'>
168 <item jid='nurse@example.com'
169 name='Nurse'>
170 <group>Servants</group>
171 </item>
172</query>
173"#.parse().unwrap();
174 let roster = Roster::try_from(elem).unwrap();
175 assert!(roster.ver.is_none());
176 assert_eq!(roster.items.len(), 1);
177 assert_eq!(roster.items[0].jid, Jid::from_str("nurse@example.com").unwrap());
178 assert_eq!(roster.items[0].name, Some(String::from("Nurse")));
179 assert_eq!(roster.items[0].groups.len(), 1);
180 assert_eq!(roster.items[0].groups[0], Group::from_str("Servants").unwrap());
181
182 let elem: Element = r#"
183<query xmlns='jabber:iq:roster'>
184 <item jid='nurse@example.com'
185 subscription='remove'/>
186</query>
187"#.parse().unwrap();
188 let roster = Roster::try_from(elem).unwrap();
189 assert!(roster.ver.is_none());
190 assert_eq!(roster.items.len(), 1);
191 assert_eq!(roster.items[0].jid, Jid::from_str("nurse@example.com").unwrap());
192 assert!(roster.items[0].name.is_none());
193 assert!(roster.items[0].groups.is_empty());
194 assert_eq!(roster.items[0].subscription, Subscription::Remove);
195 }
196
197 #[test]
198 fn test_invalid() {
199 let elem: Element = "<query xmlns='jabber:iq:roster'><coucou/></query>".parse().unwrap();
200 let error = Roster::try_from(elem).unwrap_err();
201 let message = match error {
202 Error::ParseError(string) => string,
203 _ => panic!(),
204 };
205 assert_eq!(message, "Unknown child in query element.");
206
207 let elem: Element = "<query xmlns='jabber:iq:roster' coucou=''/>".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 attribute in query element.");
214 }
215
216 #[test]
217 fn test_invalid_item() {
218 let elem: Element = "<query xmlns='jabber:iq:roster'><item/></query>".parse().unwrap();
219 let error = Roster::try_from(elem).unwrap_err();
220 let message = match error {
221 Error::ParseError(string) => string,
222 _ => panic!(),
223 };
224 assert_eq!(message, "Required attribute 'jid' missing.");
225
226 /*
227 let elem: Element = "<query xmlns='jabber:iq:roster'><item jid=''/></query>".parse().unwrap();
228 let error = Roster::try_from(elem).unwrap_err();
229 let error = match error {
230 Error::JidParseError(error) => error,
231 _ => panic!(),
232 };
233 assert_eq!(error.description(), "Invalid JID, I guess?");
234 */
235
236 let elem: Element = "<query xmlns='jabber:iq:roster'><item jid='coucou'><coucou/></item></query>".parse().unwrap();
237 let error = Roster::try_from(elem).unwrap_err();
238 let message = match error {
239 Error::ParseError(string) => string,
240 _ => panic!(),
241 };
242 assert_eq!(message, "Unknown child in item element.");
243 }
244}