Add a stanza error parser and serialiser.
This commit is contained in:
parent
e03a5a89e7
commit
2465885975
3 changed files with 279 additions and 0 deletions
|
@ -32,6 +32,8 @@ pub mod message;
|
|||
pub mod presence;
|
||||
/// RFC 6120: Extensible Messaging and Presence Protocol (XMPP): Core
|
||||
pub mod iq;
|
||||
/// RFC 6120: Extensible Messaging and Presence Protocol (XMPP): Core
|
||||
pub mod stanza_error;
|
||||
|
||||
/// RFC 6121: Extensible Messaging and Presence Protocol (XMPP): Instant Messaging and Presence
|
||||
pub mod body;
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
|
||||
/// RFC 6120: Extensible Messaging and Presence Protocol (XMPP): Core
|
||||
pub const JABBER_CLIENT: &'static str = "jabber:client";
|
||||
/// RFC 6120: Extensible Messaging and Presence Protocol (XMPP): Core
|
||||
pub const XMPP_STANZAS: &'static str = "urn:ietf:params:xml:ns:xmpp-stanzas";
|
||||
|
||||
/// XEP-0004: Data Forms
|
||||
pub const DATA_FORMS: &'static str = "jabber:x:data";
|
||||
|
|
275
src/stanza_error.rs
Normal file
275
src/stanza_error.rs
Normal file
|
@ -0,0 +1,275 @@
|
|||
// Copyright (c) 2017 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
use std::str::FromStr;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use minidom::Element;
|
||||
|
||||
use error::Error;
|
||||
use jid::Jid;
|
||||
use ns;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum ErrorType {
|
||||
Auth,
|
||||
Cancel,
|
||||
Continue,
|
||||
Modify,
|
||||
Wait,
|
||||
}
|
||||
|
||||
impl FromStr for ErrorType {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<ErrorType, Error> {
|
||||
Ok(match s {
|
||||
"auth" => ErrorType::Auth,
|
||||
"cancel" => ErrorType::Cancel,
|
||||
"continue" => ErrorType::Continue,
|
||||
"modify" => ErrorType::Modify,
|
||||
"wait" => ErrorType::Wait,
|
||||
|
||||
_ => return Err(Error::ParseError("Unknown error type.")),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ErrorType> for String {
|
||||
fn from(type_: ErrorType) -> String {
|
||||
String::from(match type_ {
|
||||
ErrorType::Auth => "auth",
|
||||
ErrorType::Cancel => "cancel",
|
||||
ErrorType::Continue => "continue",
|
||||
ErrorType::Modify => "modify",
|
||||
ErrorType::Wait => "wait",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum DefinedCondition {
|
||||
BadRequest,
|
||||
Conflict,
|
||||
FeatureNotImplemented,
|
||||
Forbidden,
|
||||
Gone,
|
||||
InternalServerError,
|
||||
ItemNotFound,
|
||||
JidMalformed,
|
||||
NotAcceptable,
|
||||
NotAllowed,
|
||||
NotAuthorized,
|
||||
PolicyViolation,
|
||||
RecipientUnavailable,
|
||||
Redirect,
|
||||
RegistrationRequired,
|
||||
RemoteServerNotFound,
|
||||
RemoteServerTimeout,
|
||||
ResourceConstraint,
|
||||
ServiceUnavailable,
|
||||
SubscriptionRequired,
|
||||
UndefinedCondition,
|
||||
UnexpectedRequest,
|
||||
}
|
||||
|
||||
impl FromStr for DefinedCondition {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<DefinedCondition, Error> {
|
||||
Ok(match s {
|
||||
"bad-request" => DefinedCondition::BadRequest,
|
||||
"conflict" => DefinedCondition::Conflict,
|
||||
"feature-not-implemented" => DefinedCondition::FeatureNotImplemented,
|
||||
"forbidden" => DefinedCondition::Forbidden,
|
||||
"gone" => DefinedCondition::Gone,
|
||||
"internal-server-error" => DefinedCondition::InternalServerError,
|
||||
"item-not-found" => DefinedCondition::ItemNotFound,
|
||||
"jid-malformed" => DefinedCondition::JidMalformed,
|
||||
"not-acceptable" => DefinedCondition::NotAcceptable,
|
||||
"not-allowed" => DefinedCondition::NotAllowed,
|
||||
"not-authorized" => DefinedCondition::NotAuthorized,
|
||||
"policy-violation" => DefinedCondition::PolicyViolation,
|
||||
"recipient-unavailable" => DefinedCondition::RecipientUnavailable,
|
||||
"redirect" => DefinedCondition::Redirect,
|
||||
"registration-required" => DefinedCondition::RegistrationRequired,
|
||||
"remote-server-not-found" => DefinedCondition::RemoteServerNotFound,
|
||||
"remote-server-timeout" => DefinedCondition::RemoteServerTimeout,
|
||||
"resource-constraint" => DefinedCondition::ResourceConstraint,
|
||||
"service-unavailable" => DefinedCondition::ServiceUnavailable,
|
||||
"subscription-required" => DefinedCondition::SubscriptionRequired,
|
||||
"undefined-condition" => DefinedCondition::UndefinedCondition,
|
||||
"unexpected-request" => DefinedCondition::UnexpectedRequest,
|
||||
|
||||
_ => return Err(Error::ParseError("Unknown defined-condition.")),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DefinedCondition> for String {
|
||||
fn from(defined_condition: DefinedCondition) -> String {
|
||||
String::from(match defined_condition {
|
||||
DefinedCondition::BadRequest => "bad-request",
|
||||
DefinedCondition::Conflict => "conflict",
|
||||
DefinedCondition::FeatureNotImplemented => "feature-not-implemented",
|
||||
DefinedCondition::Forbidden => "forbidden",
|
||||
DefinedCondition::Gone => "gone",
|
||||
DefinedCondition::InternalServerError => "internal-server-error",
|
||||
DefinedCondition::ItemNotFound => "item-not-found",
|
||||
DefinedCondition::JidMalformed => "jid-malformed",
|
||||
DefinedCondition::NotAcceptable => "not-acceptable",
|
||||
DefinedCondition::NotAllowed => "not-allowed",
|
||||
DefinedCondition::NotAuthorized => "not-authorized",
|
||||
DefinedCondition::PolicyViolation => "policy-violation",
|
||||
DefinedCondition::RecipientUnavailable => "recipient-unavailable",
|
||||
DefinedCondition::Redirect => "redirect",
|
||||
DefinedCondition::RegistrationRequired => "registration-required",
|
||||
DefinedCondition::RemoteServerNotFound => "remote-server-not-found",
|
||||
DefinedCondition::RemoteServerTimeout => "remote-server-timeout",
|
||||
DefinedCondition::ResourceConstraint => "resource-constraint",
|
||||
DefinedCondition::ServiceUnavailable => "service-unavailable",
|
||||
DefinedCondition::SubscriptionRequired => "subscription-required",
|
||||
DefinedCondition::UndefinedCondition => "undefined-condition",
|
||||
DefinedCondition::UnexpectedRequest => "unexpected-request",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub type Lang = String;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StanzaError {
|
||||
pub type_: ErrorType,
|
||||
pub by: Option<Jid>,
|
||||
pub defined_condition: DefinedCondition,
|
||||
pub texts: BTreeMap<Lang, String>,
|
||||
pub other: Option<Element>,
|
||||
}
|
||||
|
||||
pub fn parse_stanza_error(root: &Element) -> Result<StanzaError, Error> {
|
||||
if !root.is("error", ns::JABBER_CLIENT) {
|
||||
return Err(Error::ParseError("This is not an error element."));
|
||||
}
|
||||
|
||||
let type_ = root.attr("type")
|
||||
.ok_or(Error::ParseError("Error must have a 'type' attribute."))?
|
||||
.parse()?;
|
||||
let by = root.attr("by")
|
||||
.and_then(|by| by.parse().ok());
|
||||
let mut defined_condition = None;
|
||||
let mut texts = BTreeMap::new();
|
||||
let mut other = None;
|
||||
|
||||
for child in root.children() {
|
||||
if child.is("text", ns::XMPP_STANZAS) {
|
||||
for _ in child.children() {
|
||||
return Err(Error::ParseError("Unknown element in error text."));
|
||||
}
|
||||
let lang = child.attr("xml:lang").unwrap_or("").to_owned();
|
||||
if let Some(_) = texts.insert(lang, child.text()) {
|
||||
return Err(Error::ParseError("Text element present twice for the same xml:lang."));
|
||||
}
|
||||
} else if child.ns() == Some(ns::XMPP_STANZAS) {
|
||||
if defined_condition.is_some() {
|
||||
return Err(Error::ParseError("Error must not have more than one defined-condition."));
|
||||
}
|
||||
for _ in child.children() {
|
||||
return Err(Error::ParseError("Unknown element in defined-condition."));
|
||||
}
|
||||
let condition = DefinedCondition::from_str(child.name())?;
|
||||
defined_condition = Some(condition);
|
||||
} else {
|
||||
if other.is_some() {
|
||||
return Err(Error::ParseError("Error must not have more than one other element."));
|
||||
}
|
||||
other = Some(child.clone());
|
||||
}
|
||||
}
|
||||
|
||||
if defined_condition.is_none() {
|
||||
return Err(Error::ParseError("Error must have a defined-condition."));
|
||||
}
|
||||
let defined_condition = defined_condition.unwrap();
|
||||
|
||||
Ok(StanzaError {
|
||||
type_: type_,
|
||||
by: by,
|
||||
defined_condition: defined_condition,
|
||||
texts: texts,
|
||||
other: other,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn serialise(error: &StanzaError) -> Element {
|
||||
let mut root = Element::builder("error")
|
||||
.ns(ns::JABBER_CLIENT)
|
||||
.attr("type", String::from(error.type_.clone()))
|
||||
.attr("by", match error.by {
|
||||
Some(ref by) => Some(String::from(by.clone())),
|
||||
None => None,
|
||||
})
|
||||
.append(Element::builder(error.defined_condition.clone())
|
||||
.ns(ns::XMPP_STANZAS)
|
||||
.build())
|
||||
.build();
|
||||
for (lang, text) in error.texts.clone() {
|
||||
let elem = Element::builder("text")
|
||||
.ns(ns::XMPP_STANZAS)
|
||||
.attr("xml:lang", lang)
|
||||
.append(text)
|
||||
.build();
|
||||
root.append_child(elem);
|
||||
}
|
||||
if let Some(ref other) = error.other {
|
||||
root.append_child(other.clone());
|
||||
}
|
||||
root
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use minidom::Element;
|
||||
use error::Error;
|
||||
use stanza_error;
|
||||
|
||||
#[test]
|
||||
fn test_simple() {
|
||||
let elem: Element = "<error xmlns='jabber:client' type='cancel'><undefined-condition xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error>".parse().unwrap();
|
||||
let error = stanza_error::parse_stanza_error(&elem).unwrap();
|
||||
assert_eq!(error.type_, stanza_error::ErrorType::Cancel);
|
||||
assert_eq!(error.defined_condition, stanza_error::DefinedCondition::UndefinedCondition);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_type() {
|
||||
let elem: Element = "<error xmlns='jabber:client'/>".parse().unwrap();
|
||||
let error = stanza_error::parse_stanza_error(&elem).unwrap_err();
|
||||
let message = match error {
|
||||
Error::ParseError(string) => string,
|
||||
_ => panic!(),
|
||||
};
|
||||
assert_eq!(message, "Error must have a 'type' attribute.");
|
||||
|
||||
let elem: Element = "<error xmlns='jabber:client' type='coucou'/>".parse().unwrap();
|
||||
let error = stanza_error::parse_stanza_error(&elem).unwrap_err();
|
||||
let message = match error {
|
||||
Error::ParseError(string) => string,
|
||||
_ => panic!(),
|
||||
};
|
||||
assert_eq!(message, "Unknown error type.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_condition() {
|
||||
let elem: Element = "<error xmlns='jabber:client' type='cancel'/>".parse().unwrap();
|
||||
let error = stanza_error::parse_stanza_error(&elem).unwrap_err();
|
||||
let message = match error {
|
||||
Error::ParseError(string) => string,
|
||||
_ => panic!(),
|
||||
};
|
||||
assert_eq!(message, "Error must have a defined-condition.");
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue