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