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