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