lib.rs

  1// Copyright (c) 2017, 2018 lumi <lumi@pew.im>
  2// Copyright (c) 2017, 2018, 2019 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
  3// Copyright (c) 2017, 2018, 2019 Maxime “pep” Buquet <pep@bouah.net>
  4// Copyright (c) 2017, 2018 Astro <astro@spaceboyz.net>
  5// Copyright (c) 2017 Bastien Orivel <eijebong@bananium.fr>
  6//
  7// This Source Code Form is subject to the terms of the Mozilla Public
  8// License, v. 2.0. If a copy of the MPL was not distributed with this
  9// file, You can obtain one at http://mozilla.org/MPL/2.0/.
 10
 11#![deny(missing_docs)]
 12
 13//! Represents XMPP addresses, also known as JabberIDs (JIDs) for the [XMPP](https://xmpp.org/)
 14//! protocol. A [`Jid`] can have between one and three parts in the form `node@domain/resource`:
 15//! - the (optional) node part designates a specific account/service on a server, for example
 16//!   `username@server.com`
 17//! - the domain part designates a server, for example `irc.jabberfr.org`
 18//! - the (optional) resource part designates a more specific client, such as a participant in a
 19//!   groupchat (`jabberfr@chat.jabberfr.org/user`) or a specific client device associated with an
 20//!   account (`user@example.com/dino`)
 21//!
 22//! The [`Jid`] enum can be one of two variants, containing a more specific type:
 23//! - [`BareJid`] (`Jid::Bare` variant): a JID without a resource
 24//! - [`FullJid`] (`Jid::Full` variant): a JID with a resource
 25//!
 26//! Jids as per the XMPP protocol only ever contain valid UTF-8. However, creating any form of Jid
 27//! can fail in one of the following cases:
 28//! - wrong syntax: creating a Jid with an empty (yet declared) node or resource part, such as
 29//!   `@example.com` or `user@example.com/`
 30//! - stringprep error: some characters were invalid according to the stringprep algorithm, such as
 31//!   mixing left-to-write and right-to-left characters
 32
 33use core::num::NonZeroU16;
 34use std::convert::TryFrom;
 35use std::fmt;
 36use std::str::FromStr;
 37
 38#[cfg(feature = "serde")]
 39use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
 40
 41#[cfg(feature = "quote")]
 42use proc_macro2::TokenStream;
 43#[cfg(feature = "quote")]
 44use quote::{quote, ToTokens};
 45
 46mod error;
 47pub use crate::error::Error;
 48
 49mod inner;
 50use inner::InnerJid;
 51
 52mod parts;
 53pub use parts::{DomainPart, NodePart, ResourcePart};
 54
 55/// An enum representing a Jabber ID. It can be either a `FullJid` or a `BareJid`.
 56#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 57#[cfg_attr(feature = "serde", serde(untagged))]
 58#[derive(Debug, Clone, PartialEq, Eq, Hash)]
 59pub enum Jid {
 60    /// Contains a [`BareJid`], without a resource part
 61    Bare(BareJid),
 62
 63    /// Contains a [`FullJid`], with a resource part
 64    Full(FullJid),
 65}
 66
 67impl FromStr for Jid {
 68    type Err = Error;
 69
 70    fn from_str(s: &str) -> Result<Jid, Error> {
 71        Jid::new(s)
 72    }
 73}
 74
 75impl From<BareJid> for Jid {
 76    fn from(bare_jid: BareJid) -> Jid {
 77        Jid::Bare(bare_jid)
 78    }
 79}
 80
 81impl From<FullJid> for Jid {
 82    fn from(full_jid: FullJid) -> Jid {
 83        Jid::Full(full_jid)
 84    }
 85}
 86
 87impl fmt::Display for Jid {
 88    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
 89        match self {
 90            Jid::Bare(bare) => bare.fmt(fmt),
 91            Jid::Full(full) => full.fmt(fmt),
 92        }
 93    }
 94}
 95
 96impl Jid {
 97    /// Constructs a Jabber ID from a string. This is of the form
 98    /// `node`@`domain`/`resource`, where node and resource parts are optional.
 99    /// If you want a non-fallible version, use [`Jid::from_parts`] instead.
100    ///
101    /// # Examples
102    ///
103    /// ```
104    /// use jid::Jid;
105    /// # use jid::Error;
106    ///
107    /// # fn main() -> Result<(), Error> {
108    /// let jid = Jid::new("node@domain/resource")?;
109    ///
110    /// assert_eq!(jid.node_str(), Some("node"));
111    /// assert_eq!(jid.domain_str(), "domain");
112    /// assert_eq!(jid.resource_str(), Some("resource"));
113    /// # Ok(())
114    /// # }
115    /// ```
116    pub fn new(s: &str) -> Result<Jid, Error> {
117        let inner = InnerJid::new(s)?;
118        if inner.slash.is_some() {
119            Ok(Jid::Full(FullJid { inner }))
120        } else {
121            Ok(Jid::Bare(BareJid { inner }))
122        }
123    }
124
125    /// Returns the inner String of this JID.
126    pub fn into_inner(self) -> String {
127        match self {
128            Jid::Bare(BareJid { inner }) | Jid::Full(FullJid { inner }) => inner.normalized,
129        }
130    }
131
132    /// Build a [`Jid`] from typed parts. This method cannot fail because it uses parts that have
133    /// already been parsed and stringprepped into [`NodePart`], [`DomainPart`], and [`ResourcePart`].
134    /// This method allocates and does not consume the typed parts.
135    pub fn from_parts(
136        node: Option<&NodePart>,
137        domain: &DomainPart,
138        resource: Option<&ResourcePart>,
139    ) -> Jid {
140        if let Some(resource) = resource {
141            Jid::Full(FullJid::from_parts(node, domain, resource))
142        } else {
143            Jid::Bare(BareJid::from_parts(node, domain))
144        }
145    }
146
147    /// The optional node part of the JID, as a [`NodePart`]
148    pub fn node(&self) -> Option<NodePart> {
149        match self {
150            Jid::Bare(BareJid { inner }) | Jid::Full(FullJid { inner }) => {
151                inner.node().map(NodePart::new_unchecked)
152            }
153        }
154    }
155
156    /// The optional node part of the JID, as a stringy reference
157    pub fn node_str(&self) -> Option<&str> {
158        match self {
159            Jid::Bare(BareJid { inner }) | Jid::Full(FullJid { inner }) => inner.node(),
160        }
161    }
162
163    /// The domain part of the JID, as a [`DomainPart`]
164    pub fn domain(&self) -> DomainPart {
165        match self {
166            Jid::Bare(BareJid { inner }) | Jid::Full(FullJid { inner }) => {
167                DomainPart::new_unchecked(inner.domain())
168            }
169        }
170    }
171
172    /// The domain part of the JID, as a stringy reference
173    pub fn domain_str(&self) -> &str {
174        match self {
175            Jid::Bare(BareJid { inner }) | Jid::Full(FullJid { inner }) => inner.domain(),
176        }
177    }
178
179    /// The optional resource part of the JID, as a [`ResourcePart`]. It is guaranteed to be present
180    /// when the JID is a Full variant, which you can check with [`Jid::is_full`].
181    pub fn resource(&self) -> Option<ResourcePart> {
182        match self {
183            Jid::Bare(BareJid { inner }) | Jid::Full(FullJid { inner }) => {
184                inner.resource().map(ResourcePart::new_unchecked)
185            }
186        }
187    }
188
189    /// The optional resource of the Jabber ID. It is guaranteed to be present when the JID is
190    /// a Full variant, which you can check with [`Jid::is_full`].
191    pub fn resource_str(&self) -> Option<&str> {
192        match self {
193            Jid::Bare(BareJid { inner }) | Jid::Full(FullJid { inner }) => inner.resource(),
194        }
195    }
196
197    /// Allocate a new [`BareJid`] from this JID, discarding the resource.
198    pub fn to_bare(&self) -> BareJid {
199        match self {
200            Jid::Full(jid) => jid.to_bare(),
201            Jid::Bare(jid) => jid.clone(),
202        }
203    }
204
205    /// Transforms this JID into a [`BareJid`], throwing away the resource.
206    pub fn into_bare(self) -> BareJid {
207        match self {
208            Jid::Full(jid) => jid.into_bare(),
209            Jid::Bare(jid) => jid,
210        }
211    }
212
213    /// Checks if the JID contains a [`FullJid`]
214    pub fn is_full(&self) -> bool {
215        match self {
216            Self::Full(_) => true,
217            Self::Bare(_) => false,
218        }
219    }
220
221    /// Checks if the JID contains a [`BareJid`]
222    pub fn is_bare(&self) -> bool {
223        !self.is_full()
224    }
225}
226
227impl TryFrom<Jid> for FullJid {
228    type Error = Error;
229
230    fn try_from(jid: Jid) -> Result<Self, Self::Error> {
231        match jid {
232            Jid::Full(full) => Ok(full),
233            Jid::Bare(_) => Err(Error::ResourceMissingInFullJid),
234        }
235    }
236}
237
238impl PartialEq<Jid> for FullJid {
239    fn eq(&self, other: &Jid) -> bool {
240        match other {
241            Jid::Full(full) => self == full,
242            Jid::Bare(_) => false,
243        }
244    }
245}
246
247impl PartialEq<Jid> for BareJid {
248    fn eq(&self, other: &Jid) -> bool {
249        match other {
250            Jid::Full(_) => false,
251            Jid::Bare(bare) => self == bare,
252        }
253    }
254}
255
256impl PartialEq<FullJid> for Jid {
257    fn eq(&self, other: &FullJid) -> bool {
258        match self {
259            Jid::Full(full) => full == other,
260            Jid::Bare(_) => false,
261        }
262    }
263}
264
265impl PartialEq<BareJid> for Jid {
266    fn eq(&self, other: &BareJid) -> bool {
267        match self {
268            Jid::Full(_) => false,
269            Jid::Bare(bare) => bare == other,
270        }
271    }
272}
273
274/// A struct representing a full Jabber ID, with a resource part.
275///
276/// A full JID is composed of 3 components, of which only the node is optional:
277///
278/// - the (optional) node part is the part before the (optional) `@`.
279/// - the domain part is the mandatory part between the (optional) `@` and before the `/`.
280/// - the resource part after the `/`.
281///
282/// Unlike a [`BareJid`], it always contains a resource, and should only be used when you are
283/// certain there is no case where a resource can be missing.  Otherwise, use a [`Jid`] or
284/// [`BareJid`].
285#[derive(Clone, PartialEq, Eq, Hash)]
286pub struct FullJid {
287    inner: InnerJid,
288}
289
290/// A struct representing a bare Jabber ID, without a resource part.
291///
292/// A bare JID is composed of 2 components, of which only the node is optional:
293/// - the (optional) node part is the part before the (optional) `@`.
294/// - the domain part is the mandatory part between the (optional) `@` and before the `/`.
295///
296/// Unlike a [`FullJid`], it can’t contain a resource, and should only be used when you are certain
297/// there is no case where a resource can be set.  Otherwise, use a [`Jid`] or [`FullJid`].
298#[derive(Clone, PartialEq, Eq, Hash)]
299pub struct BareJid {
300    inner: InnerJid,
301}
302
303impl fmt::Debug for FullJid {
304    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
305        write!(fmt, "FullJID({})", self)
306    }
307}
308
309impl fmt::Debug for BareJid {
310    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
311        write!(fmt, "BareJID({})", self)
312    }
313}
314
315impl fmt::Display for FullJid {
316    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
317        fmt.write_str(&self.inner.normalized)
318    }
319}
320
321impl fmt::Display for BareJid {
322    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
323        fmt.write_str(&self.inner.normalized)
324    }
325}
326
327#[cfg(feature = "serde")]
328impl Serialize for FullJid {
329    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
330    where
331        S: Serializer,
332    {
333        serializer.serialize_str(&self.inner.normalized)
334    }
335}
336
337#[cfg(feature = "serde")]
338impl Serialize for BareJid {
339    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
340    where
341        S: Serializer,
342    {
343        serializer.serialize_str(&self.inner.normalized)
344    }
345}
346
347impl FromStr for FullJid {
348    type Err = Error;
349
350    fn from_str(s: &str) -> Result<FullJid, Error> {
351        FullJid::new(s)
352    }
353}
354
355#[cfg(feature = "serde")]
356impl<'de> Deserialize<'de> for FullJid {
357    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
358    where
359        D: Deserializer<'de>,
360    {
361        let s = String::deserialize(deserializer)?;
362        FullJid::from_str(&s).map_err(de::Error::custom)
363    }
364}
365
366#[cfg(feature = "serde")]
367impl<'de> Deserialize<'de> for BareJid {
368    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
369    where
370        D: Deserializer<'de>,
371    {
372        let s = String::deserialize(deserializer)?;
373        BareJid::from_str(&s).map_err(de::Error::custom)
374    }
375}
376
377#[cfg(feature = "quote")]
378impl ToTokens for Jid {
379    fn to_tokens(&self, tokens: &mut TokenStream) {
380        tokens.extend(match self {
381            Jid::Full(full) => quote! { Jid::Full(#full) },
382            Jid::Bare(bare) => quote! { Jid::Bare(#bare) },
383        });
384    }
385}
386
387#[cfg(feature = "quote")]
388impl ToTokens for FullJid {
389    fn to_tokens(&self, tokens: &mut TokenStream) {
390        let inner = &self.inner.normalized;
391        let t = quote! { FullJid::new(#inner).unwrap() };
392        tokens.extend(t);
393    }
394}
395
396#[cfg(feature = "quote")]
397impl ToTokens for BareJid {
398    fn to_tokens(&self, tokens: &mut TokenStream) {
399        let inner = &self.inner.normalized;
400        let t = quote! { BareJid::new(#inner).unwrap() };
401        tokens.extend(t);
402    }
403}
404
405impl FullJid {
406    /// Constructs a full Jabber ID containing all three components. This is of the form
407    /// `node@domain/resource`, where node part is optional.
408    /// If you want a non-fallible version, use [`FullJid::from_parts`] instead.
409    ///
410    /// # Examples
411    ///
412    /// ```
413    /// use jid::FullJid;
414    /// # use jid::Error;
415    ///
416    /// # fn main() -> Result<(), Error> {
417    /// let jid = FullJid::new("node@domain/resource")?;
418    ///
419    /// assert_eq!(jid.node_str(), Some("node"));
420    /// assert_eq!(jid.domain_str(), "domain");
421    /// assert_eq!(jid.resource_str(), "resource");
422    /// # Ok(())
423    /// # }
424    /// ```
425    pub fn new(s: &str) -> Result<FullJid, Error> {
426        let inner = InnerJid::new(s)?;
427        if inner.slash.is_some() {
428            Ok(FullJid { inner })
429        } else {
430            Err(Error::ResourceMissingInFullJid)
431        }
432    }
433
434    /// Returns the inner String of this JID.
435    pub fn into_inner(self) -> String {
436        self.inner.normalized
437    }
438
439    /// Build a [`FullJid`] from typed parts. This method cannot fail because it uses parts that have
440    /// already been parsed and stringprepped into [`NodePart`], [`DomainPart`], and [`ResourcePart`].
441    /// This method allocates and does not consume the typed parts.
442    pub fn from_parts(
443        node: Option<&NodePart>,
444        domain: &DomainPart,
445        resource: &ResourcePart,
446    ) -> FullJid {
447        let (at, slash, normalized) = if let Some(node) = node {
448            // Parts are never empty so len > 0 for NonZeroU16::new is always Some
449            (
450                NonZeroU16::new(node.0.len() as u16),
451                NonZeroU16::new((node.0.len() + 1 + domain.0.len()) as u16),
452                format!("{}@{}/{}", node.0, domain.0, resource.0),
453            )
454        } else {
455            (
456                None,
457                NonZeroU16::new(domain.0.len() as u16),
458                format!("{}/{}", domain.0, resource.0),
459            )
460        };
461
462        let inner = InnerJid {
463            normalized,
464            at,
465            slash,
466        };
467
468        FullJid { inner }
469    }
470
471    /// The optional node part of the JID, as a [`NodePart`]
472    pub fn node(&self) -> Option<NodePart> {
473        self.node_str().map(NodePart::new_unchecked)
474    }
475
476    /// The optional node part of the JID, as a stringy reference
477    pub fn node_str(&self) -> Option<&str> {
478        self.inner.node()
479    }
480
481    /// The domain part of the JID, as a [`DomainPart`]
482    pub fn domain(&self) -> DomainPart {
483        DomainPart::new_unchecked(self.domain_str())
484    }
485
486    /// The domain part of the JID, as a stringy reference
487    pub fn domain_str(&self) -> &str {
488        self.inner.domain()
489    }
490
491    /// The optional resource part of the JID, as a [`ResourcePart`].  Since this is a full JID it
492    /// is always present.
493    pub fn resource(&self) -> ResourcePart {
494        ResourcePart::new_unchecked(self.resource_str())
495    }
496
497    /// The optional resource of the Jabber ID.  Since this is a full JID it is always present.
498    pub fn resource_str(&self) -> &str {
499        self.inner.resource().unwrap()
500    }
501
502    /// Allocate a new [`BareJid`] from this full JID, discarding the resource.
503    pub fn to_bare(&self) -> BareJid {
504        let slash = self.inner.slash.unwrap().get() as usize;
505        let normalized = self.inner.normalized[..slash].to_string();
506        let inner = InnerJid {
507            normalized,
508            at: self.inner.at,
509            slash: None,
510        };
511        BareJid { inner }
512    }
513
514    /// Transforms this full JID into a [`BareJid`], discarding the resource.
515    pub fn into_bare(mut self) -> BareJid {
516        let slash = self.inner.slash.unwrap().get() as usize;
517        self.inner.normalized.truncate(slash);
518        self.inner.normalized.shrink_to_fit();
519        self.inner.slash = None;
520        BareJid { inner: self.inner }
521    }
522}
523
524impl FromStr for BareJid {
525    type Err = Error;
526
527    fn from_str(s: &str) -> Result<BareJid, Error> {
528        BareJid::new(s)
529    }
530}
531
532impl BareJid {
533    /// Constructs a bare Jabber ID, containing two components. This is of the form
534    /// `node`@`domain`, where node part is optional.
535    /// If you want a non-fallible version, use [`BareJid::from_parts`] instead.
536    ///
537    /// # Examples
538    ///
539    /// ```
540    /// use jid::BareJid;
541    /// # use jid::Error;
542    ///
543    /// # fn main() -> Result<(), Error> {
544    /// let jid = BareJid::new("node@domain")?;
545    ///
546    /// assert_eq!(jid.node_str(), Some("node"));
547    /// assert_eq!(jid.domain_str(), "domain");
548    /// # Ok(())
549    /// # }
550    /// ```
551    pub fn new(s: &str) -> Result<BareJid, Error> {
552        let inner = InnerJid::new(s)?;
553        if inner.slash.is_none() {
554            Ok(BareJid { inner })
555        } else {
556            Err(Error::ResourceInBareJid)
557        }
558    }
559
560    /// Returns the inner String of this JID.
561    pub fn into_inner(self) -> String {
562        self.inner.normalized
563    }
564
565    /// Build a [`BareJid`] from typed parts. This method cannot fail because it uses parts that have
566    /// already been parsed and stringprepped into [`NodePart`] and [`DomainPart`]. This method allocates
567    /// and does not consume the typed parts.
568    pub fn from_parts(node: Option<&NodePart>, domain: &DomainPart) -> BareJid {
569        let (at, normalized) = if let Some(node) = node {
570            // Parts are never empty so len > 0 for NonZeroU16::new is always Some
571            (
572                NonZeroU16::new(node.0.len() as u16),
573                format!("{}@{}", node.0, domain.0),
574            )
575        } else {
576            (None, domain.0.clone())
577        };
578
579        let inner = InnerJid {
580            normalized,
581            at,
582            slash: None,
583        };
584
585        BareJid { inner }
586    }
587
588    /// The optional node part of the JID, as a [`NodePart`]
589    pub fn node(&self) -> Option<NodePart> {
590        self.node_str().map(NodePart::new_unchecked)
591    }
592
593    /// The optional node part of the JID, as a stringy reference
594    pub fn node_str(&self) -> Option<&str> {
595        self.inner.node()
596    }
597
598    /// The domain part of the JID, as a [`DomainPart`]
599    pub fn domain(&self) -> DomainPart {
600        DomainPart::new_unchecked(self.domain_str())
601    }
602
603    /// The domain part of the JID, as a stringy reference
604    pub fn domain_str(&self) -> &str {
605        self.inner.domain()
606    }
607
608    /// Constructs a [`BareJid`] from the bare JID, by specifying a [`ResourcePart`].
609    /// If you'd like to specify a stringy resource, use [`BareJid::with_resource_str`] instead.
610    ///
611    /// # Examples
612    ///
613    /// ```
614    /// use jid::{BareJid, ResourcePart};
615    ///
616    /// let resource = ResourcePart::new("resource").unwrap();
617    /// let bare = BareJid::new("node@domain").unwrap();
618    /// let full = bare.with_resource(&resource);
619    ///
620    /// assert_eq!(full.node_str(), Some("node"));
621    /// assert_eq!(full.domain_str(), "domain");
622    /// assert_eq!(full.resource_str(), "resource");
623    /// ```
624    pub fn with_resource(&self, resource: &ResourcePart) -> FullJid {
625        let slash = NonZeroU16::new(self.inner.normalized.len() as u16);
626        let normalized = format!("{}/{resource}", self.inner.normalized);
627        let inner = InnerJid {
628            normalized,
629            at: self.inner.at,
630            slash,
631        };
632
633        FullJid { inner }
634    }
635
636    /// Constructs a [`FullJid`] from the bare JID, by specifying a stringy `resource`.
637    /// If your resource has already been parsed into a [`ResourcePart`], use [`BareJid::with_resource`].
638    ///
639    /// # Examples
640    ///
641    /// ```
642    /// use jid::BareJid;
643    ///
644    /// let bare = BareJid::new("node@domain").unwrap();
645    /// let full = bare.with_resource_str("resource").unwrap();
646    ///
647    /// assert_eq!(full.node_str(), Some("node"));
648    /// assert_eq!(full.domain_str(), "domain");
649    /// assert_eq!(full.resource_str(), "resource");
650    /// ```
651    pub fn with_resource_str(&self, resource: &str) -> Result<FullJid, Error> {
652        let resource = ResourcePart::new(resource)?;
653        Ok(self.with_resource(&resource))
654    }
655}
656
657#[cfg(feature = "minidom")]
658use minidom::{IntoAttributeValue, Node};
659
660#[cfg(feature = "minidom")]
661impl IntoAttributeValue for Jid {
662    fn into_attribute_value(self) -> Option<String> {
663        Some(self.to_string())
664    }
665}
666
667#[cfg(feature = "minidom")]
668impl From<Jid> for Node {
669    fn from(jid: Jid) -> Node {
670        Node::Text(jid.to_string())
671    }
672}
673
674#[cfg(feature = "minidom")]
675impl IntoAttributeValue for FullJid {
676    fn into_attribute_value(self) -> Option<String> {
677        Some(self.to_string())
678    }
679}
680
681#[cfg(feature = "minidom")]
682impl From<FullJid> for Node {
683    fn from(jid: FullJid) -> Node {
684        Node::Text(jid.to_string())
685    }
686}
687
688#[cfg(feature = "minidom")]
689impl IntoAttributeValue for BareJid {
690    fn into_attribute_value(self) -> Option<String> {
691        Some(self.to_string())
692    }
693}
694
695#[cfg(feature = "minidom")]
696impl From<BareJid> for Node {
697    fn from(jid: BareJid) -> Node {
698        Node::Text(jid.to_string())
699    }
700}
701
702#[cfg(test)]
703mod tests {
704    use super::*;
705
706    use std::collections::HashMap;
707
708    macro_rules! assert_size (
709        ($t:ty, $sz:expr) => (
710            assert_eq!(::std::mem::size_of::<$t>(), $sz);
711        );
712    );
713
714    #[cfg(target_pointer_width = "32")]
715    #[test]
716    fn test_size() {
717        assert_size!(BareJid, 16);
718        assert_size!(FullJid, 16);
719        assert_size!(Jid, 20);
720    }
721
722    #[cfg(target_pointer_width = "64")]
723    #[test]
724    fn test_size() {
725        assert_size!(BareJid, 32);
726        assert_size!(FullJid, 32);
727        assert_size!(Jid, 40);
728    }
729
730    #[test]
731    fn can_parse_full_jids() {
732        assert_eq!(
733            FullJid::from_str("a@b.c/d"),
734            Ok(FullJid::new("a@b.c/d").unwrap())
735        );
736        assert_eq!(
737            FullJid::from_str("b.c/d"),
738            Ok(FullJid::new("b.c/d").unwrap())
739        );
740
741        assert_eq!(
742            FullJid::from_str("a@b.c"),
743            Err(Error::ResourceMissingInFullJid)
744        );
745        assert_eq!(
746            FullJid::from_str("b.c"),
747            Err(Error::ResourceMissingInFullJid)
748        );
749    }
750
751    #[test]
752    fn can_parse_bare_jids() {
753        assert_eq!(
754            BareJid::from_str("a@b.c"),
755            Ok(BareJid::new("a@b.c").unwrap())
756        );
757        assert_eq!(BareJid::from_str("b.c"), Ok(BareJid::new("b.c").unwrap()));
758    }
759
760    #[test]
761    fn can_parse_jids() {
762        let full = FullJid::from_str("a@b.c/d").unwrap();
763        let bare = BareJid::from_str("e@f.g").unwrap();
764
765        assert_eq!(Jid::from_str("a@b.c/d"), Ok(Jid::Full(full)));
766        assert_eq!(Jid::from_str("e@f.g"), Ok(Jid::Bare(bare)));
767    }
768
769    #[test]
770    fn full_to_bare_jid() {
771        let bare: BareJid = FullJid::new("a@b.c/d").unwrap().to_bare();
772        assert_eq!(bare, BareJid::new("a@b.c").unwrap());
773    }
774
775    #[test]
776    fn bare_to_full_jid_str() {
777        assert_eq!(
778            BareJid::new("a@b.c")
779                .unwrap()
780                .with_resource_str("d")
781                .unwrap(),
782            FullJid::new("a@b.c/d").unwrap()
783        );
784    }
785
786    #[test]
787    fn bare_to_full_jid() {
788        assert_eq!(
789            BareJid::new("a@b.c")
790                .unwrap()
791                .with_resource(&ResourcePart::new("d").unwrap()),
792            FullJid::new("a@b.c/d").unwrap()
793        )
794    }
795
796    #[test]
797    fn node_from_jid() {
798        let jid = Jid::new("a@b.c/d").unwrap();
799
800        assert_eq!(jid.node_str(), Some("a"),);
801
802        assert_eq!(jid.node(), Some(NodePart::new("a").unwrap()));
803    }
804
805    #[test]
806    fn domain_from_jid() {
807        let jid = Jid::new("a@b.c").unwrap();
808
809        assert_eq!(jid.domain_str(), "b.c");
810
811        assert_eq!(jid.domain(), DomainPart::new("b.c").unwrap());
812    }
813
814    #[test]
815    fn resource_from_jid() {
816        let jid = Jid::new("a@b.c/d").unwrap();
817
818        assert_eq!(jid.resource_str(), Some("d"),);
819
820        assert_eq!(jid.resource(), Some(ResourcePart::new("d").unwrap()));
821    }
822
823    #[test]
824    fn jid_to_full_bare() {
825        let full = FullJid::new("a@b.c/d").unwrap();
826        let bare = BareJid::new("a@b.c").unwrap();
827
828        assert_eq!(FullJid::try_from(Jid::Full(full.clone())), Ok(full.clone()));
829        assert_eq!(
830            FullJid::try_from(Jid::Bare(bare.clone())),
831            Err(Error::ResourceMissingInFullJid),
832        );
833        assert_eq!(Jid::Bare(full.clone().to_bare()), bare.clone());
834        assert_eq!(Jid::Bare(bare.clone()), bare);
835    }
836
837    #[test]
838    fn serialise() {
839        assert_eq!(FullJid::new("a@b/c").unwrap().to_string(), "a@b/c");
840        assert_eq!(BareJid::new("a@b").unwrap().to_string(), "a@b");
841    }
842
843    #[test]
844    fn hash() {
845        let _map: HashMap<Jid, String> = HashMap::new();
846    }
847
848    #[test]
849    fn invalid_jids() {
850        assert_eq!(BareJid::from_str(""), Err(Error::DomainEmpty));
851        assert_eq!(BareJid::from_str("/c"), Err(Error::DomainEmpty));
852        assert_eq!(BareJid::from_str("a@/c"), Err(Error::DomainEmpty));
853        assert_eq!(BareJid::from_str("@b"), Err(Error::NodeEmpty));
854        assert_eq!(BareJid::from_str("b/"), Err(Error::ResourceEmpty));
855
856        assert_eq!(FullJid::from_str(""), Err(Error::DomainEmpty));
857        assert_eq!(FullJid::from_str("/c"), Err(Error::DomainEmpty));
858        assert_eq!(FullJid::from_str("a@/c"), Err(Error::DomainEmpty));
859        assert_eq!(FullJid::from_str("@b"), Err(Error::NodeEmpty));
860        assert_eq!(FullJid::from_str("b/"), Err(Error::ResourceEmpty));
861        assert_eq!(
862            FullJid::from_str("a@b"),
863            Err(Error::ResourceMissingInFullJid)
864        );
865    }
866
867    #[test]
868    fn display_jids() {
869        assert_eq!(FullJid::new("a@b/c").unwrap().to_string(), "a@b/c");
870        assert_eq!(BareJid::new("a@b").unwrap().to_string(), "a@b");
871        assert_eq!(
872            Jid::Full(FullJid::new("a@b/c").unwrap()).to_string(),
873            "a@b/c"
874        );
875        assert_eq!(Jid::Bare(BareJid::new("a@b").unwrap()).to_string(), "a@b");
876    }
877
878    #[cfg(feature = "minidom")]
879    #[test]
880    fn minidom() {
881        let elem: minidom::Element = "<message xmlns='ns1' from='a@b/c'/>".parse().unwrap();
882        let to: Jid = elem.attr("from").unwrap().parse().unwrap();
883        assert_eq!(to, Jid::Full(FullJid::new("a@b/c").unwrap()));
884
885        let elem: minidom::Element = "<message xmlns='ns1' from='a@b'/>".parse().unwrap();
886        let to: Jid = elem.attr("from").unwrap().parse().unwrap();
887        assert_eq!(to, Jid::Bare(BareJid::new("a@b").unwrap()));
888
889        let elem: minidom::Element = "<message xmlns='ns1' from='a@b/c'/>".parse().unwrap();
890        let to: FullJid = elem.attr("from").unwrap().parse().unwrap();
891        assert_eq!(to, FullJid::new("a@b/c").unwrap());
892
893        let elem: minidom::Element = "<message xmlns='ns1' from='a@b'/>".parse().unwrap();
894        let to: BareJid = elem.attr("from").unwrap().parse().unwrap();
895        assert_eq!(to, BareJid::new("a@b").unwrap());
896    }
897
898    #[cfg(feature = "minidom")]
899    #[test]
900    fn minidom_into_attr() {
901        let full = FullJid::new("a@b/c").unwrap();
902        let elem = minidom::Element::builder("message", "jabber:client")
903            .attr("from", full.clone())
904            .build();
905        assert_eq!(elem.attr("from"), Some(full.to_string().as_str()));
906
907        let bare = BareJid::new("a@b").unwrap();
908        let elem = minidom::Element::builder("message", "jabber:client")
909            .attr("from", bare.clone())
910            .build();
911        assert_eq!(elem.attr("from"), Some(bare.to_string().as_str()));
912
913        let jid = Jid::Bare(bare.clone());
914        let _elem = minidom::Element::builder("message", "jabber:client")
915            .attr("from", jid)
916            .build();
917        assert_eq!(elem.attr("from"), Some(bare.to_string().as_str()));
918    }
919
920    #[test]
921    fn stringprep() {
922        let full = FullJid::from_str("Test@☃.coM/Test™").unwrap();
923        let equiv = FullJid::new("test@☃.com/TestTM").unwrap();
924        assert_eq!(full, equiv);
925    }
926
927    #[test]
928    fn invalid_stringprep() {
929        FullJid::from_str("a@b/🎉").unwrap_err();
930    }
931
932    #[test]
933    fn jid_from_parts() {
934        let node = NodePart::new("node").unwrap();
935        let domain = DomainPart::new("domain").unwrap();
936        let resource = ResourcePart::new("resource").unwrap();
937
938        let jid = Jid::from_parts(Some(&node), &domain, Some(&resource));
939        assert_eq!(jid, Jid::new("node@domain/resource").unwrap());
940
941        let barejid = BareJid::from_parts(Some(&node), &domain);
942        assert_eq!(barejid, BareJid::new("node@domain").unwrap());
943
944        let fulljid = FullJid::from_parts(Some(&node), &domain, &resource);
945        assert_eq!(fulljid, FullJid::new("node@domain/resource").unwrap());
946    }
947
948    #[test]
949    #[cfg(feature = "serde")]
950    fn jid_ser_de() {
951        let jid: Jid = Jid::new("node@domain").unwrap();
952        serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain")]);
953
954        let jid: Jid = Jid::new("node@domain/resource").unwrap();
955        serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain/resource")]);
956
957        let jid: BareJid = BareJid::new("node@domain").unwrap();
958        serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain")]);
959
960        let jid: FullJid = FullJid::new("node@domain/resource").unwrap();
961        serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain/resource")]);
962    }
963}