jid: Refactor all three JID types
The main reason for this refactor was to make common operations simpler, for instance formatting a JID is now a simple clone of a String. Instead of having three different String for each of node, domain and resource, we now have a single String with offsets pointing to where the at and slash are (if they are present). This also reduces the size of a FullJid from 72 bytes to 32 bytes on 64-bit platforms (less so on 32-bit), and BareJid from 48 bytes to 32 bytes. Jid is still 40 bytes instead of 32, but that can be improved in a future version where InnerJid has been inlined into each struct.
This commit is contained in:
parent
187e156b0b
commit
cf25bd3fee
4 changed files with 420 additions and 437 deletions
|
|
@ -19,6 +19,7 @@ edition = "2018"
|
|||
gitlab = { repository = "xmpp-rs/xmpp-rs" }
|
||||
|
||||
[dependencies]
|
||||
memchr = "2.5"
|
||||
minidom = { version = "0.15", optional = true }
|
||||
serde = { version = "1.0", features = ["derive"], optional = true }
|
||||
stringprep = "0.1.2"
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ use std::error::Error as StdError;
|
|||
use std::fmt;
|
||||
|
||||
/// An error that signifies that a `Jid` cannot be parsed from a string.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum JidParseError {
|
||||
/// Happens when there is no domain, that is either the string is empty,
|
||||
/// starts with a /, or contains the @/ sequence.
|
||||
|
|
@ -27,47 +27,44 @@ pub enum JidParseError {
|
|||
/// Happens when the resource is empty, that is the string ends with a /.
|
||||
EmptyResource,
|
||||
|
||||
/// Happens when the JID is invalid according to stringprep. TODO: make errors
|
||||
/// meaningful.
|
||||
Stringprep(stringprep::Error),
|
||||
}
|
||||
/// Happens when the localpart is longer than 1023 bytes.
|
||||
NodeTooLong,
|
||||
|
||||
impl From<stringprep::Error> for JidParseError {
|
||||
fn from(e: stringprep::Error) -> JidParseError {
|
||||
JidParseError::Stringprep(e)
|
||||
}
|
||||
}
|
||||
/// Happens when the domain is longer than 1023 bytes.
|
||||
DomainTooLong,
|
||||
|
||||
impl PartialEq for JidParseError {
|
||||
fn eq(&self, other: &JidParseError) -> bool {
|
||||
use JidParseError as E;
|
||||
match (self, other) {
|
||||
(E::NoDomain, E::NoDomain) => true,
|
||||
(E::NoResource, E::NoResource) => true,
|
||||
(E::EmptyNode, E::EmptyNode) => true,
|
||||
(E::EmptyResource, E::EmptyResource) => true,
|
||||
(E::Stringprep(_), E::Stringprep(_)) => true, // TODO: fix that upstream.
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Happens when the resource is longer than 1023 bytes.
|
||||
ResourceTooLong,
|
||||
|
||||
impl Eq for JidParseError {}
|
||||
/// Happens when the localpart is invalid according to nodeprep.
|
||||
NodePrep,
|
||||
|
||||
/// Happens when the domain is invalid according to nameprep.
|
||||
NamePrep,
|
||||
|
||||
/// Happens when the resource is invalid according to resourceprep.
|
||||
ResourcePrep,
|
||||
|
||||
/// Happens when parsing a bare JID and there is a resource.
|
||||
ResourceInBareJid,
|
||||
}
|
||||
|
||||
impl StdError for JidParseError {}
|
||||
|
||||
impl fmt::Display for JidParseError {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
fmt,
|
||||
"{}",
|
||||
match self {
|
||||
JidParseError::NoDomain => "no domain found in this JID",
|
||||
JidParseError::NoResource => "no resource found in this full JID",
|
||||
JidParseError::EmptyNode => "nodepart empty despite the presence of a @",
|
||||
JidParseError::EmptyResource => "resource empty despite the presence of a /",
|
||||
JidParseError::Stringprep(_err) => "TODO",
|
||||
}
|
||||
)
|
||||
fmt.write_str(match self {
|
||||
JidParseError::NoDomain => "no domain found in this JID",
|
||||
JidParseError::NoResource => "no resource found in this full JID",
|
||||
JidParseError::EmptyNode => "nodepart empty despite the presence of a @",
|
||||
JidParseError::EmptyResource => "resource empty despite the presence of a /",
|
||||
JidParseError::NodeTooLong => "localpart longer than 1023 bytes",
|
||||
JidParseError::DomainTooLong => "domain longer than 1023 bytes",
|
||||
JidParseError::ResourceTooLong => "resource longer than 1023 bytes",
|
||||
JidParseError::NodePrep => "localpart doesn’t pass nodeprep validation",
|
||||
JidParseError::NamePrep => "domain doesn’t pass nameprep validation",
|
||||
JidParseError::ResourcePrep => "resource doesn’t pass resourceprep validation",
|
||||
JidParseError::ResourceInBareJid => "resource found while parsing a bare JID",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
201
jid/src/inner.rs
Normal file
201
jid/src/inner.rs
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
// Copyright (c) 2023 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#![deny(missing_docs)]
|
||||
|
||||
//! Provides a type for Jabber IDs.
|
||||
//!
|
||||
//! For usage, check the documentation on the `Jid` struct.
|
||||
|
||||
use crate::JidParseError as Error;
|
||||
use core::num::NonZeroU16;
|
||||
use memchr::memchr;
|
||||
use std::borrow::Cow;
|
||||
use std::str::FromStr;
|
||||
use stringprep::{nameprep, nodeprep, resourceprep};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct InnerJid {
|
||||
pub(crate) normalized: String,
|
||||
pub(crate) at: Option<NonZeroU16>,
|
||||
pub(crate) slash: Option<NonZeroU16>,
|
||||
}
|
||||
|
||||
impl InnerJid {
|
||||
pub(crate) fn new(unnormalized: &str) -> Result<InnerJid, Error> {
|
||||
let bytes = unnormalized.as_bytes();
|
||||
let mut orig_at = memchr(b'@', bytes);
|
||||
let mut orig_slash = memchr(b'/', bytes);
|
||||
if orig_at.is_some() && orig_slash.is_some() && orig_at > orig_slash {
|
||||
// This is part of the resource, not a node@domain separator.
|
||||
orig_at = None;
|
||||
}
|
||||
|
||||
let normalized = match (orig_at, orig_slash) {
|
||||
(Some(at), Some(slash)) => {
|
||||
let node = nodeprep(&unnormalized[..at]).map_err(|_| Error::NodePrep)?;
|
||||
if node.len() == 0 {
|
||||
return Err(Error::EmptyNode);
|
||||
}
|
||||
if node.len() > 1023 {
|
||||
return Err(Error::NodeTooLong);
|
||||
}
|
||||
let domain = nameprep(&unnormalized[at + 1..slash]).map_err(|_| Error::NamePrep)?;
|
||||
if domain.len() == 0 {
|
||||
return Err(Error::NoDomain);
|
||||
}
|
||||
if domain.len() > 1023 {
|
||||
return Err(Error::DomainTooLong);
|
||||
}
|
||||
let resource =
|
||||
resourceprep(&unnormalized[slash + 1..]).map_err(|_| Error::ResourcePrep)?;
|
||||
if resource.len() == 0 {
|
||||
return Err(Error::EmptyResource);
|
||||
}
|
||||
if resource.len() > 1023 {
|
||||
return Err(Error::ResourceTooLong);
|
||||
}
|
||||
match (node, domain, resource) {
|
||||
(Cow::Borrowed(_), Cow::Borrowed(_), Cow::Borrowed(_)) => {
|
||||
unnormalized.to_owned()
|
||||
}
|
||||
(node, domain, resource) => {
|
||||
orig_at = Some(node.len());
|
||||
orig_slash = Some(node.len() + domain.len() + 1);
|
||||
format!("{node}@{domain}/{resource}")
|
||||
}
|
||||
}
|
||||
}
|
||||
(Some(at), None) => {
|
||||
let node = nodeprep(&unnormalized[..at]).map_err(|_| Error::NodePrep)?;
|
||||
if node.len() == 0 {
|
||||
return Err(Error::EmptyNode);
|
||||
}
|
||||
if node.len() > 1023 {
|
||||
return Err(Error::NodeTooLong);
|
||||
}
|
||||
let domain = nameprep(&unnormalized[at + 1..]).map_err(|_| Error::NamePrep)?;
|
||||
if domain.len() == 0 {
|
||||
return Err(Error::NoDomain);
|
||||
}
|
||||
if domain.len() > 1023 {
|
||||
return Err(Error::DomainTooLong);
|
||||
}
|
||||
match (node, domain) {
|
||||
(Cow::Borrowed(_), Cow::Borrowed(_)) => unnormalized.to_owned(),
|
||||
(node, domain) => {
|
||||
orig_at = Some(node.len());
|
||||
format!("{node}@{domain}")
|
||||
}
|
||||
}
|
||||
}
|
||||
(None, Some(slash)) => {
|
||||
let domain = nameprep(&unnormalized[..slash]).map_err(|_| Error::NamePrep)?;
|
||||
if domain.len() == 0 {
|
||||
return Err(Error::NoDomain);
|
||||
}
|
||||
if domain.len() > 1023 {
|
||||
return Err(Error::DomainTooLong);
|
||||
}
|
||||
let resource =
|
||||
resourceprep(&unnormalized[slash + 1..]).map_err(|_| Error::ResourcePrep)?;
|
||||
if resource.len() == 0 {
|
||||
return Err(Error::EmptyResource);
|
||||
}
|
||||
if resource.len() > 1023 {
|
||||
return Err(Error::ResourceTooLong);
|
||||
}
|
||||
match (domain, resource) {
|
||||
(Cow::Borrowed(_), Cow::Borrowed(_)) => unnormalized.to_owned(),
|
||||
(domain, resource) => {
|
||||
orig_slash = Some(domain.len());
|
||||
format!("{domain}/{resource}")
|
||||
}
|
||||
}
|
||||
}
|
||||
(None, None) => {
|
||||
let domain = nameprep(unnormalized).map_err(|_| Error::NamePrep)?;
|
||||
if domain.len() == 0 {
|
||||
return Err(Error::NoDomain);
|
||||
}
|
||||
if domain.len() > 1023 {
|
||||
return Err(Error::DomainTooLong);
|
||||
}
|
||||
domain.into_owned()
|
||||
}
|
||||
};
|
||||
|
||||
Ok(InnerJid {
|
||||
normalized,
|
||||
at: orig_at.and_then(|x| NonZeroU16::new(x as u16)),
|
||||
slash: orig_slash.and_then(|x| NonZeroU16::new(x as u16)),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn node(&self) -> Option<&str> {
|
||||
self.at.and_then(|at| {
|
||||
let at = u16::from(at) as usize;
|
||||
Some(&self.normalized[..at])
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn domain(&self) -> &str {
|
||||
match (self.at, self.slash) {
|
||||
(Some(at), Some(slash)) => {
|
||||
let at = u16::from(at) as usize;
|
||||
let slash = u16::from(slash) as usize;
|
||||
&self.normalized[at + 1..slash]
|
||||
}
|
||||
(Some(at), None) => {
|
||||
let at = u16::from(at) as usize;
|
||||
&self.normalized[at + 1..]
|
||||
}
|
||||
(None, Some(slash)) => {
|
||||
let slash = u16::from(slash) as usize;
|
||||
&self.normalized[..slash]
|
||||
}
|
||||
(None, None) => &self.normalized,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn resource(&self) -> Option<&str> {
|
||||
self.slash.and_then(|slash| {
|
||||
let slash = u16::from(slash) as usize;
|
||||
Some(&self.normalized[slash + 1..])
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for InnerJid {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
InnerJid::new(s)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
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!(InnerJid, 16);
|
||||
}
|
||||
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
#[test]
|
||||
fn test_size() {
|
||||
assert_size!(InnerJid, 32);
|
||||
}
|
||||
}
|
||||
594
jid/src/lib.rs
594
jid/src/lib.rs
|
|
@ -14,10 +14,11 @@
|
|||
//!
|
||||
//! For usage, check the documentation on the `Jid` struct.
|
||||
|
||||
use std::convert::{Into, TryFrom};
|
||||
use core::num::NonZeroU16;
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
use stringprep::{nameprep, nodeprep, resourceprep};
|
||||
use stringprep::resourceprep;
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
|
@ -25,6 +26,9 @@ use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
|||
mod error;
|
||||
pub use crate::error::JidParseError;
|
||||
|
||||
mod inner;
|
||||
use inner::InnerJid;
|
||||
|
||||
/// An enum representing a Jabber ID. It can be either a `FullJid` or a `BareJid`.
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "serde", serde(untagged))]
|
||||
|
|
@ -40,19 +44,8 @@ pub enum Jid {
|
|||
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,
|
||||
}),
|
||||
})
|
||||
fn from_str(s: &str) -> Result<Jid, JidParseError> {
|
||||
Jid::new(s)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -93,26 +86,68 @@ impl fmt::Display for Jid {
|
|||
}
|
||||
|
||||
impl Jid {
|
||||
/// Constructs a Jabber ID from a string.
|
||||
///
|
||||
/// This is of the form `node`@`domain`/`resource`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use jid::Jid;
|
||||
/// # use jid::JidParseError;
|
||||
///
|
||||
/// # fn main() -> Result<(), JidParseError> {
|
||||
/// let jid = Jid::new("node@domain/resource")?;
|
||||
///
|
||||
/// assert_eq!(jid.node(), Some("node"));
|
||||
/// assert_eq!(jid.domain(), "domain");
|
||||
/// assert_eq!(jid.resource(), Some("resource"));
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn new(s: &str) -> Result<Jid, JidParseError> {
|
||||
let inner = InnerJid::new(s)?;
|
||||
if inner.slash.is_some() {
|
||||
Ok(Jid::Full(FullJid { inner }))
|
||||
} else {
|
||||
Ok(Jid::Bare(BareJid { inner }))
|
||||
}
|
||||
}
|
||||
|
||||
/// The node part of the Jabber ID, if it exists, else None.
|
||||
pub fn node(self) -> Option<String> {
|
||||
pub fn node(&self) -> Option<&str> {
|
||||
match self {
|
||||
Jid::Bare(BareJid { node, .. }) | Jid::Full(FullJid { node, .. }) => node,
|
||||
Jid::Bare(BareJid { inner }) | Jid::Full(FullJid { inner }) => inner.node(),
|
||||
}
|
||||
}
|
||||
|
||||
/// The domain of the Jabber ID.
|
||||
pub fn domain(self) -> String {
|
||||
pub fn domain(&self) -> &str {
|
||||
match self {
|
||||
Jid::Bare(BareJid { domain, .. }) | Jid::Full(FullJid { domain, .. }) => domain,
|
||||
Jid::Bare(BareJid { inner }) | Jid::Full(FullJid { inner }) => inner.domain(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Jid> for BareJid {
|
||||
fn from(jid: Jid) -> BareJid {
|
||||
match jid {
|
||||
Jid::Full(full) => full.into(),
|
||||
Jid::Bare(bare) => bare,
|
||||
/// The resource of the Jabber ID.
|
||||
pub fn resource(&self) -> Option<&str> {
|
||||
match self {
|
||||
Jid::Bare(BareJid { inner }) | Jid::Full(FullJid { inner }) => inner.resource(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract a bare JID from this JID, throwing away the resource.
|
||||
pub fn to_bare(&self) -> BareJid {
|
||||
match self {
|
||||
Jid::Full(jid) => jid.to_bare(),
|
||||
Jid::Bare(jid) => jid.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Transforms this JID into a bare JID, throwing away the resource.
|
||||
pub fn into_bare(self) -> BareJid {
|
||||
match self {
|
||||
Jid::Full(jid) => jid.into_bare(),
|
||||
Jid::Bare(jid) => jid,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -176,12 +211,7 @@ impl PartialEq<BareJid> for Jid {
|
|||
/// there is no case where a resource can be missing. Otherwise, use a `Jid` enum.
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
pub struct FullJid {
|
||||
/// 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,
|
||||
inner: InnerJid,
|
||||
}
|
||||
|
||||
/// A struct representing a bare Jabber ID.
|
||||
|
|
@ -195,10 +225,7 @@ pub struct FullJid {
|
|||
/// 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,
|
||||
inner: InnerJid,
|
||||
}
|
||||
|
||||
impl From<FullJid> for String {
|
||||
|
|
@ -209,15 +236,7 @@ impl From<FullJid> for String {
|
|||
|
||||
impl From<&FullJid> for String {
|
||||
fn from(jid: &FullJid) -> String {
|
||||
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
|
||||
jid.inner.normalized.clone()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -229,22 +248,7 @@ impl From<BareJid> for 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('@');
|
||||
}
|
||||
string.push_str(&jid.domain);
|
||||
string
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FullJid> for BareJid {
|
||||
fn from(full: FullJid) -> BareJid {
|
||||
BareJid {
|
||||
node: full.node,
|
||||
domain: full.domain,
|
||||
}
|
||||
jid.inner.normalized.clone()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -292,110 +296,11 @@ impl Serialize for BareJid {
|
|||
}
|
||||
}
|
||||
|
||||
enum ParserState {
|
||||
Node,
|
||||
Domain,
|
||||
Resource,
|
||||
}
|
||||
|
||||
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.is_empty() {
|
||||
return Err(JidParseError::EmptyNode);
|
||||
}
|
||||
state = ParserState::Domain;
|
||||
node = Some(buf.clone()); // TODO: performance tweaks, do not need to copy it
|
||||
buf.clear();
|
||||
}
|
||||
'/' => {
|
||||
if buf.is_empty() {
|
||||
return Err(JidParseError::NoDomain);
|
||||
}
|
||||
state = ParserState::Resource;
|
||||
domain = Some(buf.clone()); // TODO: performance tweaks
|
||||
buf.clear();
|
||||
}
|
||||
c => {
|
||||
buf.push(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
ParserState::Domain => {
|
||||
match c {
|
||||
'/' => {
|
||||
if buf.is_empty() {
|
||||
return Err(JidParseError::NoDomain);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
} else if let ParserState::Resource = state {
|
||||
return Err(JidParseError::EmptyResource);
|
||||
}
|
||||
let domain = domain.ok_or(JidParseError::NoDomain)?;
|
||||
let (node, domain, resource) = {
|
||||
let node = if let Some(node) = node {
|
||||
Some(nodeprep(&node)?.into_owned())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let domain = nameprep(&domain)?.into_owned();
|
||||
let resource = if let Some(resource) = resource {
|
||||
Some(resourceprep(&resource)?.into_owned())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
(node, domain, resource)
|
||||
};
|
||||
Ok((node, domain, 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)?,
|
||||
})
|
||||
FullJid::new(s)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -430,102 +335,60 @@ impl FullJid {
|
|||
///
|
||||
/// ```
|
||||
/// use jid::FullJid;
|
||||
/// # use jid::JidParseError;
|
||||
///
|
||||
/// let jid = FullJid::new("node", "domain", "resource");
|
||||
/// # fn main() -> Result<(), JidParseError> {
|
||||
/// let jid = FullJid::new("node@domain/resource")?;
|
||||
///
|
||||
/// assert_eq!(jid.node, Some("node".to_owned()));
|
||||
/// assert_eq!(jid.domain, "domain".to_owned());
|
||||
/// assert_eq!(jid.resource, "resource".to_owned());
|
||||
/// assert_eq!(jid.node(), Some("node"));
|
||||
/// assert_eq!(jid.domain(), "domain");
|
||||
/// assert_eq!(jid.resource(), "resource");
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn new<NS, DS, RS>(node: NS, domain: DS, resource: RS) -> FullJid
|
||||
where
|
||||
NS: Into<String>,
|
||||
DS: Into<String>,
|
||||
RS: Into<String>,
|
||||
{
|
||||
FullJid {
|
||||
node: Some(node.into()),
|
||||
domain: domain.into(),
|
||||
resource: resource.into(),
|
||||
pub fn new(s: &str) -> Result<FullJid, JidParseError> {
|
||||
let inner = InnerJid::new(s)?;
|
||||
if inner.slash.is_some() {
|
||||
Ok(FullJid { inner })
|
||||
} else {
|
||||
Err(JidParseError::NoResource)
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs a new Jabber ID from an existing one, with the node swapped out with a new one.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use jid::FullJid;
|
||||
///
|
||||
/// let jid = FullJid::new("node", "domain", "resource");
|
||||
///
|
||||
/// 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()));
|
||||
/// ```
|
||||
pub fn with_node<NS>(&self, node: NS) -> FullJid
|
||||
where
|
||||
NS: Into<String>,
|
||||
{
|
||||
FullJid {
|
||||
node: Some(node.into()),
|
||||
domain: self.domain.clone(),
|
||||
resource: self.resource.clone(),
|
||||
}
|
||||
/// The node part of the Jabber ID, if it exists, else None.
|
||||
pub fn node(&self) -> Option<&str> {
|
||||
self.inner.node()
|
||||
}
|
||||
|
||||
/// 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");
|
||||
/// ```
|
||||
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(),
|
||||
}
|
||||
/// The domain part of the Jabber ID.
|
||||
pub fn domain(&self) -> &str {
|
||||
self.inner.domain()
|
||||
}
|
||||
|
||||
/// Constructs a full Jabber ID from a bare Jabber ID, specifying a `resource`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use jid::FullJid;
|
||||
///
|
||||
/// let jid = FullJid::new("node", "domain", "resource");
|
||||
///
|
||||
/// assert_eq!(jid.resource, "resource".to_owned());
|
||||
///
|
||||
/// let new_jid = jid.with_resource("new_resource");
|
||||
///
|
||||
/// assert_eq!(new_jid.resource, "new_resource");
|
||||
/// ```
|
||||
pub fn with_resource<RS>(&self, resource: RS) -> FullJid
|
||||
where
|
||||
RS: Into<String>,
|
||||
{
|
||||
FullJid {
|
||||
node: self.node.clone(),
|
||||
domain: self.domain.clone(),
|
||||
resource: resource.into(),
|
||||
}
|
||||
/// The resource of the Jabber ID. Since this is a full JID it is always present.
|
||||
pub fn resource(&self) -> &str {
|
||||
self.inner.resource().unwrap()
|
||||
}
|
||||
|
||||
/// Extract a bare JID from this full JID, throwing away the resource.
|
||||
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 }
|
||||
}
|
||||
|
||||
/// Transforms this full JID into a bare JID, throwing away the resource.
|
||||
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 }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -533,11 +396,7 @@ 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,
|
||||
})
|
||||
BareJid::new(s)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -550,95 +409,33 @@ impl BareJid {
|
|||
///
|
||||
/// ```
|
||||
/// use jid::BareJid;
|
||||
/// # use jid::JidParseError;
|
||||
///
|
||||
/// let jid = BareJid::new("node", "domain");
|
||||
/// # fn main() -> Result<(), JidParseError> {
|
||||
/// let jid = BareJid::new("node@domain")?;
|
||||
///
|
||||
/// assert_eq!(jid.node, Some("node".to_owned()));
|
||||
/// assert_eq!(jid.domain, "domain".to_owned());
|
||||
/// assert_eq!(jid.node(), Some("node"));
|
||||
/// assert_eq!(jid.domain(), "domain");
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
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(),
|
||||
pub fn new(s: &str) -> Result<BareJid, JidParseError> {
|
||||
let inner = InnerJid::new(s)?;
|
||||
if inner.slash.is_none() {
|
||||
Ok(BareJid { inner })
|
||||
} else {
|
||||
Err(JidParseError::ResourceInBareJid)
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs a bare Jabber ID containing only a `domain`.
|
||||
///
|
||||
/// This is of the form `domain`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use jid::BareJid;
|
||||
///
|
||||
/// let jid = BareJid::domain("domain");
|
||||
///
|
||||
/// assert_eq!(jid.node, None);
|
||||
/// assert_eq!(jid.domain, "domain".to_owned());
|
||||
/// ```
|
||||
pub fn domain<DS>(domain: DS) -> BareJid
|
||||
where
|
||||
DS: Into<String>,
|
||||
{
|
||||
BareJid {
|
||||
node: None,
|
||||
domain: domain.into(),
|
||||
}
|
||||
/// The node part of the Jabber ID, if it exists, else None.
|
||||
pub fn node(&self) -> Option<&str> {
|
||||
self.inner.node()
|
||||
}
|
||||
|
||||
/// Constructs a new Jabber ID from an existing one, with the node swapped out with a new one.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use jid::BareJid;
|
||||
///
|
||||
/// let jid = BareJid::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<NS>(&self, node: NS) -> BareJid
|
||||
where
|
||||
NS: Into<String>,
|
||||
{
|
||||
BareJid {
|
||||
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;
|
||||
///
|
||||
/// let jid = BareJid::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<DS>(&self, domain: DS) -> BareJid
|
||||
where
|
||||
DS: Into<String>,
|
||||
{
|
||||
BareJid {
|
||||
node: self.node.clone(),
|
||||
domain: domain.into(),
|
||||
}
|
||||
/// The domain part of the Jabber ID.
|
||||
pub fn domain(&self) -> &str {
|
||||
self.inner.domain()
|
||||
}
|
||||
|
||||
/// Constructs a full Jabber ID from a bare Jabber ID, specifying a `resource`.
|
||||
|
|
@ -648,22 +445,23 @@ impl BareJid {
|
|||
/// ```
|
||||
/// use jid::BareJid;
|
||||
///
|
||||
/// let bare = BareJid::new("node", "domain");
|
||||
/// let full = bare.with_resource("resource");
|
||||
/// let bare = BareJid::new("node@domain").unwrap();
|
||||
/// let full = bare.with_resource("resource").unwrap();
|
||||
///
|
||||
/// assert_eq!(full.node, Some("node".to_owned()));
|
||||
/// assert_eq!(full.domain, "domain".to_owned());
|
||||
/// assert_eq!(full.resource, "resource".to_owned());
|
||||
/// assert_eq!(full.node(), Some("node"));
|
||||
/// assert_eq!(full.domain(), "domain");
|
||||
/// assert_eq!(full.resource(), "resource");
|
||||
/// ```
|
||||
pub fn with_resource<RS>(self, resource: RS) -> FullJid
|
||||
where
|
||||
RS: Into<String>,
|
||||
{
|
||||
FullJid {
|
||||
node: self.node,
|
||||
domain: self.domain,
|
||||
resource: resource.into(),
|
||||
}
|
||||
pub fn with_resource(&self, resource: &str) -> Result<FullJid, JidParseError> {
|
||||
let resource = resourceprep(resource).map_err(|_| JidParseError::ResourcePrep)?;
|
||||
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,
|
||||
};
|
||||
Ok(FullJid { inner })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -717,7 +515,6 @@ mod tests {
|
|||
use super::*;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::str::FromStr;
|
||||
|
||||
macro_rules! assert_size (
|
||||
($t:ty, $sz:expr) => (
|
||||
|
|
@ -728,32 +525,28 @@ mod tests {
|
|||
#[cfg(target_pointer_width = "32")]
|
||||
#[test]
|
||||
fn test_size() {
|
||||
assert_size!(BareJid, 24);
|
||||
assert_size!(FullJid, 36);
|
||||
assert_size!(Jid, 36);
|
||||
assert_size!(BareJid, 16);
|
||||
assert_size!(FullJid, 16);
|
||||
assert_size!(Jid, 20);
|
||||
}
|
||||
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
#[test]
|
||||
fn test_size() {
|
||||
assert_size!(BareJid, 48);
|
||||
assert_size!(FullJid, 72);
|
||||
assert_size!(Jid, 72);
|
||||
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"))
|
||||
Ok(FullJid::new("a@b.c/d").unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
FullJid::from_str("b.c/d"),
|
||||
Ok(FullJid {
|
||||
node: None,
|
||||
domain: "b.c".to_owned(),
|
||||
resource: "d".to_owned(),
|
||||
})
|
||||
Ok(FullJid::new("b.c/d").unwrap())
|
||||
);
|
||||
|
||||
assert_eq!(FullJid::from_str("a@b.c"), Err(JidParseError::NoResource));
|
||||
|
|
@ -762,23 +555,11 @@ mod tests {
|
|||
|
||||
#[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")));
|
||||
assert_eq!(
|
||||
BareJid::from_str("b.c"),
|
||||
Ok(BareJid {
|
||||
node: None,
|
||||
domain: "b.c".to_owned(),
|
||||
})
|
||||
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]
|
||||
|
|
@ -792,55 +573,55 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn full_to_bare_jid() {
|
||||
let bare: BareJid = FullJid::new("a", "b.c", "d").into();
|
||||
assert_eq!(bare, BareJid::new("a", "b.c"));
|
||||
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() {
|
||||
assert_eq!(
|
||||
BareJid::new("a", "b.c").with_resource("d"),
|
||||
FullJid::new("a", "b.c", "d")
|
||||
BareJid::new("a@b.c").unwrap().with_resource("d").unwrap(),
|
||||
FullJid::new("a@b.c/d").unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn node_from_jid() {
|
||||
assert_eq!(
|
||||
Jid::Full(FullJid::new("a", "b.c", "d")).node(),
|
||||
Some(String::from("a")),
|
||||
Jid::Full(FullJid::new("a@b.c/d").unwrap()).node(),
|
||||
Some("a"),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn domain_from_jid() {
|
||||
assert_eq!(
|
||||
Jid::Bare(BareJid::new("a", "b.c")).domain(),
|
||||
String::from("b.c"),
|
||||
);
|
||||
assert_eq!(Jid::Bare(BareJid::new("a@b.c").unwrap()).domain(), "b.c");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jid_to_full_bare() {
|
||||
let full = FullJid::new("a", "b.c", "d");
|
||||
let bare = BareJid::new("a", "b.c");
|
||||
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::Full(full.clone())), Ok(full.clone()));
|
||||
assert_eq!(
|
||||
FullJid::try_from(Jid::Bare(bare.clone())),
|
||||
Err(JidParseError::NoResource),
|
||||
);
|
||||
assert_eq!(BareJid::from(Jid::Full(full.clone())), bare.clone(),);
|
||||
assert_eq!(BareJid::from(Jid::Bare(bare.clone())), bare,);
|
||||
assert_eq!(Jid::Bare(full.clone().to_bare()), bare.clone());
|
||||
assert_eq!(Jid::Bare(bare.clone()), bare);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialise() {
|
||||
assert_eq!(
|
||||
String::from(FullJid::new("a", "b", "c")),
|
||||
String::from(FullJid::new("a@b/c").unwrap()),
|
||||
String::from("a@b/c")
|
||||
);
|
||||
assert_eq!(String::from(BareJid::new("a", "b")), String::from("a@b"));
|
||||
assert_eq!(
|
||||
String::from(BareJid::new("a@b").unwrap()),
|
||||
String::from("a@b")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -867,16 +648,19 @@ mod tests {
|
|||
#[test]
|
||||
fn display_jids() {
|
||||
assert_eq!(
|
||||
format!("{}", FullJid::new("a", "b", "c")),
|
||||
String::from("a@b/c")
|
||||
);
|
||||
assert_eq!(format!("{}", BareJid::new("a", "b")), String::from("a@b"));
|
||||
assert_eq!(
|
||||
format!("{}", Jid::Full(FullJid::new("a", "b", "c"))),
|
||||
format!("{}", FullJid::new("a@b/c").unwrap()),
|
||||
String::from("a@b/c")
|
||||
);
|
||||
assert_eq!(
|
||||
format!("{}", Jid::Bare(BareJid::new("a", "b"))),
|
||||
format!("{}", BareJid::new("a@b").unwrap()),
|
||||
String::from("a@b")
|
||||
);
|
||||
assert_eq!(
|
||||
format!("{}", Jid::Full(FullJid::new("a@b/c").unwrap())),
|
||||
String::from("a@b/c")
|
||||
);
|
||||
assert_eq!(
|
||||
format!("{}", Jid::Bare(BareJid::new("a@b").unwrap())),
|
||||
String::from("a@b")
|
||||
);
|
||||
}
|
||||
|
|
@ -886,31 +670,31 @@ mod tests {
|
|||
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")));
|
||||
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")));
|
||||
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"));
|
||||
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"));
|
||||
assert_eq!(to, BareJid::new("a@b").unwrap());
|
||||
}
|
||||
|
||||
#[cfg(feature = "minidom")]
|
||||
#[test]
|
||||
fn minidom_into_attr() {
|
||||
let full = FullJid::new("a", "b", "c");
|
||||
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(String::from(full).as_ref()));
|
||||
|
||||
let bare = BareJid::new("a", "b");
|
||||
let bare = BareJid::new("a@b").unwrap();
|
||||
let elem = minidom::Element::builder("message", "jabber:client")
|
||||
.attr("from", bare.clone())
|
||||
.build();
|
||||
|
|
@ -926,7 +710,7 @@ mod tests {
|
|||
#[test]
|
||||
fn stringprep() {
|
||||
let full = FullJid::from_str("Test@☃.coM/Test™").unwrap();
|
||||
let equiv = FullJid::new("test", "☃.com", "TestTM");
|
||||
let equiv = FullJid::new("test@☃.com/TestTM").unwrap();
|
||||
assert_eq!(full, equiv);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue