From bd19341f69a7482176dcede95af4c9e3e6763efe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Wed, 31 May 2017 02:54:47 +0100 Subject: [PATCH] Muc and parser --- src/muc.rs | 274 ++++++++++++++++++++++++++++++++++++++++++++++++++++- src/ns.rs | 2 + 2 files changed, 271 insertions(+), 5 deletions(-) diff --git a/src/muc.rs b/src/muc.rs index 12923fc0..c8248b75 100644 --- a/src/muc.rs +++ b/src/muc.rs @@ -6,7 +6,7 @@ use std::convert::TryFrom; -use minidom::Element; +use minidom::{Element, IntoElements, ElementEmitter}; use error::Error; @@ -40,18 +40,189 @@ impl Into for Muc { } } +#[derive(Debug, Clone, PartialEq)] +pub enum Status { + // 100 + NonAnonymousRoom, + + // 101 + AffiliationChange, + + // 102 + ConfigShowsUnavailableMembers, + + // 103 + ConfigHidesUnavailableMembers, + + // 104 + ConfigNonPrivacyRelated, + + // 110 + SelfPresence, + + // 170 + ConfigRoomLoggingEnabled, + + // 171 + ConfigRoomLoggingDisabled, + + // 172 + ConfigRoomNonAnonymous, + + // 173 + ConfigRoomSemiAnonymous, + + // 201 + RoomHasBeenCreated, + + // 210 + AssignedNick, + + // 301 + Banned, + + // 303 + NewNick, + + // 307 + Kicked, + + // 321 + RemovalFromRoom, + + // 322 + ConfigMembersOnly, + + // 332 + ServiceShutdown, +} + +impl TryFrom for Status { + type Error = Error; + + fn try_from(elem: Element) -> Result { + if !elem.is("status", ns::MUC_USER) { + return Err(Error::ParseError("This is not a status element.")); + } + for _ in elem.children() { + return Err(Error::ParseError("Unknown child in status element.")); + } + for (attr, _) in elem.attrs() { + if attr != "code" { + return Err(Error::ParseError("Unknown attribute in status element.")); + } + } + let code = get_attr!(elem, "code", required); + + Ok(match code { + 100 => Status::NonAnonymousRoom, + 101 => Status::AffiliationChange, + 102 => Status::ConfigShowsUnavailableMembers, + 103 => Status::ConfigHidesUnavailableMembers, + 104 => Status::ConfigNonPrivacyRelated, + 110 => Status::SelfPresence, + 170 => Status::ConfigRoomLoggingEnabled, + 171 => Status::ConfigRoomLoggingDisabled, + 172 => Status::ConfigRoomNonAnonymous, + 173 => Status::ConfigRoomSemiAnonymous, + 201 => Status::RoomHasBeenCreated, + 210 => Status::AssignedNick, + 301 => Status::Banned, + 303 => Status::NewNick, + 307 => Status::Kicked, + 321 => Status::RemovalFromRoom, + 322 => Status::ConfigMembersOnly, + 332 => Status::ServiceShutdown, + _ => return Err(Error::ParseError("Invalid status code.")), + }) + } +} + +impl Into for Status { + fn into(self) -> Element { + Element::builder("status") + .ns(ns::MUC_USER) + .attr("code", match self { + Status::NonAnonymousRoom => 100, + Status::AffiliationChange => 101, + Status::ConfigShowsUnavailableMembers => 102, + Status::ConfigHidesUnavailableMembers => 103, + Status::ConfigNonPrivacyRelated => 104, + Status::SelfPresence => 110, + Status::ConfigRoomLoggingEnabled => 170, + Status::ConfigRoomLoggingDisabled => 171, + Status::ConfigRoomNonAnonymous => 172, + Status::ConfigRoomSemiAnonymous => 173, + Status::RoomHasBeenCreated => 201, + Status::AssignedNick => 210, + Status::Banned => 301, + Status::NewNick => 303, + Status::Kicked => 307, + Status::RemovalFromRoom => 321, + Status::ConfigMembersOnly => 322, + Status::ServiceShutdown => 332, + }) + .build() + } +} + +impl IntoElements for Status { + fn into_elements(self, emitter: &mut ElementEmitter) { + emitter.append_child(self.into()); + } +} + +#[derive(Debug, Clone)] +pub struct MucUser { + status: Vec, +} + +impl TryFrom for MucUser { + type Error = Error; + + fn try_from(elem: Element) -> Result { + if !elem.is("x", ns::MUC_USER) { + return Err(Error::ParseError("This is not an x element.")); + } + let mut status = vec!(); + for child in elem.children() { + if child.is("status", ns::MUC_USER) { + status.push(Status::try_from(child.clone())?); + } else { + return Err(Error::ParseError("Unknown child in x element.")); + } + } + for _ in elem.attrs() { + return Err(Error::ParseError("Unknown attribute in x element.")); + } + Ok(MucUser { + status: status, + }) + } +} + +impl Into for MucUser { + fn into(self) -> Element { + Element::builder("x") + .ns(ns::MUC_USER) + .append(self.status) + .build() + } +} + #[cfg(test)] mod tests { use super::*; + use std::error::Error as StdError; #[test] - fn test_simple() { + fn test_muc_simple() { let elem: Element = "".parse().unwrap(); Muc::try_from(elem).unwrap(); } #[test] - fn test_invalid_child() { + fn test_muc_invalid_child() { let elem: Element = "".parse().unwrap(); let error = Muc::try_from(elem).unwrap_err(); let message = match error { @@ -62,7 +233,7 @@ mod tests { } #[test] - fn test_serialise() { + fn test_muc_serialise() { let elem: Element = "".parse().unwrap(); let muc = Muc; let elem2 = muc.into(); @@ -70,7 +241,7 @@ mod tests { } #[test] - fn test_invalid_attribute() { + fn test_muc_invalid_attribute() { let elem: Element = "".parse().unwrap(); let error = Muc::try_from(elem).unwrap_err(); let message = match error { @@ -79,4 +250,97 @@ mod tests { }; assert_eq!(message, "Unknown attribute in x element."); } + + #[test] + fn test_muc_user_simple() { + let elem: Element = "".parse().unwrap(); + MucUser::try_from(elem).unwrap(); + } + + #[test] + fn test_muc_user_invalid_child() { + let elem: Element = "".parse().unwrap(); + let error = MucUser::try_from(elem).unwrap_err(); + let message = match error { + Error::ParseError(string) => string, + _ => panic!(), + }; + assert_eq!(message, "Unknown child in x element."); + } + + #[test] + fn test_muc_user_serialise() { + let elem: Element = "".parse().unwrap(); + let muc = MucUser { status: vec!() }; + let elem2 = muc.into(); + assert_eq!(elem, elem2); + } + + #[test] + fn test_muc_user_invalid_attribute() { + let elem: Element = "".parse().unwrap(); + let error = MucUser::try_from(elem).unwrap_err(); + let message = match error { + Error::ParseError(string) => string, + _ => panic!(), + }; + assert_eq!(message, "Unknown attribute in x element."); + } + + #[test] + fn test_status_simple() { + let elem: Element = "".parse().unwrap(); + Status::try_from(elem).unwrap(); + } + + #[test] + fn test_status_invalid() { + let elem: Element = "".parse().unwrap(); + let error = Status::try_from(elem).unwrap_err(); + let message = match error { + Error::ParseError(string) => string, + _ => panic!(), + }; + assert_eq!(message, "Required attribute 'code' missing."); + } + + #[test] + fn test_status_invalid_child() { + let elem: Element = "".parse().unwrap(); + let error = Status::try_from(elem).unwrap_err(); + let message = match error { + Error::ParseError(string) => string, + _ => panic!(), + }; + assert_eq!(message, "Unknown child in status element."); + } + + #[test] + fn test_status_simple_code() { + let elem: Element = "".parse().unwrap(); + let status = Status::try_from(elem).unwrap(); + assert_eq!(status, Status::Kicked); + } + + #[test] + fn test_status_invalid_code() { + let elem: Element = "".parse().unwrap(); + let error = Status::try_from(elem).unwrap_err(); + let message = match error { + Error::ParseError(string) => string, + _ => panic!(), + }; + assert_eq!(message, "Invalid status code."); + } + + #[test] + fn test_status_invalid_code2() { + let elem: Element = "".parse().unwrap(); + let error = Status::try_from(elem).unwrap_err(); + let error = match error { + Error::ParseIntError(error) => error, + _ => panic!(), + }; + assert_eq!(error.description(), "invalid digit found in string"); + } } diff --git a/src/ns.rs b/src/ns.rs index d7706617..149025ac 100644 --- a/src/ns.rs +++ b/src/ns.rs @@ -21,6 +21,8 @@ pub const DISCO_INFO: &'static str = "http://jabber.org/protocol/disco#info"; /// XEP-0045: Multi-User Chat pub const MUC: &'static str = "http://jabber.org/protocol/muc"; +/// XEP-0045: Multi-User Chat +pub const MUC_USER: &'static str = "http://jabber.org/protocol/muc#user"; /// XEP-0047: In-Band Bytestreams pub const IBB: &'static str = "http://jabber.org/protocol/ibb";