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