commit 4725e5f17445a5f5b464b3011a8920177155aaae Author: lumi Date: Mon Feb 27 15:35:57 2017 +0100 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..a9d37c56 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..2197a798 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "jid" +version = "0.1.0" +authors = ["lumi "] + +[dependencies] diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 00000000..8214cf1e --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,311 @@ +//! Provides a type for Jabber IDs. + +use std::fmt; + +use std::convert::Into; + +use std::str::FromStr; + +/// An error that signifies that a `Jid` cannot be parsed from a string. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum JidParseError { + NoDomain, +} + +/// A struct representing a Jabber ID. +/// +/// A Jabber ID is composed of 3 components, of which 2 are 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 /. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Jid { + /// The node part of the Jabber ID, if it exists, else None. + pub node: Option, + /// The domain of the Jabber ID. + pub domain: String, + /// The resource of the Jabber ID, if it exists, else None. + pub resource: Option, +} + +impl fmt::Display for Jid { + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + // TODO: may need escaping + if let Some(ref node) = self.node { + write!(fmt, "{}@", node)?; + } + write!(fmt, "{}", self.domain)?; + if let Some(ref resource) = self.resource { + write!(fmt, "/{}", resource)?; + } + Ok(()) + } +} + +enum ParserState { + Node, + Domain, + Resource +} + +impl FromStr for Jid { + type Err = JidParseError; + + fn from_str(s: &str) -> Result { + // TODO: very naive, may need to do it differently + let iter = s.chars(); + let mut buf = String::new(); + 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 { + '@' => { + state = ParserState::Domain; + node = Some(buf.clone()); // TODO: performance tweaks, do not need to copy it + buf.clear(); + }, + '/' => { + state = ParserState::Resource; + domain = Some(buf.clone()); // TODO: performance tweaks + buf.clear(); + }, + c => { + buf.push(c); + }, + } + }, + ParserState::Domain => { + match c { + '/' => { + state = ParserState::Resource; + domain = Some(buf.clone()); // TODO: performance tweaks + buf.clear(); + }, + c => { + 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); + }, + } + } + Ok(Jid { + node: node, + domain: domain.ok_or(JidParseError::NoDomain)?, + resource: resource, + }) + } +} + +impl Jid { + /// Constructs a Jabber ID containing all three components. + /// + /// This is of the form `node`@`domain`/`resource`. + /// + /// # Examples + /// + /// ``` + /// use jid::Jid; + /// + /// let jid = Jid::full("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())); + /// ``` + pub fn full(node: NS, domain: DS, resource: RS) -> Jid + where NS: Into + , DS: Into + , RS: Into { + Jid { + node: Some(node.into()), + domain: domain.into(), + resource: Some(resource.into()), + } + } + + /// Constructs a Jabber ID containing only the `node` and `domain` components. + /// + /// This is of the form `node`@`domain`. + /// + /// # Examples + /// + /// ``` + /// use jid::Jid; + /// + /// let jid = Jid::bare("node", "domain"); + /// + /// assert_eq!(jid.node, Some("node".to_owned())); + /// assert_eq!(jid.domain, "domain".to_owned()); + /// assert_eq!(jid.resource, None); + /// ``` + pub fn bare(node: NS, domain: DS) -> Jid + where NS: Into + , DS: Into { + Jid { + node: Some(node.into()), + domain: domain.into(), + resource: None, + } + } + + /// Constructs a Jabber ID containing only a `domain`. + /// + /// This is of the form `domain`. + /// + /// # Examples + /// + /// ``` + /// use jid::Jid; + /// + /// let jid = Jid::domain("domain"); + /// + /// assert_eq!(jid.node, None); + /// assert_eq!(jid.domain, "domain".to_owned()); + /// assert_eq!(jid.resource, None); + /// ``` + pub fn domain(domain: DS) -> Jid + where DS: Into { + Jid { + node: None, + domain: domain.into(), + resource: None, + } + } + + /// Constructs a Jabber ID containing the `domain` and `resource` components. + /// + /// This is of the form `domain`/`resource`. + /// + /// # Examples + /// + /// ``` + /// use jid::Jid; + /// + /// let jid = Jid::domain_with_resource("domain", "resource"); + /// + /// 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(domain: DS, resource: RS) -> Jid + where DS: Into + , RS: Into { + Jid { + node: None, + domain: domain.into(), + resource: Some(resource.into()), + } + } + + /// Constructs a new Jabber ID from an existing one, with the node swapped out with a new one. + /// + /// # Examples + /// + /// ``` + /// use jid::Jid; + /// + /// let jid = Jid::domain("domain"); + /// + /// assert_eq!(jid.node, None); + /// + /// let new_jid = jid.with_node("node"); + /// + /// assert_eq!(new_jid.node, Some("node".to_owned())); + /// ``` + pub fn with_node(&self, node: S) -> Jid + where S: Into { + Jid { + node: Some(node.into()), + domain: self.domain.clone(), + resource: self.resource.clone(), + } + } + + /// Constructs a new Jabber ID from an existing one, with the domain swapped out with a new one. + /// + /// # Examples + /// + /// ``` + /// use jid::Jid; + /// + /// let jid = Jid::domain("domain"); + /// + /// assert_eq!(jid.domain, "domain"); + /// + /// let new_jid = jid.with_domain("new_domain"); + /// + /// assert_eq!(new_jid.domain, "new_domain"); + /// ``` + pub fn with_domain(&self, domain: S) -> Jid + where S: Into { + Jid { + 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. + /// + /// # Examples + /// + /// ``` + /// use jid::Jid; + /// + /// let jid = Jid::domain("domain"); + /// + /// assert_eq!(jid.resource, None); + /// + /// let new_jid = jid.with_resource("resource"); + /// + /// assert_eq!(new_jid.resource, Some("resource".to_owned())); + /// ``` + pub fn with_resource(&self, resource: S) -> Jid + where S: Into { + Jid { + node: self.node.clone(), + domain: self.domain.clone(), + resource: Some(resource.into()), + } + } + +} + +#[cfg(test)] +mod tests { + use super::*; + + 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"))); + + assert_eq!(Jid::from_str(""), Err(JidParseError::NoDomain)); + + assert_eq!(Jid::from_str("a/b@c"), Ok(Jid::domain_with_resource("a", "b@c"))); + } +}