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