lib.rs

   1// Copyright (c) 2017, 2018 lumi <lumi@pew.im>
   2// Copyright (c) 2017, 2018, 2019 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
   3// Copyright (c) 2017, 2018, 2019 Maxime “pep” Buquet <pep@bouah.net>
   4// Copyright (c) 2017, 2018 Astro <astro@spaceboyz.net>
   5// Copyright (c) 2017 Bastien Orivel <eijebong@bananium.fr>
   6//
   7// This Source Code Form is subject to the terms of the Mozilla Public
   8// License, v. 2.0. If a copy of the MPL was not distributed with this
   9// file, You can obtain one at http://mozilla.org/MPL/2.0/.
  10
  11#![no_std]
  12#![deny(missing_docs)]
  13#![cfg_attr(docsrs, feature(doc_auto_cfg))]
  14
  15//! Represents XMPP addresses, also known as JabberIDs (JIDs) for the [XMPP](https://xmpp.org/)
  16//! protocol. A [`Jid`] can have between one and three parts in the form `node@domain/resource`:
  17//! - the (optional) node part designates a specific account/service on a server, for example
  18//!   `username@server.com`
  19//! - the domain part designates a server, for example `irc.jabberfr.org`
  20//! - the (optional) resource part designates a more specific client, such as a participant in a
  21//!   groupchat (`jabberfr@chat.jabberfr.org/user`) or a specific client device associated with an
  22//!   account (`user@example.com/dino`)
  23//!
  24//! The [`Jid`] enum can be one of two variants, containing a more specific type:
  25//! - [`BareJid`] (`Jid::Bare` variant): a JID without a resource
  26//! - [`FullJid`] (`Jid::Full` variant): a JID with a resource
  27//!
  28//! Jids as per the XMPP protocol only ever contain valid UTF-8. However, creating any form of Jid
  29//! can fail in one of the following cases:
  30//! - wrong syntax: creating a Jid with an empty (yet declared) node or resource part, such as
  31//!   `@example.com` or `user@example.com/`
  32//! - stringprep error: some characters were invalid according to the stringprep algorithm, such as
  33//!   mixing left-to-write and right-to-left characters
  34
  35extern crate alloc;
  36
  37#[cfg(test)]
  38extern crate std;
  39
  40use alloc::borrow::Cow;
  41use alloc::format;
  42use alloc::string::{String, ToString};
  43use core::borrow::Borrow;
  44use core::cmp::Ordering;
  45use core::fmt;
  46use core::hash::{Hash, Hasher};
  47use core::mem;
  48use core::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;
  68mod parts;
  69
  70pub use crate::error::Error;
  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 alloc::{
 906        collections::{BTreeMap, BTreeSet},
 907        vec::Vec,
 908    };
 909
 910    macro_rules! assert_size (
 911        ($t:ty, $sz:expr) => (
 912            assert_eq!(::core::mem::size_of::<$t>(), $sz);
 913        );
 914    );
 915
 916    #[cfg(target_pointer_width = "32")]
 917    #[test]
 918    fn test_size() {
 919        assert_size!(BareJid, 16);
 920        assert_size!(FullJid, 16);
 921        assert_size!(Jid, 16);
 922    }
 923
 924    #[cfg(target_pointer_width = "64")]
 925    #[test]
 926    fn test_size() {
 927        assert_size!(BareJid, 32);
 928        assert_size!(FullJid, 32);
 929        assert_size!(Jid, 32);
 930    }
 931
 932    #[test]
 933    fn can_parse_full_jids() {
 934        assert_eq!(
 935            FullJid::from_str("a@b.c/d"),
 936            Ok(FullJid::new("a@b.c/d").unwrap())
 937        );
 938        assert_eq!(
 939            FullJid::from_str("b.c/d"),
 940            Ok(FullJid::new("b.c/d").unwrap())
 941        );
 942
 943        assert_eq!(
 944            FullJid::from_str("a@b.c"),
 945            Err(Error::ResourceMissingInFullJid)
 946        );
 947        assert_eq!(
 948            FullJid::from_str("b.c"),
 949            Err(Error::ResourceMissingInFullJid)
 950        );
 951    }
 952
 953    #[test]
 954    fn can_parse_bare_jids() {
 955        assert_eq!(
 956            BareJid::from_str("a@b.c"),
 957            Ok(BareJid::new("a@b.c").unwrap())
 958        );
 959        assert_eq!(BareJid::from_str("b.c"), Ok(BareJid::new("b.c").unwrap()));
 960    }
 961
 962    #[test]
 963    fn can_parse_jids() {
 964        let full = FullJid::from_str("a@b.c/d").unwrap();
 965        let bare = BareJid::from_str("e@f.g").unwrap();
 966
 967        assert_eq!(Jid::from_str("a@b.c/d").unwrap(), full);
 968        assert_eq!(Jid::from_str("e@f.g").unwrap(), bare);
 969    }
 970
 971    #[test]
 972    fn full_to_bare_jid() {
 973        let bare: BareJid = FullJid::new("a@b.c/d").unwrap().to_bare();
 974        assert_eq!(bare, BareJid::new("a@b.c").unwrap());
 975    }
 976
 977    #[test]
 978    fn bare_to_full_jid_str() {
 979        assert_eq!(
 980            BareJid::new("a@b.c")
 981                .unwrap()
 982                .with_resource_str("d")
 983                .unwrap(),
 984            FullJid::new("a@b.c/d").unwrap()
 985        );
 986    }
 987
 988    #[test]
 989    fn bare_to_full_jid() {
 990        assert_eq!(
 991            BareJid::new("a@b.c")
 992                .unwrap()
 993                .with_resource(&ResourcePart::new("d").unwrap()),
 994            FullJid::new("a@b.c/d").unwrap()
 995        )
 996    }
 997
 998    #[test]
 999    fn node_from_jid() {
1000        let jid = Jid::new("a@b.c/d").unwrap();
1001
1002        assert_eq!(jid.node().map(|x| x.as_str()), Some("a"),);
1003    }
1004
1005    #[test]
1006    fn domain_from_jid() {
1007        let jid = Jid::new("a@b.c").unwrap();
1008
1009        assert_eq!(jid.domain().as_str(), "b.c");
1010    }
1011
1012    #[test]
1013    fn resource_from_jid() {
1014        let jid = Jid::new("a@b.c/d").unwrap();
1015
1016        assert_eq!(jid.resource().map(|x| x.as_str()), Some("d"),);
1017    }
1018
1019    #[test]
1020    fn jid_to_full_bare() {
1021        let full = FullJid::new("a@b.c/d").unwrap();
1022        let bare = BareJid::new("a@b.c").unwrap();
1023
1024        assert_eq!(FullJid::try_from(Jid::from(full.clone())), Ok(full.clone()));
1025        assert_eq!(
1026            FullJid::try_from(Jid::from(bare.clone())),
1027            Err(Error::ResourceMissingInFullJid),
1028        );
1029        assert_eq!(Jid::from(full.clone().to_bare()), bare.clone());
1030        assert_eq!(Jid::from(bare.clone()), bare);
1031    }
1032
1033    #[test]
1034    fn serialise() {
1035        assert_eq!(FullJid::new("a@b/c").unwrap().to_string(), "a@b/c");
1036        assert_eq!(BareJid::new("a@b").unwrap().to_string(), "a@b");
1037    }
1038
1039    #[test]
1040    fn hash() {
1041        let _map: BTreeMap<Jid, String> = BTreeMap::new();
1042    }
1043
1044    #[test]
1045    fn invalid_jids() {
1046        assert_eq!(BareJid::from_str(""), Err(Error::DomainEmpty));
1047        assert_eq!(BareJid::from_str("/c"), Err(Error::DomainEmpty));
1048        assert_eq!(BareJid::from_str("a@/c"), Err(Error::DomainEmpty));
1049        assert_eq!(BareJid::from_str("@b"), Err(Error::NodeEmpty));
1050        assert_eq!(BareJid::from_str("b/"), Err(Error::ResourceEmpty));
1051
1052        assert_eq!(FullJid::from_str(""), Err(Error::DomainEmpty));
1053        assert_eq!(FullJid::from_str("/c"), Err(Error::DomainEmpty));
1054        assert_eq!(FullJid::from_str("a@/c"), Err(Error::DomainEmpty));
1055        assert_eq!(FullJid::from_str("@b"), Err(Error::NodeEmpty));
1056        assert_eq!(FullJid::from_str("b/"), Err(Error::ResourceEmpty));
1057        assert_eq!(
1058            FullJid::from_str("a@b"),
1059            Err(Error::ResourceMissingInFullJid)
1060        );
1061        assert_eq!(BareJid::from_str("a@b/c"), Err(Error::ResourceInBareJid));
1062    }
1063
1064    #[test]
1065    fn display_jids() {
1066        assert_eq!(FullJid::new("a@b/c").unwrap().to_string(), "a@b/c");
1067        assert_eq!(BareJid::new("a@b").unwrap().to_string(), "a@b");
1068        assert_eq!(
1069            Jid::from(FullJid::new("a@b/c").unwrap()).to_string(),
1070            "a@b/c"
1071        );
1072        assert_eq!(Jid::from(BareJid::new("a@b").unwrap()).to_string(), "a@b");
1073    }
1074
1075    #[cfg(feature = "minidom")]
1076    #[test]
1077    fn minidom() {
1078        let elem: minidom::Element = "<message xmlns='ns1' from='a@b/c'/>".parse().unwrap();
1079        let to: Jid = elem.attr("from").unwrap().parse().unwrap();
1080        assert_eq!(to, Jid::from(FullJid::new("a@b/c").unwrap()));
1081
1082        let elem: minidom::Element = "<message xmlns='ns1' from='a@b'/>".parse().unwrap();
1083        let to: Jid = elem.attr("from").unwrap().parse().unwrap();
1084        assert_eq!(to, Jid::from(BareJid::new("a@b").unwrap()));
1085
1086        let elem: minidom::Element = "<message xmlns='ns1' from='a@b/c'/>".parse().unwrap();
1087        let to: FullJid = elem.attr("from").unwrap().parse().unwrap();
1088        assert_eq!(to, FullJid::new("a@b/c").unwrap());
1089
1090        let elem: minidom::Element = "<message xmlns='ns1' from='a@b'/>".parse().unwrap();
1091        let to: BareJid = elem.attr("from").unwrap().parse().unwrap();
1092        assert_eq!(to, BareJid::new("a@b").unwrap());
1093    }
1094
1095    #[cfg(feature = "minidom")]
1096    #[test]
1097    fn minidom_into_attr() {
1098        let full = FullJid::new("a@b/c").unwrap();
1099        let elem = minidom::Element::builder("message", "jabber:client")
1100            .attr("from", full.clone())
1101            .build();
1102        assert_eq!(elem.attr("from"), Some(full.to_string().as_str()));
1103
1104        let bare = BareJid::new("a@b").unwrap();
1105        let elem = minidom::Element::builder("message", "jabber:client")
1106            .attr("from", bare.clone())
1107            .build();
1108        assert_eq!(elem.attr("from"), Some(bare.to_string().as_str()));
1109
1110        let jid = Jid::from(bare.clone());
1111        let _elem = minidom::Element::builder("message", "jabber:client")
1112            .attr("from", jid)
1113            .build();
1114        assert_eq!(elem.attr("from"), Some(bare.to_string().as_str()));
1115    }
1116
1117    #[test]
1118    fn stringprep() {
1119        let full = FullJid::from_str("Test@☃.coM/Test™").unwrap();
1120        let equiv = FullJid::new("test@☃.com/TestTM").unwrap();
1121        assert_eq!(full, equiv);
1122    }
1123
1124    #[test]
1125    fn invalid_stringprep() {
1126        FullJid::from_str("a@b/🎉").unwrap_err();
1127    }
1128
1129    #[test]
1130    fn jid_from_parts() {
1131        let node = NodePart::new("node").unwrap();
1132        let domain = DomainPart::new("domain").unwrap();
1133        let resource = ResourcePart::new("resource").unwrap();
1134
1135        let jid = Jid::from_parts(Some(&node), &domain, Some(&resource));
1136        assert_eq!(jid, Jid::new("node@domain/resource").unwrap());
1137
1138        let barejid = BareJid::from_parts(Some(&node), &domain);
1139        assert_eq!(barejid, BareJid::new("node@domain").unwrap());
1140
1141        let fulljid = FullJid::from_parts(Some(&node), &domain, &resource);
1142        assert_eq!(fulljid, FullJid::new("node@domain/resource").unwrap());
1143    }
1144
1145    #[test]
1146    #[cfg(feature = "serde")]
1147    fn jid_ser_de() {
1148        let jid: Jid = Jid::new("node@domain").unwrap();
1149        serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain")]);
1150
1151        let jid: Jid = Jid::new("node@domain/resource").unwrap();
1152        serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain/resource")]);
1153
1154        let jid: BareJid = BareJid::new("node@domain").unwrap();
1155        serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain")]);
1156
1157        let jid: FullJid = FullJid::new("node@domain/resource").unwrap();
1158        serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain/resource")]);
1159    }
1160
1161    #[test]
1162    fn jid_into_parts_and_from_parts() {
1163        let node = NodePart::new("node").unwrap();
1164        let domain = DomainPart::new("domain").unwrap();
1165
1166        let jid1 = domain.with_node(&node);
1167        let jid2 = node.with_domain(&domain);
1168        let jid3 = BareJid::new("node@domain").unwrap();
1169        assert_eq!(jid1, jid2);
1170        assert_eq!(jid2, jid3);
1171    }
1172
1173    #[test]
1174    fn jid_match_replacement_try_as() {
1175        let jid1 = Jid::new("foo@bar").unwrap();
1176        let jid2 = Jid::new("foo@bar/baz").unwrap();
1177
1178        match jid1.try_as_full() {
1179            Err(_) => (),
1180            other => panic!("unexpected result: {:?}", other),
1181        };
1182
1183        match jid2.try_as_full() {
1184            Ok(_) => (),
1185            other => panic!("unexpected result: {:?}", other),
1186        };
1187    }
1188
1189    #[test]
1190    fn jid_match_replacement_try_as_mut() {
1191        let mut jid1 = Jid::new("foo@bar").unwrap();
1192        let mut jid2 = Jid::new("foo@bar/baz").unwrap();
1193
1194        match jid1.try_as_full_mut() {
1195            Err(_) => (),
1196            other => panic!("unexpected result: {:?}", other),
1197        };
1198
1199        match jid2.try_as_full_mut() {
1200            Ok(_) => (),
1201            other => panic!("unexpected result: {:?}", other),
1202        };
1203    }
1204
1205    #[test]
1206    fn jid_match_replacement_try_into() {
1207        let jid1 = Jid::new("foo@bar").unwrap();
1208        let jid2 = Jid::new("foo@bar/baz").unwrap();
1209
1210        match jid1.try_as_full() {
1211            Err(_) => (),
1212            other => panic!("unexpected result: {:?}", other),
1213        };
1214
1215        match jid2.try_as_full() {
1216            Ok(_) => (),
1217            other => panic!("unexpected result: {:?}", other),
1218        };
1219    }
1220
1221    #[test]
1222    fn lookup_jid_by_full_jid() {
1223        let mut map: BTreeSet<Jid> = BTreeSet::new();
1224        let jid1 = Jid::new("foo@bar").unwrap();
1225        let jid2 = Jid::new("foo@bar/baz").unwrap();
1226        let jid3 = FullJid::new("foo@bar/baz").unwrap();
1227
1228        map.insert(jid1);
1229        assert!(!map.contains(&jid2));
1230        assert!(!map.contains(&jid3));
1231        map.insert(jid2);
1232        assert!(map.contains(&jid3));
1233    }
1234
1235    #[test]
1236    fn lookup_full_jid_by_jid() {
1237        let mut map: BTreeSet<FullJid> = BTreeSet::new();
1238        let jid1 = FullJid::new("foo@bar/baz").unwrap();
1239        let jid2 = FullJid::new("foo@bar/fnord").unwrap();
1240        let jid3 = Jid::new("foo@bar/fnord").unwrap();
1241
1242        map.insert(jid1);
1243        assert!(!map.contains(&jid2));
1244        assert!(!map.contains(&jid3));
1245        map.insert(jid2);
1246        assert!(map.contains(&jid3));
1247    }
1248
1249    #[test]
1250    fn lookup_bare_jid_by_jid() {
1251        let mut map: BTreeSet<BareJid> = BTreeSet::new();
1252        let jid1 = BareJid::new("foo@bar").unwrap();
1253        let jid2 = BareJid::new("foo@baz").unwrap();
1254        let jid3 = Jid::new("foo@baz").unwrap();
1255
1256        map.insert(jid1);
1257        assert!(!map.contains(&jid2));
1258        assert!(!map.contains(&jid3));
1259        map.insert(jid2);
1260        assert!(map.contains(&jid3));
1261    }
1262
1263    #[test]
1264    fn normalizes_all_parts() {
1265        assert_eq!(
1266            Jid::new("ßA@IX.test/\u{2168}").unwrap().as_str(),
1267            "ssa@ix.test/IX"
1268        );
1269    }
1270
1271    #[test]
1272    fn rejects_unassigned_codepoints() {
1273        match Jid::new("\u{01f601}@example.com") {
1274            Err(Error::NodePrep) => (),
1275            other => panic!("unexpected result: {:?}", other),
1276        };
1277
1278        match Jid::new("foo@\u{01f601}.example.com") {
1279            Err(Error::NamePrep) => (),
1280            other => panic!("unexpected result: {:?}", other),
1281        };
1282
1283        match Jid::new("foo@example.com/\u{01f601}") {
1284            Err(Error::ResourcePrep) => (),
1285            other => panic!("unexpected result: {:?}", other),
1286        };
1287    }
1288
1289    #[test]
1290    fn accepts_domain_only_jid() {
1291        match Jid::new("example.com") {
1292            Ok(_) => (),
1293            other => panic!("unexpected result: {:?}", other),
1294        };
1295
1296        match BareJid::new("example.com") {
1297            Ok(_) => (),
1298            other => panic!("unexpected result: {:?}", other),
1299        };
1300
1301        match FullJid::new("example.com/x") {
1302            Ok(_) => (),
1303            other => panic!("unexpected result: {:?}", other),
1304        };
1305    }
1306
1307    #[test]
1308    fn is_bare_returns_true_iff_bare() {
1309        let bare = Jid::new("foo@bar").unwrap();
1310        let full = Jid::new("foo@bar/baz").unwrap();
1311
1312        assert!(bare.is_bare());
1313        assert!(!full.is_bare());
1314    }
1315
1316    #[test]
1317    fn is_full_returns_true_iff_full() {
1318        let bare = Jid::new("foo@bar").unwrap();
1319        let full = Jid::new("foo@bar/baz").unwrap();
1320
1321        assert!(!bare.is_full());
1322        assert!(full.is_full());
1323    }
1324
1325    #[test]
1326    fn reject_long_localpart() {
1327        let mut long = Vec::with_capacity(1028);
1328        long.resize(1024, b'a');
1329        let mut long = String::from_utf8(long).unwrap();
1330        long.push_str("@foo");
1331
1332        match Jid::new(&long) {
1333            Err(Error::NodeTooLong) => (),
1334            other => panic!("unexpected result: {:?}", other),
1335        }
1336
1337        match BareJid::new(&long) {
1338            Err(Error::NodeTooLong) => (),
1339            other => panic!("unexpected result: {:?}", other),
1340        }
1341    }
1342
1343    #[test]
1344    fn reject_long_domainpart() {
1345        let mut long = Vec::with_capacity(1028);
1346        long.push(b'x');
1347        long.push(b'@');
1348        long.resize(1026, b'a');
1349        let long = String::from_utf8(long).unwrap();
1350
1351        match Jid::new(&long) {
1352            Err(Error::DomainTooLong) => (),
1353            other => panic!("unexpected result: {:?}", other),
1354        }
1355
1356        match BareJid::new(&long) {
1357            Err(Error::DomainTooLong) => (),
1358            other => panic!("unexpected result: {:?}", other),
1359        }
1360    }
1361
1362    #[test]
1363    fn reject_long_resourcepart() {
1364        let mut long = Vec::with_capacity(1028);
1365        long.push(b'x');
1366        long.push(b'@');
1367        long.push(b'y');
1368        long.push(b'/');
1369        long.resize(1028, b'a');
1370        let long = String::from_utf8(long).unwrap();
1371
1372        match Jid::new(&long) {
1373            Err(Error::ResourceTooLong) => (),
1374            other => panic!("unexpected result: {:?}", other),
1375        }
1376
1377        match FullJid::new(&long) {
1378            Err(Error::ResourceTooLong) => (),
1379            other => panic!("unexpected result: {:?}", other),
1380        }
1381    }
1382}