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