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