lib.rs

   1// Copyright (c) 2017, 2018 lumi <lumi@pew.im>
   2// Copyright (c) 2017, 2018, 2019 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
   3// Copyright (c) 2017, 2018, 2019 Maxime “pep” Buquet <pep@bouah.net>
   4// Copyright (c) 2017, 2018 Astro <astro@spaceboyz.net>
   5// Copyright (c) 2017 Bastien Orivel <eijebong@bananium.fr>
   6//
   7// This Source Code Form is subject to the terms of the Mozilla Public
   8// License, v. 2.0. If a copy of the MPL was not distributed with this
   9// file, You can obtain one at http://mozilla.org/MPL/2.0/.
  10
  11#![no_std]
  12#![deny(missing_docs)]
  13#![cfg_attr(docsrs, feature(doc_cfg))]
  14#![cfg_attr(docsrs, doc(auto_cfg))]
  15
  16//! Represents XMPP addresses, also known as JabberIDs (JIDs) for the [XMPP](https://xmpp.org/)
  17//! protocol. A [`Jid`] can have between one and three parts in the form `node@domain/resource`:
  18//! - the (optional) node part designates a specific account/service on a server, for example
  19//!   `username@server.com`
  20//! - the domain part designates a server, for example `irc.jabberfr.org`
  21//! - the (optional) resource part designates a more specific client, such as a participant in a
  22//!   groupchat (`jabberfr@chat.jabberfr.org/user`) or a specific client device associated with an
  23//!   account (`user@example.com/dino`)
  24//!
  25//! The [`Jid`] enum can be one of two variants, containing a more specific type:
  26//! - [`BareJid`] (`Jid::Bare` variant): a JID without a resource
  27//! - [`FullJid`] (`Jid::Full` variant): a JID with a resource
  28//!
  29//! Jids as per the XMPP protocol only ever contain valid UTF-8. However, creating any form of Jid
  30//! can fail in one of the following cases:
  31//! - wrong syntax: creating a Jid with an empty (yet declared) node or resource part, such as
  32//!   `@example.com` or `user@example.com/`
  33//! - stringprep error: some characters were invalid according to the stringprep algorithm, such as
  34//!   mixing left-to-write and right-to-left characters
  35
  36extern crate alloc;
  37
  38#[cfg(test)]
  39extern crate std;
  40
  41use alloc::borrow::Cow;
  42use alloc::format;
  43use alloc::string::{String, ToString};
  44use core::borrow::Borrow;
  45use core::cmp::Ordering;
  46use core::fmt;
  47use core::hash::{Hash, Hasher};
  48use core::mem;
  49use core::net::{Ipv4Addr, Ipv6Addr};
  50use core::num::NonZeroU16;
  51use core::ops::Deref;
  52use core::str::FromStr;
  53
  54use memchr::memchr2_iter;
  55
  56use idna::uts46::{AsciiDenyList, DnsLength, Hyphens, Uts46};
  57use stringprep::{nameprep, nodeprep, resourceprep};
  58
  59#[cfg(feature = "serde")]
  60use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
  61
  62#[cfg(feature = "quote")]
  63use proc_macro2::TokenStream;
  64#[cfg(feature = "quote")]
  65use quote::{quote, ToTokens};
  66
  67#[cfg(feature = "minidom")]
  68use minidom::{IntoAttributeValue, Node};
  69
  70mod error;
  71mod parts;
  72
  73pub use crate::error::Error;
  74pub use parts::{DomainPart, DomainRef, NodePart, NodeRef, ResourcePart, ResourceRef};
  75
  76fn length_check(len: usize, error_empty: Error, error_too_long: Error) -> Result<(), Error> {
  77    if len == 0 {
  78        Err(error_empty)
  79    } else if len > 1023 {
  80        Err(error_too_long)
  81    } else {
  82        Ok(())
  83    }
  84}
  85
  86fn node_check(node: &str) -> Result<Cow<'_, str>, Error> {
  87    let node = nodeprep(node).map_err(|_| Error::NodePrep)?;
  88    length_check(node.len(), Error::NodeEmpty, Error::NodeTooLong)?;
  89    Ok(node)
  90}
  91
  92fn resource_check(resource: &str) -> Result<Cow<'_, str>, Error> {
  93    let resource = resourceprep(resource).map_err(|_| Error::ResourcePrep)?;
  94    length_check(resource.len(), Error::ResourceEmpty, Error::ResourceTooLong)?;
  95    Ok(resource)
  96}
  97
  98fn domain_check(mut domain: &str) -> Result<Cow<'_, str>, Error> {
  99    // First, check if this is an IPv4 address.
 100    if Ipv4Addr::from_str(domain).is_ok() {
 101        return Ok(Cow::Borrowed(domain));
 102    }
 103
 104    // Then if this is an IPv6 address.
 105    if domain.starts_with('[')
 106        && domain.ends_with(']')
 107        && Ipv6Addr::from_str(&domain[1..domain.len() - 1]).is_ok()
 108    {
 109        return Ok(Cow::Borrowed(domain));
 110    }
 111
 112    // idna can handle the root dot for us, but we still want to remove it for normalization
 113    // purposes.
 114    if domain.ends_with('.') {
 115        domain = &domain[..domain.len() - 1];
 116    }
 117
 118    Uts46::new()
 119        .to_ascii(
 120            domain.as_bytes(),
 121            AsciiDenyList::URL,
 122            Hyphens::Check,
 123            DnsLength::Verify,
 124        )
 125        .map_err(|_| Error::Idna)?;
 126    let domain = nameprep(domain).map_err(|_| Error::NamePrep)?;
 127    Ok(domain)
 128}
 129
 130/// A struct representing a Jabber ID (JID).
 131///
 132/// This JID can either be "bare" (without a `/resource` suffix) or full (with
 133/// a resource suffix).
 134///
 135/// In many APIs, it is appropriate to use the more specific types
 136/// ([`BareJid`] or [`FullJid`]) instead, as these two JID types are generally
 137/// used in different contexts within XMPP.
 138///
 139/// This dynamic type on the other hand can be used in contexts where it is
 140/// not known, at compile-time, whether a JID is full or bare.
 141#[derive(Clone, Eq)]
 142pub struct Jid {
 143    normalized: String,
 144    at: Option<NonZeroU16>,
 145    slash: Option<NonZeroU16>,
 146}
 147
 148impl PartialEq for Jid {
 149    fn eq(&self, other: &Jid) -> bool {
 150        self.normalized == other.normalized
 151    }
 152}
 153
 154impl PartialOrd for Jid {
 155    fn partial_cmp(&self, other: &Jid) -> Option<Ordering> {
 156        Some(self.cmp(other))
 157    }
 158}
 159
 160impl Ord for Jid {
 161    fn cmp(&self, other: &Jid) -> Ordering {
 162        self.normalized.cmp(&other.normalized)
 163    }
 164}
 165
 166impl Hash for Jid {
 167    fn hash<H: Hasher>(&self, state: &mut H) {
 168        self.normalized.hash(state)
 169    }
 170}
 171
 172impl FromStr for Jid {
 173    type Err = Error;
 174
 175    fn from_str(s: &str) -> Result<Self, Self::Err> {
 176        Self::new(s)
 177    }
 178}
 179
 180impl From<BareJid> for Jid {
 181    fn from(other: BareJid) -> Self {
 182        other.inner
 183    }
 184}
 185
 186impl From<FullJid> for Jid {
 187    fn from(other: FullJid) -> Self {
 188        other.inner
 189    }
 190}
 191
 192impl fmt::Display for Jid {
 193    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
 194        fmt.write_str(&self.normalized)
 195    }
 196}
 197
 198impl Jid {
 199    /// Constructs a Jabber ID from a string. This is of the form
 200    /// `node`@`domain`/`resource`, where node and resource parts are optional.
 201    /// If you want a non-fallible version, use [`Jid::from_parts`] instead.
 202    ///
 203    /// # Examples
 204    ///
 205    /// ```
 206    /// use jid::Jid;
 207    /// # use jid::Error;
 208    ///
 209    /// # fn main() -> Result<(), Error> {
 210    /// let jid = Jid::new("node@domain/resource")?;
 211    ///
 212    /// assert_eq!(jid.node().map(|x| x.as_str()), Some("node"));
 213    /// assert_eq!(jid.domain().as_str(), "domain");
 214    /// assert_eq!(jid.resource().map(|x| x.as_str()), Some("resource"));
 215    /// # Ok(())
 216    /// # }
 217    /// ```
 218    pub fn new(unnormalized: &str) -> Result<Jid, Error> {
 219        let bytes = unnormalized.as_bytes();
 220        let orig_at;
 221        let orig_slash;
 222        let mut iter = memchr2_iter(b'@', b'/', bytes);
 223        let normalized = if let Some(first_index) = iter.next() {
 224            let byte = bytes[first_index];
 225            if byte == b'@' {
 226                if let Some(second_index) = iter.next() {
 227                    let byte = bytes[second_index];
 228                    if byte == b'/' {
 229                        let node = node_check(&unnormalized[..first_index])?;
 230                        let domain = domain_check(&unnormalized[first_index + 1..second_index])?;
 231                        let resource = resource_check(&unnormalized[second_index + 1..])?;
 232
 233                        orig_at = Some(node.len());
 234                        orig_slash = Some(node.len() + domain.len() + 1);
 235                        match (node, domain, resource) {
 236                            (Cow::Borrowed(_), Cow::Borrowed(_), Cow::Borrowed(_)) => {
 237                                unnormalized.to_string()
 238                            }
 239                            (node, domain, resource) => format!("{node}@{domain}/{resource}"),
 240                        }
 241                    } else
 242                    /* This is another '@' character. */
 243                    {
 244                        return Err(Error::TooManyAts);
 245                    }
 246                } else {
 247                    // That is a node@domain JID.
 248
 249                    let node = node_check(&unnormalized[..first_index])?;
 250                    let domain = domain_check(&unnormalized[first_index + 1..])?;
 251
 252                    orig_at = Some(node.len());
 253                    orig_slash = None;
 254                    match (node, domain) {
 255                        (Cow::Borrowed(_), Cow::Borrowed(_)) => unnormalized.to_string(),
 256                        (node, domain) => format!("{node}@{domain}"),
 257                    }
 258                }
 259            } else
 260            /* This is a '/' character. */
 261            {
 262                // The JID is of the form domain/resource, we can stop looking for further
 263                // characters.
 264
 265                let domain = domain_check(&unnormalized[..first_index])?;
 266                let resource = resource_check(&unnormalized[first_index + 1..])?;
 267
 268                orig_at = None;
 269                orig_slash = Some(domain.len());
 270                match (domain, resource) {
 271                    (Cow::Borrowed(_), Cow::Borrowed(_)) => unnormalized.to_string(),
 272                    (domain, resource) => format!("{domain}/{resource}"),
 273                }
 274            }
 275        } else {
 276            // Last possible case, just a domain JID.
 277            let domain = domain_check(unnormalized)?;
 278
 279            orig_at = None;
 280            orig_slash = None;
 281            domain.into_owned()
 282        };
 283
 284        Ok(Self {
 285            normalized,
 286            at: orig_at.and_then(|x| NonZeroU16::new(x as u16)),
 287            slash: orig_slash.and_then(|x| NonZeroU16::new(x as u16)),
 288        })
 289    }
 290
 291    /// Returns the inner String of this JID.
 292    pub fn into_inner(self) -> String {
 293        self.normalized
 294    }
 295
 296    /// Build a [`Jid`] from typed parts. This method cannot fail because it uses parts that have
 297    /// already been parsed and stringprepped into [`NodePart`], [`DomainPart`], and [`ResourcePart`].
 298    ///
 299    /// This method allocates and does not consume the typed parts. To avoid
 300    /// allocation if both `node` and `resource` are known to be `None` and
 301    /// `domain` is owned, you can use `domain.into()`.
 302    pub fn from_parts(
 303        node: Option<&NodeRef>,
 304        domain: &DomainRef,
 305        resource: Option<&ResourceRef>,
 306    ) -> Self {
 307        match resource {
 308            Some(resource) => FullJid::from_parts(node, domain, resource).into(),
 309            None => BareJid::from_parts(node, domain).into(),
 310        }
 311    }
 312
 313    /// The optional node part of the JID as reference.
 314    pub fn node(&self) -> Option<&NodeRef> {
 315        self.at.map(|at| {
 316            let at = u16::from(at) as usize;
 317            NodeRef::from_str_unchecked(&self.normalized[..at])
 318        })
 319    }
 320
 321    /// The domain part of the JID as reference
 322    pub fn domain(&self) -> &DomainRef {
 323        match (self.at, self.slash) {
 324            (Some(at), Some(slash)) => {
 325                let at = u16::from(at) as usize;
 326                let slash = u16::from(slash) as usize;
 327                DomainRef::from_str_unchecked(&self.normalized[at + 1..slash])
 328            }
 329            (Some(at), None) => {
 330                let at = u16::from(at) as usize;
 331                DomainRef::from_str_unchecked(&self.normalized[at + 1..])
 332            }
 333            (None, Some(slash)) => {
 334                let slash = u16::from(slash) as usize;
 335                DomainRef::from_str_unchecked(&self.normalized[..slash])
 336            }
 337            (None, None) => DomainRef::from_str_unchecked(&self.normalized),
 338        }
 339    }
 340
 341    /// The optional resource of the Jabber ID. It is guaranteed to be present when the JID is
 342    /// a Full variant, which you can check with [`Jid::is_full`].
 343    pub fn resource(&self) -> Option<&ResourceRef> {
 344        self.slash.map(|slash| {
 345            let slash = u16::from(slash) as usize;
 346            ResourceRef::from_str_unchecked(&self.normalized[slash + 1..])
 347        })
 348    }
 349
 350    /// Allocate a new [`BareJid`] from this JID, discarding the resource.
 351    pub fn to_bare(&self) -> BareJid {
 352        BareJid::from_parts(self.node(), self.domain())
 353    }
 354
 355    /// Transforms this JID into a [`BareJid`], throwing away the resource.
 356    ///
 357    /// ```
 358    /// # use jid::{BareJid, Jid};
 359    /// let jid: Jid = "foo@bar/baz".parse().unwrap();
 360    /// let bare = jid.into_bare();
 361    /// assert_eq!(bare.to_string(), "foo@bar");
 362    /// ```
 363    pub fn into_bare(mut self) -> BareJid {
 364        if let Some(slash) = self.slash {
 365            // truncate the string
 366            self.normalized.truncate(slash.get() as usize);
 367            self.slash = None;
 368        }
 369        BareJid { inner: self }
 370    }
 371
 372    /// Checks if the JID is a full JID.
 373    pub fn is_full(&self) -> bool {
 374        self.slash.is_some()
 375    }
 376
 377    /// Checks if the JID is a bare JID.
 378    pub fn is_bare(&self) -> bool {
 379        self.slash.is_none()
 380    }
 381
 382    /// Return a reference to the canonical string representation of the JID.
 383    pub fn as_str(&self) -> &str {
 384        &self.normalized
 385    }
 386
 387    /// Try to convert this Jid to a [`FullJid`] if it contains a resource
 388    /// and return a [`BareJid`] otherwise.
 389    ///
 390    /// This is useful for match blocks:
 391    ///
 392    /// ```
 393    /// # use jid::Jid;
 394    /// let jid: Jid = "foo@bar".parse().unwrap();
 395    /// match jid.try_into_full() {
 396    ///     Ok(full) => println!("it is full: {:?}", full),
 397    ///     Err(bare) => println!("it is bare: {:?}", bare),
 398    /// }
 399    /// ```
 400    pub fn try_into_full(self) -> Result<FullJid, BareJid> {
 401        if self.slash.is_some() {
 402            Ok(FullJid { inner: self })
 403        } else {
 404            Err(BareJid { inner: self })
 405        }
 406    }
 407
 408    /// Try to convert this Jid reference to a [`&FullJid`][`FullJid`] if it
 409    /// contains a resource and return a [`&BareJid`][`BareJid`] otherwise.
 410    ///
 411    /// This is useful for match blocks:
 412    ///
 413    /// ```
 414    /// # use jid::Jid;
 415    /// let jid: Jid = "foo@bar".parse().unwrap();
 416    /// match jid.try_as_full() {
 417    ///     Ok(full) => println!("it is full: {:?}", full),
 418    ///     Err(bare) => println!("it is bare: {:?}", bare),
 419    /// }
 420    /// ```
 421    pub fn try_as_full(&self) -> Result<&FullJid, &BareJid> {
 422        if self.slash.is_some() {
 423            Ok(unsafe {
 424                // SAFETY: FullJid is #[repr(transparent)] of Jid
 425                // SOUNDNESS: we asserted that self.slash is set above
 426                mem::transmute::<&Jid, &FullJid>(self)
 427            })
 428        } else {
 429            Err(unsafe {
 430                // SAFETY: BareJid is #[repr(transparent)] of Jid
 431                // SOUNDNESS: we asserted that self.slash is unset above
 432                mem::transmute::<&Jid, &BareJid>(self)
 433            })
 434        }
 435    }
 436
 437    /// Try to convert this mutable Jid reference to a
 438    /// [`&mut FullJid`][`FullJid`] if it contains a resource and return a
 439    /// [`&mut BareJid`][`BareJid`] otherwise.
 440    pub fn try_as_full_mut(&mut self) -> Result<&mut FullJid, &mut BareJid> {
 441        if self.slash.is_some() {
 442            Ok(unsafe {
 443                // SAFETY: FullJid is #[repr(transparent)] of Jid
 444                // SOUNDNESS: we asserted that self.slash is set above
 445                mem::transmute::<&mut Jid, &mut FullJid>(self)
 446            })
 447        } else {
 448            Err(unsafe {
 449                // SAFETY: BareJid is #[repr(transparent)] of Jid
 450                // SOUNDNESS: we asserted that self.slash is unset above
 451                mem::transmute::<&mut Jid, &mut BareJid>(self)
 452            })
 453        }
 454    }
 455
 456    #[doc(hidden)]
 457    #[allow(non_snake_case)]
 458    #[deprecated(
 459        since = "0.11.0",
 460        note = "use Jid::from (for construction of Jid values) or Jid::try_into_full/Jid::try_as_full (for match blocks) instead"
 461    )]
 462    pub fn Bare(other: BareJid) -> Self {
 463        Self::from(other)
 464    }
 465
 466    #[doc(hidden)]
 467    #[allow(non_snake_case)]
 468    #[deprecated(
 469        since = "0.11.0",
 470        note = "use Jid::from (for construction of Jid values) or Jid::try_into_full/Jid::try_as_full (for match blocks) instead"
 471    )]
 472    pub fn Full(other: FullJid) -> Self {
 473        Self::from(other)
 474    }
 475}
 476
 477impl TryFrom<Jid> for FullJid {
 478    type Error = Error;
 479
 480    fn try_from(inner: Jid) -> Result<Self, Self::Error> {
 481        if inner.slash.is_none() {
 482            return Err(Error::ResourceMissingInFullJid);
 483        }
 484        Ok(Self { inner })
 485    }
 486}
 487
 488impl TryFrom<Jid> for BareJid {
 489    type Error = Error;
 490
 491    fn try_from(inner: Jid) -> Result<Self, Self::Error> {
 492        if inner.slash.is_some() {
 493            return Err(Error::ResourceInBareJid);
 494        }
 495        Ok(Self { inner })
 496    }
 497}
 498
 499impl PartialEq<Jid> for FullJid {
 500    fn eq(&self, other: &Jid) -> bool {
 501        &self.inner == other
 502    }
 503}
 504
 505impl PartialEq<Jid> for BareJid {
 506    fn eq(&self, other: &Jid) -> bool {
 507        &self.inner == other
 508    }
 509}
 510
 511impl PartialEq<FullJid> for Jid {
 512    fn eq(&self, other: &FullJid) -> bool {
 513        self == &other.inner
 514    }
 515}
 516
 517impl PartialEq<BareJid> for Jid {
 518    fn eq(&self, other: &BareJid) -> bool {
 519        self == &other.inner
 520    }
 521}
 522
 523/// A struct representing a full Jabber ID, with a resource part.
 524///
 525/// A full JID is composed of 3 components, of which only the node is optional:
 526///
 527/// - the (optional) node part is the part before the (optional) `@`.
 528/// - the domain part is the mandatory part between the (optional) `@` and before the `/`.
 529/// - the resource part after the `/`.
 530///
 531/// Unlike a [`BareJid`], it always contains a resource, and should only be used when you are
 532/// certain there is no case where a resource can be missing.  Otherwise, use a [`Jid`] or
 533/// [`BareJid`].
 534#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
 535#[repr(transparent)] // WARNING: Jid::try_as_* relies on this for safety!
 536pub struct FullJid {
 537    inner: Jid,
 538}
 539
 540/// A struct representing a bare Jabber ID, without a resource part.
 541///
 542/// A bare JID is composed of 2 components, of which only the node is optional:
 543/// - the (optional) node part is the part before the (optional) `@`.
 544/// - the domain part is the mandatory part between the (optional) `@` and before the `/`.
 545///
 546/// Unlike a [`FullJid`], it can’t contain a resource, and should only be used when you are certain
 547/// there is no case where a resource can be set.  Otherwise, use a [`Jid`] or [`FullJid`].
 548#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
 549#[repr(transparent)] // WARNING: Jid::try_as_* relies on this for safety!
 550pub struct BareJid {
 551    inner: Jid,
 552}
 553
 554impl Deref for FullJid {
 555    type Target = Jid;
 556
 557    fn deref(&self) -> &Self::Target {
 558        &self.inner
 559    }
 560}
 561
 562impl Deref for BareJid {
 563    type Target = Jid;
 564
 565    fn deref(&self) -> &Self::Target {
 566        &self.inner
 567    }
 568}
 569
 570impl Borrow<Jid> for FullJid {
 571    fn borrow(&self) -> &Jid {
 572        &self.inner
 573    }
 574}
 575
 576impl Borrow<Jid> for BareJid {
 577    fn borrow(&self) -> &Jid {
 578        &self.inner
 579    }
 580}
 581
 582impl fmt::Debug for Jid {
 583    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
 584        fmt.debug_tuple("Jid").field(&self.normalized).finish()
 585    }
 586}
 587
 588impl fmt::Debug for FullJid {
 589    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
 590        fmt.debug_tuple("FullJid")
 591            .field(&self.inner.normalized)
 592            .finish()
 593    }
 594}
 595
 596impl fmt::Debug for BareJid {
 597    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
 598        fmt.debug_tuple("BareJid")
 599            .field(&self.inner.normalized)
 600            .finish()
 601    }
 602}
 603
 604impl fmt::Display for FullJid {
 605    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
 606        fmt::Display::fmt(&self.inner, fmt)
 607    }
 608}
 609
 610impl fmt::Display for BareJid {
 611    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
 612        fmt::Display::fmt(&self.inner, fmt)
 613    }
 614}
 615
 616#[cfg(feature = "serde")]
 617impl Serialize for Jid {
 618    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
 619    where
 620        S: Serializer,
 621    {
 622        serializer.serialize_str(&self.normalized)
 623    }
 624}
 625
 626#[cfg(feature = "serde")]
 627impl Serialize for FullJid {
 628    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
 629    where
 630        S: Serializer,
 631    {
 632        self.inner.serialize(serializer)
 633    }
 634}
 635
 636#[cfg(feature = "serde")]
 637impl Serialize for BareJid {
 638    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
 639    where
 640        S: Serializer,
 641    {
 642        self.inner.serialize(serializer)
 643    }
 644}
 645
 646impl FromStr for FullJid {
 647    type Err = Error;
 648
 649    fn from_str(s: &str) -> Result<Self, Self::Err> {
 650        Self::new(s)
 651    }
 652}
 653
 654#[cfg(feature = "serde")]
 655impl<'de> Deserialize<'de> for Jid {
 656    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
 657    where
 658        D: Deserializer<'de>,
 659    {
 660        let s = String::deserialize(deserializer)?;
 661        Jid::new(&s).map_err(de::Error::custom)
 662    }
 663}
 664
 665#[cfg(feature = "serde")]
 666impl<'de> Deserialize<'de> for FullJid {
 667    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
 668    where
 669        D: Deserializer<'de>,
 670    {
 671        let jid = Jid::deserialize(deserializer)?;
 672        jid.try_into().map_err(de::Error::custom)
 673    }
 674}
 675
 676#[cfg(feature = "serde")]
 677impl<'de> Deserialize<'de> for BareJid {
 678    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
 679    where
 680        D: Deserializer<'de>,
 681    {
 682        let jid = Jid::deserialize(deserializer)?;
 683        jid.try_into().map_err(de::Error::custom)
 684    }
 685}
 686
 687#[cfg(feature = "quote")]
 688impl ToTokens for Jid {
 689    fn to_tokens(&self, tokens: &mut TokenStream) {
 690        let s = &self.normalized;
 691        tokens.extend(quote! {
 692            jid::Jid::new(#s).unwrap()
 693        });
 694    }
 695}
 696
 697#[cfg(feature = "quote")]
 698impl ToTokens for FullJid {
 699    fn to_tokens(&self, tokens: &mut TokenStream) {
 700        let s = &self.inner.normalized;
 701        tokens.extend(quote! {
 702            jid::FullJid::new(#s).unwrap()
 703        });
 704    }
 705}
 706
 707#[cfg(feature = "quote")]
 708impl ToTokens for BareJid {
 709    fn to_tokens(&self, tokens: &mut TokenStream) {
 710        let s = &self.inner.normalized;
 711        tokens.extend(quote! {
 712            jid::BareJid::new(#s).unwrap()
 713        });
 714    }
 715}
 716
 717impl FullJid {
 718    /// Constructs a full Jabber ID containing all three components. This is of the form
 719    /// `node@domain/resource`, where node part is optional.
 720    /// If you want a non-fallible version, use [`FullJid::from_parts`] instead.
 721    ///
 722    /// # Examples
 723    ///
 724    /// ```
 725    /// use jid::FullJid;
 726    /// # use jid::Error;
 727    ///
 728    /// # fn main() -> Result<(), Error> {
 729    /// let jid = FullJid::new("node@domain/resource")?;
 730    ///
 731    /// assert_eq!(jid.node().map(|x| x.as_str()), Some("node"));
 732    /// assert_eq!(jid.domain().as_str(), "domain");
 733    /// assert_eq!(jid.resource().as_str(), "resource");
 734    /// # Ok(())
 735    /// # }
 736    /// ```
 737    pub fn new(unnormalized: &str) -> Result<Self, Error> {
 738        Jid::new(unnormalized)?.try_into()
 739    }
 740
 741    /// Build a [`FullJid`] from typed parts. This method cannot fail because it uses parts that have
 742    /// already been parsed and stringprepped into [`NodePart`], [`DomainPart`], and [`ResourcePart`].
 743    /// This method allocates and does not consume the typed parts.
 744    pub fn from_parts(
 745        node: Option<&NodeRef>,
 746        domain: &DomainRef,
 747        resource: &ResourceRef,
 748    ) -> FullJid {
 749        let (at, slash, normalized);
 750
 751        if let Some(node) = node {
 752            // Parts are never empty so len > 0 for NonZeroU16::new is always Some
 753            at = NonZeroU16::new(node.len() as u16);
 754            slash = NonZeroU16::new((node.len() + 1 + domain.len()) as u16);
 755            normalized = format!("{node}@{domain}/{resource}");
 756        } else {
 757            at = None;
 758            slash = NonZeroU16::new(domain.len() as u16);
 759            normalized = format!("{domain}/{resource}");
 760        }
 761
 762        let inner = Jid {
 763            normalized,
 764            at,
 765            slash,
 766        };
 767
 768        Self { inner }
 769    }
 770
 771    /// The optional resource of the Jabber ID.  Since this is a full JID it is always present.
 772    pub fn resource(&self) -> &ResourceRef {
 773        self.inner.resource().unwrap()
 774    }
 775
 776    /// Return the inner String of this full JID.
 777    pub fn into_inner(self) -> String {
 778        self.inner.into_inner()
 779    }
 780
 781    /// Transforms this full JID into a [`BareJid`], throwing away the
 782    /// resource.
 783    ///
 784    /// ```
 785    /// # use jid::{BareJid, FullJid};
 786    /// let jid: FullJid = "foo@bar/baz".parse().unwrap();
 787    /// let bare = jid.into_bare();
 788    /// assert_eq!(bare.to_string(), "foo@bar");
 789    /// ```
 790    pub fn into_bare(self) -> BareJid {
 791        self.inner.into_bare()
 792    }
 793}
 794
 795impl FromStr for BareJid {
 796    type Err = Error;
 797
 798    fn from_str(s: &str) -> Result<Self, Self::Err> {
 799        Self::new(s)
 800    }
 801}
 802
 803impl BareJid {
 804    /// Constructs a bare Jabber ID, containing two components. This is of the form
 805    /// `node`@`domain`, where node part is optional.
 806    /// If you want a non-fallible version, use [`BareJid::from_parts`] instead.
 807    ///
 808    /// # Examples
 809    ///
 810    /// ```
 811    /// use jid::BareJid;
 812    /// # use jid::Error;
 813    ///
 814    /// # fn main() -> Result<(), Error> {
 815    /// let jid = BareJid::new("node@domain")?;
 816    ///
 817    /// assert_eq!(jid.node().map(|x| x.as_str()), Some("node"));
 818    /// assert_eq!(jid.domain().as_str(), "domain");
 819    /// # Ok(())
 820    /// # }
 821    /// ```
 822    pub fn new(unnormalized: &str) -> Result<Self, Error> {
 823        Jid::new(unnormalized)?.try_into()
 824    }
 825
 826    /// Build a [`BareJid`] from typed parts. This method cannot fail because it uses parts that have
 827    /// already been parsed and stringprepped into [`NodePart`] and [`DomainPart`].
 828    ///
 829    /// This method allocates and does not consume the typed parts. To avoid
 830    /// allocation if `node` is known to be `None` and `domain` is owned, you
 831    /// can use `domain.into()`.
 832    pub fn from_parts(node: Option<&NodeRef>, domain: &DomainRef) -> Self {
 833        let (at, normalized);
 834
 835        if let Some(node) = node {
 836            // Parts are never empty so len > 0 for NonZeroU16::new is always Some
 837            at = NonZeroU16::new(node.len() as u16);
 838            normalized = format!("{node}@{domain}");
 839        } else {
 840            at = None;
 841            normalized = domain.to_string();
 842        }
 843
 844        let inner = Jid {
 845            normalized,
 846            at,
 847            slash: None,
 848        };
 849
 850        Self { inner }
 851    }
 852
 853    /// Constructs a [`BareJid`] from the bare JID, by specifying a [`ResourcePart`].
 854    /// If you'd like to specify a stringy resource, use [`BareJid::with_resource_str`] instead.
 855    ///
 856    /// # Examples
 857    ///
 858    /// ```
 859    /// use jid::{BareJid, ResourcePart};
 860    ///
 861    /// let resource = ResourcePart::new("resource").unwrap();
 862    /// let bare = BareJid::new("node@domain").unwrap();
 863    /// let full = bare.with_resource(&resource);
 864    ///
 865    /// assert_eq!(full.node().map(|x| x.as_str()), Some("node"));
 866    /// assert_eq!(full.domain().as_str(), "domain");
 867    /// assert_eq!(full.resource().as_str(), "resource");
 868    /// ```
 869    pub fn with_resource(&self, resource: &ResourceRef) -> FullJid {
 870        let slash = NonZeroU16::new(self.inner.normalized.len() as u16);
 871        let normalized = format!("{}/{resource}", self.inner.normalized);
 872        let inner = Jid {
 873            normalized,
 874            at: self.inner.at,
 875            slash,
 876        };
 877
 878        FullJid { inner }
 879    }
 880
 881    /// Constructs a [`FullJid`] from the bare JID, by specifying a stringy `resource`.
 882    /// If your resource has already been parsed into a [`ResourcePart`], use [`BareJid::with_resource`].
 883    ///
 884    /// # Examples
 885    ///
 886    /// ```
 887    /// use jid::BareJid;
 888    ///
 889    /// let bare = BareJid::new("node@domain").unwrap();
 890    /// let full = bare.with_resource_str("resource").unwrap();
 891    ///
 892    /// assert_eq!(full.node().map(|x| x.as_str()), Some("node"));
 893    /// assert_eq!(full.domain().as_str(), "domain");
 894    /// assert_eq!(full.resource().as_str(), "resource");
 895    /// ```
 896    pub fn with_resource_str(&self, resource: &str) -> Result<FullJid, Error> {
 897        let resource = ResourcePart::new(resource)?;
 898        Ok(self.with_resource(&resource))
 899    }
 900
 901    /// Return the inner String of this bare JID.
 902    pub fn into_inner(self) -> String {
 903        self.inner.into_inner()
 904    }
 905}
 906
 907#[cfg(feature = "minidom")]
 908impl IntoAttributeValue for Jid {
 909    fn into_attribute_value(self) -> Option<String> {
 910        Some(self.to_string())
 911    }
 912}
 913
 914#[cfg(feature = "minidom")]
 915impl From<Jid> for Node {
 916    fn from(jid: Jid) -> Self {
 917        Node::Text(jid.to_string())
 918    }
 919}
 920
 921#[cfg(feature = "minidom")]
 922impl IntoAttributeValue for FullJid {
 923    fn into_attribute_value(self) -> Option<String> {
 924        self.inner.into_attribute_value()
 925    }
 926}
 927
 928#[cfg(feature = "minidom")]
 929impl From<FullJid> for Node {
 930    fn from(jid: FullJid) -> Self {
 931        jid.inner.into()
 932    }
 933}
 934
 935#[cfg(feature = "minidom")]
 936impl IntoAttributeValue for BareJid {
 937    fn into_attribute_value(self) -> Option<String> {
 938        self.inner.into_attribute_value()
 939    }
 940}
 941
 942#[cfg(feature = "minidom")]
 943impl From<BareJid> for Node {
 944    fn from(other: BareJid) -> Self {
 945        other.inner.into()
 946    }
 947}
 948
 949#[cfg(test)]
 950mod tests {
 951    use super::*;
 952
 953    use alloc::{
 954        collections::{BTreeMap, BTreeSet},
 955        vec::Vec,
 956    };
 957
 958    macro_rules! assert_size (
 959        ($t:ty, $sz:expr) => (
 960            assert_eq!(::core::mem::size_of::<$t>(), $sz);
 961        );
 962    );
 963
 964    #[cfg(target_pointer_width = "32")]
 965    #[test]
 966    fn test_size() {
 967        assert_size!(BareJid, 16);
 968        assert_size!(FullJid, 16);
 969        assert_size!(Jid, 16);
 970    }
 971
 972    #[cfg(target_pointer_width = "64")]
 973    #[test]
 974    fn test_size() {
 975        assert_size!(BareJid, 32);
 976        assert_size!(FullJid, 32);
 977        assert_size!(Jid, 32);
 978    }
 979
 980    #[test]
 981    fn can_parse_full_jids() {
 982        assert_eq!(
 983            FullJid::from_str("a@b.c/d"),
 984            Ok(FullJid::new("a@b.c/d").unwrap())
 985        );
 986        assert_eq!(
 987            FullJid::from_str("b.c/d"),
 988            Ok(FullJid::new("b.c/d").unwrap())
 989        );
 990
 991        assert_eq!(
 992            FullJid::from_str("a@b.c"),
 993            Err(Error::ResourceMissingInFullJid)
 994        );
 995        assert_eq!(
 996            FullJid::from_str("b.c"),
 997            Err(Error::ResourceMissingInFullJid)
 998        );
 999    }
1000
1001    #[test]
1002    fn can_parse_bare_jids() {
1003        assert_eq!(
1004            BareJid::from_str("a@b.c"),
1005            Ok(BareJid::new("a@b.c").unwrap())
1006        );
1007        assert_eq!(BareJid::from_str("b.c"), Ok(BareJid::new("b.c").unwrap()));
1008    }
1009
1010    #[test]
1011    fn can_parse_jids() {
1012        let full = FullJid::from_str("a@b.c/d").unwrap();
1013        let bare = BareJid::from_str("e@f.g").unwrap();
1014
1015        assert_eq!(Jid::from_str("a@b.c/d").unwrap(), full);
1016        assert_eq!(Jid::from_str("e@f.g").unwrap(), bare);
1017    }
1018
1019    #[test]
1020    fn full_to_bare_jid() {
1021        let bare: BareJid = FullJid::new("a@b.c/d").unwrap().to_bare();
1022        assert_eq!(bare, BareJid::new("a@b.c").unwrap());
1023    }
1024
1025    #[test]
1026    fn bare_to_full_jid_str() {
1027        assert_eq!(
1028            BareJid::new("a@b.c")
1029                .unwrap()
1030                .with_resource_str("d")
1031                .unwrap(),
1032            FullJid::new("a@b.c/d").unwrap()
1033        );
1034    }
1035
1036    #[test]
1037    fn bare_to_full_jid() {
1038        assert_eq!(
1039            BareJid::new("a@b.c")
1040                .unwrap()
1041                .with_resource(&ResourcePart::new("d").unwrap()),
1042            FullJid::new("a@b.c/d").unwrap()
1043        )
1044    }
1045
1046    #[test]
1047    fn node_from_jid() {
1048        let jid = Jid::new("a@b.c/d").unwrap();
1049
1050        assert_eq!(jid.node().map(|x| x.as_str()), Some("a"),);
1051    }
1052
1053    #[test]
1054    fn domain_from_jid() {
1055        let jid = Jid::new("a@b.c").unwrap();
1056
1057        assert_eq!(jid.domain().as_str(), "b.c");
1058    }
1059
1060    #[test]
1061    fn resource_from_jid() {
1062        let jid = Jid::new("a@b.c/d").unwrap();
1063
1064        assert_eq!(jid.resource().map(|x| x.as_str()), Some("d"),);
1065    }
1066
1067    #[test]
1068    fn jid_to_full_bare() {
1069        let full = FullJid::new("a@b.c/d").unwrap();
1070        let bare = BareJid::new("a@b.c").unwrap();
1071
1072        assert_eq!(FullJid::try_from(Jid::from(full.clone())), Ok(full.clone()));
1073        assert_eq!(
1074            FullJid::try_from(Jid::from(bare.clone())),
1075            Err(Error::ResourceMissingInFullJid),
1076        );
1077        assert_eq!(Jid::from(full.clone().to_bare()), bare.clone());
1078        assert_eq!(Jid::from(bare.clone()), bare);
1079    }
1080
1081    #[test]
1082    fn serialise() {
1083        assert_eq!(FullJid::new("a@b/c").unwrap().to_string(), "a@b/c");
1084        assert_eq!(BareJid::new("a@b").unwrap().to_string(), "a@b");
1085    }
1086
1087    #[test]
1088    fn hash() {
1089        let _map: BTreeMap<Jid, String> = BTreeMap::new();
1090    }
1091
1092    #[test]
1093    fn invalid_jids() {
1094        assert_eq!(BareJid::from_str(""), Err(Error::Idna));
1095        assert_eq!(BareJid::from_str("/c"), Err(Error::Idna));
1096        assert_eq!(BareJid::from_str("a@/c"), Err(Error::Idna));
1097        assert_eq!(BareJid::from_str("@b"), Err(Error::NodeEmpty));
1098        assert_eq!(BareJid::from_str("b/"), Err(Error::ResourceEmpty));
1099
1100        assert_eq!(FullJid::from_str(""), Err(Error::Idna));
1101        assert_eq!(FullJid::from_str("/c"), Err(Error::Idna));
1102        assert_eq!(FullJid::from_str("a@/c"), Err(Error::Idna));
1103        assert_eq!(FullJid::from_str("@b"), Err(Error::NodeEmpty));
1104        assert_eq!(FullJid::from_str("b/"), Err(Error::ResourceEmpty));
1105        assert_eq!(
1106            FullJid::from_str("a@b"),
1107            Err(Error::ResourceMissingInFullJid)
1108        );
1109        assert_eq!(BareJid::from_str("a@b/c"), Err(Error::ResourceInBareJid));
1110        assert_eq!(BareJid::from_str("a@b@c"), Err(Error::TooManyAts));
1111        assert_eq!(FullJid::from_str("a@b@c/d"), Err(Error::TooManyAts));
1112    }
1113
1114    #[test]
1115    fn display_jids() {
1116        assert_eq!(FullJid::new("a@b/c").unwrap().to_string(), "a@b/c");
1117        assert_eq!(BareJid::new("a@b").unwrap().to_string(), "a@b");
1118        assert_eq!(
1119            Jid::from(FullJid::new("a@b/c").unwrap()).to_string(),
1120            "a@b/c"
1121        );
1122        assert_eq!(Jid::from(BareJid::new("a@b").unwrap()).to_string(), "a@b");
1123    }
1124
1125    #[cfg(feature = "minidom")]
1126    #[test]
1127    fn minidom() {
1128        let elem: minidom::Element = "<message xmlns='ns1' from='a@b/c'/>".parse().unwrap();
1129        let to: Jid = elem.attr("from").unwrap().parse().unwrap();
1130        assert_eq!(to, Jid::from(FullJid::new("a@b/c").unwrap()));
1131
1132        let elem: minidom::Element = "<message xmlns='ns1' from='a@b'/>".parse().unwrap();
1133        let to: Jid = elem.attr("from").unwrap().parse().unwrap();
1134        assert_eq!(to, Jid::from(BareJid::new("a@b").unwrap()));
1135
1136        let elem: minidom::Element = "<message xmlns='ns1' from='a@b/c'/>".parse().unwrap();
1137        let to: FullJid = elem.attr("from").unwrap().parse().unwrap();
1138        assert_eq!(to, FullJid::new("a@b/c").unwrap());
1139
1140        let elem: minidom::Element = "<message xmlns='ns1' from='a@b'/>".parse().unwrap();
1141        let to: BareJid = elem.attr("from").unwrap().parse().unwrap();
1142        assert_eq!(to, BareJid::new("a@b").unwrap());
1143    }
1144
1145    #[cfg(feature = "minidom")]
1146    #[test]
1147    fn minidom_into_attr() {
1148        let full = FullJid::new("a@b/c").unwrap();
1149        let elem = minidom::Element::builder("message", "jabber:client")
1150            .attr("from".try_into().unwrap(), full.clone())
1151            .build();
1152        assert_eq!(elem.attr("from"), Some(full.to_string().as_str()));
1153
1154        let bare = BareJid::new("a@b").unwrap();
1155        let elem = minidom::Element::builder("message", "jabber:client")
1156            .attr("from".try_into().unwrap(), bare.clone())
1157            .build();
1158        assert_eq!(elem.attr("from"), Some(bare.to_string().as_str()));
1159
1160        let jid = Jid::from(bare.clone());
1161        let _elem = minidom::Element::builder("message", "jabber:client")
1162            .attr("from".try_into().unwrap(), jid)
1163            .build();
1164        assert_eq!(elem.attr("from"), Some(bare.to_string().as_str()));
1165    }
1166
1167    #[test]
1168    fn stringprep() {
1169        let full = FullJid::from_str("Test@☃.coM/Test™").unwrap();
1170        let equiv = FullJid::new("test@☃.com/TestTM").unwrap();
1171        assert_eq!(full, equiv);
1172    }
1173
1174    #[test]
1175    fn invalid_stringprep() {
1176        FullJid::from_str("a@b/🎉").unwrap_err();
1177    }
1178
1179    #[test]
1180    fn idna() {
1181        let bare = BareJid::from_str("Weiß.com.").unwrap();
1182        let equiv = BareJid::new("weiss.com").unwrap();
1183        assert_eq!(bare, equiv);
1184        BareJid::from_str("127.0.0.1").unwrap();
1185        BareJid::from_str("[::1]").unwrap();
1186        BareJid::from_str("domain.tld.").unwrap();
1187    }
1188
1189    #[test]
1190    fn invalid_idna() {
1191        BareJid::from_str("a@b@c").unwrap_err();
1192        FullJid::from_str("a@b@c/d").unwrap_err();
1193        BareJid::from_str("[::1234").unwrap_err();
1194        BareJid::from_str("1::1234]").unwrap_err();
1195        BareJid::from_str("domain.tld:5222").unwrap_err();
1196        BareJid::from_str("-domain.tld").unwrap_err();
1197        BareJid::from_str("domain.tld-").unwrap_err();
1198        BareJid::from_str("domain..tld").unwrap_err();
1199        BareJid::from_str("domain.tld..").unwrap_err();
1200        BareJid::from_str("1234567890123456789012345678901234567890123456789012345678901234.com")
1201            .unwrap_err();
1202    }
1203
1204    #[test]
1205    fn jid_from_parts() {
1206        let node = NodePart::new("node").unwrap();
1207        let domain = DomainPart::new("domain").unwrap();
1208        let resource = ResourcePart::new("resource").unwrap();
1209
1210        let jid = Jid::from_parts(Some(&node), &domain, Some(&resource));
1211        assert_eq!(jid, Jid::new("node@domain/resource").unwrap());
1212
1213        let barejid = BareJid::from_parts(Some(&node), &domain);
1214        assert_eq!(barejid, BareJid::new("node@domain").unwrap());
1215
1216        let fulljid = FullJid::from_parts(Some(&node), &domain, &resource);
1217        assert_eq!(fulljid, FullJid::new("node@domain/resource").unwrap());
1218    }
1219
1220    #[test]
1221    #[cfg(feature = "serde")]
1222    fn jid_ser_de() {
1223        let jid: Jid = Jid::new("node@domain").unwrap();
1224        serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain")]);
1225
1226        let jid: Jid = Jid::new("node@domain/resource").unwrap();
1227        serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain/resource")]);
1228
1229        let jid: BareJid = BareJid::new("node@domain").unwrap();
1230        serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain")]);
1231
1232        let jid: FullJid = FullJid::new("node@domain/resource").unwrap();
1233        serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain/resource")]);
1234    }
1235
1236    #[test]
1237    fn jid_into_parts_and_from_parts() {
1238        let node = NodePart::new("node").unwrap();
1239        let domain = DomainPart::new("domain").unwrap();
1240
1241        let jid1 = domain.with_node(&node);
1242        let jid2 = node.with_domain(&domain);
1243        let jid3 = BareJid::new("node@domain").unwrap();
1244        assert_eq!(jid1, jid2);
1245        assert_eq!(jid2, jid3);
1246    }
1247
1248    #[test]
1249    fn jid_match_replacement_try_as() {
1250        let jid1 = Jid::new("foo@bar").unwrap();
1251        let jid2 = Jid::new("foo@bar/baz").unwrap();
1252
1253        match jid1.try_as_full() {
1254            Err(_) => (),
1255            other => panic!("unexpected result: {:?}", other),
1256        };
1257
1258        match jid2.try_as_full() {
1259            Ok(_) => (),
1260            other => panic!("unexpected result: {:?}", other),
1261        };
1262    }
1263
1264    #[test]
1265    fn jid_match_replacement_try_as_mut() {
1266        let mut jid1 = Jid::new("foo@bar").unwrap();
1267        let mut jid2 = Jid::new("foo@bar/baz").unwrap();
1268
1269        match jid1.try_as_full_mut() {
1270            Err(_) => (),
1271            other => panic!("unexpected result: {:?}", other),
1272        };
1273
1274        match jid2.try_as_full_mut() {
1275            Ok(_) => (),
1276            other => panic!("unexpected result: {:?}", other),
1277        };
1278    }
1279
1280    #[test]
1281    fn jid_match_replacement_try_into() {
1282        let jid1 = Jid::new("foo@bar").unwrap();
1283        let jid2 = Jid::new("foo@bar/baz").unwrap();
1284
1285        match jid1.try_as_full() {
1286            Err(_) => (),
1287            other => panic!("unexpected result: {:?}", other),
1288        };
1289
1290        match jid2.try_as_full() {
1291            Ok(_) => (),
1292            other => panic!("unexpected result: {:?}", other),
1293        };
1294    }
1295
1296    #[test]
1297    fn lookup_jid_by_full_jid() {
1298        let mut map: BTreeSet<Jid> = BTreeSet::new();
1299        let jid1 = Jid::new("foo@bar").unwrap();
1300        let jid2 = Jid::new("foo@bar/baz").unwrap();
1301        let jid3 = FullJid::new("foo@bar/baz").unwrap();
1302
1303        map.insert(jid1);
1304        assert!(!map.contains(&jid2));
1305        assert!(!map.contains(&jid3));
1306        map.insert(jid2);
1307        assert!(map.contains(&jid3));
1308    }
1309
1310    #[test]
1311    fn lookup_full_jid_by_jid() {
1312        let mut map: BTreeSet<FullJid> = BTreeSet::new();
1313        let jid1 = FullJid::new("foo@bar/baz").unwrap();
1314        let jid2 = FullJid::new("foo@bar/fnord").unwrap();
1315        let jid3 = Jid::new("foo@bar/fnord").unwrap();
1316
1317        map.insert(jid1);
1318        assert!(!map.contains(&jid2));
1319        assert!(!map.contains(&jid3));
1320        map.insert(jid2);
1321        assert!(map.contains(&jid3));
1322    }
1323
1324    #[test]
1325    fn lookup_bare_jid_by_jid() {
1326        let mut map: BTreeSet<BareJid> = BTreeSet::new();
1327        let jid1 = BareJid::new("foo@bar").unwrap();
1328        let jid2 = BareJid::new("foo@baz").unwrap();
1329        let jid3 = Jid::new("foo@baz").unwrap();
1330
1331        map.insert(jid1);
1332        assert!(!map.contains(&jid2));
1333        assert!(!map.contains(&jid3));
1334        map.insert(jid2);
1335        assert!(map.contains(&jid3));
1336    }
1337
1338    #[test]
1339    fn normalizes_all_parts() {
1340        assert_eq!(
1341            Jid::new("ßA@IX.test/\u{2168}").unwrap().as_str(),
1342            "ssa@ix.test/IX"
1343        );
1344    }
1345
1346    #[test]
1347    fn rejects_unassigned_codepoints() {
1348        match Jid::new("\u{01f601}@example.com") {
1349            Err(Error::NodePrep) => (),
1350            other => panic!("unexpected result: {:?}", other),
1351        };
1352
1353        match Jid::new("foo@\u{01f601}.example.com") {
1354            Err(Error::NamePrep) => (),
1355            other => panic!("unexpected result: {:?}", other),
1356        };
1357
1358        match Jid::new("foo@example.com/\u{01f601}") {
1359            Err(Error::ResourcePrep) => (),
1360            other => panic!("unexpected result: {:?}", other),
1361        };
1362    }
1363
1364    #[test]
1365    fn accepts_domain_only_jid() {
1366        match Jid::new("example.com") {
1367            Ok(_) => (),
1368            other => panic!("unexpected result: {:?}", other),
1369        };
1370
1371        match BareJid::new("example.com") {
1372            Ok(_) => (),
1373            other => panic!("unexpected result: {:?}", other),
1374        };
1375
1376        match FullJid::new("example.com/x") {
1377            Ok(_) => (),
1378            other => panic!("unexpected result: {:?}", other),
1379        };
1380    }
1381
1382    #[test]
1383    fn is_bare_returns_true_iff_bare() {
1384        let bare = Jid::new("foo@bar").unwrap();
1385        let full = Jid::new("foo@bar/baz").unwrap();
1386
1387        assert!(bare.is_bare());
1388        assert!(!full.is_bare());
1389    }
1390
1391    #[test]
1392    fn is_full_returns_true_iff_full() {
1393        let bare = Jid::new("foo@bar").unwrap();
1394        let full = Jid::new("foo@bar/baz").unwrap();
1395
1396        assert!(!bare.is_full());
1397        assert!(full.is_full());
1398    }
1399
1400    #[test]
1401    fn reject_long_localpart() {
1402        let mut long = Vec::with_capacity(1028);
1403        long.resize(1024, b'a');
1404        let mut long = String::from_utf8(long).unwrap();
1405        long.push_str("@foo");
1406
1407        match Jid::new(&long) {
1408            Err(Error::NodeTooLong) => (),
1409            other => panic!("unexpected result: {:?}", other),
1410        }
1411
1412        match BareJid::new(&long) {
1413            Err(Error::NodeTooLong) => (),
1414            other => panic!("unexpected result: {:?}", other),
1415        }
1416    }
1417
1418    #[test]
1419    fn reject_long_domainpart() {
1420        let mut long = Vec::with_capacity(66);
1421        long.push(b'x');
1422        long.push(b'@');
1423        long.resize(66, b'a');
1424        let long = String::from_utf8(long).unwrap();
1425
1426        Jid::new(&long).unwrap_err();
1427        BareJid::new(&long).unwrap_err();
1428
1429        // A domain can be up to 253 bytes.
1430        let mut long = Vec::with_capacity(256);
1431        long.push(b'x');
1432        long.push(b'@');
1433        long.resize(65, b'a');
1434        long.push(b'.');
1435        long.resize(129, b'b');
1436        long.push(b'.');
1437        long.resize(193, b'c');
1438        long.push(b'.');
1439        long.resize(256, b'd');
1440        let long = String::from_utf8(long).unwrap();
1441
1442        Jid::new(&long).unwrap_err();
1443        BareJid::new(&long).unwrap_err();
1444    }
1445
1446    #[test]
1447    fn reject_long_resourcepart() {
1448        let mut long = Vec::with_capacity(1028);
1449        long.push(b'x');
1450        long.push(b'@');
1451        long.push(b'y');
1452        long.push(b'/');
1453        long.resize(1028, b'a');
1454        let long = String::from_utf8(long).unwrap();
1455
1456        match Jid::new(&long) {
1457            Err(Error::ResourceTooLong) => (),
1458            other => panic!("unexpected result: {:?}", other),
1459        }
1460
1461        match FullJid::new(&long) {
1462            Err(Error::ResourceTooLong) => (),
1463            other => panic!("unexpected result: {:?}", other),
1464        }
1465    }
1466}