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}