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