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, IntoAttributeValue};
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
25generate_element_enum!(DefinedCondition, "condition", ns::XMPP_STANZAS, {
26 BadRequest => "bad-request",
27 Conflict => "conflict",
28 FeatureNotImplemented => "feature-not-implemented",
29 Forbidden => "forbidden",
30 Gone => "gone",
31 InternalServerError => "internal-server-error",
32 ItemNotFound => "item-not-found",
33 JidMalformed => "jid-malformed",
34 NotAcceptable => "not-acceptable",
35 NotAllowed => "not-allowed",
36 NotAuthorized => "not-authorized",
37 PolicyViolation => "policy-violation",
38 RecipientUnavailable => "recipient-unavailable",
39 Redirect => "redirect",
40 RegistrationRequired => "registration-required",
41 RemoteServerNotFound => "remote-server-not-found",
42 RemoteServerTimeout => "remote-server-timeout",
43 ResourceConstraint => "resource-constraint",
44 ServiceUnavailable => "service-unavailable",
45 SubscriptionRequired => "subscription-required",
46 UndefinedCondition => "undefined-condition",
47 UnexpectedRequest => "unexpected-request",
48});
49
50pub type Lang = String;
51
52#[derive(Debug, Clone)]
53pub struct StanzaError {
54 pub type_: ErrorType,
55 pub by: Option<Jid>,
56 pub defined_condition: DefinedCondition,
57 pub texts: BTreeMap<Lang, String>,
58 pub other: Option<Element>,
59}
60
61impl TryFrom<Element> for StanzaError {
62 type Err = Error;
63
64 fn try_from(elem: Element) -> Result<StanzaError, Error> {
65 if !elem.is("error", ns::DEFAULT_NS) {
66 return Err(Error::ParseError("This is not an error element."));
67 }
68
69 let type_ = get_attr!(elem, "type", required);
70 let by = get_attr!(elem, "by", optional);
71 let mut defined_condition = None;
72 let mut texts = BTreeMap::new();
73 let mut other = None;
74
75 for child in elem.children() {
76 if child.is("text", ns::XMPP_STANZAS) {
77 for _ in child.children() {
78 return Err(Error::ParseError("Unknown element in error text."));
79 }
80 let lang = get_attr!(elem, "xml:lang", default);
81 if texts.insert(lang, child.text()).is_some() {
82 return Err(Error::ParseError("Text element present twice for the same xml:lang."));
83 }
84 } else if child.has_ns(ns::XMPP_STANZAS) {
85 if defined_condition.is_some() {
86 return Err(Error::ParseError("Error must not have more than one defined-condition."));
87 }
88 for _ in child.children() {
89 return Err(Error::ParseError("Unknown element in defined-condition."));
90 }
91 let condition = DefinedCondition::try_from(child.clone())?;
92 defined_condition = Some(condition);
93 } else {
94 if other.is_some() {
95 return Err(Error::ParseError("Error must not have more than one other element."));
96 }
97 other = Some(child.clone());
98 }
99 }
100 let defined_condition = defined_condition.ok_or(Error::ParseError("Error must have a defined-condition."))?;
101
102 Ok(StanzaError {
103 type_: type_,
104 by: by,
105 defined_condition: defined_condition,
106 texts: texts,
107 other: other,
108 })
109 }
110}
111
112impl From<StanzaError> for Element {
113 fn from(err: StanzaError) -> Element {
114 let mut root = Element::builder("error")
115 .ns(ns::DEFAULT_NS)
116 .attr("type", err.type_)
117 .attr("by", err.by)
118 .append(err.defined_condition)
119 .build();
120 for (lang, text) in err.texts {
121 let elem = Element::builder("text")
122 .ns(ns::XMPP_STANZAS)
123 .attr("xml:lang", lang)
124 .append(text)
125 .build();
126 root.append_child(elem);
127 }
128 if let Some(other) = err.other {
129 root.append_child(other);
130 }
131 root
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 #[test]
140 fn test_simple() {
141 #[cfg(not(feature = "component"))]
142 let elem: Element = "<error xmlns='jabber:client' type='cancel'><undefined-condition xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error>".parse().unwrap();
143 #[cfg(feature = "component")]
144 let elem: Element = "<error xmlns='jabber:component:accept' type='cancel'><undefined-condition xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error>".parse().unwrap();
145 let error = StanzaError::try_from(elem).unwrap();
146 assert_eq!(error.type_, ErrorType::Cancel);
147 assert_eq!(error.defined_condition, DefinedCondition::UndefinedCondition);
148 }
149
150 #[test]
151 fn test_invalid_type() {
152 #[cfg(not(feature = "component"))]
153 let elem: Element = "<error xmlns='jabber:client'/>".parse().unwrap();
154 #[cfg(feature = "component")]
155 let elem: Element = "<error xmlns='jabber:component:accept'/>".parse().unwrap();
156 let error = StanzaError::try_from(elem).unwrap_err();
157 let message = match error {
158 Error::ParseError(string) => string,
159 _ => panic!(),
160 };
161 assert_eq!(message, "Required attribute 'type' missing.");
162
163 #[cfg(not(feature = "component"))]
164 let elem: Element = "<error xmlns='jabber:client' type='coucou'/>".parse().unwrap();
165 #[cfg(feature = "component")]
166 let elem: Element = "<error xmlns='jabber:component:accept' type='coucou'/>".parse().unwrap();
167 let error = StanzaError::try_from(elem).unwrap_err();
168 let message = match error {
169 Error::ParseError(string) => string,
170 _ => panic!(),
171 };
172 assert_eq!(message, "Unknown value for 'type' attribute.");
173 }
174
175 #[test]
176 fn test_invalid_condition() {
177 #[cfg(not(feature = "component"))]
178 let elem: Element = "<error xmlns='jabber:client' type='cancel'/>".parse().unwrap();
179 #[cfg(feature = "component")]
180 let elem: Element = "<error xmlns='jabber:component:accept' type='cancel'/>".parse().unwrap();
181 let error = StanzaError::try_from(elem).unwrap_err();
182 let message = match error {
183 Error::ParseError(string) => string,
184 _ => panic!(),
185 };
186 assert_eq!(message, "Error must have a defined-condition.");
187 }
188}