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