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