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