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