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