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