1// Copyright (c) 2017 Maxime “pep” Buquet <pep+code@bouah.net>
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 std::convert::TryFrom;
8
9use minidom::{Element, IntoElements, ElementEmitter};
10
11use error::Error;
12
13use ns;
14
15#[derive(Debug, Clone)]
16pub struct Muc;
17
18impl TryFrom<Element> for Muc {
19 type Error = Error;
20
21 fn try_from(elem: Element) -> Result<Muc, Error> {
22 if !elem.is("x", ns::MUC) {
23 return Err(Error::ParseError("This is not an x element."));
24 }
25 for _ in elem.children() {
26 return Err(Error::ParseError("Unknown child in x element."));
27 }
28 for _ in elem.attrs() {
29 return Err(Error::ParseError("Unknown attribute in x element."));
30 }
31 Ok(Muc)
32 }
33}
34
35impl Into<Element> for Muc {
36 fn into(self) -> Element {
37 Element::builder("x")
38 .ns(ns::MUC)
39 .build()
40 }
41}
42
43#[derive(Debug, Clone, PartialEq)]
44pub enum Status {
45 // 100
46 NonAnonymousRoom,
47
48 // 101
49 AffiliationChange,
50
51 // 102
52 ConfigShowsUnavailableMembers,
53
54 // 103
55 ConfigHidesUnavailableMembers,
56
57 // 104
58 ConfigNonPrivacyRelated,
59
60 // 110
61 SelfPresence,
62
63 // 170
64 ConfigRoomLoggingEnabled,
65
66 // 171
67 ConfigRoomLoggingDisabled,
68
69 // 172
70 ConfigRoomNonAnonymous,
71
72 // 173
73 ConfigRoomSemiAnonymous,
74
75 // 201
76 RoomHasBeenCreated,
77
78 // 210
79 AssignedNick,
80
81 // 301
82 Banned,
83
84 // 303
85 NewNick,
86
87 // 307
88 Kicked,
89
90 // 321
91 RemovalFromRoom,
92
93 // 322
94 ConfigMembersOnly,
95
96 // 332
97 ServiceShutdown,
98}
99
100impl TryFrom<Element> for Status {
101 type Error = Error;
102
103 fn try_from(elem: Element) -> Result<Status, Error> {
104 if !elem.is("status", ns::MUC_USER) {
105 return Err(Error::ParseError("This is not a status element."));
106 }
107 for _ in elem.children() {
108 return Err(Error::ParseError("Unknown child in status element."));
109 }
110 for (attr, _) in elem.attrs() {
111 if attr != "code" {
112 return Err(Error::ParseError("Unknown attribute in status element."));
113 }
114 }
115 let code = get_attr!(elem, "code", required);
116
117 Ok(match code {
118 100 => Status::NonAnonymousRoom,
119 101 => Status::AffiliationChange,
120 102 => Status::ConfigShowsUnavailableMembers,
121 103 => Status::ConfigHidesUnavailableMembers,
122 104 => Status::ConfigNonPrivacyRelated,
123 110 => Status::SelfPresence,
124 170 => Status::ConfigRoomLoggingEnabled,
125 171 => Status::ConfigRoomLoggingDisabled,
126 172 => Status::ConfigRoomNonAnonymous,
127 173 => Status::ConfigRoomSemiAnonymous,
128 201 => Status::RoomHasBeenCreated,
129 210 => Status::AssignedNick,
130 301 => Status::Banned,
131 303 => Status::NewNick,
132 307 => Status::Kicked,
133 321 => Status::RemovalFromRoom,
134 322 => Status::ConfigMembersOnly,
135 332 => Status::ServiceShutdown,
136 _ => return Err(Error::ParseError("Invalid status code.")),
137 })
138 }
139}
140
141impl Into<Element> for Status {
142 fn into(self) -> Element {
143 Element::builder("status")
144 .ns(ns::MUC_USER)
145 .attr("code", match self {
146 Status::NonAnonymousRoom => 100,
147 Status::AffiliationChange => 101,
148 Status::ConfigShowsUnavailableMembers => 102,
149 Status::ConfigHidesUnavailableMembers => 103,
150 Status::ConfigNonPrivacyRelated => 104,
151 Status::SelfPresence => 110,
152 Status::ConfigRoomLoggingEnabled => 170,
153 Status::ConfigRoomLoggingDisabled => 171,
154 Status::ConfigRoomNonAnonymous => 172,
155 Status::ConfigRoomSemiAnonymous => 173,
156 Status::RoomHasBeenCreated => 201,
157 Status::AssignedNick => 210,
158 Status::Banned => 301,
159 Status::NewNick => 303,
160 Status::Kicked => 307,
161 Status::RemovalFromRoom => 321,
162 Status::ConfigMembersOnly => 322,
163 Status::ServiceShutdown => 332,
164 })
165 .build()
166 }
167}
168
169impl IntoElements for Status {
170 fn into_elements(self, emitter: &mut ElementEmitter) {
171 emitter.append_child(self.into());
172 }
173}
174
175#[derive(Debug, Clone)]
176pub struct MucUser {
177 status: Vec<Status>,
178}
179
180impl TryFrom<Element> for MucUser {
181 type Error = Error;
182
183 fn try_from(elem: Element) -> Result<MucUser, Error> {
184 if !elem.is("x", ns::MUC_USER) {
185 return Err(Error::ParseError("This is not an x element."));
186 }
187 let mut status = vec!();
188 for child in elem.children() {
189 if child.is("status", ns::MUC_USER) {
190 status.push(Status::try_from(child.clone())?);
191 } else {
192 return Err(Error::ParseError("Unknown child in x element."));
193 }
194 }
195 for _ in elem.attrs() {
196 return Err(Error::ParseError("Unknown attribute in x element."));
197 }
198 Ok(MucUser {
199 status: status,
200 })
201 }
202}
203
204impl Into<Element> for MucUser {
205 fn into(self) -> Element {
206 Element::builder("x")
207 .ns(ns::MUC_USER)
208 .append(self.status)
209 .build()
210 }
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216 use std::error::Error as StdError;
217
218 #[test]
219 fn test_muc_simple() {
220 let elem: Element = "<x xmlns='http://jabber.org/protocol/muc'/>".parse().unwrap();
221 Muc::try_from(elem).unwrap();
222 }
223
224 #[test]
225 fn test_muc_invalid_child() {
226 let elem: Element = "<x xmlns='http://jabber.org/protocol/muc'><coucou/></x>".parse().unwrap();
227 let error = Muc::try_from(elem).unwrap_err();
228 let message = match error {
229 Error::ParseError(string) => string,
230 _ => panic!(),
231 };
232 assert_eq!(message, "Unknown child in x element.");
233 }
234
235 #[test]
236 fn test_muc_serialise() {
237 let elem: Element = "<x xmlns='http://jabber.org/protocol/muc'/>".parse().unwrap();
238 let muc = Muc;
239 let elem2 = muc.into();
240 assert_eq!(elem, elem2);
241 }
242
243 #[test]
244 fn test_muc_invalid_attribute() {
245 let elem: Element = "<x xmlns='http://jabber.org/protocol/muc' coucou=''/>".parse().unwrap();
246 let error = Muc::try_from(elem).unwrap_err();
247 let message = match error {
248 Error::ParseError(string) => string,
249 _ => panic!(),
250 };
251 assert_eq!(message, "Unknown attribute in x element.");
252 }
253
254 #[test]
255 fn test_muc_user_simple() {
256 let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user'/>".parse().unwrap();
257 MucUser::try_from(elem).unwrap();
258 }
259
260 #[test]
261 fn test_muc_user_invalid_child() {
262 let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user'><coucou/></x>".parse().unwrap();
263 let error = MucUser::try_from(elem).unwrap_err();
264 let message = match error {
265 Error::ParseError(string) => string,
266 _ => panic!(),
267 };
268 assert_eq!(message, "Unknown child in x element.");
269 }
270
271 #[test]
272 fn test_muc_user_serialise() {
273 let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user'/>".parse().unwrap();
274 let muc = MucUser { status: vec!() };
275 let elem2 = muc.into();
276 assert_eq!(elem, elem2);
277 }
278
279 #[test]
280 fn test_muc_user_invalid_attribute() {
281 let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user' coucou=''/>".parse().unwrap();
282 let error = MucUser::try_from(elem).unwrap_err();
283 let message = match error {
284 Error::ParseError(string) => string,
285 _ => panic!(),
286 };
287 assert_eq!(message, "Unknown attribute in x element.");
288 }
289
290 #[test]
291 fn test_status_simple() {
292 let elem: Element = "<status xmlns='http://jabber.org/protocol/muc#user' code='110'/>".parse().unwrap();
293 Status::try_from(elem).unwrap();
294 }
295
296 #[test]
297 fn test_status_invalid() {
298 let elem: Element = "<status xmlns='http://jabber.org/protocol/muc#user'/>".parse().unwrap();
299 let error = Status::try_from(elem).unwrap_err();
300 let message = match error {
301 Error::ParseError(string) => string,
302 _ => panic!(),
303 };
304 assert_eq!(message, "Required attribute 'code' missing.");
305 }
306
307 #[test]
308 fn test_status_invalid_child() {
309 let elem: Element = "<status xmlns='http://jabber.org/protocol/muc#user' code='110'><foo/></status>".parse().unwrap();
310 let error = Status::try_from(elem).unwrap_err();
311 let message = match error {
312 Error::ParseError(string) => string,
313 _ => panic!(),
314 };
315 assert_eq!(message, "Unknown child in status element.");
316 }
317
318 #[test]
319 fn test_status_simple_code() {
320 let elem: Element = "<status xmlns='http://jabber.org/protocol/muc#user' code='307'/>".parse().unwrap();
321 let status = Status::try_from(elem).unwrap();
322 assert_eq!(status, Status::Kicked);
323 }
324
325 #[test]
326 fn test_status_invalid_code() {
327 let elem: Element = "<status xmlns='http://jabber.org/protocol/muc#user' code='666'/>".parse().unwrap();
328 let error = Status::try_from(elem).unwrap_err();
329 let message = match error {
330 Error::ParseError(string) => string,
331 _ => panic!(),
332 };
333 assert_eq!(message, "Invalid status code.");
334 }
335
336 #[test]
337 fn test_status_invalid_code2() {
338 let elem: Element = "<status xmlns='http://jabber.org/protocol/muc#user' code='coucou'/>".parse().unwrap();
339 let error = Status::try_from(elem).unwrap_err();
340 let error = match error {
341 Error::ParseIntError(error) => error,
342 _ => panic!(),
343 };
344 assert_eq!(error.description(), "invalid digit found in string");
345 }
346}