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 check_self!(elem, "error", ns::DEFAULT_NS);
66
67 let type_ = get_attr!(elem, "type", required);
68 let by = get_attr!(elem, "by", optional);
69 let mut defined_condition = None;
70 let mut texts = BTreeMap::new();
71 let mut other = None;
72
73 for child in elem.children() {
74 if child.is("text", ns::XMPP_STANZAS) {
75 for _ in child.children() {
76 return Err(Error::ParseError("Unknown element in error text."));
77 }
78 let lang = get_attr!(elem, "xml:lang", default);
79 if texts.insert(lang, child.text()).is_some() {
80 return Err(Error::ParseError("Text element present twice for the same xml:lang."));
81 }
82 } else if child.has_ns(ns::XMPP_STANZAS) {
83 if defined_condition.is_some() {
84 return Err(Error::ParseError("Error must not have more than one defined-condition."));
85 }
86 for _ in child.children() {
87 return Err(Error::ParseError("Unknown element in defined-condition."));
88 }
89 let condition = DefinedCondition::try_from(child.clone())?;
90 defined_condition = Some(condition);
91 } else {
92 if other.is_some() {
93 return Err(Error::ParseError("Error must not have more than one other element."));
94 }
95 other = Some(child.clone());
96 }
97 }
98 let defined_condition = defined_condition.ok_or(Error::ParseError("Error must have a defined-condition."))?;
99
100 Ok(StanzaError {
101 type_: type_,
102 by: by,
103 defined_condition: defined_condition,
104 texts: texts,
105 other: other,
106 })
107 }
108}
109
110impl From<StanzaError> for Element {
111 fn from(err: StanzaError) -> Element {
112 let mut root = Element::builder("error")
113 .ns(ns::DEFAULT_NS)
114 .attr("type", err.type_)
115 .attr("by", err.by)
116 .append(err.defined_condition)
117 .build();
118 for (lang, text) in err.texts {
119 let elem = Element::builder("text")
120 .ns(ns::XMPP_STANZAS)
121 .attr("xml:lang", lang)
122 .append(text)
123 .build();
124 root.append_child(elem);
125 }
126 if let Some(other) = err.other {
127 root.append_child(other);
128 }
129 root
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[test]
138 fn test_simple() {
139 #[cfg(not(feature = "component"))]
140 let elem: Element = "<error xmlns='jabber:client' type='cancel'><undefined-condition xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error>".parse().unwrap();
141 #[cfg(feature = "component")]
142 let elem: Element = "<error xmlns='jabber:component:accept' type='cancel'><undefined-condition xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error>".parse().unwrap();
143 let error = StanzaError::try_from(elem).unwrap();
144 assert_eq!(error.type_, ErrorType::Cancel);
145 assert_eq!(error.defined_condition, DefinedCondition::UndefinedCondition);
146 }
147
148 #[test]
149 fn test_invalid_type() {
150 #[cfg(not(feature = "component"))]
151 let elem: Element = "<error xmlns='jabber:client'/>".parse().unwrap();
152 #[cfg(feature = "component")]
153 let elem: Element = "<error xmlns='jabber:component:accept'/>".parse().unwrap();
154 let error = StanzaError::try_from(elem).unwrap_err();
155 let message = match error {
156 Error::ParseError(string) => string,
157 _ => panic!(),
158 };
159 assert_eq!(message, "Required attribute 'type' missing.");
160
161 #[cfg(not(feature = "component"))]
162 let elem: Element = "<error xmlns='jabber:client' type='coucou'/>".parse().unwrap();
163 #[cfg(feature = "component")]
164 let elem: Element = "<error xmlns='jabber:component:accept' type='coucou'/>".parse().unwrap();
165 let error = StanzaError::try_from(elem).unwrap_err();
166 let message = match error {
167 Error::ParseError(string) => string,
168 _ => panic!(),
169 };
170 assert_eq!(message, "Unknown value for 'type' attribute.");
171 }
172
173 #[test]
174 fn test_invalid_condition() {
175 #[cfg(not(feature = "component"))]
176 let elem: Element = "<error xmlns='jabber:client' type='cancel'/>".parse().unwrap();
177 #[cfg(feature = "component")]
178 let elem: Element = "<error xmlns='jabber:component:accept' type='cancel'/>".parse().unwrap();
179 let error = StanzaError::try_from(elem).unwrap_err();
180 let message = match error {
181 Error::ParseError(string) => string,
182 _ => panic!(),
183 };
184 assert_eq!(message, "Error must have a defined-condition.");
185 }
186}