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