initial commit
This commit is contained in:
commit
4725e5f174
3 changed files with 319 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
target
|
||||
Cargo.lock
|
6
Cargo.toml
Normal file
6
Cargo.toml
Normal file
|
@ -0,0 +1,6 @@
|
|||
[package]
|
||||
name = "jid"
|
||||
version = "0.1.0"
|
||||
authors = ["lumi <lumi@pew.im>"]
|
||||
|
||||
[dependencies]
|
311
src/lib.rs
Normal file
311
src/lib.rs
Normal file
|
@ -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")));
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue