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 std::convert::TryFrom;
8use std::str::FromStr;
9use std::collections::BTreeMap;
10
11use minidom::Element;
12
13use error::Error;
14use jid::Jid;
15use ns;
16
17#[derive(Debug, Clone, PartialEq)]
18pub enum ErrorType {
19 Auth,
20 Cancel,
21 Continue,
22 Modify,
23 Wait,
24}
25
26impl FromStr for ErrorType {
27 type Err = Error;
28
29 fn from_str(s: &str) -> Result<ErrorType, Error> {
30 Ok(match s {
31 "auth" => ErrorType::Auth,
32 "cancel" => ErrorType::Cancel,
33 "continue" => ErrorType::Continue,
34 "modify" => ErrorType::Modify,
35 "wait" => ErrorType::Wait,
36
37 _ => return Err(Error::ParseError("Unknown error type.")),
38 })
39 }
40}
41
42impl From<ErrorType> for String {
43 fn from(type_: ErrorType) -> String {
44 String::from(match type_ {
45 ErrorType::Auth => "auth",
46 ErrorType::Cancel => "cancel",
47 ErrorType::Continue => "continue",
48 ErrorType::Modify => "modify",
49 ErrorType::Wait => "wait",
50 })
51 }
52}
53
54#[derive(Debug, Clone, PartialEq)]
55pub enum DefinedCondition {
56 BadRequest,
57 Conflict,
58 FeatureNotImplemented,
59 Forbidden,
60 Gone,
61 InternalServerError,
62 ItemNotFound,
63 JidMalformed,
64 NotAcceptable,
65 NotAllowed,
66 NotAuthorized,
67 PolicyViolation,
68 RecipientUnavailable,
69 Redirect,
70 RegistrationRequired,
71 RemoteServerNotFound,
72 RemoteServerTimeout,
73 ResourceConstraint,
74 ServiceUnavailable,
75 SubscriptionRequired,
76 UndefinedCondition,
77 UnexpectedRequest,
78}
79
80impl FromStr for DefinedCondition {
81 type Err = Error;
82
83 fn from_str(s: &str) -> Result<DefinedCondition, Error> {
84 Ok(match s {
85 "bad-request" => DefinedCondition::BadRequest,
86 "conflict" => DefinedCondition::Conflict,
87 "feature-not-implemented" => DefinedCondition::FeatureNotImplemented,
88 "forbidden" => DefinedCondition::Forbidden,
89 "gone" => DefinedCondition::Gone,
90 "internal-server-error" => DefinedCondition::InternalServerError,
91 "item-not-found" => DefinedCondition::ItemNotFound,
92 "jid-malformed" => DefinedCondition::JidMalformed,
93 "not-acceptable" => DefinedCondition::NotAcceptable,
94 "not-allowed" => DefinedCondition::NotAllowed,
95 "not-authorized" => DefinedCondition::NotAuthorized,
96 "policy-violation" => DefinedCondition::PolicyViolation,
97 "recipient-unavailable" => DefinedCondition::RecipientUnavailable,
98 "redirect" => DefinedCondition::Redirect,
99 "registration-required" => DefinedCondition::RegistrationRequired,
100 "remote-server-not-found" => DefinedCondition::RemoteServerNotFound,
101 "remote-server-timeout" => DefinedCondition::RemoteServerTimeout,
102 "resource-constraint" => DefinedCondition::ResourceConstraint,
103 "service-unavailable" => DefinedCondition::ServiceUnavailable,
104 "subscription-required" => DefinedCondition::SubscriptionRequired,
105 "undefined-condition" => DefinedCondition::UndefinedCondition,
106 "unexpected-request" => DefinedCondition::UnexpectedRequest,
107
108 _ => return Err(Error::ParseError("Unknown defined-condition.")),
109 })
110 }
111}
112
113impl From<DefinedCondition> for String {
114 fn from(defined_condition: DefinedCondition) -> String {
115 String::from(match defined_condition {
116 DefinedCondition::BadRequest => "bad-request",
117 DefinedCondition::Conflict => "conflict",
118 DefinedCondition::FeatureNotImplemented => "feature-not-implemented",
119 DefinedCondition::Forbidden => "forbidden",
120 DefinedCondition::Gone => "gone",
121 DefinedCondition::InternalServerError => "internal-server-error",
122 DefinedCondition::ItemNotFound => "item-not-found",
123 DefinedCondition::JidMalformed => "jid-malformed",
124 DefinedCondition::NotAcceptable => "not-acceptable",
125 DefinedCondition::NotAllowed => "not-allowed",
126 DefinedCondition::NotAuthorized => "not-authorized",
127 DefinedCondition::PolicyViolation => "policy-violation",
128 DefinedCondition::RecipientUnavailable => "recipient-unavailable",
129 DefinedCondition::Redirect => "redirect",
130 DefinedCondition::RegistrationRequired => "registration-required",
131 DefinedCondition::RemoteServerNotFound => "remote-server-not-found",
132 DefinedCondition::RemoteServerTimeout => "remote-server-timeout",
133 DefinedCondition::ResourceConstraint => "resource-constraint",
134 DefinedCondition::ServiceUnavailable => "service-unavailable",
135 DefinedCondition::SubscriptionRequired => "subscription-required",
136 DefinedCondition::UndefinedCondition => "undefined-condition",
137 DefinedCondition::UnexpectedRequest => "unexpected-request",
138 })
139 }
140}
141
142pub type Lang = String;
143
144#[derive(Debug, Clone)]
145pub struct StanzaError {
146 pub type_: ErrorType,
147 pub by: Option<Jid>,
148 pub defined_condition: DefinedCondition,
149 pub texts: BTreeMap<Lang, String>,
150 pub other: Option<Element>,
151}
152
153impl<'a> TryFrom<&'a Element> for StanzaError {
154 type Error = Error;
155
156 fn try_from(elem: &'a Element) -> Result<StanzaError, Error> {
157 if !elem.is("error", ns::JABBER_CLIENT) {
158 return Err(Error::ParseError("This is not an error element."));
159 }
160
161 let type_ = elem.attr("type")
162 .ok_or(Error::ParseError("Error must have a 'type' attribute."))?
163 .parse()?;
164 let by = elem.attr("by")
165 .and_then(|by| by.parse().ok());
166 let mut defined_condition = None;
167 let mut texts = BTreeMap::new();
168 let mut other = None;
169
170 for child in elem.children() {
171 if child.is("text", ns::XMPP_STANZAS) {
172 for _ in child.children() {
173 return Err(Error::ParseError("Unknown element in error text."));
174 }
175 let lang = child.attr("xml:lang").unwrap_or("").to_owned();
176 if let Some(_) = texts.insert(lang, child.text()) {
177 return Err(Error::ParseError("Text element present twice for the same xml:lang."));
178 }
179 } else if child.ns() == Some(ns::XMPP_STANZAS) {
180 if defined_condition.is_some() {
181 return Err(Error::ParseError("Error must not have more than one defined-condition."));
182 }
183 for _ in child.children() {
184 return Err(Error::ParseError("Unknown element in defined-condition."));
185 }
186 let condition = DefinedCondition::from_str(child.name())?;
187 defined_condition = Some(condition);
188 } else {
189 if other.is_some() {
190 return Err(Error::ParseError("Error must not have more than one other element."));
191 }
192 other = Some(child.clone());
193 }
194 }
195
196 if defined_condition.is_none() {
197 return Err(Error::ParseError("Error must have a defined-condition."));
198 }
199 let defined_condition = defined_condition.unwrap();
200
201 Ok(StanzaError {
202 type_: type_,
203 by: by,
204 defined_condition: defined_condition,
205 texts: texts,
206 other: other,
207 })
208 }
209}
210
211impl<'a> Into<Element> for &'a StanzaError {
212 fn into(self) -> Element {
213 let mut root = Element::builder("error")
214 .ns(ns::JABBER_CLIENT)
215 .attr("type", String::from(self.type_.clone()))
216 .attr("by", match self.by {
217 Some(ref by) => Some(String::from(by.clone())),
218 None => None,
219 })
220 .append(Element::builder(self.defined_condition.clone())
221 .ns(ns::XMPP_STANZAS)
222 .build())
223 .build();
224 for (lang, text) in self.texts.clone() {
225 let elem = Element::builder("text")
226 .ns(ns::XMPP_STANZAS)
227 .attr("xml:lang", lang)
228 .append(text)
229 .build();
230 root.append_child(elem);
231 }
232 if let Some(ref other) = self.other {
233 root.append_child(other.clone());
234 }
235 root
236 }
237}
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242
243 #[test]
244 fn test_simple() {
245 let elem: Element = "<error xmlns='jabber:client' type='cancel'><undefined-condition xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error>".parse().unwrap();
246 let error = StanzaError::try_from(&elem).unwrap();
247 assert_eq!(error.type_, ErrorType::Cancel);
248 assert_eq!(error.defined_condition, DefinedCondition::UndefinedCondition);
249 }
250
251 #[test]
252 fn test_invalid_type() {
253 let elem: Element = "<error xmlns='jabber:client'/>".parse().unwrap();
254 let error = StanzaError::try_from(&elem).unwrap_err();
255 let message = match error {
256 Error::ParseError(string) => string,
257 _ => panic!(),
258 };
259 assert_eq!(message, "Error must have a 'type' attribute.");
260
261 let elem: Element = "<error xmlns='jabber:client' type='coucou'/>".parse().unwrap();
262 let error = StanzaError::try_from(&elem).unwrap_err();
263 let message = match error {
264 Error::ParseError(string) => string,
265 _ => panic!(),
266 };
267 assert_eq!(message, "Unknown error type.");
268 }
269
270 #[test]
271 fn test_invalid_condition() {
272 let elem: Element = "<error xmlns='jabber:client' type='cancel'/>".parse().unwrap();
273 let error = StanzaError::try_from(&elem).unwrap_err();
274 let message = match error {
275 Error::ParseError(string) => string,
276 _ => panic!(),
277 };
278 assert_eq!(message, "Error must have a defined-condition.");
279 }
280}