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