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