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            if child.is("text", ns::XMPP_STANZAS) {
140                for _ in child.children() {
141                    return Err(Error::ParseError("Unknown element in error text."));
142                }
143                let lang = get_attr!(elem, "xml:lang", default);
144                if texts.insert(lang, child.text()).is_some() {
145                    return Err(Error::ParseError("Text element present twice for the same xml:lang."));
146                }
147            } else if child.has_ns(ns::XMPP_STANZAS) {
148                if defined_condition.is_some() {
149                    return Err(Error::ParseError("Error must not have more than one defined-condition."));
150                }
151                for _ in child.children() {
152                    return Err(Error::ParseError("Unknown element in defined-condition."));
153                }
154                let condition = DefinedCondition::from_str(child.name())?;
155                defined_condition = Some(condition);
156            } else {
157                if other.is_some() {
158                    return Err(Error::ParseError("Error must not have more than one other element."));
159                }
160                other = Some(child.clone());
161            }
162        }
163        let defined_condition = defined_condition.ok_or(Error::ParseError("Error must have a defined-condition."))?;
164
165        Ok(StanzaError {
166            type_: type_,
167            by: by,
168            defined_condition: defined_condition,
169            texts: texts,
170            other: other,
171        })
172    }
173}
174
175impl From<StanzaError> for Element {
176    fn from(err: StanzaError) -> Element {
177        let mut root = Element::builder("error")
178                               .ns(ns::DEFAULT_NS)
179                               .attr("type", err.type_)
180                               .attr("by", err.by)
181                               .append(err.defined_condition)
182                               .build();
183        for (lang, text) in err.texts {
184            let elem = Element::builder("text")
185                               .ns(ns::XMPP_STANZAS)
186                               .attr("xml:lang", lang)
187                               .append(text)
188                               .build();
189            root.append_child(elem);
190        }
191        if let Some(other) = err.other {
192            root.append_child(other);
193        }
194        root
195    }
196}
197
198#[cfg(test)]
199mod tests {
200    use super::*;
201
202    #[test]
203    fn test_simple() {
204        #[cfg(not(feature = "component"))]
205        let elem: Element = "<error xmlns='jabber:client' type='cancel'><undefined-condition xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error>".parse().unwrap();
206        #[cfg(feature = "component")]
207        let elem: Element = "<error xmlns='jabber:component:accept' type='cancel'><undefined-condition xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error>".parse().unwrap();
208        let error = StanzaError::try_from(elem).unwrap();
209        assert_eq!(error.type_, ErrorType::Cancel);
210        assert_eq!(error.defined_condition, DefinedCondition::UndefinedCondition);
211    }
212
213    #[test]
214    fn test_invalid_type() {
215        #[cfg(not(feature = "component"))]
216        let elem: Element = "<error xmlns='jabber:client'/>".parse().unwrap();
217        #[cfg(feature = "component")]
218        let elem: Element = "<error xmlns='jabber:component:accept'/>".parse().unwrap();
219        let error = StanzaError::try_from(elem).unwrap_err();
220        let message = match error {
221            Error::ParseError(string) => string,
222            _ => panic!(),
223        };
224        assert_eq!(message, "Required attribute 'type' missing.");
225
226        #[cfg(not(feature = "component"))]
227        let elem: Element = "<error xmlns='jabber:client' type='coucou'/>".parse().unwrap();
228        #[cfg(feature = "component")]
229        let elem: Element = "<error xmlns='jabber:component:accept' type='coucou'/>".parse().unwrap();
230        let error = StanzaError::try_from(elem).unwrap_err();
231        let message = match error {
232            Error::ParseError(string) => string,
233            _ => panic!(),
234        };
235        assert_eq!(message, "Unknown value for 'type' attribute.");
236    }
237
238    #[test]
239    fn test_invalid_condition() {
240        #[cfg(not(feature = "component"))]
241        let elem: Element = "<error xmlns='jabber:client' type='cancel'/>".parse().unwrap();
242        #[cfg(feature = "component")]
243        let elem: Element = "<error xmlns='jabber:component:accept' type='cancel'/>".parse().unwrap();
244        let error = StanzaError::try_from(elem).unwrap_err();
245        let message = match error {
246            Error::ParseError(string) => string,
247            _ => panic!(),
248        };
249        assert_eq!(message, "Error must have a defined-condition.");
250    }
251}