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