lib.rs

  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((
239        node,
240        domain.ok_or(JidParseError::NoDomain)?,
241        resource,
242    ))
243}
244
245impl FromStr for FullJid {
246    type Err = JidParseError;
247
248    fn from_str(s: &str) -> Result<FullJid, JidParseError> {
249        let (ns, ds, rs): StringJid = _from_str(s)?;
250        Ok(FullJid {
251            node: ns,
252            domain: ds,
253            resource: rs.ok_or(JidParseError::NoResource)?,
254        })
255    }
256}
257
258impl FullJid {
259    /// Constructs a Full Jabber ID containing all three components.
260    ///
261    /// This is of the form `node`@`domain`/`resource`.
262    ///
263    /// # Examples
264    ///
265    /// ```
266    /// use jid::FullJid;
267    ///
268    /// let jid = FullJid::new("node", "domain", "resource");
269    ///
270    /// assert_eq!(jid.node, Some("node".to_owned()));
271    /// assert_eq!(jid.domain, "domain".to_owned());
272    /// assert_eq!(jid.resource, "resource".to_owned());
273    /// ```
274    pub fn new<NS, DS, RS>(node: NS, domain: DS, resource: RS) -> FullJid
275    where
276        NS: Into<String>,
277        DS: Into<String>,
278        RS: Into<String>,
279    {
280        FullJid {
281            node: Some(node.into()),
282            domain: domain.into(),
283            resource: resource.into()
284        }
285    }
286
287    /// Constructs a new Jabber ID from an existing one, with the node swapped out with a new one.
288    ///
289    /// # Examples
290    ///
291    /// ```
292    /// use jid::FullJid;
293    ///
294    /// let jid = FullJid::new("node", "domain", "resource");
295    ///
296    /// assert_eq!(jid.node, Some("node".to_owned()));
297    ///
298    /// let new_jid = jid.with_node("new_node");
299    ///
300    /// assert_eq!(new_jid.node, Some("new_node".to_owned()));
301    /// ```
302    pub fn with_node<NS>(&self, node: NS) -> FullJid
303    where
304        NS: Into<String>,
305    {
306        FullJid {
307            node: Some(node.into()),
308            domain: self.domain.clone(),
309            resource: self.resource.clone(),
310        }
311    }
312
313    /// Constructs a new Jabber ID from an existing one, with the domain swapped out with a new one.
314    ///
315    /// # Examples
316    ///
317    /// ```
318    /// use jid::FullJid;
319    ///
320    /// let jid = FullJid::new("node", "domain", "resource");
321    ///
322    /// assert_eq!(jid.domain, "domain".to_owned());
323    ///
324    /// let new_jid = jid.with_domain("new_domain");
325    ///
326    /// assert_eq!(new_jid.domain, "new_domain");
327    /// ```
328    pub fn with_domain<DS>(&self, domain: DS) -> FullJid
329    where
330        DS: Into<String>,
331    {
332        FullJid {
333            node: self.node.clone(),
334            domain: domain.into(),
335            resource: self.resource.clone(),
336        }
337    }
338
339    /// Constructs a Full Jabber ID from a Bare Jabber ID, specifying a `resource`.
340    ///
341    /// # Examples
342    ///
343    /// ```
344    /// use jid::FullJid;
345    ///
346    /// let jid = FullJid::new("node", "domain", "resource");
347    ///
348    /// assert_eq!(jid.resource, "resource".to_owned());
349    ///
350    /// let new_jid = jid.with_resource("new_resource");
351    ///
352    /// assert_eq!(new_jid.resource, "new_resource");
353    /// ```
354    pub fn with_resource<RS>(&self, resource: RS) -> FullJid
355    where
356        RS: Into<String>,
357    {
358        FullJid {
359            node: self.node.clone(),
360            domain: self.domain.clone(),
361            resource: resource.into(),
362        }
363    }
364}
365
366impl FromStr for BareJid {
367    type Err = JidParseError;
368
369    fn from_str(s: &str) -> Result<BareJid, JidParseError> {
370        let (ns, ds, _rs): StringJid = _from_str(s)?;
371        Ok(BareJid {
372            node: ns,
373            domain: ds,
374        })
375    }
376}
377
378impl BareJid {
379    /// Constructs a Bare Jabber ID, containing two components.
380    ///
381    /// This is of the form `node`@`domain`.
382    ///
383    /// # Examples
384    ///
385    /// ```
386    /// use jid::BareJid;
387    ///
388    /// let jid = BareJid::new("node", "domain");
389    ///
390    /// assert_eq!(jid.node, Some("node".to_owned()));
391    /// assert_eq!(jid.domain, "domain".to_owned());
392    /// ```
393    pub fn new<NS, DS>(node: NS, domain: DS) -> BareJid
394    where
395        NS: Into<String>,
396        DS: Into<String>,
397    {
398        BareJid {
399            node: Some(node.into()),
400            domain: domain.into(),
401        }
402    }
403
404    /// Constructs a Bare Jabber ID containing only a `domain`.
405    ///
406    /// This is of the form `domain`.
407    ///
408    /// # Examples
409    ///
410    /// ```
411    /// use jid::BareJid;
412    ///
413    /// let jid = BareJid::domain("domain");
414    ///
415    /// assert_eq!(jid.node, None);
416    /// assert_eq!(jid.domain, "domain".to_owned());
417    /// ```
418    pub fn domain<DS>(domain: DS) -> BareJid
419    where
420        DS: Into<String>,
421    {
422        BareJid {
423            node: None,
424            domain: domain.into(),
425        }
426    }
427
428    /// Constructs a new Jabber ID from an existing one, with the node swapped out with a new one.
429    ///
430    /// # Examples
431    ///
432    /// ```
433    /// use jid::BareJid;
434    ///
435    /// let jid = BareJid::domain("domain");
436    ///
437    /// assert_eq!(jid.node, None);
438    ///
439    /// let new_jid = jid.with_node("node");
440    ///
441    /// assert_eq!(new_jid.node, Some("node".to_owned()));
442    /// ```
443    pub fn with_node<NS>(&self, node: NS) -> BareJid
444    where
445        NS: Into<String>,
446    {
447        BareJid {
448            node: Some(node.into()),
449            domain: self.domain.clone(),
450        }
451    }
452
453    /// Constructs a new Jabber ID from an existing one, with the domain swapped out with a new one.
454    ///
455    /// # Examples
456    ///
457    /// ```
458    /// use jid::BareJid;
459    ///
460    /// let jid = BareJid::domain("domain");
461    ///
462    /// assert_eq!(jid.domain, "domain");
463    ///
464    /// let new_jid = jid.with_domain("new_domain");
465    ///
466    /// assert_eq!(new_jid.domain, "new_domain");
467    /// ```
468    pub fn with_domain<DS>(&self, domain: DS) -> BareJid
469    where
470        DS: Into<String>,
471    {
472        BareJid {
473            node: self.node.clone(),
474            domain: domain.into(),
475        }
476    }
477
478    /// Constructs a Full Jabber ID from a Bare Jabber ID, specifying a `resource`.
479    ///
480    /// # Examples
481    ///
482    /// ```
483    /// use jid::BareJid;
484    ///
485    /// let bare = BareJid::new("node", "domain");
486    /// let full = bare.with_resource("resource");
487    ///
488    /// assert_eq!(full.node, Some("node".to_owned()));
489    /// assert_eq!(full.domain, "domain".to_owned());
490    /// assert_eq!(full.resource, "resource".to_owned());
491    /// ```
492    pub fn with_resource<RS>(self, resource: RS) -> FullJid
493    where
494        RS: Into<String>,
495    {
496        FullJid {
497            node: self.node,
498            domain: self.domain,
499            resource: resource.into(),
500        }
501    }
502}
503
504#[cfg(feature = "minidom")]
505use minidom::{ElementEmitter, IntoAttributeValue, IntoElements};
506
507#[cfg(feature = "minidom")]
508impl IntoAttributeValue for Jid {
509    fn into_attribute_value(self) -> Option<String> {
510        Some(String::from(self))
511    }
512}
513
514#[cfg(feature = "minidom")]
515impl IntoElements for Jid {
516    fn into_elements(self, emitter: &mut ElementEmitter) {
517        emitter.append_text_node(String::from(self))
518    }
519}
520
521#[cfg(feature = "minidom")]
522impl IntoAttributeValue for FullJid {
523    fn into_attribute_value(self) -> Option<String> {
524        Some(String::from(self))
525    }
526}
527
528#[cfg(feature = "minidom")]
529impl IntoElements for FullJid {
530    fn into_elements(self, emitter: &mut ElementEmitter) {
531        emitter.append_text_node(String::from(self))
532    }
533}
534
535#[cfg(feature = "minidom")]
536impl IntoAttributeValue for BareJid {
537    fn into_attribute_value(self) -> Option<String> {
538        Some(String::from(self))
539    }
540}
541
542#[cfg(feature = "minidom")]
543impl IntoElements for BareJid {
544    fn into_elements(self, emitter: &mut ElementEmitter) {
545        emitter.append_text_node(String::from(self))
546    }
547}
548
549#[cfg(test)]
550mod tests {
551    use super::*;
552
553    use std::str::FromStr;
554
555    #[test]
556    fn can_parse_full_jids() {
557        assert_eq!(FullJid::from_str("a@b.c/d"), Ok(FullJid::new("a", "b.c", "d")));
558        assert_eq!(
559            FullJid::from_str("b.c/d"),
560            Ok(FullJid {
561                node: None,
562                domain: "b.c".to_owned(),
563                resource: "d".to_owned(),
564            })
565        );
566
567        assert_eq!(FullJid::from_str("a@b.c"), Err(JidParseError::NoResource));
568        assert_eq!(FullJid::from_str("b.c"), Err(JidParseError::NoResource));
569    }
570
571    #[test]
572    fn can_parse_bare_jids() {
573        assert_eq!(BareJid::from_str("a@b.c/d"), Ok(BareJid::new("a", "b.c")));
574        assert_eq!(
575            BareJid::from_str("b.c/d"),
576            Ok(BareJid {
577                node: None,
578                domain: "b.c".to_owned(),
579            })
580        );
581
582        assert_eq!(BareJid::from_str("a@b.c"), Ok(BareJid::new("a", "b.c")));
583        assert_eq!(
584            BareJid::from_str("b.c"),
585            Ok(BareJid {
586                node: None,
587                domain: "b.c".to_owned(),
588            })
589        );
590    }
591
592    #[test]
593    fn can_parse_jids() {
594        let full = FullJid::from_str("a@b.c/d").unwrap();
595        let bare = BareJid::from_str("e@f.g").unwrap();
596
597        assert_eq!(Jid::from_str("a@b.c/d"), Ok(Jid::Full(full)));
598        assert_eq!(Jid::from_str("e@f.g"), Ok(Jid::Bare(bare)));
599    }
600
601    #[test]
602    fn full_to_bare_jid() {
603        let bare: BareJid = FullJid::new("a", "b.c", "d").into();
604        assert_eq!(bare, BareJid::new("a", "b.c"));
605    }
606
607    #[test]
608    fn bare_to_full_jid() {
609        assert_eq!(BareJid::new("a", "b.c").with_resource("d"), FullJid::new("a", "b.c", "d"));
610    }
611
612    #[test]
613    fn serialise() {
614        assert_eq!(
615            String::from(FullJid::new("a", "b", "c")),
616            String::from("a@b/c")
617        );
618        assert_eq!(
619            String::from(BareJid::new("a", "b")),
620            String::from("a@b")
621        );
622    }
623
624    #[test]
625    fn invalid_jids() {
626        assert_eq!(BareJid::from_str(""), Err(JidParseError::NoDomain));
627        assert_eq!(BareJid::from_str("/c"), Err(JidParseError::NoDomain));
628        assert_eq!(BareJid::from_str("a@/c"), Err(JidParseError::NoDomain));
629        assert_eq!(BareJid::from_str("@b"), Err(JidParseError::EmptyNode));
630        assert_eq!(BareJid::from_str("b/"), Err(JidParseError::EmptyResource));
631
632        assert_eq!(FullJid::from_str(""), Err(JidParseError::NoDomain));
633        assert_eq!(FullJid::from_str("/c"), Err(JidParseError::NoDomain));
634        assert_eq!(FullJid::from_str("a@/c"), Err(JidParseError::NoDomain));
635        assert_eq!(FullJid::from_str("@b"), Err(JidParseError::EmptyNode));
636        assert_eq!(FullJid::from_str("b/"), Err(JidParseError::EmptyResource));
637        assert_eq!(FullJid::from_str("a@b"), Err(JidParseError::NoResource));
638    }
639
640    #[cfg(feature = "minidom")]
641    #[test]
642    fn minidom() {
643        let elem: minidom::Element = "<message from='a@b/c'/>".parse().unwrap();
644        let to: Jid = elem.attr("from").unwrap().parse().unwrap();
645        assert_eq!(to, Jid::Full(FullJid::new("a", "b", "c")));
646
647        let elem: minidom::Element = "<message from='a@b'/>".parse().unwrap();
648        let to: Jid = elem.attr("from").unwrap().parse().unwrap();
649        assert_eq!(to, Jid::Bare(BareJid::new("a", "b")));
650
651        let elem: minidom::Element = "<message from='a@b/c'/>".parse().unwrap();
652        let to: FullJid = elem.attr("from").unwrap().parse().unwrap();
653        assert_eq!(to, FullJid::new("a", "b", "c"));
654
655        let elem: minidom::Element = "<message from='a@b'/>".parse().unwrap();
656        let to: BareJid = elem.attr("from").unwrap().parse().unwrap();
657        assert_eq!(to, BareJid::new("a", "b"));
658    }
659
660    #[cfg(feature = "minidom")]
661    #[test]
662    fn minidom_into_attr() {
663        let full = FullJid::new("a", "b", "c");
664        let elem = minidom::Element::builder("message")
665            .ns("jabber:client")
666            .attr("from", full.clone())
667            .build();
668        assert_eq!(elem.attr("from"), Some(String::from(full).as_ref()));
669
670        let bare = BareJid::new("a", "b");
671        let elem = minidom::Element::builder("message")
672            .ns("jabber:client")
673            .attr("from", bare.clone())
674            .build();
675        assert_eq!(elem.attr("from"), Some(String::from(bare.clone()).as_ref()));
676
677        let jid = Jid::Bare(bare.clone());
678        let _elem = minidom::Element::builder("message")
679            .ns("jabber:client")
680            .attr("from", jid)
681            .build();
682        assert_eq!(elem.attr("from"), Some(String::from(bare).as_ref()));
683    }
684}