muc::Muc and muc::MucUser

Maxime “pep” Buquet created

Change summary

src/muc.rs      | 346 ---------------------
src/muc/mod.rs  |  11 
src/muc/muc.rs  | 108 ++++++
src/muc/user.rs | 830 +++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 949 insertions(+), 346 deletions(-)

Detailed changes

src/muc.rs 🔗

@@ -1,346 +0,0 @@
-// Copyright (c) 2017 Maxime “pep” Buquet <pep+code@bouah.net>
-//
-// 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::convert::TryFrom;
-
-use minidom::{Element, IntoElements, ElementEmitter};
-
-use error::Error;
-
-use ns;
-
-#[derive(Debug, Clone)]
-pub struct Muc;
-
-impl TryFrom<Element> for Muc {
-    type Error = Error;
-
-    fn try_from(elem: Element) -> Result<Muc, Error> {
-        if !elem.is("x", ns::MUC) {
-            return Err(Error::ParseError("This is not an x element."));
-        }
-        for _ in elem.children() {
-            return Err(Error::ParseError("Unknown child in x element."));
-        }
-        for _ in elem.attrs() {
-            return Err(Error::ParseError("Unknown attribute in x element."));
-        }
-        Ok(Muc)
-    }
-}
-
-impl Into<Element> for Muc {
-    fn into(self) -> Element {
-        Element::builder("x")
-                .ns(ns::MUC)
-                .build()
-    }
-}
-
-#[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<Element> for Status {
-    type Error = Error;
-
-    fn try_from(elem: Element) -> Result<Status, Error> {
-        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<Element> 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<Status>,
-}
-
-impl TryFrom<Element> for MucUser {
-    type Error = Error;
-
-    fn try_from(elem: Element) -> Result<MucUser, Error> {
-        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<Element> 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_muc_simple() {
-        let elem: Element = "<x xmlns='http://jabber.org/protocol/muc'/>".parse().unwrap();
-        Muc::try_from(elem).unwrap();
-    }
-
-    #[test]
-    fn test_muc_invalid_child() {
-        let elem: Element = "<x xmlns='http://jabber.org/protocol/muc'><coucou/></x>".parse().unwrap();
-        let error = Muc::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_serialise() {
-        let elem: Element = "<x xmlns='http://jabber.org/protocol/muc'/>".parse().unwrap();
-        let muc = Muc;
-        let elem2 = muc.into();
-        assert_eq!(elem, elem2);
-    }
-
-    #[test]
-    fn test_muc_invalid_attribute() {
-        let elem: Element = "<x xmlns='http://jabber.org/protocol/muc' coucou=''/>".parse().unwrap();
-        let error = Muc::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_muc_user_simple() {
-        let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user'/>".parse().unwrap();
-        MucUser::try_from(elem).unwrap();
-    }
-
-    #[test]
-    fn test_muc_user_invalid_child() {
-        let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user'><coucou/></x>".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 = "<x xmlns='http://jabber.org/protocol/muc#user'/>".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 = "<x xmlns='http://jabber.org/protocol/muc#user' coucou=''/>".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 = "<status xmlns='http://jabber.org/protocol/muc#user' code='110'/>".parse().unwrap();
-        Status::try_from(elem).unwrap();
-    }
-
-    #[test]
-    fn test_status_invalid() {
-        let elem: Element = "<status xmlns='http://jabber.org/protocol/muc#user'/>".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 = "<status xmlns='http://jabber.org/protocol/muc#user' code='110'><foo/></status>".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 = "<status xmlns='http://jabber.org/protocol/muc#user' code='307'/>".parse().unwrap();
-        let status = Status::try_from(elem).unwrap();
-        assert_eq!(status, Status::Kicked);
-    }
-
-    #[test]
-    fn test_status_invalid_code() {
-        let elem: Element = "<status xmlns='http://jabber.org/protocol/muc#user' code='666'/>".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 = "<status xmlns='http://jabber.org/protocol/muc#user' code='coucou'/>".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");
-    }
-}

src/muc/mod.rs 🔗

@@ -0,0 +1,11 @@
+// Copyright (c) 2017 Maxime “pep” Buquet <pep+code@bouah.net>
+//
+// 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/.
+
+pub mod muc;
+pub mod user;
+
+pub use self::muc::Muc;
+pub use self::user::MucUser;

src/muc/muc.rs 🔗

@@ -0,0 +1,108 @@
+// Copyright (c) 2017 Maxime “pep” Buquet <pep+code@bouah.net>
+//
+// 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::convert::TryFrom;
+
+use minidom::Element;
+
+use error::Error;
+
+use ns;
+
+#[derive(Debug, Clone)]
+pub struct Muc {
+    pub password: Option<String>,
+}
+
+impl TryFrom<Element> for Muc {
+    type Error = Error;
+
+    fn try_from(elem: Element) -> Result<Muc, Error> {
+        if !elem.is("x", ns::MUC) {
+            return Err(Error::ParseError("This is not an x element."));
+        }
+
+        let mut password = None;
+        for child in elem.children() {
+            if child.is("password", ns::MUC) {
+                password = Some(child.text());
+            } else {
+                return Err(Error::ParseError("Unknown child in x element."));
+            }
+        }
+
+        for _ in elem.attrs() {
+            return Err(Error::ParseError("Unknown attribute in x element."));
+        }
+
+        Ok(Muc {
+            password: password,
+        })
+    }
+}
+
+impl Into<Element> for Muc {
+    fn into(self) -> Element {
+        Element::builder("x")
+                .ns(ns::MUC)
+                .append(self.password)
+                .build()
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_muc_simple() {
+        let elem: Element = "<x xmlns='http://jabber.org/protocol/muc'/>".parse().unwrap();
+        Muc::try_from(elem).unwrap();
+    }
+
+    #[test]
+    fn test_muc_invalid_child() {
+        let elem: Element = "<x xmlns='http://jabber.org/protocol/muc'><coucou/></x>".parse().unwrap();
+        let error = Muc::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_serialise() {
+        let elem: Element = "<x xmlns='http://jabber.org/protocol/muc'/>".parse().unwrap();
+        let muc = Muc {
+            password: None,
+        };
+        let elem2 = muc.into();
+        assert_eq!(elem, elem2);
+    }
+
+    #[test]
+    fn test_muc_invalid_attribute() {
+        let elem: Element = "<x xmlns='http://jabber.org/protocol/muc' coucou=''/>".parse().unwrap();
+        let error = Muc::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_muc_simple_password() {
+        let elem: Element = "
+            <x xmlns='http://jabber.org/protocol/muc'>
+                <password>coucou</password>
+            </x>"
+        .parse().unwrap();
+        let muc = Muc::try_from(elem).unwrap();
+        assert_eq!(muc.password, Some("coucou".to_owned()));
+    }
+}

src/muc/user.rs 🔗

@@ -0,0 +1,830 @@
+// Copyright (c) 2017 Maxime “pep” Buquet <pep+code@bouah.net>
+//
+// 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::convert::TryFrom;
+use std::convert::TryInto;
+use std::str::FromStr;
+
+use minidom::{Element, IntoElements, IntoAttributeValue, ElementEmitter};
+
+use jid::Jid;
+
+use error::Error;
+
+use ns;
+
+#[derive(Debug, Clone, PartialEq)]
+pub enum Status {
+    /// Status: 100
+    NonAnonymousRoom,
+
+    /// Status: 101
+    AffiliationChange,
+
+    /// Status: 102
+    ConfigShowsUnavailableMembers,
+
+    /// Status: 103
+    ConfigHidesUnavailableMembers,
+
+    /// Status: 104
+    ConfigNonPrivacyRelated,
+
+    /// Status: 110
+    SelfPresence,
+
+    /// Status: 170
+    ConfigRoomLoggingEnabled,
+
+    /// Status: 171
+    ConfigRoomLoggingDisabled,
+
+    /// Status: 172
+    ConfigRoomNonAnonymous,
+
+    /// Status: 173
+    ConfigRoomSemiAnonymous,
+
+    /// Status: 201
+    RoomHasBeenCreated,
+
+    /// Status: 210
+    AssignedNick,
+
+    /// Status: 301
+    Banned,
+
+    /// Status: 303
+    NewNick,
+
+    /// Status: 307
+    Kicked,
+
+    /// Status: 321
+    RemovalFromRoom,
+
+    /// Status: 322
+    ConfigMembersOnly,
+
+    /// Status: 332
+    ServiceShutdown,
+}
+
+impl TryFrom<Element> for Status {
+    type Error = Error;
+
+    fn try_from(elem: Element) -> Result<Status, Error> {
+        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<Element> 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());
+    }
+}
+
+/// Optional <actor/> element used in <item/> elements inside presence stanzas of type
+/// "unavailable" that are sent to users who are kick or banned, as well as within IQs for tracking
+/// purposes. -- CHANGELOG  0.17 (2002-10-23)
+/// Possesses a 'jid' and a 'nick' attribute, so that an action can be attributed either to a real
+/// JID or to a roomnick. -- CHANGELOG  1.25 (2012-02-08)
+#[derive(Debug, Clone, PartialEq)]
+pub enum Actor {
+    Jid(Jid),
+    Nick(String),
+}
+
+impl TryFrom<Element> for Actor {
+    type Error = Error;
+
+    fn try_from(elem: Element) -> Result<Actor, Error> {
+        if !elem.is("actor", ns::MUC_USER) {
+            return Err(Error::ParseError("This is not a actor element."));
+        }
+        for _ in elem.children() {
+            return Err(Error::ParseError("Unknown child in actor element."));
+        }
+        for (attr, _) in elem.attrs() {
+            if attr != "jid" && attr != "nick" {
+                return Err(Error::ParseError("Unknown attribute in actor element."));
+            }
+        }
+        let jid: Option<Jid> = get_attr!(elem, "jid", optional);
+        let nick = get_attr!(elem, "nick", optional);
+
+        match (jid, nick) {
+            (Some(_), Some(_))
+          | (None, None) =>
+                return Err(Error::ParseError("Either 'jid' or 'nick' attribute is required.")),
+            (Some(jid), _) => Ok(Actor::Jid(jid)),
+            (_, Some(nick)) => Ok(Actor::Nick(nick)),
+        }
+    }
+}
+
+impl Into<Element> for Actor {
+    fn into(self) -> Element {
+        let elem = Element::builder("actor").ns(ns::MUC_USER);
+
+        (match self {
+            Actor::Jid(jid) => elem.attr("jid", String::from(jid)),
+            Actor::Nick(nick) => elem.attr("nick", nick),
+        }).build()
+    }
+}
+
+impl IntoElements for Actor {
+    fn into_elements(self, emitter: &mut ElementEmitter) {
+        emitter.append_child(self.into());
+    }
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub struct Continue {
+    thread: Option<String>,
+}
+
+impl TryFrom<Element> for Continue {
+    type Error = Error;
+
+    fn try_from(elem: Element) -> Result<Continue, Error> {
+        if !elem.is("continue", ns::MUC_USER) {
+            return Err(Error::ParseError("This is not a continue element."));
+        }
+        for _ in elem.children() {
+            return Err(Error::ParseError("Unknown child in continue element."));
+        }
+        for (attr, _) in elem.attrs() {
+            if attr != "thread" {
+                return Err(Error::ParseError("Unknown attribute in continue element."));
+            }
+        }
+        Ok(Continue { thread: get_attr!(elem, "thread", optional) })
+    }
+}
+
+impl Into<Element> for Continue {
+    fn into(self) -> Element {
+        Element::builder("continue")
+                .ns(ns::MUC_USER)
+                .attr("thread", self.thread)
+                .build()
+    }
+}
+
+impl IntoElements for Continue {
+    fn into_elements(self, emitter: &mut ElementEmitter) {
+        emitter.append_child(self.into());
+    }
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub struct Reason(String);
+
+impl TryFrom<Element> for Reason {
+    type Error = Error;
+
+    fn try_from(elem: Element) -> Result<Reason, Error> {
+        if !elem.is("reason", ns::MUC_USER) {
+            return Err(Error::ParseError("This is not a reason element."));
+        }
+        for _ in elem.children() {
+            return Err(Error::ParseError("Unknown child in reason element."));
+        }
+        for _ in elem.attrs() {
+            return Err(Error::ParseError("Unknown attribute in reason element."));
+        }
+        Ok(Reason(elem.text()))
+    }
+}
+
+impl Into<Element> for Reason {
+    fn into(self) -> Element {
+        Element::builder("reason")
+                .ns(ns::MUC_USER)
+                .append(self.0)
+                .build()
+    }
+}
+
+impl IntoElements for Reason {
+    fn into_elements(self, emitter: &mut ElementEmitter) {
+        emitter.append_child(self.into());
+    }
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub enum Affiliation {
+    Owner,
+    Admin,
+    Member,
+    Outcast,
+    None,
+}
+
+impl FromStr for Affiliation {
+    type Err = Error;
+
+    fn from_str(s: &str) -> Result<Affiliation, Error> {
+        Ok(match s {
+            "owner" => Affiliation::Owner,
+            "admin" => Affiliation::Admin,
+            "member" => Affiliation::Member,
+            "outcast" => Affiliation::Outcast,
+            "none" => Affiliation::None,
+
+            _ => return Err(Error::ParseError("Unknown affiliation.")),
+        })
+    }
+}
+
+impl IntoAttributeValue for Affiliation {
+    fn into_attribute_value(self) -> Option<String> {
+        Some(String::from(match self {
+            Affiliation::Owner => "owner",
+            Affiliation::Admin => "admin",
+            Affiliation::Member => "member",
+            Affiliation::Outcast => "outcast",
+            Affiliation::None => "none",
+        }))
+    }
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub enum Role {
+    Moderator,
+    Participant,
+    Visitor,
+    None,
+}
+
+impl FromStr for Role {
+    type Err = Error;
+
+    fn from_str(s: &str) -> Result<Role, Error> {
+        Ok(match s {
+            "moderator" => Role::Moderator,
+            "participant" => Role::Participant,
+            "visitor" => Role::Visitor,
+            "none" => Role::None,
+
+            _ => return Err(Error::ParseError("Unknown role.")),
+        })
+    }
+}
+
+impl IntoAttributeValue for Role {
+    fn into_attribute_value(self) -> Option<String> {
+        Some(String::from(match self {
+            Role::Moderator => "moderator",
+            Role::Participant => "participant",
+            Role::Visitor => "visitor",
+            Role::None => "none",
+        }))
+    }
+}
+
+#[derive(Debug, Clone)]
+pub struct Item {
+    pub affiliation: Affiliation,
+    pub jid: Option<Jid>,
+    pub nick: Option<String>,
+    pub role: Role,
+    pub actor: Option<Actor>,
+    pub continue_: Option<Continue>,
+    pub reason: Option<Reason>,
+}
+
+impl TryFrom<Element> for Item {
+    type Error = Error;
+
+    fn try_from(elem: Element) -> Result<Item, Error> {
+        if !elem.is("item", ns::MUC_USER) {
+            return Err(Error::ParseError("This is not a item element."));
+        }
+        let mut actor: Option<Actor> = None;
+        let mut continue_: Option<Continue> = None;
+        let mut reason: Option<Reason> = None;
+        for child in elem.children() {
+            if child.is("actor", ns::MUC_USER) {
+                actor = Some(child.clone().try_into()?);
+            } else if child.is("continue", ns::MUC_USER) {
+                continue_ = Some(child.clone().try_into()?);
+            } else if child.is("reason", ns::MUC_USER) {
+                reason = Some(child.clone().try_into()?);
+            } else {
+                return Err(Error::ParseError("Unknown child in item element."));
+            }
+        }
+        for (attr, _) in elem.attrs() {
+            if attr != "affiliation" && attr != "jid" &&
+               attr != "nick" && attr != "role" {
+                return Err(Error::ParseError("Unknown attribute in item element."));
+            }
+        }
+
+        let affiliation: Affiliation = get_attr!(elem, "affiliation", required);
+        let jid: Option<Jid> = get_attr!(elem, "jid", optional);
+        let nick: Option<String> = get_attr!(elem, "nick", optional);
+        let role: Role = get_attr!(elem, "role", required);
+
+        Ok(Item{
+            affiliation: affiliation,
+            jid: jid,
+            nick: nick,
+            role: role,
+            actor: actor,
+            continue_: continue_,
+            reason: reason,
+        })
+    }
+}
+
+impl Into<Element> for Item {
+    fn into(self) -> Element {
+        Element::builder("item")
+                .ns(ns::MUC_USER)
+                .attr("affiliation", self.affiliation)
+                .attr("jid", match self.jid {
+                    Some(jid) => Some(String::from(jid)),
+                    None => None,
+                })
+                .attr("nick", self.nick)
+                .attr("role", self.role)
+                .append(self.actor)
+                .append(self.continue_)
+                .append(self.reason)
+                .build()
+    }
+}
+
+#[derive(Debug, Clone)]
+pub struct MucUser {
+    pub status: Vec<Status>,
+    pub items: Vec<Item>,
+}
+
+impl TryFrom<Element> for MucUser {
+    type Error = Error;
+
+    fn try_from(elem: Element) -> Result<MucUser, Error> {
+        if !elem.is("x", ns::MUC_USER) {
+            return Err(Error::ParseError("This is not an x element."));
+        }
+        let mut status = vec!();
+        let mut items = vec!();
+        for child in elem.children() {
+            if child.is("status", ns::MUC_USER) {
+                status.push(Status::try_from(child.clone())?);
+            } else if child.is("item", ns::MUC_USER) {
+                items.push(Item::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,
+            items,
+        })
+    }
+}
+
+impl Into<Element> 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() {
+        let elem: Element = "
+            <x xmlns='http://jabber.org/protocol/muc#user'/>
+        ".parse().unwrap();
+        MucUser::try_from(elem).unwrap();
+    }
+
+    #[test]
+    fn test_invalid_child() {
+        let elem: Element = "
+            <x xmlns='http://jabber.org/protocol/muc#user'>
+                <coucou/>
+            </x>
+        ".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_serialise() {
+        let elem: Element = "
+            <x xmlns='http://jabber.org/protocol/muc#user'/>
+        ".parse().unwrap();
+        let muc = MucUser { status: vec!(), items: vec!() };
+        let elem2 = muc.into();
+        assert_eq!(elem, elem2);
+    }
+
+    #[test]
+    fn test_invalid_attribute() {
+        let elem: Element = "
+            <x xmlns='http://jabber.org/protocol/muc#user' coucou=''/>
+        ".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 = "
+            <status xmlns='http://jabber.org/protocol/muc#user' code='110'/>
+        ".parse().unwrap();
+        Status::try_from(elem).unwrap();
+    }
+
+    #[test]
+    fn test_status_invalid() {
+        let elem: Element = "
+            <status xmlns='http://jabber.org/protocol/muc#user'/>
+        ".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 = "
+            <status xmlns='http://jabber.org/protocol/muc#user' code='110'>
+                <foo/>
+            </status>
+        ".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 = "
+            <status xmlns='http://jabber.org/protocol/muc#user' code='307'/>
+        ".parse().unwrap();
+        let status = Status::try_from(elem).unwrap();
+        assert_eq!(status, Status::Kicked);
+    }
+
+    #[test]
+    fn test_status_invalid_code() {
+        let elem: Element = "
+            <status xmlns='http://jabber.org/protocol/muc#user' code='666'/>
+        ".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 = "
+            <status xmlns='http://jabber.org/protocol/muc#user' code='coucou'/>
+        ".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");
+    }
+
+    #[test]
+    fn test_actor_required_attributes() {
+        let elem: Element = "
+            <actor xmlns='http://jabber.org/protocol/muc#user'/>
+        ".parse().unwrap();
+        let error = Actor::try_from(elem).unwrap_err();
+        let message = match error {
+            Error::ParseError(string) => string,
+            _ => panic!(),
+        };
+        assert_eq!(message, "Either 'jid' or 'nick' attribute is required.");
+    }
+
+    #[test]
+    fn test_actor_required_attributes2() {
+        let elem: Element = "
+            <actor xmlns='http://jabber.org/protocol/muc#user'
+                   jid='foo@bar/baz'
+                   nick='baz'/>
+        ".parse().unwrap();
+        let error = Actor::try_from(elem).unwrap_err();
+        let message = match error {
+            Error::ParseError(string) => string,
+            _ => panic!(),
+        };
+        assert_eq!(message, "Either 'jid' or 'nick' attribute is required.");
+    }
+
+    #[test]
+    fn test_actor_jid() {
+        let elem: Element = "
+            <actor xmlns='http://jabber.org/protocol/muc#user'
+                   jid='foo@bar/baz'/>
+        ".parse().unwrap();
+        let actor = Actor::try_from(elem).unwrap();
+        let jid = match actor {
+            Actor::Jid(jid) => jid,
+            _ => panic!(),
+        };
+        assert_eq!(jid, "foo@bar/baz".parse::<Jid>().unwrap());
+    }
+
+    #[test]
+    fn test_actor_nick() {
+        let elem: Element = "
+            <actor xmlns='http://jabber.org/protocol/muc#user' nick='baz'/>
+        ".parse().unwrap();
+        let actor = Actor::try_from(elem).unwrap();
+        let nick = match actor {
+            Actor::Nick(nick) => nick,
+            _ => panic!(),
+        };
+        assert_eq!(nick, "baz".to_owned());
+    }
+
+    #[test]
+    fn test_continue_simple() {
+        let elem: Element = "
+            <continue xmlns='http://jabber.org/protocol/muc#user'/>
+        ".parse().unwrap();
+        Continue::try_from(elem).unwrap();
+    }
+
+    #[test]
+    fn test_continue_thread_attribute() {
+        let elem: Element = "
+            <continue xmlns='http://jabber.org/protocol/muc#user'
+                      thread='foo'/>
+        ".parse().unwrap();
+        let continue_ = Continue::try_from(elem).unwrap();
+        assert_eq!(continue_, Continue { thread: Some("foo".to_owned()) });
+    }
+
+    #[test]
+    fn test_continue_invalid() {
+        let elem: Element = "
+            <continue xmlns='http://jabber.org/protocol/muc#user'>
+                <foobar/>
+            </continue>
+        ".parse().unwrap();
+        let continue_ = Continue::try_from(elem).unwrap_err();
+        let message = match continue_ {
+            Error::ParseError(string) => string,
+            _ => panic!(),
+        };
+        assert_eq!(message, "Unknown child in continue element.".to_owned());
+    }
+
+    #[test]
+    fn test_reason_simple() {
+        let elem: Element = "
+            <reason xmlns='http://jabber.org/protocol/muc#user'>Reason</reason>"
+        .parse().unwrap();
+        let reason = Reason::try_from(elem).unwrap();
+        assert_eq!(reason.0, "Reason".to_owned());
+    }
+
+    #[test]
+    fn test_reason_invalid_attribute() {
+        let elem: Element = "
+            <reason xmlns='http://jabber.org/protocol/muc#user' foo='bar'/>
+        ".parse().unwrap();
+        let error = Reason::try_from(elem).unwrap_err();
+        let message = match error {
+            Error::ParseError(string) => string,
+            _ => panic!(),
+        };
+        assert_eq!(message, "Unknown attribute in reason element.".to_owned());
+    }
+
+    #[test]
+    fn test_reason_invalid() {
+        let elem: Element = "
+            <reason xmlns='http://jabber.org/protocol/muc#user'>
+                <foobar/>
+            </reason>
+        ".parse().unwrap();
+        let error = Reason::try_from(elem).unwrap_err();
+        let message = match error {
+            Error::ParseError(string) => string,
+            _ => panic!(),
+        };
+        assert_eq!(message, "Unknown child in reason element.".to_owned());
+    }
+
+    #[test]
+    fn test_item_invalid_attr(){
+        let elem: Element = "
+            <item xmlns='http://jabber.org/protocol/muc#user'
+                  foo='bar'/>
+        ".parse().unwrap();
+        let error = Item::try_from(elem).unwrap_err();
+        let message = match error {
+            Error::ParseError(string) => string,
+            _ => panic!(),
+        };
+        assert_eq!(message, "Unknown attribute in item element.".to_owned());
+    }
+
+    #[test]
+    fn test_item_affiliation_role_attr(){
+        let elem: Element = "
+            <item xmlns='http://jabber.org/protocol/muc#user'
+                  affiliation='member'
+                  role='moderator'/>
+        ".parse().unwrap();
+        Item::try_from(elem).unwrap();
+    }
+
+    #[test]
+    fn test_item_affiliation_role_invalid_attr(){
+        let elem: Element = "
+            <item xmlns='http://jabber.org/protocol/muc#user'
+                  affiliation='member'/>
+        ".parse().unwrap();
+        let error = Item::try_from(elem).unwrap_err();
+        let message = match error {
+            Error::ParseError(string) => string,
+            _ => panic!(),
+        };
+        assert_eq!(message, "Required attribute 'role' missing.".to_owned());
+    }
+
+    #[test]
+    fn test_item_nick_attr(){
+        let elem: Element = "
+            <item xmlns='http://jabber.org/protocol/muc#user'
+                  affiliation='member'
+                  role='moderator'
+                  nick='foobar'/>
+        ".parse().unwrap();
+        let item = Item::try_from(elem).unwrap();
+        match item {
+            Item { nick, .. } => assert_eq!(nick, Some("foobar".to_owned())),
+        }
+    }
+
+    #[test]
+    fn test_item_affiliation_role_invalid_attr2(){
+        let elem: Element = "
+            <item xmlns='http://jabber.org/protocol/muc#user'
+                  role='moderator'/>
+        ".parse().unwrap();
+        let error = Item::try_from(elem).unwrap_err();
+        let message = match error {
+            Error::ParseError(string) => string,
+            _ => panic!(),
+        };
+        assert_eq!(message, "Required attribute 'affiliation' missing.".to_owned());
+    }
+
+    #[test]
+    fn test_item_role_actor_child(){
+        let elem: Element = "
+            <item xmlns='http://jabber.org/protocol/muc#user'
+                  affiliation='member'
+                  role='moderator'>
+                <actor nick='foobar'/>
+            </item>
+        ".parse().unwrap();
+        let item = Item::try_from(elem).unwrap();
+        match item {
+            Item { actor, .. } =>
+                assert_eq!(actor, Some(Actor::Nick("foobar".to_owned()))),
+        }
+    }
+
+    #[test]
+    fn test_item_role_continue_child(){
+        let elem: Element = "
+            <item xmlns='http://jabber.org/protocol/muc#user'
+                  affiliation='member'
+                  role='moderator'>
+                <continue thread='foobar'/>
+            </item>
+        ".parse().unwrap();
+        let item = Item::try_from(elem).unwrap();
+        let continue_1 = Continue { thread: Some("foobar".to_owned()) };
+        match item {
+            Item { continue_: Some(continue_2), .. } => assert_eq!(continue_2, continue_1),
+            _ => panic!(),
+        }
+    }
+
+    #[test]
+    fn test_item_role_reason_child(){
+        let elem: Element = "
+            <item xmlns='http://jabber.org/protocol/muc#user'
+                  affiliation='member'
+                  role='moderator'>
+                <reason>foobar</reason>
+            </item>
+        ".parse().unwrap();
+        let item = Item::try_from(elem).unwrap();
+        match item {
+            Item { reason, .. } =>
+                assert_eq!(reason, Some(Reason("foobar".to_owned()))),
+        }
+    }
+}