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        Some(self.cmp(other))
 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);
 691
 692        if let Some(node) = node {
 693            // Parts are never empty so len > 0 for NonZeroU16::new is always Some
 694            at = NonZeroU16::new(node.len() as u16);
 695            slash = NonZeroU16::new((node.len() + 1 + domain.len()) as u16);
 696            normalized = format!("{node}@{domain}/{resource}");
 697        } else {
 698            at = None;
 699            slash = NonZeroU16::new(domain.len() as u16);
 700            normalized = format!("{domain}/{resource}");
 701        }
 702
 703        let inner = Jid {
 704            normalized,
 705            at,
 706            slash,
 707        };
 708
 709        Self { inner }
 710    }
 711
 712    /// The optional resource of the Jabber ID.  Since this is a full JID it is always present.
 713    pub fn resource(&self) -> &ResourceRef {
 714        self.inner.resource().unwrap()
 715    }
 716
 717    /// Return the inner String of this full JID.
 718    pub fn into_inner(self) -> String {
 719        self.inner.into_inner()
 720    }
 721
 722    /// Transforms this full JID into a [`BareJid`], throwing away the
 723    /// resource.
 724    ///
 725    /// ```
 726    /// # use jid::{BareJid, FullJid};
 727    /// let jid: FullJid = "foo@bar/baz".parse().unwrap();
 728    /// let bare = jid.into_bare();
 729    /// assert_eq!(bare.to_string(), "foo@bar");
 730    /// ```
 731    pub fn into_bare(self) -> BareJid {
 732        self.inner.into_bare()
 733    }
 734}
 735
 736impl FromStr for BareJid {
 737    type Err = Error;
 738
 739    fn from_str(s: &str) -> Result<Self, Self::Err> {
 740        Self::new(s)
 741    }
 742}
 743
 744impl BareJid {
 745    /// Constructs a bare Jabber ID, containing two components. This is of the form
 746    /// `node`@`domain`, where node part is optional.
 747    /// If you want a non-fallible version, use [`BareJid::from_parts`] instead.
 748    ///
 749    /// # Examples
 750    ///
 751    /// ```
 752    /// use jid::BareJid;
 753    /// # use jid::Error;
 754    ///
 755    /// # fn main() -> Result<(), Error> {
 756    /// let jid = BareJid::new("node@domain")?;
 757    ///
 758    /// assert_eq!(jid.node().map(|x| x.as_str()), Some("node"));
 759    /// assert_eq!(jid.domain().as_str(), "domain");
 760    /// # Ok(())
 761    /// # }
 762    /// ```
 763    pub fn new(unnormalized: &str) -> Result<Self, Error> {
 764        Jid::new(unnormalized)?.try_into()
 765    }
 766
 767    /// Build a [`BareJid`] from typed parts. This method cannot fail because it uses parts that have
 768    /// already been parsed and stringprepped into [`NodePart`] and [`DomainPart`].
 769    ///
 770    /// This method allocates and does not consume the typed parts. To avoid
 771    /// allocation if `node` is known to be `None` and `domain` is owned, you
 772    /// can use `domain.into()`.
 773    pub fn from_parts(node: Option<&NodeRef>, domain: &DomainRef) -> Self {
 774        let (at, normalized);
 775
 776        if let Some(node) = node {
 777            // Parts are never empty so len > 0 for NonZeroU16::new is always Some
 778            at = NonZeroU16::new(node.len() as u16);
 779            normalized = format!("{node}@{domain}");
 780        } else {
 781            at = None;
 782            normalized = domain.to_string();
 783        }
 784
 785        let inner = Jid {
 786            normalized,
 787            at,
 788            slash: None,
 789        };
 790
 791        Self { inner }
 792    }
 793
 794    /// Constructs a [`BareJid`] from the bare JID, by specifying a [`ResourcePart`].
 795    /// If you'd like to specify a stringy resource, use [`BareJid::with_resource_str`] instead.
 796    ///
 797    /// # Examples
 798    ///
 799    /// ```
 800    /// use jid::{BareJid, ResourcePart};
 801    ///
 802    /// let resource = ResourcePart::new("resource").unwrap();
 803    /// let bare = BareJid::new("node@domain").unwrap();
 804    /// let full = bare.with_resource(&resource);
 805    ///
 806    /// assert_eq!(full.node().map(|x| x.as_str()), Some("node"));
 807    /// assert_eq!(full.domain().as_str(), "domain");
 808    /// assert_eq!(full.resource().as_str(), "resource");
 809    /// ```
 810    pub fn with_resource(&self, resource: &ResourceRef) -> FullJid {
 811        let slash = NonZeroU16::new(self.inner.normalized.len() as u16);
 812        let normalized = format!("{}/{resource}", self.inner.normalized);
 813        let inner = Jid {
 814            normalized,
 815            at: self.inner.at,
 816            slash,
 817        };
 818
 819        FullJid { inner }
 820    }
 821
 822    /// Constructs a [`FullJid`] from the bare JID, by specifying a stringy `resource`.
 823    /// If your resource has already been parsed into a [`ResourcePart`], use [`BareJid::with_resource`].
 824    ///
 825    /// # Examples
 826    ///
 827    /// ```
 828    /// use jid::BareJid;
 829    ///
 830    /// let bare = BareJid::new("node@domain").unwrap();
 831    /// let full = bare.with_resource_str("resource").unwrap();
 832    ///
 833    /// assert_eq!(full.node().map(|x| x.as_str()), Some("node"));
 834    /// assert_eq!(full.domain().as_str(), "domain");
 835    /// assert_eq!(full.resource().as_str(), "resource");
 836    /// ```
 837    pub fn with_resource_str(&self, resource: &str) -> Result<FullJid, Error> {
 838        let resource = ResourcePart::new(resource)?;
 839        Ok(self.with_resource(&resource))
 840    }
 841
 842    /// Return the inner String of this bare JID.
 843    pub fn into_inner(self) -> String {
 844        self.inner.into_inner()
 845    }
 846}
 847
 848#[cfg(feature = "minidom")]
 849impl IntoAttributeValue for Jid {
 850    fn into_attribute_value(self) -> Option<String> {
 851        Some(self.to_string())
 852    }
 853}
 854
 855#[cfg(feature = "minidom")]
 856impl From<Jid> for Node {
 857    fn from(jid: Jid) -> Self {
 858        Node::Text(jid.to_string())
 859    }
 860}
 861
 862#[cfg(feature = "minidom")]
 863impl IntoAttributeValue for FullJid {
 864    fn into_attribute_value(self) -> Option<String> {
 865        self.inner.into_attribute_value()
 866    }
 867}
 868
 869#[cfg(feature = "minidom")]
 870impl From<FullJid> for Node {
 871    fn from(jid: FullJid) -> Self {
 872        jid.inner.into()
 873    }
 874}
 875
 876#[cfg(feature = "minidom")]
 877impl IntoAttributeValue for BareJid {
 878    fn into_attribute_value(self) -> Option<String> {
 879        self.inner.into_attribute_value()
 880    }
 881}
 882
 883#[cfg(feature = "minidom")]
 884impl From<BareJid> for Node {
 885    fn from(other: BareJid) -> Self {
 886        other.inner.into()
 887    }
 888}
 889
 890#[cfg(test)]
 891mod tests {
 892    use super::*;
 893
 894    use std::collections::{HashMap, HashSet};
 895    use std::vec::Vec;
 896
 897    macro_rules! assert_size (
 898        ($t:ty, $sz:expr) => (
 899            assert_eq!(::core::mem::size_of::<$t>(), $sz);
 900        );
 901    );
 902
 903    #[cfg(target_pointer_width = "32")]
 904    #[test]
 905    fn test_size() {
 906        assert_size!(BareJid, 16);
 907        assert_size!(FullJid, 16);
 908        assert_size!(Jid, 16);
 909    }
 910
 911    #[cfg(target_pointer_width = "64")]
 912    #[test]
 913    fn test_size() {
 914        assert_size!(BareJid, 32);
 915        assert_size!(FullJid, 32);
 916        assert_size!(Jid, 32);
 917    }
 918
 919    #[test]
 920    fn can_parse_full_jids() {
 921        assert_eq!(
 922            FullJid::from_str("a@b.c/d"),
 923            Ok(FullJid::new("a@b.c/d").unwrap())
 924        );
 925        assert_eq!(
 926            FullJid::from_str("b.c/d"),
 927            Ok(FullJid::new("b.c/d").unwrap())
 928        );
 929
 930        assert_eq!(
 931            FullJid::from_str("a@b.c"),
 932            Err(Error::ResourceMissingInFullJid)
 933        );
 934        assert_eq!(
 935            FullJid::from_str("b.c"),
 936            Err(Error::ResourceMissingInFullJid)
 937        );
 938    }
 939
 940    #[test]
 941    fn can_parse_bare_jids() {
 942        assert_eq!(
 943            BareJid::from_str("a@b.c"),
 944            Ok(BareJid::new("a@b.c").unwrap())
 945        );
 946        assert_eq!(BareJid::from_str("b.c"), Ok(BareJid::new("b.c").unwrap()));
 947    }
 948
 949    #[test]
 950    fn can_parse_jids() {
 951        let full = FullJid::from_str("a@b.c/d").unwrap();
 952        let bare = BareJid::from_str("e@f.g").unwrap();
 953
 954        assert_eq!(Jid::from_str("a@b.c/d").unwrap(), full);
 955        assert_eq!(Jid::from_str("e@f.g").unwrap(), bare);
 956    }
 957
 958    #[test]
 959    fn full_to_bare_jid() {
 960        let bare: BareJid = FullJid::new("a@b.c/d").unwrap().to_bare();
 961        assert_eq!(bare, BareJid::new("a@b.c").unwrap());
 962    }
 963
 964    #[test]
 965    fn bare_to_full_jid_str() {
 966        assert_eq!(
 967            BareJid::new("a@b.c")
 968                .unwrap()
 969                .with_resource_str("d")
 970                .unwrap(),
 971            FullJid::new("a@b.c/d").unwrap()
 972        );
 973    }
 974
 975    #[test]
 976    fn bare_to_full_jid() {
 977        assert_eq!(
 978            BareJid::new("a@b.c")
 979                .unwrap()
 980                .with_resource(&ResourcePart::new("d").unwrap()),
 981            FullJid::new("a@b.c/d").unwrap()
 982        )
 983    }
 984
 985    #[test]
 986    fn node_from_jid() {
 987        let jid = Jid::new("a@b.c/d").unwrap();
 988
 989        assert_eq!(jid.node().map(|x| x.as_str()), Some("a"),);
 990    }
 991
 992    #[test]
 993    fn domain_from_jid() {
 994        let jid = Jid::new("a@b.c").unwrap();
 995
 996        assert_eq!(jid.domain().as_str(), "b.c");
 997    }
 998
 999    #[test]
1000    fn resource_from_jid() {
1001        let jid = Jid::new("a@b.c/d").unwrap();
1002
1003        assert_eq!(jid.resource().map(|x| x.as_str()), Some("d"),);
1004    }
1005
1006    #[test]
1007    fn jid_to_full_bare() {
1008        let full = FullJid::new("a@b.c/d").unwrap();
1009        let bare = BareJid::new("a@b.c").unwrap();
1010
1011        assert_eq!(FullJid::try_from(Jid::from(full.clone())), Ok(full.clone()));
1012        assert_eq!(
1013            FullJid::try_from(Jid::from(bare.clone())),
1014            Err(Error::ResourceMissingInFullJid),
1015        );
1016        assert_eq!(Jid::from(full.clone().to_bare()), bare.clone());
1017        assert_eq!(Jid::from(bare.clone()), bare);
1018    }
1019
1020    #[test]
1021    fn serialise() {
1022        assert_eq!(FullJid::new("a@b/c").unwrap().to_string(), "a@b/c");
1023        assert_eq!(BareJid::new("a@b").unwrap().to_string(), "a@b");
1024    }
1025
1026    #[test]
1027    fn hash() {
1028        let _map: HashMap<Jid, String> = HashMap::new();
1029    }
1030
1031    #[test]
1032    fn invalid_jids() {
1033        assert_eq!(BareJid::from_str(""), Err(Error::DomainEmpty));
1034        assert_eq!(BareJid::from_str("/c"), Err(Error::DomainEmpty));
1035        assert_eq!(BareJid::from_str("a@/c"), Err(Error::DomainEmpty));
1036        assert_eq!(BareJid::from_str("@b"), Err(Error::NodeEmpty));
1037        assert_eq!(BareJid::from_str("b/"), Err(Error::ResourceEmpty));
1038
1039        assert_eq!(FullJid::from_str(""), Err(Error::DomainEmpty));
1040        assert_eq!(FullJid::from_str("/c"), Err(Error::DomainEmpty));
1041        assert_eq!(FullJid::from_str("a@/c"), Err(Error::DomainEmpty));
1042        assert_eq!(FullJid::from_str("@b"), Err(Error::NodeEmpty));
1043        assert_eq!(FullJid::from_str("b/"), Err(Error::ResourceEmpty));
1044        assert_eq!(
1045            FullJid::from_str("a@b"),
1046            Err(Error::ResourceMissingInFullJid)
1047        );
1048        assert_eq!(BareJid::from_str("a@b/c"), Err(Error::ResourceInBareJid));
1049    }
1050
1051    #[test]
1052    fn display_jids() {
1053        assert_eq!(FullJid::new("a@b/c").unwrap().to_string(), "a@b/c");
1054        assert_eq!(BareJid::new("a@b").unwrap().to_string(), "a@b");
1055        assert_eq!(
1056            Jid::from(FullJid::new("a@b/c").unwrap()).to_string(),
1057            "a@b/c"
1058        );
1059        assert_eq!(Jid::from(BareJid::new("a@b").unwrap()).to_string(), "a@b");
1060    }
1061
1062    #[cfg(feature = "minidom")]
1063    #[test]
1064    fn minidom() {
1065        let elem: minidom::Element = "<message xmlns='ns1' from='a@b/c'/>".parse().unwrap();
1066        let to: Jid = elem.attr("from").unwrap().parse().unwrap();
1067        assert_eq!(to, Jid::from(FullJid::new("a@b/c").unwrap()));
1068
1069        let elem: minidom::Element = "<message xmlns='ns1' from='a@b'/>".parse().unwrap();
1070        let to: Jid = elem.attr("from").unwrap().parse().unwrap();
1071        assert_eq!(to, Jid::from(BareJid::new("a@b").unwrap()));
1072
1073        let elem: minidom::Element = "<message xmlns='ns1' from='a@b/c'/>".parse().unwrap();
1074        let to: FullJid = elem.attr("from").unwrap().parse().unwrap();
1075        assert_eq!(to, FullJid::new("a@b/c").unwrap());
1076
1077        let elem: minidom::Element = "<message xmlns='ns1' from='a@b'/>".parse().unwrap();
1078        let to: BareJid = elem.attr("from").unwrap().parse().unwrap();
1079        assert_eq!(to, BareJid::new("a@b").unwrap());
1080    }
1081
1082    #[cfg(feature = "minidom")]
1083    #[test]
1084    fn minidom_into_attr() {
1085        let full = FullJid::new("a@b/c").unwrap();
1086        let elem = minidom::Element::builder("message", "jabber:client")
1087            .attr("from", full.clone())
1088            .build();
1089        assert_eq!(elem.attr("from"), Some(full.to_string().as_str()));
1090
1091        let bare = BareJid::new("a@b").unwrap();
1092        let elem = minidom::Element::builder("message", "jabber:client")
1093            .attr("from", bare.clone())
1094            .build();
1095        assert_eq!(elem.attr("from"), Some(bare.to_string().as_str()));
1096
1097        let jid = Jid::from(bare.clone());
1098        let _elem = minidom::Element::builder("message", "jabber:client")
1099            .attr("from", jid)
1100            .build();
1101        assert_eq!(elem.attr("from"), Some(bare.to_string().as_str()));
1102    }
1103
1104    #[test]
1105    fn stringprep() {
1106        let full = FullJid::from_str("Test@☃.coM/Test™").unwrap();
1107        let equiv = FullJid::new("test@☃.com/TestTM").unwrap();
1108        assert_eq!(full, equiv);
1109    }
1110
1111    #[test]
1112    fn invalid_stringprep() {
1113        FullJid::from_str("a@b/🎉").unwrap_err();
1114    }
1115
1116    #[test]
1117    fn jid_from_parts() {
1118        let node = NodePart::new("node").unwrap();
1119        let domain = DomainPart::new("domain").unwrap();
1120        let resource = ResourcePart::new("resource").unwrap();
1121
1122        let jid = Jid::from_parts(Some(&node), &domain, Some(&resource));
1123        assert_eq!(jid, Jid::new("node@domain/resource").unwrap());
1124
1125        let barejid = BareJid::from_parts(Some(&node), &domain);
1126        assert_eq!(barejid, BareJid::new("node@domain").unwrap());
1127
1128        let fulljid = FullJid::from_parts(Some(&node), &domain, &resource);
1129        assert_eq!(fulljid, FullJid::new("node@domain/resource").unwrap());
1130    }
1131
1132    #[test]
1133    #[cfg(feature = "serde")]
1134    fn jid_ser_de() {
1135        let jid: Jid = Jid::new("node@domain").unwrap();
1136        serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain")]);
1137
1138        let jid: Jid = Jid::new("node@domain/resource").unwrap();
1139        serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain/resource")]);
1140
1141        let jid: BareJid = BareJid::new("node@domain").unwrap();
1142        serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain")]);
1143
1144        let jid: FullJid = FullJid::new("node@domain/resource").unwrap();
1145        serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain/resource")]);
1146    }
1147
1148    #[test]
1149    fn jid_into_parts_and_from_parts() {
1150        let node = NodePart::new("node").unwrap();
1151        let domain = DomainPart::new("domain").unwrap();
1152
1153        let jid1 = domain.with_node(&node);
1154        let jid2 = node.with_domain(&domain);
1155        let jid3 = BareJid::new("node@domain").unwrap();
1156        assert_eq!(jid1, jid2);
1157        assert_eq!(jid2, jid3);
1158    }
1159
1160    #[test]
1161    fn jid_match_replacement_try_as() {
1162        let jid1 = Jid::new("foo@bar").unwrap();
1163        let jid2 = Jid::new("foo@bar/baz").unwrap();
1164
1165        match jid1.try_as_full() {
1166            Err(_) => (),
1167            other => panic!("unexpected result: {:?}", other),
1168        };
1169
1170        match jid2.try_as_full() {
1171            Ok(_) => (),
1172            other => panic!("unexpected result: {:?}", other),
1173        };
1174    }
1175
1176    #[test]
1177    fn jid_match_replacement_try_as_mut() {
1178        let mut jid1 = Jid::new("foo@bar").unwrap();
1179        let mut jid2 = Jid::new("foo@bar/baz").unwrap();
1180
1181        match jid1.try_as_full_mut() {
1182            Err(_) => (),
1183            other => panic!("unexpected result: {:?}", other),
1184        };
1185
1186        match jid2.try_as_full_mut() {
1187            Ok(_) => (),
1188            other => panic!("unexpected result: {:?}", other),
1189        };
1190    }
1191
1192    #[test]
1193    fn jid_match_replacement_try_into() {
1194        let jid1 = Jid::new("foo@bar").unwrap();
1195        let jid2 = Jid::new("foo@bar/baz").unwrap();
1196
1197        match jid1.try_as_full() {
1198            Err(_) => (),
1199            other => panic!("unexpected result: {:?}", other),
1200        };
1201
1202        match jid2.try_as_full() {
1203            Ok(_) => (),
1204            other => panic!("unexpected result: {:?}", other),
1205        };
1206    }
1207
1208    #[test]
1209    fn lookup_jid_by_full_jid() {
1210        let mut map: HashSet<Jid> = HashSet::new();
1211        let jid1 = Jid::new("foo@bar").unwrap();
1212        let jid2 = Jid::new("foo@bar/baz").unwrap();
1213        let jid3 = FullJid::new("foo@bar/baz").unwrap();
1214
1215        map.insert(jid1);
1216        assert!(!map.contains(&jid2));
1217        assert!(!map.contains(&jid3));
1218        map.insert(jid2);
1219        assert!(map.contains(&jid3));
1220    }
1221
1222    #[test]
1223    fn lookup_full_jid_by_jid() {
1224        let mut map: HashSet<FullJid> = HashSet::new();
1225        let jid1 = FullJid::new("foo@bar/baz").unwrap();
1226        let jid2 = FullJid::new("foo@bar/fnord").unwrap();
1227        let jid3 = Jid::new("foo@bar/fnord").unwrap();
1228
1229        map.insert(jid1);
1230        assert!(!map.contains(&jid2));
1231        assert!(!map.contains(&jid3));
1232        map.insert(jid2);
1233        assert!(map.contains(&jid3));
1234    }
1235
1236    #[test]
1237    fn lookup_bare_jid_by_jid() {
1238        let mut map: HashSet<BareJid> = HashSet::new();
1239        let jid1 = BareJid::new("foo@bar").unwrap();
1240        let jid2 = BareJid::new("foo@baz").unwrap();
1241        let jid3 = Jid::new("foo@baz").unwrap();
1242
1243        map.insert(jid1);
1244        assert!(!map.contains(&jid2));
1245        assert!(!map.contains(&jid3));
1246        map.insert(jid2);
1247        assert!(map.contains(&jid3));
1248    }
1249
1250    #[test]
1251    fn normalizes_all_parts() {
1252        assert_eq!(
1253            Jid::new("ßA@IX.test/\u{2168}").unwrap().as_str(),
1254            "ssa@ix.test/IX"
1255        );
1256    }
1257
1258    #[test]
1259    fn rejects_unassigned_codepoints() {
1260        match Jid::new("\u{01f601}@example.com") {
1261            Err(Error::NodePrep) => (),
1262            other => panic!("unexpected result: {:?}", other),
1263        };
1264
1265        match Jid::new("foo@\u{01f601}.example.com") {
1266            Err(Error::NamePrep) => (),
1267            other => panic!("unexpected result: {:?}", other),
1268        };
1269
1270        match Jid::new("foo@example.com/\u{01f601}") {
1271            Err(Error::ResourcePrep) => (),
1272            other => panic!("unexpected result: {:?}", other),
1273        };
1274    }
1275
1276    #[test]
1277    fn accepts_domain_only_jid() {
1278        match Jid::new("example.com") {
1279            Ok(_) => (),
1280            other => panic!("unexpected result: {:?}", other),
1281        };
1282
1283        match BareJid::new("example.com") {
1284            Ok(_) => (),
1285            other => panic!("unexpected result: {:?}", other),
1286        };
1287
1288        match FullJid::new("example.com/x") {
1289            Ok(_) => (),
1290            other => panic!("unexpected result: {:?}", other),
1291        };
1292    }
1293
1294    #[test]
1295    fn is_bare_returns_true_iff_bare() {
1296        let bare = Jid::new("foo@bar").unwrap();
1297        let full = Jid::new("foo@bar/baz").unwrap();
1298
1299        assert!(bare.is_bare());
1300        assert!(!full.is_bare());
1301    }
1302
1303    #[test]
1304    fn is_full_returns_true_iff_full() {
1305        let bare = Jid::new("foo@bar").unwrap();
1306        let full = Jid::new("foo@bar/baz").unwrap();
1307
1308        assert!(!bare.is_full());
1309        assert!(full.is_full());
1310    }
1311
1312    #[test]
1313    fn reject_long_localpart() {
1314        let mut long = Vec::with_capacity(1028);
1315        long.resize(1024, b'a');
1316        let mut long = String::from_utf8(long).unwrap();
1317        long.push_str("@foo");
1318
1319        match Jid::new(&long) {
1320            Err(Error::NodeTooLong) => (),
1321            other => panic!("unexpected result: {:?}", other),
1322        }
1323
1324        match BareJid::new(&long) {
1325            Err(Error::NodeTooLong) => (),
1326            other => panic!("unexpected result: {:?}", other),
1327        }
1328    }
1329
1330    #[test]
1331    fn reject_long_domainpart() {
1332        let mut long = Vec::with_capacity(1028);
1333        long.push(b'x');
1334        long.push(b'@');
1335        long.resize(1026, b'a');
1336        let long = String::from_utf8(long).unwrap();
1337
1338        match Jid::new(&long) {
1339            Err(Error::DomainTooLong) => (),
1340            other => panic!("unexpected result: {:?}", other),
1341        }
1342
1343        match BareJid::new(&long) {
1344            Err(Error::DomainTooLong) => (),
1345            other => panic!("unexpected result: {:?}", other),
1346        }
1347    }
1348
1349    #[test]
1350    fn reject_long_resourcepart() {
1351        let mut long = Vec::with_capacity(1028);
1352        long.push(b'x');
1353        long.push(b'@');
1354        long.push(b'y');
1355        long.push(b'/');
1356        long.resize(1028, b'a');
1357        let long = String::from_utf8(long).unwrap();
1358
1359        match Jid::new(&long) {
1360            Err(Error::ResourceTooLong) => (),
1361            other => panic!("unexpected result: {:?}", other),
1362        }
1363
1364        match FullJid::new(&long) {
1365            Err(Error::ResourceTooLong) => (),
1366            other => panic!("unexpected result: {:?}", other),
1367        }
1368    }
1369}