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