Compare commits

...

7 Commits

Author SHA1 Message Date
Werner Kroneman 65c91439f8 Used Element::append_text instead of append_text_node in TreeBuilder::process_text to prevent weird splitting of text when there is no element in between. 2024-03-10 10:46:30 +00:00
Werner Kroneman f54776ca0a Added Element::append_text 2024-03-10 10:46:30 +00:00
Jonas Schäfer 2c701038cd Implement `as_str` on all Jid types
This is useful for printing with quotes without copying any data.
2024-03-10 10:51:53 +01:00
Jonas Schäfer 9608b59f60 Add more conversion/construction paths between Jid and part types 2024-03-10 10:51:53 +01:00
Jonas Schäfer 7fce1146e0 Offer {Resource,Node,Domain}Ref on Jid API
This provides a non-copying API, which is generally favourable. The
other accessors were removed, because the intent was to provide this
"most sensible" API via the "default" (i.e. shortest, most concisely
named) functions.
2024-03-10 10:51:01 +01:00
Jonas Schäfer 45f1567ff2 Use debug_tuple instead of string formatting
This provides standard-library-like output.
2024-03-09 09:00:22 +01:00
Jonas Schäfer 8238e81c66 Unify declaration of {Node,Domain,Resource}Part
This introduces a str-like type for each of these, which will allow
returning a ref instead of the copied data from various methods in
{Full,Bare}Jid.

The use of a macro ensures that all types are declared consistently.
2024-03-09 09:00:22 +01:00
12 changed files with 428 additions and 190 deletions

View File

@ -1,7 +1,26 @@
Version xxx, release xxx:
* Breaking:
- With the addition of `str`-like types for `DomainPart`, `NodePart` and
`ResourcePart`, the functions on `Jid`, `BareJid` and `FullJid` which
return the respective types have been changed to return references
instead. Use `ToOwned::to_owned` to obtain a copy or `.as_str()` to
obtain a plain `str` reference.
- The `node_str`, `domain_str` and `resource_str` functions returning str
references have been removed from the JID types. Use `.as_str()` or
`.map(|x| x.as_str())` on the corresponding `node`/`domain`/`resource`
functions instead.
* Additions:
- Add optional quote support. Implement quote::ToTokens for Jid, FullJid
and BareJid.
- `str`-like reference types have been added for `DomainPart`, `NodePart`
and `ResourcePart`, called `DomainRef`, `NodeRef` and `ResourceRef`
respectively.
- Convenience methods to combine `DomainPart` and `NodePart` to a
`BareJid` have been added, including
`impl From<DomainPart> for BareJid` and
`impl From<DomainPart> for Jid`, both of which are (unlike
`::from_parts`) copy-free.
- `as_str` methods have been added on all Jid types.
Version 0.10.0, release 2023-08-17:
* Breaking

View File

@ -17,6 +17,8 @@ use std::borrow::Cow;
use std::str::FromStr;
use stringprep::{nameprep, nodeprep, resourceprep};
use crate::parts::{DomainRef, NodeRef, ResourceRef};
fn length_check(len: usize, error_empty: Error, error_too_long: Error) -> Result<(), Error> {
if len == 0 {
Err(error_empty)
@ -107,38 +109,43 @@ impl InnerJid {
})
}
pub(crate) fn node(&self) -> Option<&str> {
pub(crate) fn node(&self) -> Option<&NodeRef> {
self.at.map(|at| {
let at = u16::from(at) as usize;
&self.normalized[..at]
NodeRef::from_str_unchecked(&self.normalized[..at])
})
}
pub(crate) fn domain(&self) -> &str {
pub(crate) fn domain(&self) -> &DomainRef {
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]
DomainRef::from_str_unchecked(&self.normalized[at + 1..slash])
}
(Some(at), None) => {
let at = u16::from(at) as usize;
&self.normalized[at + 1..]
DomainRef::from_str_unchecked(&self.normalized[at + 1..])
}
(None, Some(slash)) => {
let slash = u16::from(slash) as usize;
&self.normalized[..slash]
DomainRef::from_str_unchecked(&self.normalized[..slash])
}
(None, None) => &self.normalized,
(None, None) => DomainRef::from_str_unchecked(&self.normalized),
}
}
pub(crate) fn resource(&self) -> Option<&str> {
pub(crate) fn resource(&self) -> Option<&ResourceRef> {
self.slash.map(|slash| {
let slash = u16::from(slash) as usize;
&self.normalized[slash + 1..]
ResourceRef::from_str_unchecked(&self.normalized[slash + 1..])
})
}
#[inline(always)]
pub(crate) fn as_str(&self) -> &str {
self.normalized.as_str()
}
}
impl FromStr for InnerJid {

View File

@ -49,7 +49,7 @@ mod inner;
use inner::InnerJid;
mod parts;
pub use parts::{DomainPart, NodePart, ResourcePart};
pub use parts::{DomainPart, DomainRef, NodePart, NodeRef, ResourcePart, ResourceRef};
/// An enum representing a Jabber ID. It can be either a `FullJid` or a `BareJid`.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@ -106,9 +106,9 @@ impl Jid {
/// # fn main() -> Result<(), Error> {
/// let jid = Jid::new("node@domain/resource")?;
///
/// assert_eq!(jid.node_str(), Some("node"));
/// assert_eq!(jid.domain_str(), "domain");
/// assert_eq!(jid.resource_str(), Some("resource"));
/// assert_eq!(jid.node().map(|x| x.as_str()), Some("node"));
/// assert_eq!(jid.domain().as_str(), "domain");
/// assert_eq!(jid.resource().map(|x| x.as_str()), Some("resource"));
/// # Ok(())
/// # }
/// ```
@ -130,11 +130,14 @@ impl Jid {
/// Build a [`Jid`] from typed parts. This method cannot fail because it uses parts that have
/// already been parsed and stringprepped into [`NodePart`], [`DomainPart`], and [`ResourcePart`].
/// This method allocates and does not consume the typed parts.
///
/// This method allocates and does not consume the typed parts. To avoid
/// allocation if both `node` and `resource` are known to be `None` and
/// `domain` is owned, you can use `domain.into()`.
pub fn from_parts(
node: Option<&NodePart>,
domain: &DomainPart,
resource: Option<&ResourcePart>,
node: Option<&NodeRef>,
domain: &DomainRef,
resource: Option<&ResourceRef>,
) -> Jid {
if let Some(resource) = resource {
Jid::Full(FullJid::from_parts(node, domain, resource))
@ -143,51 +146,23 @@ impl Jid {
}
}
/// The optional node part of the JID, as a [`NodePart`]
pub fn node(&self) -> Option<NodePart> {
match self {
Jid::Bare(BareJid { inner }) | Jid::Full(FullJid { inner }) => {
inner.node().map(NodePart::new_unchecked)
}
}
}
/// The optional node part of the JID, as a stringy reference
pub fn node_str(&self) -> Option<&str> {
/// The optional node part of the JID as reference.
pub fn node(&self) -> Option<&NodeRef> {
match self {
Jid::Bare(BareJid { inner }) | Jid::Full(FullJid { inner }) => inner.node(),
}
}
/// The domain part of the JID, as a [`DomainPart`]
pub fn domain(&self) -> DomainPart {
match self {
Jid::Bare(BareJid { inner }) | Jid::Full(FullJid { inner }) => {
DomainPart::new_unchecked(inner.domain())
}
}
}
/// The domain part of the JID, as a stringy reference
pub fn domain_str(&self) -> &str {
/// The domain part of the JID as reference
pub fn domain(&self) -> &DomainRef {
match self {
Jid::Bare(BareJid { inner }) | Jid::Full(FullJid { inner }) => inner.domain(),
}
}
/// The optional resource part of the JID, as a [`ResourcePart`]. It is guaranteed to be present
/// when the JID is a Full variant, which you can check with [`Jid::is_full`].
pub fn resource(&self) -> Option<ResourcePart> {
match self {
Jid::Bare(BareJid { inner }) | Jid::Full(FullJid { inner }) => {
inner.resource().map(ResourcePart::new_unchecked)
}
}
}
/// The optional resource of the Jabber ID. It is guaranteed to be present when the JID is
/// a Full variant, which you can check with [`Jid::is_full`].
pub fn resource_str(&self) -> Option<&str> {
pub fn resource(&self) -> Option<&ResourceRef> {
match self {
Jid::Bare(BareJid { inner }) | Jid::Full(FullJid { inner }) => inner.resource(),
}
@ -221,6 +196,13 @@ impl Jid {
pub fn is_bare(&self) -> bool {
!self.is_full()
}
/// Return a reference to the canonical string representation of the JID.
pub fn as_str(&self) -> &str {
match self {
Jid::Bare(BareJid { inner }) | Jid::Full(FullJid { inner }) => inner.as_str(),
}
}
}
impl TryFrom<Jid> for FullJid {
@ -301,13 +283,13 @@ pub struct BareJid {
impl fmt::Debug for FullJid {
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(fmt, "FullJID({})", self)
fmt.debug_tuple("FullJid").field(&self.inner).finish()
}
}
impl fmt::Debug for BareJid {
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(fmt, "BareJID({})", self)
fmt.debug_tuple("BareJid").field(&self.inner).finish()
}
}
@ -415,9 +397,9 @@ impl FullJid {
/// # fn main() -> Result<(), Error> {
/// let jid = FullJid::new("node@domain/resource")?;
///
/// assert_eq!(jid.node_str(), Some("node"));
/// assert_eq!(jid.domain_str(), "domain");
/// assert_eq!(jid.resource_str(), "resource");
/// assert_eq!(jid.node().map(|x| x.as_str()), Some("node"));
/// assert_eq!(jid.domain().as_str(), "domain");
/// assert_eq!(jid.resource().as_str(), "resource");
/// # Ok(())
/// # }
/// ```
@ -439,22 +421,27 @@ impl FullJid {
/// already been parsed and stringprepped into [`NodePart`], [`DomainPart`], and [`ResourcePart`].
/// This method allocates and does not consume the typed parts.
pub fn from_parts(
node: Option<&NodePart>,
domain: &DomainPart,
resource: &ResourcePart,
node: Option<&NodeRef>,
domain: &DomainRef,
resource: &ResourceRef,
) -> FullJid {
let (at, slash, normalized) = if let Some(node) = node {
// Parts are never empty so len > 0 for NonZeroU16::new is always Some
(
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),
NonZeroU16::new(node.len() as u16),
NonZeroU16::new((node.len() + 1 + domain.len()) as u16),
format!(
"{}@{}/{}",
node.as_str(),
domain.as_str(),
resource.as_str()
),
)
} else {
(
None,
NonZeroU16::new(domain.0.len() as u16),
format!("{}/{}", domain.0, resource.0),
NonZeroU16::new(domain.len() as u16),
format!("{}/{}", domain.as_str(), resource.as_str()),
)
};
@ -467,34 +454,18 @@ impl FullJid {
FullJid { inner }
}
/// The optional node part of the JID, as a [`NodePart`]
pub fn node(&self) -> Option<NodePart> {
self.node_str().map(NodePart::new_unchecked)
}
/// The optional node part of the JID, as a stringy reference
pub fn node_str(&self) -> Option<&str> {
/// The optional node part of the JID as reference.
pub fn node(&self) -> Option<&NodeRef> {
self.inner.node()
}
/// The domain part of the JID, as a [`DomainPart`]
pub fn domain(&self) -> DomainPart {
DomainPart::new_unchecked(self.domain_str())
}
/// The domain part of the JID, as a stringy reference
pub fn domain_str(&self) -> &str {
/// The domain part of the JID as reference
pub fn domain(&self) -> &DomainRef {
self.inner.domain()
}
/// The optional resource part of the JID, as a [`ResourcePart`]. Since this is a full JID it
/// is always present.
pub fn resource(&self) -> ResourcePart {
ResourcePart::new_unchecked(self.resource_str())
}
/// The optional resource of the Jabber ID. Since this is a full JID it is always present.
pub fn resource_str(&self) -> &str {
pub fn resource(&self) -> &ResourceRef {
self.inner.resource().unwrap()
}
@ -518,6 +489,11 @@ impl FullJid {
self.inner.slash = None;
BareJid { inner: self.inner }
}
/// Return a reference to the canonical string representation of the JID.
pub fn as_str(&self) -> &str {
self.inner.as_str()
}
}
impl FromStr for BareJid {
@ -542,8 +518,8 @@ impl BareJid {
/// # fn main() -> Result<(), Error> {
/// let jid = BareJid::new("node@domain")?;
///
/// assert_eq!(jid.node_str(), Some("node"));
/// assert_eq!(jid.domain_str(), "domain");
/// assert_eq!(jid.node().map(|x| x.as_str()), Some("node"));
/// assert_eq!(jid.domain().as_str(), "domain");
/// # Ok(())
/// # }
/// ```
@ -562,17 +538,20 @@ impl BareJid {
}
/// Build a [`BareJid`] from typed parts. This method cannot fail because it uses parts that have
/// already been parsed and stringprepped into [`NodePart`] and [`DomainPart`]. This method allocates
/// and does not consume the typed parts.
pub fn from_parts(node: Option<&NodePart>, domain: &DomainPart) -> BareJid {
/// already been parsed and stringprepped into [`NodePart`] and [`DomainPart`].
///
/// This method allocates and does not consume the typed parts. To avoid
/// allocation if `node` is known to be `None` and `domain` is owned, you
/// can use `domain.into()`.
pub fn from_parts(node: Option<&NodeRef>, domain: &DomainRef) -> BareJid {
let (at, normalized) = if let Some(node) = node {
// Parts are never empty so len > 0 for NonZeroU16::new is always Some
(
NonZeroU16::new(node.0.len() as u16),
format!("{}@{}", node.0, domain.0),
NonZeroU16::new(node.len() as u16),
format!("{}@{}", node.as_str(), domain.as_str()),
)
} else {
(None, domain.0.clone())
(None, domain.to_string())
};
let inner = InnerJid {
@ -584,23 +563,13 @@ impl BareJid {
BareJid { inner }
}
/// The optional node part of the JID, as a [`NodePart`]
pub fn node(&self) -> Option<NodePart> {
self.node_str().map(NodePart::new_unchecked)
}
/// The optional node part of the JID, as a stringy reference
pub fn node_str(&self) -> Option<&str> {
/// The optional node part of the JID as reference.
pub fn node(&self) -> Option<&NodeRef> {
self.inner.node()
}
/// The domain part of the JID, as a [`DomainPart`]
pub fn domain(&self) -> DomainPart {
DomainPart::new_unchecked(self.domain_str())
}
/// The domain part of the JID, as a stringy reference
pub fn domain_str(&self) -> &str {
/// The domain part of the JID as reference
pub fn domain(&self) -> &DomainRef {
self.inner.domain()
}
@ -616,11 +585,11 @@ impl BareJid {
/// let bare = BareJid::new("node@domain").unwrap();
/// let full = bare.with_resource(&resource);
///
/// assert_eq!(full.node_str(), Some("node"));
/// assert_eq!(full.domain_str(), "domain");
/// assert_eq!(full.resource_str(), "resource");
/// assert_eq!(full.node().map(|x| x.as_str()), Some("node"));
/// assert_eq!(full.domain().as_str(), "domain");
/// assert_eq!(full.resource().as_str(), "resource");
/// ```
pub fn with_resource(&self, resource: &ResourcePart) -> FullJid {
pub fn with_resource(&self, resource: &ResourceRef) -> FullJid {
let slash = NonZeroU16::new(self.inner.normalized.len() as u16);
let normalized = format!("{}/{resource}", self.inner.normalized);
let inner = InnerJid {
@ -643,14 +612,19 @@ impl BareJid {
/// let bare = BareJid::new("node@domain").unwrap();
/// let full = bare.with_resource_str("resource").unwrap();
///
/// assert_eq!(full.node_str(), Some("node"));
/// assert_eq!(full.domain_str(), "domain");
/// assert_eq!(full.resource_str(), "resource");
/// assert_eq!(full.node().map(|x| x.as_str()), Some("node"));
/// assert_eq!(full.domain().as_str(), "domain");
/// assert_eq!(full.resource().as_str(), "resource");
/// ```
pub fn with_resource_str(&self, resource: &str) -> Result<FullJid, Error> {
let resource = ResourcePart::new(resource)?;
Ok(self.with_resource(&resource))
}
/// Return a reference to the canonical string representation of the JID.
pub fn as_str(&self) -> &str {
self.inner.as_str()
}
}
#[cfg(feature = "minidom")]
@ -796,27 +770,21 @@ mod tests {
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()));
assert_eq!(jid.node().map(|x| x.as_str()), Some("a"),);
}
#[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());
assert_eq!(jid.domain().as_str(), "b.c");
}
#[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()));
assert_eq!(jid.resource().map(|x| x.as_str()), Some("d"),);
}
#[test]
@ -959,4 +927,16 @@ mod tests {
let jid: FullJid = FullJid::new("node@domain/resource").unwrap();
serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain/resource")]);
}
#[test]
fn jid_into_parts_and_from_parts() {
let node = NodePart::new("node").unwrap();
let domain = DomainPart::new("domain").unwrap();
let jid1 = domain.with_node(&node);
let jid2 = node.with_domain(&domain);
let jid3 = BareJid::new("node@domain").unwrap();
assert_eq!(jid1, jid2);
assert_eq!(jid2, jid3);
}
}

View File

@ -1,8 +1,11 @@
use std::borrow::{Borrow, Cow};
use std::fmt;
use std::ops::Deref;
use std::str::FromStr;
use stringprep::{nameprep, nodeprep, resourceprep};
use std::fmt;
use crate::Error;
use crate::{BareJid, Error, InnerJid, Jid};
fn length_check(len: usize, error_empty: Error, error_too_long: Error) -> Result<(), Error> {
if len == 0 {
@ -14,77 +17,278 @@ fn length_check(len: usize, error_empty: Error, error_too_long: Error) -> Result
}
}
/// The [`NodePart`] is the optional part before the (optional) `@` in any [`Jid`][crate::Jid],
/// whether [`BareJid`][crate::BareJid] or [`FullJid`][crate::FullJid].
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct NodePart(pub(crate) String);
macro_rules! def_part_parse_doc {
($name:ident, $other:ident, $more:expr) => {
concat!(
"Parse a [`",
stringify!($name),
"`] from a `",
stringify!($other),
"`, copying its contents.\n",
"\n",
"If the given `",
stringify!($other),
"` does not conform to the restrictions imposed by `",
stringify!($name),
"`, an error is returned.\n",
$more,
)
};
}
impl NodePart {
/// Build a new [`NodePart`] from a string slice. Will fail in case of stringprep validation
/// error.
pub fn new(s: &str) -> Result<NodePart, Error> {
let node = nodeprep(s).map_err(|_| Error::NodePrep)?;
length_check(node.len(), Error::NodeEmpty, Error::NodeTooLong)?;
Ok(NodePart(node.to_string()))
}
macro_rules! def_part_into_inner_doc {
($name:ident, $other:ident, $more:expr) => {
concat!(
"Consume the `",
stringify!($name),
"` and return the inner `",
stringify!($other),
"`.\n",
$more,
)
};
}
pub(crate) fn new_unchecked(s: &str) -> NodePart {
NodePart(s.to_string())
macro_rules! def_part_types {
(
$(#[$mainmeta:meta])*
pub struct $name:ident(String) use $prepfn:ident(err = $preperr:path, empty = $emptyerr:path, long = $longerr:path);
$(#[$refmeta:meta])*
pub struct ref $borrowed:ident(str);
) => {
$(#[$mainmeta])*
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(transparent)]
pub struct $name(pub(crate) String);
impl $name {
#[doc = def_part_parse_doc!($name, str, "Depending on whether the contents are changed by normalisation operations, this function either returns a copy or a reference to the original data.")]
pub fn new(s: &str) -> Result<Cow<'_, $borrowed>, Error> {
let node = $prepfn(s).map_err(|_| $preperr)?;
length_check(node.len(), $emptyerr, $longerr)?;
match node {
Cow::Borrowed(v) => Ok(Cow::Borrowed($borrowed::from_str_unchecked(v))),
Cow::Owned(v) => Ok(Cow::Owned(Self(v))),
}
}
#[doc = def_part_into_inner_doc!($name, String, "")]
pub fn into_inner(self) -> String {
self.0
}
}
impl FromStr for $name {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Error> {
Ok(Self::new(s)?.into_owned())
}
}
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
<$borrowed as fmt::Display>::fmt(Borrow::<$borrowed>::borrow(self), f)
}
}
impl Deref for $name {
type Target = $borrowed;
fn deref(&self) -> &Self::Target {
Borrow::<$borrowed>::borrow(self)
}
}
impl AsRef<$borrowed> for $name {
fn as_ref(&self) -> &$borrowed {
Borrow::<$borrowed>::borrow(self)
}
}
impl AsRef<String> for $name {
fn as_ref(&self) -> &String {
&self.0
}
}
impl Borrow<$borrowed> for $name {
fn borrow(&self) -> &$borrowed {
$borrowed::from_str_unchecked(self.0.as_str())
}
}
// useful for use in hashmaps
impl Borrow<String> for $name {
fn borrow(&self) -> &String {
&self.0
}
}
// useful for use in hashmaps
impl Borrow<str> for $name {
fn borrow(&self) -> &str {
self.0.as_str()
}
}
impl<'x> TryFrom<&'x str> for $name {
type Error = Error;
fn try_from(s: &str) -> Result<Self, Error> {
Self::from_str(s)
}
}
impl From<&$borrowed> for $name {
fn from(other: &$borrowed) -> Self {
other.to_owned()
}
}
impl<'x> From<Cow<'x, $borrowed>> for $name {
fn from(other: Cow<'x, $borrowed>) -> Self {
other.into_owned()
}
}
$(#[$refmeta])*
#[repr(transparent)]
#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct $borrowed(pub(crate) str);
impl $borrowed {
pub(crate) fn from_str_unchecked(s: &str) -> &Self {
// SAFETY: repr(transparent) thing can be transmuted to/from
// its inner.
unsafe { std::mem::transmute(s) }
}
/// Access the contents as [`str`] slice.
pub fn as_str(&self) -> &str {
&self.0
}
}
impl Deref for $borrowed {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl ToOwned for $borrowed {
type Owned = $name;
fn to_owned(&self) -> Self::Owned {
$name(self.0.to_string())
}
}
impl AsRef<str> for $borrowed {
fn as_ref(&self) -> &str {
&self.0
}
}
impl fmt::Display for $borrowed {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", &self.0)
}
}
}
}
impl fmt::Display for NodePart {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
def_part_types! {
/// The [`NodePart`] is the optional part before the (optional) `@` in any
/// [`Jid`][crate::Jid], whether [`BareJid`][crate::BareJid] or
/// [`FullJid`][crate::FullJid].
///
/// The corresponding slice type is [`NodeRef`].
pub struct NodePart(String) use nodeprep(err = Error::NodePrep, empty = Error::NodeEmpty, long = Error::NodeTooLong);
/// `str`-like type which conforms to the requirements of [`NodePart`].
///
/// See [`NodePart`] for details.
pub struct ref NodeRef(str);
}
def_part_types! {
/// The [`DomainPart`] is the part between the (optional) `@` and the
/// (optional) `/` in any [`Jid`][crate::Jid], whether
/// [`BareJid`][crate::BareJid] or [`FullJid`][crate::FullJid].
pub struct DomainPart(String) use nameprep(err = Error::NamePrep, empty = Error::DomainEmpty, long = Error::DomainTooLong);
/// `str`-like type which conforms to the requirements of [`DomainPart`].
///
/// See [`DomainPart`] for details.
pub struct ref DomainRef(str);
}
def_part_types! {
/// The [`ResourcePart`] is the optional part after the `/` in a
/// [`Jid`][crate::Jid]. It is mandatory in [`FullJid`][crate::FullJid].
pub struct ResourcePart(String) use resourceprep(err = Error::ResourcePrep, empty = Error::ResourceEmpty, long = Error::ResourceTooLong);
/// `str`-like type which conforms to the requirements of
/// [`ResourcePart`].
///
/// See [`ResourcePart`] for details.
pub struct ref ResourceRef(str);
}
impl DomainRef {
/// Construct a bare JID (a JID without a resource) from this domain and
/// the given node (local part).
pub fn with_node(&self, node: &NodeRef) -> BareJid {
BareJid::from_parts(Some(node), self)
}
}
/// The [`DomainPart`] is the part between the (optional) `@` and the (optional) `/` in any
/// [`Jid`][crate::Jid], whether [`BareJid`][crate::BareJid] or [`FullJid`][crate::FullJid].
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct DomainPart(pub(crate) String);
impl DomainPart {
/// Build a new [`DomainPart`] from a string slice. Will fail in case of stringprep validation
/// error.
pub fn new(s: &str) -> Result<DomainPart, Error> {
let domain = nameprep(s).map_err(|_| Error::NamePrep)?;
length_check(domain.len(), Error::DomainEmpty, Error::DomainTooLong)?;
Ok(DomainPart(domain.to_string()))
}
pub(crate) fn new_unchecked(s: &str) -> DomainPart {
DomainPart(s.to_string())
impl From<DomainPart> for BareJid {
fn from(other: DomainPart) -> Self {
BareJid {
inner: InnerJid {
normalized: other.0,
at: None,
slash: None,
},
}
}
}
impl fmt::Display for DomainPart {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
impl From<DomainPart> for Jid {
fn from(other: DomainPart) -> Self {
Jid::Bare(other.into())
}
}
/// The [`ResourcePart`] is the optional part after the `/` in a [`Jid`][crate::Jid]. It is
/// mandatory in [`FullJid`][crate::FullJid].
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ResourcePart(pub(crate) String);
impl ResourcePart {
/// Build a new [`ResourcePart`] from a string slice. Will fail in case of stringprep
/// validation error.
pub fn new(s: &str) -> Result<ResourcePart, Error> {
let resource = resourceprep(s).map_err(|_| Error::ResourcePrep)?;
length_check(resource.len(), Error::ResourceEmpty, Error::ResourceTooLong)?;
Ok(ResourcePart(resource.to_string()))
}
pub(crate) fn new_unchecked(s: &str) -> ResourcePart {
ResourcePart(s.to_string())
impl<'x> From<&'x DomainRef> for BareJid {
fn from(other: &'x DomainRef) -> Self {
Self::from_parts(None, other)
}
}
impl fmt::Display for ResourcePart {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
impl NodeRef {
/// Construct a bare JID (a JID without a resource) from this node (the
/// local part) and the given domain.
pub fn with_domain(&self, domain: &DomainRef) -> BareJid {
BareJid::from_parts(Some(self), domain)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn nodepart_comparison() {
let n1 = NodePart::new("foo").unwrap();
let n2 = NodePart::new("bar").unwrap();
let n3 = NodePart::new("foo").unwrap();
assert_eq!(n1, n3);
assert_ne!(n1, n2);
}
}

View File

@ -587,6 +587,34 @@ impl Element {
self.children.push(Node::Text(child.into()));
}
/// Appends a string as plain text to an `Element`.
///
/// If the last child node of the element is a text node, the string will be appended to it.
/// Otherwise, a new text node will be created.
///
/// # Examples
///
/// ```rust
/// use minidom::Element;
///
/// let mut elem = Element::bare("node", "ns1");
///
/// assert_eq!(elem.text(), "");
///
/// elem.append_text_node("text");
///
/// elem.append_text(" and more text");
///
/// assert_eq!(elem.nodes().count(), 1);
/// ```
pub fn append_text<S: Into<String>>(&mut self, text: S) {
if let Some(Node::Text(ref mut child)) = self.children.last_mut() {
child.push_str(&text.into());
} else {
self.append_text_node(text);
}
}
/// Appends a node to an `Element`.
///
/// # Examples

View File

@ -97,7 +97,7 @@ impl TreeBuilder {
fn process_text(&mut self, text: String) {
if self.depth() > 0 {
let top = self.stack.len() - 1;
self.stack[top].append_text_node(text);
self.stack[top].append_text(text);
}
}

View File

@ -15,8 +15,8 @@ pub async fn bind<S: AsyncRead + AsyncWrite + Unpin>(
if stream.stream_features.can_bind() {
let resource = stream
.jid
.resource_str()
.and_then(|resource| Some(resource.to_owned()));
.resource()
.and_then(|resource| Some(resource.to_string()));
let iq = Iq::from_set(BIND_REQ_ID, BindQuery::new(resource));
stream.send_stanza(iq).await?;

View File

@ -13,7 +13,7 @@ pub async fn client_login<C: ServerConnector>(
jid: Jid,
password: String,
) -> Result<XMPPStream<C::Stream>, Error> {
let username = jid.node_str().unwrap();
let username = jid.node().unwrap().as_str();
let password = password;
let xmpp_stream = server.connect(&jid, ns::JABBER_CLIENT).await?;

View File

@ -64,7 +64,7 @@ impl ServerConnector for ServerConfig {
// TCP connection
let tcp_stream = match self {
ServerConfig::UseSrv => {
connect_with_srv(jid.domain_str(), "_xmpp-client._tcp", 5222).await?
connect_with_srv(jid.domain().as_str(), "_xmpp-client._tcp", 5222).await?
}
ServerConfig::Manual { host, port } => connect_to_host(host.as_str(), *port).await?,
};
@ -126,7 +126,7 @@ async fn get_tls_stream<S: AsyncRead + AsyncWrite + Unpin>(
async fn get_tls_stream<S: AsyncRead + AsyncWrite + Unpin>(
xmpp_stream: XMPPStream<S>,
) -> Result<TlsStream<S>, Error> {
let domain = xmpp_stream.jid.domain_str().to_owned();
let domain = xmpp_stream.jid.domain().to_string();
let domain = ServerName::try_from(domain.as_str())?;
let stream = xmpp_stream.into_inner();
let mut root_store = RootCertStore::empty();

View File

@ -15,7 +15,7 @@ pub async fn start<S: AsyncRead + AsyncWrite + Unpin>(
ns: String,
) -> Result<XMPPStream<S>, Error> {
let attrs = [
("to".to_owned(), jid.domain_str().to_owned()),
("to".to_owned(), jid.domain().to_string()),
("version".to_owned(), "1.0".to_owned()),
("xmlns".to_owned(), ns.clone()),
("xmlns:stream".to_owned(), ns::STREAM.to_owned()),

View File

@ -39,7 +39,7 @@ pub async fn handle_message_chat<C: ServerConnector>(
Jid::Full(full) => Event::RoomPrivateMessage(
message.id.clone(),
full.to_bare(),
full.resource_str().to_owned(),
full.resource().to_string(),
body.clone(),
time_info.clone(),
),

View File

@ -21,7 +21,7 @@ pub async fn handle_message_group_chat<C: ServerConnector>(
if let Some((_lang, subject)) = message.get_best_subject(langs.clone()) {
events.push(Event::RoomSubject(
from.to_bare(),
from.resource_str().map(String::from),
from.resource().map(|x| x.to_string()),
subject.0.clone(),
time_info.clone(),
));
@ -32,7 +32,7 @@ pub async fn handle_message_group_chat<C: ServerConnector>(
Jid::Full(full) => Event::RoomMessage(
message.id.clone(),
from.to_bare(),
full.resource_str().to_owned(),
full.resource().to_string(),
body.clone(),
time_info,
),