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