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