Merge branch 'split-jids' into 'master'

Maxime Buquet created

Split jids

See merge request xmpp-rs/jid-rs!14

Change summary

src/lib.rs | 597 ++++++++++++++++++++++++++++++++++++++-----------------
1 file changed, 414 insertions(+), 183 deletions(-)

Detailed changes

src/lib.rs 🔗

@@ -19,6 +19,10 @@ pub enum JidParseError {
     #[fail(display = "no domain found in this JID")]
     NoDomain,
 
+    /// Happens when there is no resource, that is string contains no /.
+    #[fail(display = "no resource found in this JID")]
+    NoResource,
+
     /// Happens when the node is empty, that is the string starts with a @.
     #[fail(display = "nodepart empty despite the presence of a @")]
     EmptyNode,
@@ -28,46 +32,129 @@ pub enum JidParseError {
     EmptyResource,
 }
 
-/// A struct representing a Jabber ID.
+/// An enum representing a Jabber ID. It can be either a `FullJid` or a `BareJid`.
+#[derive(Debug, Clone, PartialEq)]
+pub enum Jid {
+    /// Bare Jid
+    Bare(BareJid),
+
+    /// Full Jid
+    Full(FullJid),
+}
+
+impl FromStr for Jid {
+    type Err = JidParseError;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        let (ns, ds, rs): StringJid = _from_str(s)?;
+        Ok(match rs {
+            Some(rs) => Jid::Full(FullJid {
+                node: ns,
+                domain: ds,
+                resource: rs,
+            }),
+            None => Jid::Bare(BareJid {
+                node: ns,
+                domain: ds,
+            })
+        })
+    }
+}
+
+impl From<Jid> for String {
+    fn from(jid: Jid) -> String {
+        match jid {
+            Jid::Bare(bare) => String::from(bare),
+            Jid::Full(full) => String::from(full),
+        }
+    }
+}
+
+/// A struct representing a Full Jabber ID.
 ///
-/// A Jabber ID is composed of 3 components, of which 2 are optional:
+/// A Full Jabber ID is composed of 3 components, of which one is optional:
 ///
 ///  - A node/name, `node`, which is the optional part before the @.
 ///  - A domain, `domain`, which is the mandatory part after the @ but before the /.
-///  - A resource, `resource`, which is the optional part after the /.
+///  - A resource, `resource`, which is the part after the /.
 #[derive(Clone, PartialEq, Eq, Hash)]
-pub struct Jid {
+pub struct FullJid {
     /// The node part of the Jabber ID, if it exists, else None.
     pub node: Option<String>,
     /// The domain of the Jabber ID.
     pub domain: String,
-    /// The resource of the Jabber ID, if it exists, else None.
-    pub resource: Option<String>,
+    /// The resource of the Jabber ID.
+    pub resource: String,
 }
 
-impl From<Jid> for String {
-    fn from(jid: Jid) -> String {
+/// A struct representing a Bare Jabber ID.
+///
+/// A Bare Jabber ID is composed of 2 components, of which one is optional:
+///
+///  - A node/name, `node`, which is the optional part before the @.
+///  - A domain, `domain`, which is the mandatory part after the @ but before the /.
+#[derive(Clone, PartialEq, Eq, Hash)]
+pub struct BareJid {
+    /// The node part of the Jabber ID, if it exists, else None.
+    pub node: Option<String>,
+    /// The domain of the Jabber ID.
+    pub domain: String,
+}
+
+impl From<FullJid> for String {
+    fn from(jid: FullJid) -> String {
         let mut string = String::new();
         if let Some(ref node) = jid.node {
             string.push_str(node);
             string.push('@');
         }
         string.push_str(&jid.domain);
-        if let Some(ref resource) = jid.resource {
-            string.push('/');
-            string.push_str(resource);
+        string.push('/');
+        string.push_str(&jid.resource);
+        string
+    }
+}
+
+impl From<BareJid> for String {
+    fn from(jid: BareJid) -> String {
+        let mut string = String::new();
+        if let Some(ref node) = jid.node {
+            string.push_str(node);
+            string.push('@');
         }
+        string.push_str(&jid.domain);
         string
     }
 }
 
-impl fmt::Debug for Jid {
+impl Into<BareJid> for FullJid {
+    fn into(self) -> BareJid {
+        BareJid {
+            node: self.node,
+            domain: self.domain,
+        }
+    }
+}
+
+impl fmt::Debug for FullJid {
     fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
-        write!(fmt, "JID({})", self)
+        write!(fmt, "FullJID({})", self)
     }
 }
 
-impl fmt::Display for Jid {
+impl fmt::Debug for BareJid {
+    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+        write!(fmt, "BareJID({})", self)
+    }
+}
+
+impl fmt::Display for FullJid {
+    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+        fmt.write_str(String::from(self.clone()).as_ref())
+    }
+}
+
+impl fmt::Display for BareJid {
     fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
         fmt.write_str(String::from(self.clone()).as_ref())
     }
@@ -79,237 +166,262 @@ enum ParserState {
     Resource,
 }
 
-impl FromStr for Jid {
-    type Err = JidParseError;
-
-    fn from_str(s: &str) -> Result<Jid, JidParseError> {
-        // TODO: very naive, may need to do it differently
-        let iter = s.chars();
-        let mut buf = String::with_capacity(s.len());
-        let mut state = ParserState::Node;
-        let mut node = None;
-        let mut domain = None;
-        let mut resource = None;
-        for c in iter {
-            match state {
-                ParserState::Node => {
-                    match c {
-                        '@' => {
-                            if buf == "" {
-                                return Err(JidParseError::EmptyNode);
-                            }
-                            state = ParserState::Domain;
-                            node = Some(buf.clone()); // TODO: performance tweaks, do not need to copy it
-                            buf.clear();
-                        }
-                        '/' => {
-                            if buf == "" {
-                                return Err(JidParseError::NoDomain);
-                            }
-                            state = ParserState::Resource;
-                            domain = Some(buf.clone()); // TODO: performance tweaks
-                            buf.clear();
+type StringJid = (Option<String>, String, Option<String>);
+fn _from_str(s: &str) -> Result<StringJid, JidParseError> {
+    // TODO: very naive, may need to do it differently
+    let iter = s.chars();
+    let mut buf = String::with_capacity(s.len());
+    let mut state = ParserState::Node;
+    let mut node = None;
+    let mut domain = None;
+    let mut resource = None;
+    for c in iter {
+        match state {
+            ParserState::Node => {
+                match c {
+                    '@' => {
+                        if buf == "" {
+                            return Err(JidParseError::EmptyNode);
                         }
-                        c => {
-                            buf.push(c);
+                        state = ParserState::Domain;
+                        node = Some(buf.clone()); // TODO: performance tweaks, do not need to copy it
+                        buf.clear();
+                    }
+                    '/' => {
+                        if buf == "" {
+                            return Err(JidParseError::NoDomain);
                         }
+                        state = ParserState::Resource;
+                        domain = Some(buf.clone()); // TODO: performance tweaks
+                        buf.clear();
+                    }
+                    c => {
+                        buf.push(c);
                     }
                 }
-                ParserState::Domain => {
-                    match c {
-                        '/' => {
-                            if buf == "" {
-                                return Err(JidParseError::NoDomain);
-                            }
-                            state = ParserState::Resource;
-                            domain = Some(buf.clone()); // TODO: performance tweaks
-                            buf.clear();
-                        }
-                        c => {
-                            buf.push(c);
+            }
+            ParserState::Domain => {
+                match c {
+                    '/' => {
+                        if buf == "" {
+                            return Err(JidParseError::NoDomain);
                         }
+                        state = ParserState::Resource;
+                        domain = Some(buf.clone()); // TODO: performance tweaks
+                        buf.clear();
+                    }
+                    c => {
+                        buf.push(c);
                     }
                 }
-                ParserState::Resource => {
-                    buf.push(c);
-                }
+            }
+            ParserState::Resource => {
+                buf.push(c);
             }
         }
-        if !buf.is_empty() {
-            match state {
-                ParserState::Node => {
-                    domain = Some(buf);
-                }
-                ParserState::Domain => {
-                    domain = Some(buf);
-                }
-                ParserState::Resource => {
-                    resource = Some(buf);
-                }
+    }
+    if !buf.is_empty() {
+        match state {
+            ParserState::Node => {
+                domain = Some(buf);
+            }
+            ParserState::Domain => {
+                domain = Some(buf);
+            }
+            ParserState::Resource => {
+                resource = Some(buf);
             }
-        } else if let ParserState::Resource = state {
-            return Err(JidParseError::EmptyResource);
         }
-        Ok(Jid {
-            node: node,
-            domain: domain.ok_or(JidParseError::NoDomain)?,
-            resource: resource,
+    } else if let ParserState::Resource = state {
+        return Err(JidParseError::EmptyResource);
+    }
+    Ok((
+        node,
+        domain.ok_or(JidParseError::NoDomain)?,
+        resource,
+    ))
+}
+
+impl FromStr for FullJid {
+    type Err = JidParseError;
+
+    fn from_str(s: &str) -> Result<FullJid, JidParseError> {
+        let (ns, ds, rs): StringJid = _from_str(s)?;
+        Ok(FullJid {
+            node: ns,
+            domain: ds,
+            resource: rs.ok_or(JidParseError::NoResource)?,
         })
     }
 }
 
-impl Jid {
-    /// Constructs a Jabber ID containing all three components.
+impl FullJid {
+    /// Constructs a Full Jabber ID containing all three components.
     ///
     /// This is of the form `node`@`domain`/`resource`.
     ///
     /// # Examples
     ///
     /// ```
-    /// use jid::Jid;
+    /// use jid::FullJid;
     ///
-    /// let jid = Jid::full("node", "domain", "resource");
+    /// let jid = FullJid::new("node", "domain", "resource");
     ///
     /// assert_eq!(jid.node, Some("node".to_owned()));
     /// assert_eq!(jid.domain, "domain".to_owned());
-    /// assert_eq!(jid.resource, Some("resource".to_owned()));
+    /// assert_eq!(jid.resource, "resource".to_owned());
     /// ```
-    pub fn full<NS, DS, RS>(node: NS, domain: DS, resource: RS) -> Jid
+    pub fn new<NS, DS, RS>(node: NS, domain: DS, resource: RS) -> FullJid
     where
         NS: Into<String>,
         DS: Into<String>,
         RS: Into<String>,
     {
-        Jid {
+        FullJid {
             node: Some(node.into()),
             domain: domain.into(),
-            resource: Some(resource.into()),
+            resource: resource.into()
         }
     }
 
-    /// Constructs a Jabber ID containing only the `node` and `domain` components.
-    ///
-    /// This is of the form `node`@`domain`.
+    /// Constructs a new Jabber ID from an existing one, with the node swapped out with a new one.
     ///
     /// # Examples
     ///
     /// ```
-    /// use jid::Jid;
+    /// use jid::FullJid;
     ///
-    /// let jid = Jid::bare("node", "domain");
+    /// let jid = FullJid::new("node", "domain", "resource");
     ///
     /// assert_eq!(jid.node, Some("node".to_owned()));
-    /// assert_eq!(jid.domain, "domain".to_owned());
-    /// assert_eq!(jid.resource, None);
+    ///
+    /// let new_jid = jid.with_node("new_node");
+    ///
+    /// assert_eq!(new_jid.node, Some("new_node".to_owned()));
     /// ```
-    pub fn bare<NS, DS>(node: NS, domain: DS) -> Jid
+    pub fn with_node<NS>(&self, node: NS) -> FullJid
     where
         NS: Into<String>,
-        DS: Into<String>,
     {
-        Jid {
+        FullJid {
             node: Some(node.into()),
-            domain: domain.into(),
-            resource: None,
+            domain: self.domain.clone(),
+            resource: self.resource.clone(),
         }
     }
 
-    /// Returns a new Jabber ID from the current one with only node and domain.
-    ///
-    /// This is of the form `node`@`domain`.
+    /// Constructs a new Jabber ID from an existing one, with the domain swapped out with a new one.
     ///
     /// # Examples
     ///
     /// ```
-    /// use jid::Jid;
+    /// use jid::FullJid;
     ///
-    /// let jid = Jid::full("node", "domain", "resource").into_bare_jid();
+    /// let jid = FullJid::new("node", "domain", "resource");
     ///
-    /// assert_eq!(jid.node, Some("node".to_owned()));
     /// assert_eq!(jid.domain, "domain".to_owned());
-    /// assert_eq!(jid.resource, None);
+    ///
+    /// let new_jid = jid.with_domain("new_domain");
+    ///
+    /// assert_eq!(new_jid.domain, "new_domain");
     /// ```
-    pub fn into_bare_jid(self) -> Jid {
-        Jid {
-            node: self.node,
-            domain: self.domain,
-            resource: None,
+    pub fn with_domain<DS>(&self, domain: DS) -> FullJid
+    where
+        DS: Into<String>,
+    {
+        FullJid {
+            node: self.node.clone(),
+            domain: domain.into(),
+            resource: self.resource.clone(),
         }
     }
 
-    /// Constructs a Jabber ID containing only a `domain`.
-    ///
-    /// This is of the form `domain`.
+    /// Constructs a Full Jabber ID from a Bare Jabber ID, specifying a `resource`.
     ///
     /// # Examples
     ///
     /// ```
-    /// use jid::Jid;
+    /// use jid::FullJid;
     ///
-    /// let jid = Jid::domain("domain");
+    /// let jid = FullJid::new("node", "domain", "resource");
     ///
-    /// assert_eq!(jid.node, None);
-    /// assert_eq!(jid.domain, "domain".to_owned());
-    /// assert_eq!(jid.resource, None);
+    /// assert_eq!(jid.resource, "resource".to_owned());
+    ///
+    /// let new_jid = jid.with_resource("new_resource");
+    ///
+    /// assert_eq!(new_jid.resource, "new_resource");
     /// ```
-    pub fn domain<DS>(domain: DS) -> Jid
+    pub fn with_resource<RS>(&self, resource: RS) -> FullJid
     where
-        DS: Into<String>,
+        RS: Into<String>,
     {
-        Jid {
-            node: None,
-            domain: domain.into(),
-            resource: None,
+        FullJid {
+            node: self.node.clone(),
+            domain: self.domain.clone(),
+            resource: resource.into(),
         }
     }
+}
+
+impl FromStr for BareJid {
+    type Err = JidParseError;
+
+    fn from_str(s: &str) -> Result<BareJid, JidParseError> {
+        let (ns, ds, _rs): StringJid = _from_str(s)?;
+        Ok(BareJid {
+            node: ns,
+            domain: ds,
+        })
+    }
+}
 
-    /// Returns a new Jabber ID from the current one with only domain.
+impl BareJid {
+    /// Constructs a Bare Jabber ID, containing two components.
     ///
-    /// This is of the form `domain`.
+    /// This is of the form `node`@`domain`.
     ///
     /// # Examples
     ///
     /// ```
-    /// use jid::Jid;
+    /// use jid::BareJid;
     ///
-    /// let jid = Jid::full("node", "domain", "resource").into_domain_jid();
+    /// let jid = BareJid::new("node", "domain");
     ///
-    /// assert_eq!(jid.node, None);
+    /// assert_eq!(jid.node, Some("node".to_owned()));
     /// assert_eq!(jid.domain, "domain".to_owned());
-    /// assert_eq!(jid.resource, None);
     /// ```
-    pub fn into_domain_jid(self) -> Jid {
-        Jid {
-            node: None,
-            domain: self.domain,
-            resource: None,
+    pub fn new<NS, DS>(node: NS, domain: DS) -> BareJid
+    where
+        NS: Into<String>,
+        DS: Into<String>,
+    {
+        BareJid {
+            node: Some(node.into()),
+            domain: domain.into(),
         }
     }
 
-    /// Constructs a Jabber ID containing the `domain` and `resource` components.
+    /// Constructs a Bare Jabber ID containing only a `domain`.
     ///
-    /// This is of the form `domain`/`resource`.
+    /// This is of the form `domain`.
     ///
     /// # Examples
     ///
     /// ```
-    /// use jid::Jid;
+    /// use jid::BareJid;
     ///
-    /// let jid = Jid::domain_with_resource("domain", "resource");
+    /// let jid = BareJid::domain("domain");
     ///
     /// assert_eq!(jid.node, None);
     /// assert_eq!(jid.domain, "domain".to_owned());
-    /// assert_eq!(jid.resource, Some("resource".to_owned()));
     /// ```
-    pub fn domain_with_resource<DS, RS>(domain: DS, resource: RS) -> Jid
+    pub fn domain<DS>(domain: DS) -> BareJid
     where
         DS: Into<String>,
-        RS: Into<String>,
     {
-        Jid {
+        BareJid {
             node: None,
             domain: domain.into(),
-            resource: Some(resource.into()),
         }
     }
 
@@ -318,9 +430,9 @@ impl Jid {
     /// # Examples
     ///
     /// ```
-    /// use jid::Jid;
+    /// use jid::BareJid;
     ///
-    /// let jid = Jid::domain("domain");
+    /// let jid = BareJid::domain("domain");
     ///
     /// assert_eq!(jid.node, None);
     ///
@@ -328,14 +440,13 @@ impl Jid {
     ///
     /// assert_eq!(new_jid.node, Some("node".to_owned()));
     /// ```
-    pub fn with_node<S>(&self, node: S) -> Jid
+    pub fn with_node<NS>(&self, node: NS) -> BareJid
     where
-        S: Into<String>,
+        NS: Into<String>,
     {
-        Jid {
+        BareJid {
             node: Some(node.into()),
             domain: self.domain.clone(),
-            resource: self.resource.clone(),
         }
     }
 
@@ -344,9 +455,9 @@ impl Jid {
     /// # Examples
     ///
     /// ```
-    /// use jid::Jid;
+    /// use jid::BareJid;
     ///
-    /// let jid = Jid::domain("domain");
+    /// let jid = BareJid::domain("domain");
     ///
     /// assert_eq!(jid.domain, "domain");
     ///
@@ -354,40 +465,38 @@ impl Jid {
     ///
     /// assert_eq!(new_jid.domain, "new_domain");
     /// ```
-    pub fn with_domain<S>(&self, domain: S) -> Jid
+    pub fn with_domain<DS>(&self, domain: DS) -> BareJid
     where
-        S: Into<String>,
+        DS: Into<String>,
     {
-        Jid {
+        BareJid {
             node: self.node.clone(),
             domain: domain.into(),
-            resource: self.resource.clone(),
         }
     }
 
-    /// Constructs a new Jabber ID from an existing one, with the resource swapped out with a new one.
+    /// Constructs a Full Jabber ID from a Bare Jabber ID, specifying a `resource`.
     ///
     /// # Examples
     ///
     /// ```
-    /// use jid::Jid;
+    /// use jid::BareJid;
     ///
-    /// let jid = Jid::domain("domain");
+    /// let bare = BareJid::new("node", "domain");
+    /// let full = bare.with_resource("resource");
     ///
-    /// assert_eq!(jid.resource, None);
-    ///
-    /// let new_jid = jid.with_resource("resource");
-    ///
-    /// assert_eq!(new_jid.resource, Some("resource".to_owned()));
+    /// assert_eq!(full.node, Some("node".to_owned()));
+    /// assert_eq!(full.domain, "domain".to_owned());
+    /// assert_eq!(full.resource, "resource".to_owned());
     /// ```
-    pub fn with_resource<S>(&self, resource: S) -> Jid
+    pub fn with_resource<RS>(self, resource: RS) -> FullJid
     where
-        S: Into<String>,
+        RS: Into<String>,
     {
-        Jid {
-            node: self.node.clone(),
-            domain: self.domain.clone(),
-            resource: Some(resource.into()),
+        FullJid {
+            node: self.node,
+            domain: self.domain,
+            resource: resource.into(),
         }
     }
 }
@@ -409,6 +518,34 @@ impl IntoElements for Jid {
     }
 }
 
+#[cfg(feature = "minidom")]
+impl IntoAttributeValue for FullJid {
+    fn into_attribute_value(self) -> Option<String> {
+        Some(String::from(self))
+    }
+}
+
+#[cfg(feature = "minidom")]
+impl IntoElements for FullJid {
+    fn into_elements(self, emitter: &mut ElementEmitter) {
+        emitter.append_text_node(String::from(self))
+    }
+}
+
+#[cfg(feature = "minidom")]
+impl IntoAttributeValue for BareJid {
+    fn into_attribute_value(self) -> Option<String> {
+        Some(String::from(self))
+    }
+}
+
+#[cfg(feature = "minidom")]
+impl IntoElements for BareJid {
+    fn into_elements(self, emitter: &mut ElementEmitter) {
+        emitter.append_text_node(String::from(self))
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -416,31 +553,88 @@ mod tests {
     use std::str::FromStr;
 
     #[test]
-    fn can_parse_jids() {
-        assert_eq!(Jid::from_str("a@b.c/d"), Ok(Jid::full("a", "b.c", "d")));
-        assert_eq!(Jid::from_str("a@b.c"), Ok(Jid::bare("a", "b.c")));
-        assert_eq!(Jid::from_str("b.c"), Ok(Jid::domain("b.c")));
+    fn can_parse_full_jids() {
+        assert_eq!(FullJid::from_str("a@b.c/d"), Ok(FullJid::new("a", "b.c", "d")));
+        assert_eq!(
+            FullJid::from_str("b.c/d"),
+            Ok(FullJid {
+                node: None,
+                domain: "b.c".to_owned(),
+                resource: "d".to_owned(),
+            })
+        );
+
+        assert_eq!(FullJid::from_str("a@b.c"), Err(JidParseError::NoResource));
+        assert_eq!(FullJid::from_str("b.c"), Err(JidParseError::NoResource));
+    }
+
+    #[test]
+    fn can_parse_bare_jids() {
+        assert_eq!(BareJid::from_str("a@b.c/d"), Ok(BareJid::new("a", "b.c")));
+        assert_eq!(
+            BareJid::from_str("b.c/d"),
+            Ok(BareJid {
+                node: None,
+                domain: "b.c".to_owned(),
+            })
+        );
+
+        assert_eq!(BareJid::from_str("a@b.c"), Ok(BareJid::new("a", "b.c")));
         assert_eq!(
-            Jid::from_str("a/b@c"),
-            Ok(Jid::domain_with_resource("a", "b@c"))
+            BareJid::from_str("b.c"),
+            Ok(BareJid {
+                node: None,
+                domain: "b.c".to_owned(),
+            })
         );
     }
 
+    #[test]
+    fn can_parse_jids() {
+        let full = FullJid::from_str("a@b.c/d").unwrap();
+        let bare = BareJid::from_str("e@f.g").unwrap();
+
+        assert_eq!(Jid::from_str("a@b.c/d"), Ok(Jid::Full(full)));
+        assert_eq!(Jid::from_str("e@f.g"), Ok(Jid::Bare(bare)));
+    }
+
+    #[test]
+    fn full_to_bare_jid() {
+        let bare: BareJid = FullJid::new("a", "b.c", "d").into();
+        assert_eq!(bare, BareJid::new("a", "b.c"));
+    }
+
+    #[test]
+    fn bare_to_full_jid() {
+        assert_eq!(BareJid::new("a", "b.c").with_resource("d"), FullJid::new("a", "b.c", "d"));
+    }
+
     #[test]
     fn serialise() {
         assert_eq!(
-            String::from(Jid::full("a", "b", "c")),
+            String::from(FullJid::new("a", "b", "c")),
             String::from("a@b/c")
         );
+        assert_eq!(
+            String::from(BareJid::new("a", "b")),
+            String::from("a@b")
+        );
     }
 
     #[test]
     fn invalid_jids() {
-        assert_eq!(Jid::from_str(""), Err(JidParseError::NoDomain));
-        assert_eq!(Jid::from_str("/c"), Err(JidParseError::NoDomain));
-        assert_eq!(Jid::from_str("a@/c"), Err(JidParseError::NoDomain));
-        assert_eq!(Jid::from_str("@b"), Err(JidParseError::EmptyNode));
-        assert_eq!(Jid::from_str("b/"), Err(JidParseError::EmptyResource));
+        assert_eq!(BareJid::from_str(""), Err(JidParseError::NoDomain));
+        assert_eq!(BareJid::from_str("/c"), Err(JidParseError::NoDomain));
+        assert_eq!(BareJid::from_str("a@/c"), Err(JidParseError::NoDomain));
+        assert_eq!(BareJid::from_str("@b"), Err(JidParseError::EmptyNode));
+        assert_eq!(BareJid::from_str("b/"), Err(JidParseError::EmptyResource));
+
+        assert_eq!(FullJid::from_str(""), Err(JidParseError::NoDomain));
+        assert_eq!(FullJid::from_str("/c"), Err(JidParseError::NoDomain));
+        assert_eq!(FullJid::from_str("a@/c"), Err(JidParseError::NoDomain));
+        assert_eq!(FullJid::from_str("@b"), Err(JidParseError::EmptyNode));
+        assert_eq!(FullJid::from_str("b/"), Err(JidParseError::EmptyResource));
+        assert_eq!(FullJid::from_str("a@b"), Err(JidParseError::NoResource));
     }
 
     #[cfg(feature = "minidom")]
@@ -448,6 +642,43 @@ mod tests {
     fn minidom() {
         let elem: minidom::Element = "<message from='a@b/c'/>".parse().unwrap();
         let to: Jid = elem.attr("from").unwrap().parse().unwrap();
-        assert_eq!(to, Jid::full("a", "b", "c"));
+        assert_eq!(to, Jid::Full(FullJid::new("a", "b", "c")));
+
+        let elem: minidom::Element = "<message from='a@b'/>".parse().unwrap();
+        let to: Jid = elem.attr("from").unwrap().parse().unwrap();
+        assert_eq!(to, Jid::Bare(BareJid::new("a", "b")));
+
+        let elem: minidom::Element = "<message from='a@b/c'/>".parse().unwrap();
+        let to: FullJid = elem.attr("from").unwrap().parse().unwrap();
+        assert_eq!(to, FullJid::new("a", "b", "c"));
+
+        let elem: minidom::Element = "<message from='a@b'/>".parse().unwrap();
+        let to: BareJid = elem.attr("from").unwrap().parse().unwrap();
+        assert_eq!(to, BareJid::new("a", "b"));
+    }
+
+    #[cfg(feature = "minidom")]
+    #[test]
+    fn minidom_into_attr() {
+        let full = FullJid::new("a", "b", "c");
+        let elem = minidom::Element::builder("message")
+            .ns("jabber:client")
+            .attr("from", full.clone())
+            .build();
+        assert_eq!(elem.attr("from"), Some(String::from(full).as_ref()));
+
+        let bare = BareJid::new("a", "b");
+        let elem = minidom::Element::builder("message")
+            .ns("jabber:client")
+            .attr("from", bare.clone())
+            .build();
+        assert_eq!(elem.attr("from"), Some(String::from(bare.clone()).as_ref()));
+
+        let jid = Jid::Bare(bare.clone());
+        let _elem = minidom::Element::builder("message")
+            .ns("jabber:client")
+            .attr("from", jid)
+            .build();
+        assert_eq!(elem.attr("from"), Some(String::from(bare).as_ref()));
     }
 }