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