From 8238e81c66774f6f5f008e374f1ff09662886658 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Sch=C3=A4fer?= Date: Sun, 3 Mar 2024 15:53:09 +0100 Subject: [PATCH] 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. --- jid/src/lib.rs | 11 +- jid/src/parts.rs | 290 +++++++++++++++++++++++++++++++++++++---------- 2 files changed, 236 insertions(+), 65 deletions(-) diff --git a/jid/src/lib.rs b/jid/src/lib.rs index d5374d2..ab24bf4 100644 --- a/jid/src/lib.rs +++ b/jid/src/lib.rs @@ -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] diff --git a/jid/src/parts.rs b/jid/src/parts.rs index 4cd2ea5..cd01be0 100644 --- a/jid/src/parts.rs +++ b/jid/src/parts.rs @@ -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 { - 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, 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 { + 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 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 for $name { + fn borrow(&self) -> &String { + &self.0 + } + } + + // useful for use in hashmaps + impl Borrow 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::from_str(s) + } + } + + impl From<&$borrowed> for $name { + fn from(other: &$borrowed) -> Self { + other.to_owned() + } + } + + impl<'x> From> 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 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 { - 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 { - 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); } }