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