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