.gitignore 🔗
@@ -0,0 +1,2 @@
+target
+Cargo.lock
lumi created
.gitignore | 2
Cargo.toml | 6 +
src/lib.rs | 311 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 319 insertions(+)
@@ -0,0 +1,2 @@
+target
+Cargo.lock
@@ -0,0 +1,6 @@
+[package]
+name = "jid"
+version = "0.1.0"
+authors = ["lumi <lumi@pew.im>"]
+
+[dependencies]
@@ -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<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>,
+}
+
+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<Jid, JidParseError> {
+ // 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<NS, DS, RS>(node: NS, domain: DS, resource: RS) -> Jid
+ where NS: Into<String>
+ , DS: Into<String>
+ , RS: Into<String> {
+ 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<NS, DS>(node: NS, domain: DS) -> Jid
+ where NS: Into<String>
+ , DS: Into<String> {
+ 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<DS>(domain: DS) -> Jid
+ where DS: Into<String> {
+ 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<DS, RS>(domain: DS, resource: RS) -> Jid
+ where DS: Into<String>
+ , RS: Into<String> {
+ 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<S>(&self, node: S) -> Jid
+ where S: Into<String> {
+ 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<S>(&self, domain: S) -> Jid
+ where S: Into<String> {
+ 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<S>(&self, resource: S) -> Jid
+ where S: Into<String> {
+ 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")));
+ }
+}