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