@@ -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]
@@ -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);
-
-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_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,
+ )
+ };
+}
- pub(crate) fn new_unchecked(s: &str) -> NodePart {
- NodePart(s.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,
+ )
+ };
}
-impl fmt::Display for NodePart {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(f, "{}", self.0)
+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)
+ }
+ }
}
}
-/// 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()))
- }
+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);
- pub(crate) fn new_unchecked(s: &str) -> DomainPart {
- DomainPart(s.to_string())
- }
+ /// `str`-like type which conforms to the requirements of [`NodePart`].
+ ///
+ /// See [`NodePart`] for details.
+ pub struct ref NodeRef(str);
}
-impl fmt::Display for DomainPart {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(f, "{}", self.0)
- }
+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);
}
-/// 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()))
- }
+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);
- pub(crate) fn new_unchecked(s: &str) -> ResourcePart {
- ResourcePart(s.to_string())
- }
+ /// `str`-like type which conforms to the requirements of
+ /// [`ResourcePart`].
+ ///
+ /// See [`ResourcePart`] for details.
+ pub struct ref ResourceRef(str);
}
-impl fmt::Display for ResourcePart {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(f, "{}", self.0)
+#[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);
}
}