#![deny(missing_docs)]
use core::num::NonZeroU16;
use std::fmt;
use std::str::FromStr;
#[cfg(feature = "serde")]
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
#[cfg(feature = "quote")]
use proc_macro2::TokenStream;
#[cfg(feature = "quote")]
use quote::{quote, ToTokens};
mod error;
pub use crate::error::Error;
mod inner;
use inner::InnerJid;
mod parts;
pub use parts::{DomainPart, NodePart, ResourcePart};
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(untagged))]
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Jid {
Bare(BareJid),
Full(FullJid),
}
impl FromStr for Jid {
type Err = Error;
fn from_str(s: &str) -> Result<Jid, Error> {
Jid::new(s)
}
}
impl From<BareJid> for Jid {
fn from(bare_jid: BareJid) -> Jid {
Jid::Bare(bare_jid)
}
}
impl From<FullJid> for Jid {
fn from(full_jid: FullJid) -> Jid {
Jid::Full(full_jid)
}
}
impl fmt::Display for Jid {
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match self {
Jid::Bare(bare) => bare.fmt(fmt),
Jid::Full(full) => full.fmt(fmt),
}
}
}
impl Jid {
pub fn new(s: &str) -> Result<Jid, Error> {
let inner = InnerJid::new(s)?;
if inner.slash.is_some() {
Ok(Jid::Full(FullJid { inner }))
} else {
Ok(Jid::Bare(BareJid { inner }))
}
}
pub fn into_inner(self) -> String {
match self {
Jid::Bare(BareJid { inner }) | Jid::Full(FullJid { inner }) => inner.normalized,
}
}
pub fn from_parts(
node: Option<&NodePart>,
domain: &DomainPart,
resource: Option<&ResourcePart>,
) -> Jid {
if let Some(resource) = resource {
Jid::Full(FullJid::from_parts(node, domain, resource))
} else {
Jid::Bare(BareJid::from_parts(node, domain))
}
}
pub fn node(&self) -> Option<NodePart> {
match self {
Jid::Bare(BareJid { inner }) | Jid::Full(FullJid { inner }) => {
inner.node().map(NodePart::new_unchecked)
}
}
}
pub fn node_str(&self) -> Option<&str> {
match self {
Jid::Bare(BareJid { inner }) | Jid::Full(FullJid { inner }) => inner.node(),
}
}
pub fn domain(&self) -> DomainPart {
match self {
Jid::Bare(BareJid { inner }) | Jid::Full(FullJid { inner }) => {
DomainPart::new_unchecked(inner.domain())
}
}
}
pub fn domain_str(&self) -> &str {
match self {
Jid::Bare(BareJid { inner }) | Jid::Full(FullJid { inner }) => inner.domain(),
}
}
pub fn resource(&self) -> Option<ResourcePart> {
match self {
Jid::Bare(BareJid { inner }) | Jid::Full(FullJid { inner }) => {
inner.resource().map(ResourcePart::new_unchecked)
}
}
}
pub fn resource_str(&self) -> Option<&str> {
match self {
Jid::Bare(BareJid { inner }) | Jid::Full(FullJid { inner }) => inner.resource(),
}
}
pub fn to_bare(&self) -> BareJid {
match self {
Jid::Full(jid) => jid.to_bare(),
Jid::Bare(jid) => jid.clone(),
}
}
pub fn into_bare(self) -> BareJid {
match self {
Jid::Full(jid) => jid.into_bare(),
Jid::Bare(jid) => jid,
}
}
pub fn is_full(&self) -> bool {
match self {
Self::Full(_) => true,
Self::Bare(_) => false,
}
}
pub fn is_bare(&self) -> bool {
!self.is_full()
}
}
impl TryFrom<Jid> for FullJid {
type Error = Error;
fn try_from(jid: Jid) -> Result<Self, Self::Error> {
match jid {
Jid::Full(full) => Ok(full),
Jid::Bare(_) => Err(Error::ResourceMissingInFullJid),
}
}
}
impl PartialEq<Jid> for FullJid {
fn eq(&self, other: &Jid) -> bool {
match other {
Jid::Full(full) => self == full,
Jid::Bare(_) => false,
}
}
}
impl PartialEq<Jid> for BareJid {
fn eq(&self, other: &Jid) -> bool {
match other {
Jid::Full(_) => false,
Jid::Bare(bare) => self == bare,
}
}
}
impl PartialEq<FullJid> for Jid {
fn eq(&self, other: &FullJid) -> bool {
match self {
Jid::Full(full) => full == other,
Jid::Bare(_) => false,
}
}
}
impl PartialEq<BareJid> for Jid {
fn eq(&self, other: &BareJid) -> bool {
match self {
Jid::Full(_) => false,
Jid::Bare(bare) => bare == other,
}
}
}
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct FullJid {
inner: InnerJid,
}
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct BareJid {
inner: InnerJid,
}
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(&self.inner.normalized)
}
}
impl fmt::Display for BareJid {
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
fmt.write_str(&self.inner.normalized)
}
}
#[cfg(feature = "serde")]
impl Serialize for FullJid {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.inner.normalized)
}
}
#[cfg(feature = "serde")]
impl Serialize for BareJid {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.inner.normalized)
}
}
impl FromStr for FullJid {
type Err = Error;
fn from_str(s: &str) -> Result<FullJid, Error> {
FullJid::new(s)
}
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for FullJid {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
FullJid::from_str(&s).map_err(de::Error::custom)
}
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for BareJid {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
BareJid::from_str(&s).map_err(de::Error::custom)
}
}
#[cfg(feature = "quote")]
impl ToTokens for Jid {
fn to_tokens(&self, tokens: &mut TokenStream) {
tokens.extend(match self {
Jid::Full(full) => quote! { Jid::Full(#full) },
Jid::Bare(bare) => quote! { Jid::Bare(#bare) },
});
}
}
#[cfg(feature = "quote")]
impl ToTokens for FullJid {
fn to_tokens(&self, tokens: &mut TokenStream) {
let inner = &self.inner.normalized;
let t = quote! { FullJid::new(#inner).unwrap() };
tokens.extend(t);
}
}
#[cfg(feature = "quote")]
impl ToTokens for BareJid {
fn to_tokens(&self, tokens: &mut TokenStream) {
let inner = &self.inner.normalized;
let t = quote! { BareJid::new(#inner).unwrap() };
tokens.extend(t);
}
}
impl FullJid {
pub fn new(s: &str) -> Result<FullJid, Error> {
let inner = InnerJid::new(s)?;
if inner.slash.is_some() {
Ok(FullJid { inner })
} else {
Err(Error::ResourceMissingInFullJid)
}
}
pub fn into_inner(self) -> String {
self.inner.normalized
}
pub fn from_parts(
node: Option<&NodePart>,
domain: &DomainPart,
resource: &ResourcePart,
) -> FullJid {
let (at, slash, normalized) = if let Some(node) = node {
(
NonZeroU16::new(node.0.len() as u16),
NonZeroU16::new((node.0.len() + 1 + domain.0.len()) as u16),
format!("{}@{}/{}", node.0, domain.0, resource.0),
)
} else {
(
None,
NonZeroU16::new(domain.0.len() as u16),
format!("{}/{}", domain.0, resource.0),
)
};
let inner = InnerJid {
normalized,
at,
slash,
};
FullJid { inner }
}
pub fn node(&self) -> Option<NodePart> {
self.node_str().map(NodePart::new_unchecked)
}
pub fn node_str(&self) -> Option<&str> {
self.inner.node()
}
pub fn domain(&self) -> DomainPart {
DomainPart::new_unchecked(self.domain_str())
}
pub fn domain_str(&self) -> &str {
self.inner.domain()
}
pub fn resource(&self) -> ResourcePart {
ResourcePart::new_unchecked(self.resource_str())
}
pub fn resource_str(&self) -> &str {
self.inner.resource().unwrap()
}
pub fn to_bare(&self) -> BareJid {
let slash = self.inner.slash.unwrap().get() as usize;
let normalized = self.inner.normalized[..slash].to_string();
let inner = InnerJid {
normalized,
at: self.inner.at,
slash: None,
};
BareJid { inner }
}
pub fn into_bare(mut self) -> BareJid {
let slash = self.inner.slash.unwrap().get() as usize;
self.inner.normalized.truncate(slash);
self.inner.normalized.shrink_to_fit();
self.inner.slash = None;
BareJid { inner: self.inner }
}
}
impl FromStr for BareJid {
type Err = Error;
fn from_str(s: &str) -> Result<BareJid, Error> {
BareJid::new(s)
}
}
impl BareJid {
pub fn new(s: &str) -> Result<BareJid, Error> {
let inner = InnerJid::new(s)?;
if inner.slash.is_none() {
Ok(BareJid { inner })
} else {
Err(Error::ResourceInBareJid)
}
}
pub fn into_inner(self) -> String {
self.inner.normalized
}
pub fn from_parts(node: Option<&NodePart>, domain: &DomainPart) -> BareJid {
let (at, normalized) = if let Some(node) = node {
(
NonZeroU16::new(node.0.len() as u16),
format!("{}@{}", node.0, domain.0),
)
} else {
(None, domain.0.clone())
};
let inner = InnerJid {
normalized,
at,
slash: None,
};
BareJid { inner }
}
pub fn node(&self) -> Option<NodePart> {
self.node_str().map(NodePart::new_unchecked)
}
pub fn node_str(&self) -> Option<&str> {
self.inner.node()
}
pub fn domain(&self) -> DomainPart {
DomainPart::new_unchecked(self.domain_str())
}
pub fn domain_str(&self) -> &str {
self.inner.domain()
}
pub fn with_resource(&self, resource: &ResourcePart) -> FullJid {
let slash = NonZeroU16::new(self.inner.normalized.len() as u16);
let normalized = format!("{}/{resource}", self.inner.normalized);
let inner = InnerJid {
normalized,
at: self.inner.at,
slash,
};
FullJid { inner }
}
pub fn with_resource_str(&self, resource: &str) -> Result<FullJid, Error> {
let resource = ResourcePart::new(resource)?;
Ok(self.with_resource(&resource))
}
}
#[cfg(feature = "minidom")]
use minidom::{IntoAttributeValue, Node};
#[cfg(feature = "minidom")]
impl IntoAttributeValue for Jid {
fn into_attribute_value(self) -> Option<String> {
Some(self.to_string())
}
}
#[cfg(feature = "minidom")]
impl From<Jid> for Node {
fn from(jid: Jid) -> Node {
Node::Text(jid.to_string())
}
}
#[cfg(feature = "minidom")]
impl IntoAttributeValue for FullJid {
fn into_attribute_value(self) -> Option<String> {
Some(self.to_string())
}
}
#[cfg(feature = "minidom")]
impl From<FullJid> for Node {
fn from(jid: FullJid) -> Node {
Node::Text(jid.to_string())
}
}
#[cfg(feature = "minidom")]
impl IntoAttributeValue for BareJid {
fn into_attribute_value(self) -> Option<String> {
Some(self.to_string())
}
}
#[cfg(feature = "minidom")]
impl From<BareJid> for Node {
fn from(jid: BareJid) -> Node {
Node::Text(jid.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
macro_rules! assert_size (
($t:ty, $sz:expr) => (
assert_eq!(::std::mem::size_of::<$t>(), $sz);
);
);
#[cfg(target_pointer_width = "32")]
#[test]
fn test_size() {
assert_size!(BareJid, 16);
assert_size!(FullJid, 16);
assert_size!(Jid, 20);
}
#[cfg(target_pointer_width = "64")]
#[test]
fn test_size() {
assert_size!(BareJid, 32);
assert_size!(FullJid, 32);
assert_size!(Jid, 40);
}
#[test]
fn can_parse_full_jids() {
assert_eq!(
FullJid::from_str("a@b.c/d"),
Ok(FullJid::new("a@b.c/d").unwrap())
);
assert_eq!(
FullJid::from_str("b.c/d"),
Ok(FullJid::new("b.c/d").unwrap())
);
assert_eq!(
FullJid::from_str("a@b.c"),
Err(Error::ResourceMissingInFullJid)
);
assert_eq!(
FullJid::from_str("b.c"),
Err(Error::ResourceMissingInFullJid)
);
}
#[test]
fn can_parse_bare_jids() {
assert_eq!(
BareJid::from_str("a@b.c"),
Ok(BareJid::new("a@b.c").unwrap())
);
assert_eq!(BareJid::from_str("b.c"), Ok(BareJid::new("b.c").unwrap()));
}
#[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").unwrap().to_bare();
assert_eq!(bare, BareJid::new("a@b.c").unwrap());
}
#[test]
fn bare_to_full_jid_str() {
assert_eq!(
BareJid::new("a@b.c")
.unwrap()
.with_resource_str("d")
.unwrap(),
FullJid::new("a@b.c/d").unwrap()
);
}
#[test]
fn bare_to_full_jid() {
assert_eq!(
BareJid::new("a@b.c")
.unwrap()
.with_resource(&ResourcePart::new("d").unwrap()),
FullJid::new("a@b.c/d").unwrap()
)
}
#[test]
fn node_from_jid() {
let jid = Jid::new("a@b.c/d").unwrap();
assert_eq!(jid.node_str(), Some("a"),);
assert_eq!(jid.node(), Some(NodePart::new("a").unwrap()));
}
#[test]
fn domain_from_jid() {
let jid = Jid::new("a@b.c").unwrap();
assert_eq!(jid.domain_str(), "b.c");
assert_eq!(jid.domain(), DomainPart::new("b.c").unwrap());
}
#[test]
fn resource_from_jid() {
let jid = Jid::new("a@b.c/d").unwrap();
assert_eq!(jid.resource_str(), Some("d"),);
assert_eq!(jid.resource(), Some(ResourcePart::new("d").unwrap()));
}
#[test]
fn jid_to_full_bare() {
let full = FullJid::new("a@b.c/d").unwrap();
let bare = BareJid::new("a@b.c").unwrap();
assert_eq!(FullJid::try_from(Jid::Full(full.clone())), Ok(full.clone()));
assert_eq!(
FullJid::try_from(Jid::Bare(bare.clone())),
Err(Error::ResourceMissingInFullJid),
);
assert_eq!(Jid::Bare(full.clone().to_bare()), bare.clone());
assert_eq!(Jid::Bare(bare.clone()), bare);
}
#[test]
fn serialise() {
assert_eq!(FullJid::new("a@b/c").unwrap().to_string(), "a@b/c");
assert_eq!(BareJid::new("a@b").unwrap().to_string(), "a@b");
}
#[test]
fn hash() {
let _map: HashMap<Jid, String> = HashMap::new();
}
#[test]
fn invalid_jids() {
assert_eq!(BareJid::from_str(""), Err(Error::DomainEmpty));
assert_eq!(BareJid::from_str("/c"), Err(Error::DomainEmpty));
assert_eq!(BareJid::from_str("a@/c"), Err(Error::DomainEmpty));
assert_eq!(BareJid::from_str("@b"), Err(Error::NodeEmpty));
assert_eq!(BareJid::from_str("b/"), Err(Error::ResourceEmpty));
assert_eq!(FullJid::from_str(""), Err(Error::DomainEmpty));
assert_eq!(FullJid::from_str("/c"), Err(Error::DomainEmpty));
assert_eq!(FullJid::from_str("a@/c"), Err(Error::DomainEmpty));
assert_eq!(FullJid::from_str("@b"), Err(Error::NodeEmpty));
assert_eq!(FullJid::from_str("b/"), Err(Error::ResourceEmpty));
assert_eq!(
FullJid::from_str("a@b"),
Err(Error::ResourceMissingInFullJid)
);
}
#[test]
fn display_jids() {
assert_eq!(FullJid::new("a@b/c").unwrap().to_string(), "a@b/c");
assert_eq!(BareJid::new("a@b").unwrap().to_string(), "a@b");
assert_eq!(
Jid::Full(FullJid::new("a@b/c").unwrap()).to_string(),
"a@b/c"
);
assert_eq!(Jid::Bare(BareJid::new("a@b").unwrap()).to_string(), "a@b");
}
#[cfg(feature = "minidom")]
#[test]
fn minidom() {
let elem: minidom::Element = "<message xmlns='ns1' 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").unwrap()));
let elem: minidom::Element = "<message xmlns='ns1' from='a@b'/>".parse().unwrap();
let to: Jid = elem.attr("from").unwrap().parse().unwrap();
assert_eq!(to, Jid::Bare(BareJid::new("a@b").unwrap()));
let elem: minidom::Element = "<message xmlns='ns1' from='a@b/c'/>".parse().unwrap();
let to: FullJid = elem.attr("from").unwrap().parse().unwrap();
assert_eq!(to, FullJid::new("a@b/c").unwrap());
let elem: minidom::Element = "<message xmlns='ns1' from='a@b'/>".parse().unwrap();
let to: BareJid = elem.attr("from").unwrap().parse().unwrap();
assert_eq!(to, BareJid::new("a@b").unwrap());
}
#[cfg(feature = "minidom")]
#[test]
fn minidom_into_attr() {
let full = FullJid::new("a@b/c").unwrap();
let elem = minidom::Element::builder("message", "jabber:client")
.attr("from", full.clone())
.build();
assert_eq!(elem.attr("from"), Some(full.to_string().as_str()));
let bare = BareJid::new("a@b").unwrap();
let elem = minidom::Element::builder("message", "jabber:client")
.attr("from", bare.clone())
.build();
assert_eq!(elem.attr("from"), Some(bare.to_string().as_str()));
let jid = Jid::Bare(bare.clone());
let _elem = minidom::Element::builder("message", "jabber:client")
.attr("from", jid)
.build();
assert_eq!(elem.attr("from"), Some(bare.to_string().as_str()));
}
#[test]
fn stringprep() {
let full = FullJid::from_str("Test@☃.coM/Test™").unwrap();
let equiv = FullJid::new("test@☃.com/TestTM").unwrap();
assert_eq!(full, equiv);
}
#[test]
fn invalid_stringprep() {
FullJid::from_str("a@b/🎉").unwrap_err();
}
#[test]
fn jid_from_parts() {
let node = NodePart::new("node").unwrap();
let domain = DomainPart::new("domain").unwrap();
let resource = ResourcePart::new("resource").unwrap();
let jid = Jid::from_parts(Some(&node), &domain, Some(&resource));
assert_eq!(jid, Jid::new("node@domain/resource").unwrap());
let barejid = BareJid::from_parts(Some(&node), &domain);
assert_eq!(barejid, BareJid::new("node@domain").unwrap());
let fulljid = FullJid::from_parts(Some(&node), &domain, &resource);
assert_eq!(fulljid, FullJid::new("node@domain/resource").unwrap());
}
#[test]
#[cfg(feature = "serde")]
fn jid_ser_de() {
let jid: Jid = Jid::new("node@domain").unwrap();
serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain")]);
let jid: Jid = Jid::new("node@domain/resource").unwrap();
serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain/resource")]);
let jid: BareJid = BareJid::new("node@domain").unwrap();
serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain")]);
let jid: FullJid = FullJid::new("node@domain/resource").unwrap();
serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain/resource")]);
}
}