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