xmpp-rs-mirror/src/lib.rs

690 lines
19 KiB
Rust
Raw Normal View History

#![deny(missing_docs)]
2017-02-27 14:35:57 +00:00
//! Provides a type for Jabber IDs.
2017-02-27 15:42:09 +00:00
//!
//! For usage, check the documentation on the `Jid` struct.
2017-02-27 14:35:57 +00:00
2018-12-18 15:40:44 +00:00
#[macro_use]
extern crate failure_derive;
2017-02-27 14:35:57 +00:00
use std::convert::Into;
2018-12-18 15:40:44 +00:00
use std::fmt;
2017-02-27 14:35:57 +00:00
use std::str::FromStr;
/// An error that signifies that a `Jid` cannot be parsed from a string.
2018-03-01 15:25:59 +00:00
#[derive(Debug, Clone, PartialEq, Eq, Fail)]
2017-02-27 14:35:57 +00:00
pub enum JidParseError {
2018-02-18 20:36:36 +00:00
/// Happens when there is no domain, that is either the string is empty,
/// starts with a /, or contains the @/ sequence.
2018-03-01 15:25:59 +00:00
#[fail(display = "no domain found in this JID")]
2017-02-27 14:35:57 +00:00
NoDomain,
2018-03-01 15:25:59 +00:00
/// Happens when there is no resource, that is string contains no /.
#[fail(display = "no resource found in this full JID")]
NoResource,
2018-02-18 20:36:36 +00:00
/// Happens when the node is empty, that is the string starts with a @.
2018-03-01 15:25:59 +00:00
#[fail(display = "nodepart empty despite the presence of a @")]
2018-02-18 20:36:36 +00:00
EmptyNode,
2018-03-01 15:25:59 +00:00
2018-02-18 20:36:36 +00:00
/// Happens when the resource is empty, that is the string ends with a /.
2018-03-01 15:25:59 +00:00
#[fail(display = "resource empty despite the presence of a /")]
2018-02-18 20:36:36 +00:00
EmptyResource,
2017-02-27 14:35:57 +00:00
}
/// 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.
2017-02-27 14:35:57 +00:00
///
/// A full Jabber ID is composed of 3 components, of which one is optional:
2017-02-27 14:35:57 +00:00
///
/// - 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 part after the /.
///
/// Unlike a `BareJid`, it always contains a resource, and should only be used when you are certain
/// there is no case where a resource can be missing. Otherwise, use a `Jid` enum.
2017-05-27 19:45:00 +00:00
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct FullJid {
2017-02-27 14:35:57 +00:00
/// 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.
pub resource: String,
2017-02-27 14:35:57 +00:00
}
/// 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 @.
///
/// Unlike a `FullJid`, it cant contain a resource, and should only be used when you are certain
/// there is no case where a resource can be set. Otherwise, use a `Jid` enum.
#[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 {
2017-04-23 13:49:00 +00:00
let mut string = String::new();
if let Some(ref node) = jid.node {
string.push_str(node);
string.push('@');
}
string.push_str(&jid.domain);
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('@');
2017-04-23 13:49:00 +00:00
}
string.push_str(&jid.domain);
2017-04-23 13:49:00 +00:00
string
}
}
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, "FullJID({})", self)
}
}
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 {
2017-02-27 14:35:57 +00:00
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
fmt.write_str(String::from(self.clone()).as_ref())
2017-02-27 14:35:57 +00:00
}
}
enum ParserState {
Node,
Domain,
2018-12-18 15:40:44 +00:00
Resource,
2017-02-27 14:35:57 +00:00
}
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);
2018-12-18 15:40:44 +00:00
}
state = ParserState::Domain;
node = Some(buf.clone()); // TODO: performance tweaks, do not need to copy it
buf.clear();
}
'/' => {
if buf == "" {
return Err(JidParseError::NoDomain);
2018-12-18 15:40:44 +00:00
}
state = ParserState::Resource;
domain = Some(buf.clone()); // TODO: performance tweaks
buf.clear();
}
c => {
buf.push(c);
2017-02-27 14:35:57 +00:00
}
2018-12-18 15:40:44 +00:00
}
}
ParserState::Domain => {
match c {
'/' => {
if buf == "" {
return Err(JidParseError::NoDomain);
2018-12-18 15:40:44 +00:00
}
state = ParserState::Resource;
domain = Some(buf.clone()); // TODO: performance tweaks
buf.clear();
}
c => {
buf.push(c);
2017-02-27 14:35:57 +00:00
}
2018-12-18 15:40:44 +00:00
}
}
ParserState::Resource => {
buf.push(c);
2017-02-27 14:35:57 +00:00
}
}
}
if !buf.is_empty() {
match state {
ParserState::Node => {
domain = Some(buf);
}
ParserState::Domain => {
domain = Some(buf);
}
ParserState::Resource => {
resource = Some(buf);
2017-02-27 14:35:57 +00:00
}
}
} 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)?,
2017-02-27 14:35:57 +00:00
})
}
}
impl FullJid {
/// Constructs a full Jabber ID containing all three components.
2017-02-27 14:35:57 +00:00
///
/// This is of the form `node`@`domain`/`resource`.
///
/// # Examples
///
/// ```
/// use jid::FullJid;
2017-02-27 14:35:57 +00:00
///
/// let jid = FullJid::new("node", "domain", "resource");
2017-02-27 14:35:57 +00:00
///
/// assert_eq!(jid.node, Some("node".to_owned()));
/// assert_eq!(jid.domain, "domain".to_owned());
/// assert_eq!(jid.resource, "resource".to_owned());
2017-02-27 14:35:57 +00:00
/// ```
pub fn new<NS, DS, RS>(node: NS, domain: DS, resource: RS) -> FullJid
2018-12-18 15:40:44 +00:00
where
NS: Into<String>,
DS: Into<String>,
RS: Into<String>,
{
FullJid {
2017-02-27 14:35:57 +00:00
node: Some(node.into()),
domain: domain.into(),
resource: resource.into(),
2017-02-27 14:35:57 +00:00
}
}
/// Constructs a new Jabber ID from an existing one, with the node swapped out with a new one.
2017-02-27 14:35:57 +00:00
///
/// # Examples
///
/// ```
/// use jid::FullJid;
2017-02-27 14:35:57 +00:00
///
/// let jid = FullJid::new("node", "domain", "resource");
2017-02-27 14:35:57 +00:00
///
/// assert_eq!(jid.node, Some("node".to_owned()));
///
/// let new_jid = jid.with_node("new_node");
///
/// assert_eq!(new_jid.node, Some("new_node".to_owned()));
2017-02-27 14:35:57 +00:00
/// ```
pub fn with_node<NS>(&self, node: NS) -> FullJid
2018-12-18 15:40:44 +00:00
where
NS: Into<String>,
{
FullJid {
2017-02-27 14:35:57 +00:00
node: Some(node.into()),
domain: self.domain.clone(),
resource: self.resource.clone(),
2017-02-27 14:35:57 +00:00
}
}
/// Constructs a new Jabber ID from an existing one, with the domain swapped out with a new one.
///
/// # Examples
///
/// ```
/// use jid::FullJid;
///
/// let jid = FullJid::new("node", "domain", "resource");
///
/// assert_eq!(jid.domain, "domain".to_owned());
///
/// let new_jid = jid.with_domain("new_domain");
///
/// assert_eq!(new_jid.domain, "new_domain");
2017-06-12 17:20:42 +00:00
/// ```
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 full Jabber ID from a bare Jabber ID, specifying a `resource`.
2017-02-27 14:35:57 +00:00
///
/// # Examples
///
/// ```
/// use jid::FullJid;
2017-02-27 14:35:57 +00:00
///
/// let jid = FullJid::new("node", "domain", "resource");
2017-02-27 14:35:57 +00:00
///
/// assert_eq!(jid.resource, "resource".to_owned());
///
/// let new_jid = jid.with_resource("new_resource");
///
/// assert_eq!(new_jid.resource, "new_resource");
2017-02-27 14:35:57 +00:00
/// ```
pub fn with_resource<RS>(&self, resource: RS) -> FullJid
2018-12-18 15:40:44 +00:00
where
RS: Into<String>,
2018-12-18 15:40:44 +00:00
{
FullJid {
node: self.node.clone(),
domain: self.domain.clone(),
resource: resource.into(),
2017-02-27 14:35:57 +00:00
}
}
}
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,
})
}
}
2017-02-27 14:35:57 +00:00
impl BareJid {
/// Constructs a bare Jabber ID, containing two components.
///
/// This is of the form `node`@`domain`.
///
/// # Examples
///
/// ```
/// use jid::BareJid;
///
/// let jid = BareJid::new("node", "domain");
///
/// assert_eq!(jid.node, Some("node".to_owned()));
/// assert_eq!(jid.domain, "domain".to_owned());
2017-06-12 17:20:42 +00:00
/// ```
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 bare Jabber ID containing only a `domain`.
2017-02-27 14:35:57 +00:00
///
/// This is of the form `domain`.
2017-02-27 14:35:57 +00:00
///
/// # Examples
///
/// ```
/// use jid::BareJid;
2017-02-27 14:35:57 +00:00
///
/// let jid = BareJid::domain("domain");
2017-02-27 14:35:57 +00:00
///
/// assert_eq!(jid.node, None);
/// assert_eq!(jid.domain, "domain".to_owned());
/// ```
pub fn domain<DS>(domain: DS) -> BareJid
2018-12-18 15:40:44 +00:00
where
DS: Into<String>,
{
BareJid {
2017-02-27 14:35:57 +00:00
node: None,
domain: domain.into(),
}
}
/// Constructs a new Jabber ID from an existing one, with the node swapped out with a new one.
///
/// # Examples
///
/// ```
/// use jid::BareJid;
2017-02-27 14:35:57 +00:00
///
/// let jid = BareJid::domain("domain");
2017-02-27 14:35:57 +00:00
///
/// 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<NS>(&self, node: NS) -> BareJid
2018-12-18 15:40:44 +00:00
where
NS: Into<String>,
2018-12-18 15:40:44 +00:00
{
BareJid {
2017-02-27 14:35:57 +00:00
node: Some(node.into()),
domain: self.domain.clone(),
}
}
/// Constructs a new Jabber ID from an existing one, with the domain swapped out with a new one.
///
/// # Examples
///
/// ```
/// use jid::BareJid;
2017-02-27 14:35:57 +00:00
///
/// let jid = BareJid::domain("domain");
2017-02-27 14:35:57 +00:00
///
/// assert_eq!(jid.domain, "domain");
///
/// let new_jid = jid.with_domain("new_domain");
///
/// assert_eq!(new_jid.domain, "new_domain");
/// ```
pub fn with_domain<DS>(&self, domain: DS) -> BareJid
2018-12-18 15:40:44 +00:00
where
DS: Into<String>,
2018-12-18 15:40:44 +00:00
{
BareJid {
2017-02-27 14:35:57 +00:00
node: self.node.clone(),
domain: domain.into(),
}
}
/// Constructs a full Jabber ID from a bare Jabber ID, specifying a `resource`.
2017-02-27 14:35:57 +00:00
///
/// # Examples
///
/// ```
/// use jid::BareJid;
2017-02-27 14:35:57 +00:00
///
/// let bare = BareJid::new("node", "domain");
/// let full = bare.with_resource("resource");
2017-02-27 14:35:57 +00:00
///
/// assert_eq!(full.node, Some("node".to_owned()));
/// assert_eq!(full.domain, "domain".to_owned());
/// assert_eq!(full.resource, "resource".to_owned());
2017-02-27 14:35:57 +00:00
/// ```
pub fn with_resource<RS>(self, resource: RS) -> FullJid
2018-12-18 15:40:44 +00:00
where
RS: Into<String>,
2018-12-18 15:40:44 +00:00
{
FullJid {
node: self.node,
domain: self.domain,
resource: resource.into(),
2017-02-27 14:35:57 +00:00
}
}
}
#[cfg(feature = "minidom")]
2018-12-18 15:40:44 +00:00
use minidom::{ElementEmitter, IntoAttributeValue, IntoElements};
#[cfg(feature = "minidom")]
impl IntoAttributeValue for Jid {
fn into_attribute_value(self) -> Option<String> {
Some(String::from(self))
}
}
#[cfg(feature = "minidom")]
impl IntoElements for Jid {
fn into_elements(self, emitter: &mut ElementEmitter) {
emitter.append_text_node(String::from(self))
}
}
#[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))
}
}
2017-02-27 14:35:57 +00:00
#[cfg(test)]
mod tests {
use super::*;
use std::str::FromStr;
#[test]
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")));
2018-12-18 15:40:44 +00:00
assert_eq!(
BareJid::from_str("b.c"),
Ok(BareJid {
node: None,
domain: "b.c".to_owned(),
})
2018-12-18 15:40:44 +00:00
);
2017-02-27 14:35:57 +00:00
}
2017-04-23 13:49:00 +00:00
#[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")
);
}
2017-04-23 13:49:00 +00:00
#[test]
fn serialise() {
2018-12-18 15:40:44 +00:00
assert_eq!(
String::from(FullJid::new("a", "b", "c")),
2018-12-18 15:40:44 +00:00
String::from("a@b/c")
);
assert_eq!(String::from(BareJid::new("a", "b")), String::from("a@b"));
2017-04-23 13:49:00 +00:00
}
2018-02-18 20:36:36 +00:00
#[test]
2018-03-01 15:26:44 +00:00
fn invalid_jids() {
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));
2018-02-18 20:36:36 +00:00
}
#[cfg(feature = "minidom")]
#[test]
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(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()));
}
2017-02-27 14:35:57 +00:00
}