stanza_error.rs

  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 std::convert::TryFrom;
  8use std::str::FromStr;
  9use std::collections::BTreeMap;
 10
 11use minidom::Element;
 12
 13use error::Error;
 14use jid::Jid;
 15use ns;
 16
 17#[derive(Debug, Clone, PartialEq)]
 18pub enum ErrorType {
 19    Auth,
 20    Cancel,
 21    Continue,
 22    Modify,
 23    Wait,
 24}
 25
 26impl FromStr for ErrorType {
 27    type Err = Error;
 28
 29    fn from_str(s: &str) -> Result<ErrorType, Error> {
 30        Ok(match s {
 31            "auth" => ErrorType::Auth,
 32            "cancel" => ErrorType::Cancel,
 33            "continue" => ErrorType::Continue,
 34            "modify" => ErrorType::Modify,
 35            "wait" => ErrorType::Wait,
 36
 37            _ => return Err(Error::ParseError("Unknown error type.")),
 38        })
 39    }
 40}
 41
 42impl From<ErrorType> for String {
 43    fn from(type_: ErrorType) -> String {
 44        String::from(match type_ {
 45            ErrorType::Auth => "auth",
 46            ErrorType::Cancel => "cancel",
 47            ErrorType::Continue => "continue",
 48            ErrorType::Modify => "modify",
 49            ErrorType::Wait => "wait",
 50        })
 51    }
 52}
 53
 54#[derive(Debug, Clone, PartialEq)]
 55pub enum DefinedCondition {
 56    BadRequest,
 57    Conflict,
 58    FeatureNotImplemented,
 59    Forbidden,
 60    Gone,
 61    InternalServerError,
 62    ItemNotFound,
 63    JidMalformed,
 64    NotAcceptable,
 65    NotAllowed,
 66    NotAuthorized,
 67    PolicyViolation,
 68    RecipientUnavailable,
 69    Redirect,
 70    RegistrationRequired,
 71    RemoteServerNotFound,
 72    RemoteServerTimeout,
 73    ResourceConstraint,
 74    ServiceUnavailable,
 75    SubscriptionRequired,
 76    UndefinedCondition,
 77    UnexpectedRequest,
 78}
 79
 80impl FromStr for DefinedCondition {
 81    type Err = Error;
 82
 83    fn from_str(s: &str) -> Result<DefinedCondition, Error> {
 84        Ok(match s {
 85            "bad-request" => DefinedCondition::BadRequest,
 86            "conflict" => DefinedCondition::Conflict,
 87            "feature-not-implemented" => DefinedCondition::FeatureNotImplemented,
 88            "forbidden" => DefinedCondition::Forbidden,
 89            "gone" => DefinedCondition::Gone,
 90            "internal-server-error" => DefinedCondition::InternalServerError,
 91            "item-not-found" => DefinedCondition::ItemNotFound,
 92            "jid-malformed" => DefinedCondition::JidMalformed,
 93            "not-acceptable" => DefinedCondition::NotAcceptable,
 94            "not-allowed" => DefinedCondition::NotAllowed,
 95            "not-authorized" => DefinedCondition::NotAuthorized,
 96            "policy-violation" => DefinedCondition::PolicyViolation,
 97            "recipient-unavailable" => DefinedCondition::RecipientUnavailable,
 98            "redirect" => DefinedCondition::Redirect,
 99            "registration-required" => DefinedCondition::RegistrationRequired,
100            "remote-server-not-found" => DefinedCondition::RemoteServerNotFound,
101            "remote-server-timeout" => DefinedCondition::RemoteServerTimeout,
102            "resource-constraint" => DefinedCondition::ResourceConstraint,
103            "service-unavailable" => DefinedCondition::ServiceUnavailable,
104            "subscription-required" => DefinedCondition::SubscriptionRequired,
105            "undefined-condition" => DefinedCondition::UndefinedCondition,
106            "unexpected-request" => DefinedCondition::UnexpectedRequest,
107
108            _ => return Err(Error::ParseError("Unknown defined-condition.")),
109        })
110    }
111}
112
113impl From<DefinedCondition> for String {
114    fn from(defined_condition: DefinedCondition) -> String {
115        String::from(match defined_condition {
116            DefinedCondition::BadRequest => "bad-request",
117            DefinedCondition::Conflict => "conflict",
118            DefinedCondition::FeatureNotImplemented => "feature-not-implemented",
119            DefinedCondition::Forbidden => "forbidden",
120            DefinedCondition::Gone => "gone",
121            DefinedCondition::InternalServerError => "internal-server-error",
122            DefinedCondition::ItemNotFound => "item-not-found",
123            DefinedCondition::JidMalformed => "jid-malformed",
124            DefinedCondition::NotAcceptable => "not-acceptable",
125            DefinedCondition::NotAllowed => "not-allowed",
126            DefinedCondition::NotAuthorized => "not-authorized",
127            DefinedCondition::PolicyViolation => "policy-violation",
128            DefinedCondition::RecipientUnavailable => "recipient-unavailable",
129            DefinedCondition::Redirect => "redirect",
130            DefinedCondition::RegistrationRequired => "registration-required",
131            DefinedCondition::RemoteServerNotFound => "remote-server-not-found",
132            DefinedCondition::RemoteServerTimeout => "remote-server-timeout",
133            DefinedCondition::ResourceConstraint => "resource-constraint",
134            DefinedCondition::ServiceUnavailable => "service-unavailable",
135            DefinedCondition::SubscriptionRequired => "subscription-required",
136            DefinedCondition::UndefinedCondition => "undefined-condition",
137            DefinedCondition::UnexpectedRequest => "unexpected-request",
138        })
139    }
140}
141
142pub type Lang = String;
143
144#[derive(Debug, Clone)]
145pub struct StanzaError {
146    pub type_: ErrorType,
147    pub by: Option<Jid>,
148    pub defined_condition: DefinedCondition,
149    pub texts: BTreeMap<Lang, String>,
150    pub other: Option<Element>,
151}
152
153impl TryFrom<Element> for StanzaError {
154    type Error = Error;
155
156    fn try_from(elem: Element) -> Result<StanzaError, Error> {
157        if !elem.is("error", ns::JABBER_CLIENT) {
158            return Err(Error::ParseError("This is not an error element."));
159        }
160
161        let type_ = get_attr!(elem, "type", required);
162        let by = get_attr!(elem, "by", optional);
163        let mut defined_condition = None;
164        let mut texts = BTreeMap::new();
165        let mut other = None;
166
167        for child in elem.children() {
168            if child.is("text", ns::XMPP_STANZAS) {
169                for _ in child.children() {
170                    return Err(Error::ParseError("Unknown element in error text."));
171                }
172                let lang = get_attr!(elem, "xml:lang", default);
173                if texts.insert(lang, child.text()).is_some() {
174                    return Err(Error::ParseError("Text element present twice for the same xml:lang."));
175                }
176            } else if child.ns() == Some(ns::XMPP_STANZAS) {
177                if defined_condition.is_some() {
178                    return Err(Error::ParseError("Error must not have more than one defined-condition."));
179                }
180                for _ in child.children() {
181                    return Err(Error::ParseError("Unknown element in defined-condition."));
182                }
183                let condition = DefinedCondition::from_str(child.name())?;
184                defined_condition = Some(condition);
185            } else {
186                if other.is_some() {
187                    return Err(Error::ParseError("Error must not have more than one other element."));
188                }
189                other = Some(child.clone());
190            }
191        }
192
193        if defined_condition.is_none() {
194            return Err(Error::ParseError("Error must have a defined-condition."));
195        }
196        let defined_condition = defined_condition.unwrap();
197
198        Ok(StanzaError {
199            type_: type_,
200            by: by,
201            defined_condition: defined_condition,
202            texts: texts,
203            other: other,
204        })
205    }
206}
207
208impl Into<Element> for StanzaError {
209    fn into(self) -> Element {
210        let mut root = Element::builder("error")
211                               .ns(ns::JABBER_CLIENT)
212                               .attr("type", String::from(self.type_.clone()))
213                               .attr("by", match self.by {
214                                    Some(ref by) => Some(String::from(by.clone())),
215                                    None => None,
216                                })
217                               .append(Element::builder(self.defined_condition.clone())
218                                               .ns(ns::XMPP_STANZAS)
219                                               .build())
220                               .build();
221        for (lang, text) in self.texts.clone() {
222            let elem = Element::builder("text")
223                               .ns(ns::XMPP_STANZAS)
224                               .attr("xml:lang", lang)
225                               .append(text)
226                               .build();
227            root.append_child(elem);
228        }
229        if let Some(ref other) = self.other {
230            root.append_child(other.clone());
231        }
232        root
233    }
234}
235
236#[cfg(test)]
237mod tests {
238    use super::*;
239
240    #[test]
241    fn test_simple() {
242        let elem: Element = "<error xmlns='jabber:client' type='cancel'><undefined-condition xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error>".parse().unwrap();
243        let error = StanzaError::try_from(elem).unwrap();
244        assert_eq!(error.type_, ErrorType::Cancel);
245        assert_eq!(error.defined_condition, DefinedCondition::UndefinedCondition);
246    }
247
248    #[test]
249    fn test_invalid_type() {
250        let elem: Element = "<error xmlns='jabber:client'/>".parse().unwrap();
251        let error = StanzaError::try_from(elem).unwrap_err();
252        let message = match error {
253            Error::ParseError(string) => string,
254            _ => panic!(),
255        };
256        assert_eq!(message, "Required attribute 'type' missing.");
257
258        let elem: Element = "<error xmlns='jabber:client' type='coucou'/>".parse().unwrap();
259        let error = StanzaError::try_from(elem).unwrap_err();
260        let message = match error {
261            Error::ParseError(string) => string,
262            _ => panic!(),
263        };
264        assert_eq!(message, "Unknown error type.");
265    }
266
267    #[test]
268    fn test_invalid_condition() {
269        let elem: Element = "<error xmlns='jabber:client' type='cancel'/>".parse().unwrap();
270        let error = StanzaError::try_from(elem).unwrap_err();
271        let message = match error {
272            Error::ParseError(string) => string,
273            _ => panic!(),
274        };
275        assert_eq!(message, "Error must have a defined-condition.");
276    }
277}