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}