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