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, IntoAttributeValue};
 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", ns::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", ns::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                for _ in child.children() {
 75                    return Err(Error::ParseError("Unknown element in error text."));
 76                }
 77                let lang = get_attr!(elem, "xml:lang", default);
 78                if texts.insert(lang, child.text()).is_some() {
 79                    return Err(Error::ParseError("Text element present twice for the same xml:lang."));
 80                }
 81            } else if child.has_ns(ns::XMPP_STANZAS) {
 82                if defined_condition.is_some() {
 83                    return Err(Error::ParseError("Error must not have more than one defined-condition."));
 84                }
 85                for _ in child.children() {
 86                    return Err(Error::ParseError("Unknown element in defined-condition."));
 87                }
 88                let condition = DefinedCondition::try_from(child.clone())?;
 89                defined_condition = Some(condition);
 90            } else {
 91                if other.is_some() {
 92                    return Err(Error::ParseError("Error must not have more than one other element."));
 93                }
 94                other = Some(child.clone());
 95            }
 96        }
 97        let defined_condition = defined_condition.ok_or(Error::ParseError("Error must have a defined-condition."))?;
 98
 99        Ok(StanzaError {
100            type_: type_,
101            by: by,
102            defined_condition: defined_condition,
103            texts: texts,
104            other: other,
105        })
106    }
107}
108
109impl From<StanzaError> for Element {
110    fn from(err: StanzaError) -> Element {
111        let mut root = Element::builder("error")
112                               .ns(ns::DEFAULT_NS)
113                               .attr("type", err.type_)
114                               .attr("by", err.by)
115                               .append(err.defined_condition)
116                               .build();
117        for (lang, text) in err.texts {
118            let elem = Element::builder("text")
119                               .ns(ns::XMPP_STANZAS)
120                               .attr("xml:lang", lang)
121                               .append(text)
122                               .build();
123            root.append_child(elem);
124        }
125        if let Some(other) = err.other {
126            root.append_child(other);
127        }
128        root
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135
136    #[test]
137    fn test_simple() {
138        #[cfg(not(feature = "component"))]
139        let elem: Element = "<error xmlns='jabber:client' type='cancel'><undefined-condition xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error>".parse().unwrap();
140        #[cfg(feature = "component")]
141        let elem: Element = "<error xmlns='jabber:component:accept' type='cancel'><undefined-condition xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error>".parse().unwrap();
142        let error = StanzaError::try_from(elem).unwrap();
143        assert_eq!(error.type_, ErrorType::Cancel);
144        assert_eq!(error.defined_condition, DefinedCondition::UndefinedCondition);
145    }
146
147    #[test]
148    fn test_invalid_type() {
149        #[cfg(not(feature = "component"))]
150        let elem: Element = "<error xmlns='jabber:client'/>".parse().unwrap();
151        #[cfg(feature = "component")]
152        let elem: Element = "<error xmlns='jabber:component:accept'/>".parse().unwrap();
153        let error = StanzaError::try_from(elem).unwrap_err();
154        let message = match error {
155            Error::ParseError(string) => string,
156            _ => panic!(),
157        };
158        assert_eq!(message, "Required attribute 'type' missing.");
159
160        #[cfg(not(feature = "component"))]
161        let elem: Element = "<error xmlns='jabber:client' type='coucou'/>".parse().unwrap();
162        #[cfg(feature = "component")]
163        let elem: Element = "<error xmlns='jabber:component:accept' type='coucou'/>".parse().unwrap();
164        let error = StanzaError::try_from(elem).unwrap_err();
165        let message = match error {
166            Error::ParseError(string) => string,
167            _ => panic!(),
168        };
169        assert_eq!(message, "Unknown value for 'type' attribute.");
170    }
171
172    #[test]
173    fn test_invalid_condition() {
174        #[cfg(not(feature = "component"))]
175        let elem: Element = "<error xmlns='jabber:client' type='cancel'/>".parse().unwrap();
176        #[cfg(feature = "component")]
177        let elem: Element = "<error xmlns='jabber:component:accept' type='cancel'/>".parse().unwrap();
178        let error = StanzaError::try_from(elem).unwrap_err();
179        let message = match error {
180            Error::ParseError(string) => string,
181            _ => panic!(),
182        };
183        assert_eq!(message, "Error must have a defined-condition.");
184    }
185}