Add more conversion/construction paths between Jid and part types

Jonas Schรคfer created

Change summary

jid/CHANGELOG.md |  5 +++++
jid/src/lib.rs   | 24 +++++++++++++++++++++---
jid/src/parts.rs | 42 +++++++++++++++++++++++++++++++++++++++++-
3 files changed, 67 insertions(+), 4 deletions(-)

Detailed changes

jid/CHANGELOG.md ๐Ÿ”—

@@ -15,6 +15,11 @@ Version xxx, release xxx:
     - `str`-like reference types have been added for `DomainPart`, `NodePart`
       and `ResourcePart`, called `DomainRef`, `NodeRef` and `ResourceRef`
       respectively.
+    - Convenience methods to combine `DomainPart` and `NodePart` to a
+      `BareJid` have been added, including
+      `impl From<DomainPart> for BareJid` and
+      `impl From<DomainPart> for Jid`, both of which are (unlike
+      `::from_parts`) copy-free.
 
 Version 0.10.0, release 2023-08-17:
   * Breaking

jid/src/lib.rs ๐Ÿ”—

@@ -130,7 +130,10 @@ impl Jid {
 
     /// Build a [`Jid`] from typed parts. This method cannot fail because it uses parts that have
     /// already been parsed and stringprepped into [`NodePart`], [`DomainPart`], and [`ResourcePart`].
-    /// This method allocates and does not consume the typed parts.
+    ///
+    /// This method allocates and does not consume the typed parts. To avoid
+    /// allocation if both `node` and `resource` are known to be `None` and
+    /// `domain` is owned, you can use `domain.into()`.
     pub fn from_parts(
         node: Option<&NodeRef>,
         domain: &DomainRef,
@@ -523,8 +526,11 @@ impl BareJid {
     }
 
     /// Build a [`BareJid`] from typed parts. This method cannot fail because it uses parts that have
-    /// already been parsed and stringprepped into [`NodePart`] and [`DomainPart`]. This method allocates
-    /// and does not consume the typed parts.
+    /// already been parsed and stringprepped into [`NodePart`] and [`DomainPart`].
+    ///
+    /// This method allocates and does not consume the typed parts. To avoid
+    /// allocation if `node` is known to be `None` and `domain` is owned, you
+    /// can use `domain.into()`.
     pub fn from_parts(node: Option<&NodeRef>, domain: &DomainRef) -> BareJid {
         let (at, normalized) = if let Some(node) = node {
             // Parts are never empty so len > 0 for NonZeroU16::new is always Some
@@ -904,4 +910,16 @@ mod tests {
         let jid: FullJid = FullJid::new("node@domain/resource").unwrap();
         serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain/resource")]);
     }
+
+    #[test]
+    fn jid_into_parts_and_from_parts() {
+        let node = NodePart::new("node").unwrap();
+        let domain = DomainPart::new("domain").unwrap();
+
+        let jid1 = domain.with_node(&node);
+        let jid2 = node.with_domain(&domain);
+        let jid3 = BareJid::new("node@domain").unwrap();
+        assert_eq!(jid1, jid2);
+        assert_eq!(jid2, jid3);
+    }
 }

jid/src/parts.rs ๐Ÿ”—

@@ -5,7 +5,7 @@ use std::str::FromStr;
 
 use stringprep::{nameprep, nodeprep, resourceprep};
 
-use crate::Error;
+use crate::{BareJid, Error, InnerJid, Jid};
 
 fn length_check(len: usize, error_empty: Error, error_too_long: Error) -> Result<(), Error> {
     if len == 0 {
@@ -239,6 +239,46 @@ def_part_types! {
     pub struct ref ResourceRef(str);
 }
 
+impl DomainRef {
+    /// Construct a bare JID (a JID without a resource) from this domain and
+    /// the given node (local part).
+    pub fn with_node(&self, node: &NodeRef) -> BareJid {
+        BareJid::from_parts(Some(node), self)
+    }
+}
+
+impl From<DomainPart> for BareJid {
+    fn from(other: DomainPart) -> Self {
+        BareJid {
+            inner: InnerJid {
+                normalized: other.0,
+                at: None,
+                slash: None,
+            },
+        }
+    }
+}
+
+impl From<DomainPart> for Jid {
+    fn from(other: DomainPart) -> Self {
+        Jid::Bare(other.into())
+    }
+}
+
+impl<'x> From<&'x DomainRef> for BareJid {
+    fn from(other: &'x DomainRef) -> Self {
+        Self::from_parts(None, other)
+    }
+}
+
+impl NodeRef {
+    /// Construct a bare JID (a JID without a resource) from this node (the
+    /// local part) and the given domain.
+    pub fn with_domain(&self, domain: &DomainRef) -> BareJid {
+        BareJid::from_parts(Some(self), domain)
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;