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