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