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.
This commit is contained in:
Jonas Schäfer 2024-03-03 15:53:09 +01:00
parent e7fa6460f4
commit 8238e81c66
2 changed files with 236 additions and 65 deletions

View file

@ -49,7 +49,7 @@ mod inner;
use inner::InnerJid; use inner::InnerJid;
mod parts; 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`. /// 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", derive(Serialize, Deserialize))]
@ -798,7 +798,7 @@ mod tests {
assert_eq!(jid.node_str(), Some("a"),); assert_eq!(jid.node_str(), Some("a"),);
assert_eq!(jid.node(), Some(NodePart::new("a").unwrap())); assert_eq!(jid.node(), Some(NodePart::new("a").unwrap().into_owned()));
} }
#[test] #[test]
@ -807,7 +807,7 @@ mod tests {
assert_eq!(jid.domain_str(), "b.c"); assert_eq!(jid.domain_str(), "b.c");
assert_eq!(jid.domain(), DomainPart::new("b.c").unwrap()); assert_eq!(jid.domain(), DomainPart::new("b.c").unwrap().into_owned());
} }
#[test] #[test]
@ -816,7 +816,10 @@ mod tests {
assert_eq!(jid.resource_str(), Some("d"),); assert_eq!(jid.resource_str(), Some("d"),);
assert_eq!(jid.resource(), Some(ResourcePart::new("d").unwrap())); assert_eq!(
jid.resource(),
Some(ResourcePart::new("d").unwrap().into_owned())
);
} }
#[test] #[test]

View file

@ -1,6 +1,9 @@
use stringprep::{nameprep, nodeprep, resourceprep}; use std::borrow::{Borrow, Cow};
use std::fmt; use std::fmt;
use std::ops::Deref;
use std::str::FromStr;
use stringprep::{nameprep, nodeprep, resourceprep};
use crate::Error; use crate::Error;
@ -14,77 +17,242 @@ 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], macro_rules! def_part_parse_doc {
/// whether [`BareJid`][crate::BareJid] or [`FullJid`][crate::FullJid]. ($name:ident, $other:ident, $more:expr) => {
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] concat!(
pub struct NodePart(pub(crate) String); "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 { macro_rules! def_part_into_inner_doc {
/// Build a new [`NodePart`] from a string slice. Will fail in case of stringprep validation ($name:ident, $other:ident, $more:expr) => {
/// error. concat!(
pub fn new(s: &str) -> Result<NodePart, Error> { "Consume the `",
let node = nodeprep(s).map_err(|_| Error::NodePrep)?; stringify!($name),
length_check(node.len(), Error::NodeEmpty, Error::NodeTooLong)?; "` and return the inner `",
Ok(NodePart(node.to_string())) stringify!($other),
} "`.\n",
$more,
)
};
}
pub(crate) fn new_unchecked(s: &str) -> NodePart { macro_rules! def_part_types {
NodePart(s.to_string()) (
$(#[$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
}
pub(crate) fn new_unchecked(s: &str) -> Self {
$name(s.to_string())
}
}
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 {
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 { def_part_types! {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { /// The [`NodePart`] is the optional part before the (optional) `@` in any
write!(f, "{}", self.0) /// [`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);
} }
/// The [`DomainPart`] is the part between the (optional) `@` and the (optional) `/` in any def_part_types! {
/// [`Jid`][crate::Jid], whether [`BareJid`][crate::BareJid] or [`FullJid`][crate::FullJid]. /// The [`DomainPart`] is the part between the (optional) `@` and the
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] /// (optional) `/` in any [`Jid`][crate::Jid], whether
pub struct DomainPart(pub(crate) String); /// [`BareJid`][crate::BareJid] or [`FullJid`][crate::FullJid].
pub struct DomainPart(String) use nameprep(err = Error::NamePrep, empty = Error::DomainEmpty, long = Error::DomainTooLong);
impl DomainPart { /// `str`-like type which conforms to the requirements of [`DomainPart`].
/// Build a new [`DomainPart`] from a string slice. Will fail in case of stringprep validation ///
/// error. /// See [`DomainPart`] for details.
pub fn new(s: &str) -> Result<DomainPart, Error> { pub struct ref DomainRef(str);
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 fmt::Display for DomainPart { def_part_types! {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { /// The [`ResourcePart`] is the optional part after the `/` in a
write!(f, "{}", self.0) /// [`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);
} }
/// The [`ResourcePart`] is the optional part after the `/` in a [`Jid`][crate::Jid]. It is #[cfg(test)]
/// mandatory in [`FullJid`][crate::FullJid]. mod tests {
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] use super::*;
pub struct ResourcePart(pub(crate) String);
impl ResourcePart { #[test]
/// Build a new [`ResourcePart`] from a string slice. Will fail in case of stringprep fn nodepart_comparison() {
/// validation error. let n1 = NodePart::new("foo").unwrap();
pub fn new(s: &str) -> Result<ResourcePart, Error> { let n2 = NodePart::new("bar").unwrap();
let resource = resourceprep(s).map_err(|_| Error::ResourcePrep)?; let n3 = NodePart::new("foo").unwrap();
length_check(resource.len(), Error::ResourceEmpty, Error::ResourceTooLong)?; assert_eq!(n1, n3);
Ok(ResourcePart(resource.to_string())) assert_ne!(n1, n2);
}
pub(crate) fn new_unchecked(s: &str) -> ResourcePart {
ResourcePart(s.to_string())
}
}
impl fmt::Display for ResourcePart {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
} }
} }