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