mirror of
https://gitlab.com/xmpp-rs/xmpp-rs.git
synced 2024-07-12 22:21:53 +00:00
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:
parent
e7fa6460f4
commit
8238e81c66
2 changed files with 236 additions and 65 deletions
|
@ -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]
|
||||
|
|
290
jid/src/parts.rs
290
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<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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue