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