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;
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))]
@ -798,7 +798,7 @@ mod tests {
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]
@ -807,7 +807,7 @@ mod tests {
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]
@ -816,7 +816,10 @@ mod tests {
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]

View file

@ -1,6 +1,9 @@
use stringprep::{nameprep, nodeprep, resourceprep};
use std::borrow::{Borrow, Cow};
use std::fmt;
use std::ops::Deref;
use std::str::FromStr;
use stringprep::{nameprep, nodeprep, resourceprep};
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],
/// 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
}
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 {
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);
}
/// 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);
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);
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())
}
/// `str`-like type which conforms to the requirements of [`DomainPart`].
///
/// See [`DomainPart`] for details.
pub struct ref DomainRef(str);
}
impl fmt::Display for DomainPart {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
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);
}
/// 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);
#[cfg(test)]
mod tests {
use super::*;
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 fmt::Display for ResourcePart {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
#[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);
}
}