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