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