lib.rs

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