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, IntoElements, IntoAttributeValue, ElementEmitter};
 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
 25#[derive(Debug, Clone, PartialEq)]
 26pub enum DefinedCondition {
 27    BadRequest,
 28    Conflict,
 29    FeatureNotImplemented,
 30    Forbidden,
 31    Gone,
 32    InternalServerError,
 33    ItemNotFound,
 34    JidMalformed,
 35    NotAcceptable,
 36    NotAllowed,
 37    NotAuthorized,
 38    PolicyViolation,
 39    RecipientUnavailable,
 40    Redirect,
 41    RegistrationRequired,
 42    RemoteServerNotFound,
 43    RemoteServerTimeout,
 44    ResourceConstraint,
 45    ServiceUnavailable,
 46    SubscriptionRequired,
 47    UndefinedCondition,
 48    UnexpectedRequest,
 49}
 50
 51impl FromStr for DefinedCondition {
 52    type Err = Error;
 53
 54    fn from_str(s: &str) -> Result<DefinedCondition, Error> {
 55        Ok(match s {
 56            "bad-request" => DefinedCondition::BadRequest,
 57            "conflict" => DefinedCondition::Conflict,
 58            "feature-not-implemented" => DefinedCondition::FeatureNotImplemented,
 59            "forbidden" => DefinedCondition::Forbidden,
 60            "gone" => DefinedCondition::Gone,
 61            "internal-server-error" => DefinedCondition::InternalServerError,
 62            "item-not-found" => DefinedCondition::ItemNotFound,
 63            "jid-malformed" => DefinedCondition::JidMalformed,
 64            "not-acceptable" => DefinedCondition::NotAcceptable,
 65            "not-allowed" => DefinedCondition::NotAllowed,
 66            "not-authorized" => DefinedCondition::NotAuthorized,
 67            "policy-violation" => DefinedCondition::PolicyViolation,
 68            "recipient-unavailable" => DefinedCondition::RecipientUnavailable,
 69            "redirect" => DefinedCondition::Redirect,
 70            "registration-required" => DefinedCondition::RegistrationRequired,
 71            "remote-server-not-found" => DefinedCondition::RemoteServerNotFound,
 72            "remote-server-timeout" => DefinedCondition::RemoteServerTimeout,
 73            "resource-constraint" => DefinedCondition::ResourceConstraint,
 74            "service-unavailable" => DefinedCondition::ServiceUnavailable,
 75            "subscription-required" => DefinedCondition::SubscriptionRequired,
 76            "undefined-condition" => DefinedCondition::UndefinedCondition,
 77            "unexpected-request" => DefinedCondition::UnexpectedRequest,
 78
 79            _ => return Err(Error::ParseError("Unknown defined-condition.")),
 80        })
 81    }
 82}
 83
 84impl IntoElements for DefinedCondition {
 85    fn into_elements(self, emitter: &mut ElementEmitter) {
 86        emitter.append_child(Element::builder(match self {
 87            DefinedCondition::BadRequest => "bad-request",
 88            DefinedCondition::Conflict => "conflict",
 89            DefinedCondition::FeatureNotImplemented => "feature-not-implemented",
 90            DefinedCondition::Forbidden => "forbidden",
 91            DefinedCondition::Gone => "gone",
 92            DefinedCondition::InternalServerError => "internal-server-error",
 93            DefinedCondition::ItemNotFound => "item-not-found",
 94            DefinedCondition::JidMalformed => "jid-malformed",
 95            DefinedCondition::NotAcceptable => "not-acceptable",
 96            DefinedCondition::NotAllowed => "not-allowed",
 97            DefinedCondition::NotAuthorized => "not-authorized",
 98            DefinedCondition::PolicyViolation => "policy-violation",
 99            DefinedCondition::RecipientUnavailable => "recipient-unavailable",
100            DefinedCondition::Redirect => "redirect",
101            DefinedCondition::RegistrationRequired => "registration-required",
102            DefinedCondition::RemoteServerNotFound => "remote-server-not-found",
103            DefinedCondition::RemoteServerTimeout => "remote-server-timeout",
104            DefinedCondition::ResourceConstraint => "resource-constraint",
105            DefinedCondition::ServiceUnavailable => "service-unavailable",
106            DefinedCondition::SubscriptionRequired => "subscription-required",
107            DefinedCondition::UndefinedCondition => "undefined-condition",
108            DefinedCondition::UnexpectedRequest => "unexpected-request",
109        }).ns(ns::XMPP_STANZAS).build());
110    }
111}
112
113pub type Lang = String;
114
115#[derive(Debug, Clone)]
116pub struct StanzaError {
117    pub type_: ErrorType,
118    pub by: Option<Jid>,
119    pub defined_condition: DefinedCondition,
120    pub texts: BTreeMap<Lang, String>,
121    pub other: Option<Element>,
122}
123
124impl TryFrom<Element> for StanzaError {
125    type Err = Error;
126
127    fn try_from(elem: Element) -> Result<StanzaError, Error> {
128        if !elem.is("error", ns::DEFAULT_NS) {
129            return Err(Error::ParseError("This is not an error element."));
130        }
131
132        let type_ = get_attr!(elem, "type", required);
133        let by = get_attr!(elem, "by", optional);
134        let mut defined_condition = None;
135        let mut texts = BTreeMap::new();
136        let mut other = None;
137
138        for child in elem.children() {
139            let child_ns = child.ns();
140            if child.is("text", ns::XMPP_STANZAS) {
141                for _ in child.children() {
142                    return Err(Error::ParseError("Unknown element in error text."));
143                }
144                let lang = get_attr!(elem, "xml:lang", default);
145                if texts.insert(lang, child.text()).is_some() {
146                    return Err(Error::ParseError("Text element present twice for the same xml:lang."));
147                }
148            } else if child_ns.as_ref().map(|ns| ns.as_str()) == Some(ns::XMPP_STANZAS) {
149                if defined_condition.is_some() {
150                    return Err(Error::ParseError("Error must not have more than one defined-condition."));
151                }
152                for _ in child.children() {
153                    return Err(Error::ParseError("Unknown element in defined-condition."));
154                }
155                let condition = DefinedCondition::from_str(child.name())?;
156                defined_condition = Some(condition);
157            } else {
158                if other.is_some() {
159                    return Err(Error::ParseError("Error must not have more than one other element."));
160                }
161                other = Some(child.clone());
162            }
163        }
164        let defined_condition = defined_condition.ok_or(Error::ParseError("Error must have a defined-condition."))?;
165
166        Ok(StanzaError {
167            type_: type_,
168            by: by,
169            defined_condition: defined_condition,
170            texts: texts,
171            other: other,
172        })
173    }
174}
175
176impl From<StanzaError> for Element {
177    fn from(err: StanzaError) -> Element {
178        let mut root = Element::builder("error")
179                               .ns(ns::DEFAULT_NS)
180                               .attr("type", err.type_)
181                               .attr("by", err.by)
182                               .append(err.defined_condition)
183                               .build();
184        for (lang, text) in err.texts {
185            let elem = Element::builder("text")
186                               .ns(ns::XMPP_STANZAS)
187                               .attr("xml:lang", lang)
188                               .append(text)
189                               .build();
190            root.append_child(elem);
191        }
192        if let Some(other) = err.other {
193            root.append_child(other);
194        }
195        root
196    }
197}
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202
203    #[test]
204    fn test_simple() {
205        #[cfg(not(feature = "component"))]
206        let elem: Element = "<error xmlns='jabber:client' type='cancel'><undefined-condition xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error>".parse().unwrap();
207        #[cfg(feature = "component")]
208        let elem: Element = "<error xmlns='jabber:component:accept' type='cancel'><undefined-condition xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error>".parse().unwrap();
209        let error = StanzaError::try_from(elem).unwrap();
210        assert_eq!(error.type_, ErrorType::Cancel);
211        assert_eq!(error.defined_condition, DefinedCondition::UndefinedCondition);
212    }
213
214    #[test]
215    fn test_invalid_type() {
216        #[cfg(not(feature = "component"))]
217        let elem: Element = "<error xmlns='jabber:client'/>".parse().unwrap();
218        #[cfg(feature = "component")]
219        let elem: Element = "<error xmlns='jabber:component:accept'/>".parse().unwrap();
220        let error = StanzaError::try_from(elem).unwrap_err();
221        let message = match error {
222            Error::ParseError(string) => string,
223            _ => panic!(),
224        };
225        assert_eq!(message, "Required attribute 'type' missing.");
226
227        #[cfg(not(feature = "component"))]
228        let elem: Element = "<error xmlns='jabber:client' type='coucou'/>".parse().unwrap();
229        #[cfg(feature = "component")]
230        let elem: Element = "<error xmlns='jabber:component:accept' type='coucou'/>".parse().unwrap();
231        let error = StanzaError::try_from(elem).unwrap_err();
232        let message = match error {
233            Error::ParseError(string) => string,
234            _ => panic!(),
235        };
236        assert_eq!(message, "Unknown value for 'type' attribute.");
237    }
238
239    #[test]
240    fn test_invalid_condition() {
241        #[cfg(not(feature = "component"))]
242        let elem: Element = "<error xmlns='jabber:client' type='cancel'/>".parse().unwrap();
243        #[cfg(feature = "component")]
244        let elem: Element = "<error xmlns='jabber:component:accept' type='cancel'/>".parse().unwrap();
245        let error = StanzaError::try_from(elem).unwrap_err();
246        let message = match error {
247            Error::ParseError(string) => string,
248            _ => panic!(),
249        };
250        assert_eq!(message, "Error must have a defined-condition.");
251    }
252}