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