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, IntoAttributeValue};
 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 IntoAttributeValue for ErrorType {
 43    fn into_attribute_value(self) -> Option<String> {
 44        Some(String::from(match self {
 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        let defined_condition = defined_condition.ok_or(Error::ParseError("Error must have a defined-condition."))?;
193
194        Ok(StanzaError {
195            type_: type_,
196            by: by,
197            defined_condition: defined_condition,
198            texts: texts,
199            other: other,
200        })
201    }
202}
203
204impl Into<Element> for StanzaError {
205    fn into(self) -> Element {
206        let mut root = Element::builder("error")
207                               .ns(ns::JABBER_CLIENT)
208                               .attr("type", self.type_)
209                               .attr("by", self.by.and_then(|by| Some(String::from(by))))
210                               .append(Element::builder(self.defined_condition)
211                                               .ns(ns::XMPP_STANZAS)
212                                               .build())
213                               .build();
214        for (lang, text) in self.texts {
215            let elem = Element::builder("text")
216                               .ns(ns::XMPP_STANZAS)
217                               .attr("xml:lang", lang)
218                               .append(text)
219                               .build();
220            root.append_child(elem);
221        }
222        if let Some(other) = self.other {
223            root.append_child(other);
224        }
225        root
226    }
227}
228
229#[cfg(test)]
230mod tests {
231    use super::*;
232
233    #[test]
234    fn test_simple() {
235        let elem: Element = "<error xmlns='jabber:client' type='cancel'><undefined-condition xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error>".parse().unwrap();
236        let error = StanzaError::try_from(elem).unwrap();
237        assert_eq!(error.type_, ErrorType::Cancel);
238        assert_eq!(error.defined_condition, DefinedCondition::UndefinedCondition);
239    }
240
241    #[test]
242    fn test_invalid_type() {
243        let elem: Element = "<error xmlns='jabber:client'/>".parse().unwrap();
244        let error = StanzaError::try_from(elem).unwrap_err();
245        let message = match error {
246            Error::ParseError(string) => string,
247            _ => panic!(),
248        };
249        assert_eq!(message, "Required attribute 'type' missing.");
250
251        let elem: Element = "<error xmlns='jabber:client' type='coucou'/>".parse().unwrap();
252        let error = StanzaError::try_from(elem).unwrap_err();
253        let message = match error {
254            Error::ParseError(string) => string,
255            _ => panic!(),
256        };
257        assert_eq!(message, "Unknown error type.");
258    }
259
260    #[test]
261    fn test_invalid_condition() {
262        let elem: Element = "<error xmlns='jabber:client' type='cancel'/>".parse().unwrap();
263        let error = StanzaError::try_from(elem).unwrap_err();
264        let message = match error {
265            Error::ParseError(string) => string,
266            _ => panic!(),
267        };
268        assert_eq!(message, "Error must have a defined-condition.");
269    }
270}