@@ -45,6 +45,9 @@ pub use crate::error::Error;
mod inner;
use inner::InnerJid;
+mod parts;
+pub use parts::{DomainPart, NodePart, ResourcePart};
+
/// An enum representing a Jabber ID. It can be either a `FullJid` or a `BareJid`.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(untagged))]
@@ -89,6 +92,7 @@ impl fmt::Display for Jid {
impl Jid {
/// Constructs a Jabber ID from a string. This is of the form
/// `node`@`domain`/`resource`, where node and resource parts are optional.
+ /// If you want a non-fallible version, use [`Jid::from_parts`] instead.
///
/// # Examples
///
@@ -114,6 +118,21 @@ 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.
+ pub fn from_parts(
+ node: Option<&NodePart>,
+ domain: &DomainPart,
+ resource: Option<&ResourcePart>,
+ ) -> Jid {
+ if let Some(resource) = resource {
+ Jid::Full(FullJid::from_parts(node, domain, resource))
+ } else {
+ Jid::Bare(BareJid::from_parts(node, domain))
+ }
+ }
+
/// The optional node part of the JID.
pub fn node(&self) -> Option<&str> {
match self {
@@ -305,6 +324,7 @@ impl<'de> Deserialize<'de> for BareJid {
impl FullJid {
/// Constructs a full Jabber ID containing all three components. This is of the form
/// `node@domain/resource`, where node part is optional.
+ /// If you want a non-fallible version, use [`FullJid::from_parts`] instead.
///
/// # Examples
///
@@ -330,6 +350,38 @@ impl FullJid {
}
}
+ /// Build a [`FullJid`] 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.
+ pub fn from_parts(
+ node: Option<&NodePart>,
+ domain: &DomainPart,
+ resource: &ResourcePart,
+ ) -> FullJid {
+ let (at, slash, normalized) = if let Some(node) = node {
+ // Parts are never empty so len > 0 for NonZeroU16::new is always Some
+ (
+ NonZeroU16::new(node.0.len() as u16),
+ NonZeroU16::new((node.0.len() + 1 + domain.0.len()) as u16),
+ format!("{}@{}/{}", node.0, domain.0, resource.0),
+ )
+ } else {
+ (
+ None,
+ NonZeroU16::new(domain.0.len() as u16),
+ format!("{}/{}", domain.0, resource.0),
+ )
+ };
+
+ let inner = InnerJid {
+ normalized,
+ at,
+ slash,
+ };
+
+ FullJid { inner }
+ }
+
/// The optional node part of the JID.
pub fn node(&self) -> Option<&str> {
self.inner.node()
@@ -378,6 +430,7 @@ impl FromStr for BareJid {
impl BareJid {
/// Constructs a bare Jabber ID, containing two components. This is of the form
/// `node`@`domain`, where node part is optional.
+ /// If you want a non-fallible version, use [`BareJid::from_parts`] instead.
///
/// # Examples
///
@@ -402,6 +455,29 @@ 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.
+ pub fn from_parts(node: Option<&NodePart>, domain: &DomainPart) -> BareJid {
+ let (at, normalized) = if let Some(node) = node {
+ // Parts are never empty so len > 0 for NonZeroU16::new is always Some
+ (
+ NonZeroU16::new(node.0.len() as u16),
+ format!("{}@{}", node.0, domain.0),
+ )
+ } else {
+ (None, domain.0.clone())
+ };
+
+ let inner = InnerJid {
+ normalized,
+ at,
+ slash: None,
+ };
+
+ BareJid { inner }
+ }
+
/// The optional node part of the JID.
pub fn node(&self) -> Option<&str> {
self.inner.node()
@@ -0,0 +1,52 @@
+use stringprep::{nameprep, nodeprep, resourceprep};
+
+use crate::Error;
+
+/// The [`NodePart`] is the optional part before the (optional) `@` in any [`Jid`], whether [`BareJid`] or [`FullJid`].
+#[derive(Clone, Debug, PartialEq, Hash, PartialOrd)]
+pub struct NodePart(pub(crate) String);
+
+fn length_check(len: usize, error_empty: Error, error_too_long: Error) -> Result<(), Error> {
+ if len == 0 {
+ Err(error_empty)
+ } else if len > 1023 {
+ Err(error_too_long)
+ } else {
+ Ok(())
+ }
+}
+
+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()))
+ }
+}
+
+/// The [`DomainPart`] is the part between the (optional) `@` and the (optional) `/` in any [`Jid`], whether [`BareJid`] or [`FullJid`].
+#[derive(Clone, Debug, PartialEq, Hash, PartialOrd)]
+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()))
+ }
+}
+
+/// The [`ResourcePart`] is the optional part after the `/` in a [`Jid`]. It is mandatory in [`FullJid`].
+#[derive(Clone, Debug, PartialEq, Hash, PartialOrd)]
+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()))
+ }
+}