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