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<'a> TryFrom<&'a Element> for StanzaError {
154    type Error = Error;
155
156    fn try_from(elem: &'a 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_ = elem.attr("type")
162                        .ok_or(Error::ParseError("Error must have a 'type' attribute."))?
163                        .parse()?;
164        let by = elem.attr("by")
165                     .and_then(|by| by.parse().ok());
166        let mut defined_condition = None;
167        let mut texts = BTreeMap::new();
168        let mut other = None;
169
170        for child in elem.children() {
171            if child.is("text", ns::XMPP_STANZAS) {
172                for _ in child.children() {
173                    return Err(Error::ParseError("Unknown element in error text."));
174                }
175                let lang = child.attr("xml:lang").unwrap_or("").to_owned();
176                if let Some(_) = texts.insert(lang, child.text()) {
177                    return Err(Error::ParseError("Text element present twice for the same xml:lang."));
178                }
179            } else if child.ns() == Some(ns::XMPP_STANZAS) {
180                if defined_condition.is_some() {
181                    return Err(Error::ParseError("Error must not have more than one defined-condition."));
182                }
183                for _ in child.children() {
184                    return Err(Error::ParseError("Unknown element in defined-condition."));
185                }
186                let condition = DefinedCondition::from_str(child.name())?;
187                defined_condition = Some(condition);
188            } else {
189                if other.is_some() {
190                    return Err(Error::ParseError("Error must not have more than one other element."));
191                }
192                other = Some(child.clone());
193            }
194        }
195
196        if defined_condition.is_none() {
197            return Err(Error::ParseError("Error must have a defined-condition."));
198        }
199        let defined_condition = defined_condition.unwrap();
200
201        Ok(StanzaError {
202            type_: type_,
203            by: by,
204            defined_condition: defined_condition,
205            texts: texts,
206            other: other,
207        })
208    }
209}
210
211impl<'a> Into<Element> for &'a StanzaError {
212    fn into(self) -> Element {
213        let mut root = Element::builder("error")
214                               .ns(ns::JABBER_CLIENT)
215                               .attr("type", String::from(self.type_.clone()))
216                               .attr("by", match self.by {
217                                    Some(ref by) => Some(String::from(by.clone())),
218                                    None => None,
219                                })
220                               .append(Element::builder(self.defined_condition.clone())
221                                               .ns(ns::XMPP_STANZAS)
222                                               .build())
223                               .build();
224        for (lang, text) in self.texts.clone() {
225            let elem = Element::builder("text")
226                               .ns(ns::XMPP_STANZAS)
227                               .attr("xml:lang", lang)
228                               .append(text)
229                               .build();
230            root.append_child(elem);
231        }
232        if let Some(ref other) = self.other {
233            root.append_child(other.clone());
234        }
235        root
236    }
237}
238
239#[cfg(test)]
240mod tests {
241    use super::*;
242
243    #[test]
244    fn test_simple() {
245        let elem: Element = "<error xmlns='jabber:client' type='cancel'><undefined-condition xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error>".parse().unwrap();
246        let error = StanzaError::try_from(&elem).unwrap();
247        assert_eq!(error.type_, ErrorType::Cancel);
248        assert_eq!(error.defined_condition, DefinedCondition::UndefinedCondition);
249    }
250
251    #[test]
252    fn test_invalid_type() {
253        let elem: Element = "<error xmlns='jabber:client'/>".parse().unwrap();
254        let error = StanzaError::try_from(&elem).unwrap_err();
255        let message = match error {
256            Error::ParseError(string) => string,
257            _ => panic!(),
258        };
259        assert_eq!(message, "Error must have a 'type' attribute.");
260
261        let elem: Element = "<error xmlns='jabber:client' type='coucou'/>".parse().unwrap();
262        let error = StanzaError::try_from(&elem).unwrap_err();
263        let message = match error {
264            Error::ParseError(string) => string,
265            _ => panic!(),
266        };
267        assert_eq!(message, "Unknown error type.");
268    }
269
270    #[test]
271    fn test_invalid_condition() {
272        let elem: Element = "<error xmlns='jabber:client' type='cancel'/>".parse().unwrap();
273        let error = StanzaError::try_from(&elem).unwrap_err();
274        let message = match error {
275            Error::ParseError(string) => string,
276            _ => panic!(),
277        };
278        assert_eq!(message, "Error must have a defined-condition.");
279    }
280}