lib.rs

  1#![deny(missing_docs)]
  2
  3//! Provides a type for Jabber IDs.
  4//!
  5//! For usage, check the documentation on the `Jid` struct.
  6
  7#[macro_use]
  8extern crate failure_derive;
  9
 10use std::convert::Into;
 11use std::fmt;
 12use std::str::FromStr;
 13
 14/// An error that signifies that a `Jid` cannot be parsed from a string.
 15#[derive(Debug, Clone, PartialEq, Eq, Fail)]
 16pub enum JidParseError {
 17    /// Happens when there is no domain, that is either the string is empty,
 18    /// starts with a /, or contains the @/ sequence.
 19    #[fail(display = "no domain found in this JID")]
 20    NoDomain,
 21
 22    /// Happens when the node is empty, that is the string starts with a @.
 23    #[fail(display = "nodepart empty despite the presence of a @")]
 24    EmptyNode,
 25
 26    /// Happens when the resource is empty, that is the string ends with a /.
 27    #[fail(display = "resource empty despite the presence of a /")]
 28    EmptyResource,
 29}
 30
 31/// A struct representing a Jabber ID.
 32///
 33/// A Jabber ID is composed of 3 components, of which 2 are optional:
 34///
 35///  - A node/name, `node`, which is the optional part before the @.
 36///  - A domain, `domain`, which is the mandatory part after the @ but before the /.
 37///  - A resource, `resource`, which is the optional part after the /.
 38#[derive(Clone, PartialEq, Eq, Hash)]
 39pub struct Jid {
 40    /// The node part of the Jabber ID, if it exists, else None.
 41    pub node: Option<String>,
 42    /// The domain of the Jabber ID.
 43    pub domain: String,
 44    /// The resource of the Jabber ID, if it exists, else None.
 45    pub resource: Option<String>,
 46}
 47
 48impl From<Jid> for String {
 49    fn from(jid: Jid) -> String {
 50        let mut string = String::new();
 51        if let Some(ref node) = jid.node {
 52            string.push_str(node);
 53            string.push('@');
 54        }
 55        string.push_str(&jid.domain);
 56        if let Some(ref resource) = jid.resource {
 57            string.push('/');
 58            string.push_str(resource);
 59        }
 60        string
 61    }
 62}
 63
 64impl fmt::Debug for Jid {
 65    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
 66        write!(fmt, "JID({})", self)
 67    }
 68}
 69
 70impl fmt::Display for Jid {
 71    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
 72        fmt.write_str(String::from(self.clone()).as_ref())
 73    }
 74}
 75
 76enum ParserState {
 77    Node,
 78    Domain,
 79    Resource,
 80}
 81
 82impl FromStr for Jid {
 83    type Err = JidParseError;
 84
 85    fn from_str(s: &str) -> Result<Jid, JidParseError> {
 86        // TODO: very naive, may need to do it differently
 87        let iter = s.chars();
 88        let mut buf = String::with_capacity(s.len());
 89        let mut state = ParserState::Node;
 90        let mut node = None;
 91        let mut domain = None;
 92        let mut resource = None;
 93        for c in iter {
 94            match state {
 95                ParserState::Node => {
 96                    match c {
 97                        '@' => {
 98                            if buf == "" {
 99                                return Err(JidParseError::EmptyNode);
100                            }
101                            state = ParserState::Domain;
102                            node = Some(buf.clone()); // TODO: performance tweaks, do not need to copy it
103                            buf.clear();
104                        }
105                        '/' => {
106                            if buf == "" {
107                                return Err(JidParseError::NoDomain);
108                            }
109                            state = ParserState::Resource;
110                            domain = Some(buf.clone()); // TODO: performance tweaks
111                            buf.clear();
112                        }
113                        c => {
114                            buf.push(c);
115                        }
116                    }
117                }
118                ParserState::Domain => {
119                    match c {
120                        '/' => {
121                            if buf == "" {
122                                return Err(JidParseError::NoDomain);
123                            }
124                            state = ParserState::Resource;
125                            domain = Some(buf.clone()); // TODO: performance tweaks
126                            buf.clear();
127                        }
128                        c => {
129                            buf.push(c);
130                        }
131                    }
132                }
133                ParserState::Resource => {
134                    buf.push(c);
135                }
136            }
137        }
138        if !buf.is_empty() {
139            match state {
140                ParserState::Node => {
141                    domain = Some(buf);
142                }
143                ParserState::Domain => {
144                    domain = Some(buf);
145                }
146                ParserState::Resource => {
147                    resource = Some(buf);
148                }
149            }
150        } else if let ParserState::Resource = state {
151            return Err(JidParseError::EmptyResource);
152        }
153        Ok(Jid {
154            node: node,
155            domain: domain.ok_or(JidParseError::NoDomain)?,
156            resource: resource,
157        })
158    }
159}
160
161impl Jid {
162    /// Constructs a Jabber ID containing all three components.
163    ///
164    /// This is of the form `node`@`domain`/`resource`.
165    ///
166    /// # Examples
167    ///
168    /// ```
169    /// use jid::Jid;
170    ///
171    /// let jid = Jid::full("node", "domain", "resource");
172    ///
173    /// assert_eq!(jid.node, Some("node".to_owned()));
174    /// assert_eq!(jid.domain, "domain".to_owned());
175    /// assert_eq!(jid.resource, Some("resource".to_owned()));
176    /// ```
177    pub fn full<NS, DS, RS>(node: NS, domain: DS, resource: RS) -> Jid
178    where
179        NS: Into<String>,
180        DS: Into<String>,
181        RS: Into<String>,
182    {
183        Jid {
184            node: Some(node.into()),
185            domain: domain.into(),
186            resource: Some(resource.into()),
187        }
188    }
189
190    /// Constructs a Jabber ID containing only the `node` and `domain` components.
191    ///
192    /// This is of the form `node`@`domain`.
193    ///
194    /// # Examples
195    ///
196    /// ```
197    /// use jid::Jid;
198    ///
199    /// let jid = Jid::bare("node", "domain");
200    ///
201    /// assert_eq!(jid.node, Some("node".to_owned()));
202    /// assert_eq!(jid.domain, "domain".to_owned());
203    /// assert_eq!(jid.resource, None);
204    /// ```
205    pub fn bare<NS, DS>(node: NS, domain: DS) -> Jid
206    where
207        NS: Into<String>,
208        DS: Into<String>,
209    {
210        Jid {
211            node: Some(node.into()),
212            domain: domain.into(),
213            resource: None,
214        }
215    }
216
217    /// Returns a new Jabber ID from the current one with only node and domain.
218    ///
219    /// This is of the form `node`@`domain`.
220    ///
221    /// # Examples
222    ///
223    /// ```
224    /// use jid::Jid;
225    ///
226    /// let jid = Jid::full("node", "domain", "resource").into_bare_jid();
227    ///
228    /// assert_eq!(jid.node, Some("node".to_owned()));
229    /// assert_eq!(jid.domain, "domain".to_owned());
230    /// assert_eq!(jid.resource, None);
231    /// ```
232    pub fn into_bare_jid(self) -> Jid {
233        Jid {
234            node: self.node,
235            domain: self.domain,
236            resource: None,
237        }
238    }
239
240    /// Constructs a Jabber ID containing only a `domain`.
241    ///
242    /// This is of the form `domain`.
243    ///
244    /// # Examples
245    ///
246    /// ```
247    /// use jid::Jid;
248    ///
249    /// let jid = Jid::domain("domain");
250    ///
251    /// assert_eq!(jid.node, None);
252    /// assert_eq!(jid.domain, "domain".to_owned());
253    /// assert_eq!(jid.resource, None);
254    /// ```
255    pub fn domain<DS>(domain: DS) -> Jid
256    where
257        DS: Into<String>,
258    {
259        Jid {
260            node: None,
261            domain: domain.into(),
262            resource: None,
263        }
264    }
265
266    /// Returns a new Jabber ID from the current one with only domain.
267    ///
268    /// This is of the form `domain`.
269    ///
270    /// # Examples
271    ///
272    /// ```
273    /// use jid::Jid;
274    ///
275    /// let jid = Jid::full("node", "domain", "resource").into_domain_jid();
276    ///
277    /// assert_eq!(jid.node, None);
278    /// assert_eq!(jid.domain, "domain".to_owned());
279    /// assert_eq!(jid.resource, None);
280    /// ```
281    pub fn into_domain_jid(self) -> Jid {
282        Jid {
283            node: None,
284            domain: self.domain,
285            resource: None,
286        }
287    }
288
289    /// Constructs a Jabber ID containing the `domain` and `resource` components.
290    ///
291    /// This is of the form `domain`/`resource`.
292    ///
293    /// # Examples
294    ///
295    /// ```
296    /// use jid::Jid;
297    ///
298    /// let jid = Jid::domain_with_resource("domain", "resource");
299    ///
300    /// assert_eq!(jid.node, None);
301    /// assert_eq!(jid.domain, "domain".to_owned());
302    /// assert_eq!(jid.resource, Some("resource".to_owned()));
303    /// ```
304    pub fn domain_with_resource<DS, RS>(domain: DS, resource: RS) -> Jid
305    where
306        DS: Into<String>,
307        RS: Into<String>,
308    {
309        Jid {
310            node: None,
311            domain: domain.into(),
312            resource: Some(resource.into()),
313        }
314    }
315
316    /// Constructs a new Jabber ID from an existing one, with the node swapped out with a new one.
317    ///
318    /// # Examples
319    ///
320    /// ```
321    /// use jid::Jid;
322    ///
323    /// let jid = Jid::domain("domain");
324    ///
325    /// assert_eq!(jid.node, None);
326    ///
327    /// let new_jid = jid.with_node("node");
328    ///
329    /// assert_eq!(new_jid.node, Some("node".to_owned()));
330    /// ```
331    pub fn with_node<S>(&self, node: S) -> Jid
332    where
333        S: Into<String>,
334    {
335        Jid {
336            node: Some(node.into()),
337            domain: self.domain.clone(),
338            resource: self.resource.clone(),
339        }
340    }
341
342    /// Constructs a new Jabber ID from an existing one, with the domain swapped out with a new one.
343    ///
344    /// # Examples
345    ///
346    /// ```
347    /// use jid::Jid;
348    ///
349    /// let jid = Jid::domain("domain");
350    ///
351    /// assert_eq!(jid.domain, "domain");
352    ///
353    /// let new_jid = jid.with_domain("new_domain");
354    ///
355    /// assert_eq!(new_jid.domain, "new_domain");
356    /// ```
357    pub fn with_domain<S>(&self, domain: S) -> Jid
358    where
359        S: Into<String>,
360    {
361        Jid {
362            node: self.node.clone(),
363            domain: domain.into(),
364            resource: self.resource.clone(),
365        }
366    }
367
368    /// Constructs a new Jabber ID from an existing one, with the resource swapped out with a new one.
369    ///
370    /// # Examples
371    ///
372    /// ```
373    /// use jid::Jid;
374    ///
375    /// let jid = Jid::domain("domain");
376    ///
377    /// assert_eq!(jid.resource, None);
378    ///
379    /// let new_jid = jid.with_resource("resource");
380    ///
381    /// assert_eq!(new_jid.resource, Some("resource".to_owned()));
382    /// ```
383    pub fn with_resource<S>(&self, resource: S) -> Jid
384    where
385        S: Into<String>,
386    {
387        Jid {
388            node: self.node.clone(),
389            domain: self.domain.clone(),
390            resource: Some(resource.into()),
391        }
392    }
393}
394
395#[cfg(feature = "minidom")]
396use minidom::{ElementEmitter, IntoAttributeValue, IntoElements};
397
398#[cfg(feature = "minidom")]
399impl IntoAttributeValue for Jid {
400    fn into_attribute_value(self) -> Option<String> {
401        Some(String::from(self))
402    }
403}
404
405#[cfg(feature = "minidom")]
406impl IntoElements for Jid {
407    fn into_elements(self, emitter: &mut ElementEmitter) {
408        emitter.append_text_node(String::from(self))
409    }
410}
411
412#[cfg(test)]
413mod tests {
414    use super::*;
415
416    use std::str::FromStr;
417
418    #[test]
419    fn can_parse_jids() {
420        assert_eq!(Jid::from_str("a@b.c/d"), Ok(Jid::full("a", "b.c", "d")));
421        assert_eq!(Jid::from_str("a@b.c"), Ok(Jid::bare("a", "b.c")));
422        assert_eq!(Jid::from_str("b.c"), Ok(Jid::domain("b.c")));
423        assert_eq!(
424            Jid::from_str("a/b@c"),
425            Ok(Jid::domain_with_resource("a", "b@c"))
426        );
427    }
428
429    #[test]
430    fn serialise() {
431        assert_eq!(
432            String::from(Jid::full("a", "b", "c")),
433            String::from("a@b/c")
434        );
435    }
436
437    #[test]
438    fn invalid_jids() {
439        assert_eq!(Jid::from_str(""), Err(JidParseError::NoDomain));
440        assert_eq!(Jid::from_str("/c"), Err(JidParseError::NoDomain));
441        assert_eq!(Jid::from_str("a@/c"), Err(JidParseError::NoDomain));
442        assert_eq!(Jid::from_str("@b"), Err(JidParseError::EmptyNode));
443        assert_eq!(Jid::from_str("b/"), Err(JidParseError::EmptyResource));
444    }
445
446    #[cfg(feature = "minidom")]
447    #[test]
448    fn minidom() {
449        let elem: minidom::Element = "<message from='a@b/c'/>".parse().unwrap();
450        let to: Jid = elem.attr("from").unwrap().parse().unwrap();
451        assert_eq!(to, Jid::full("a", "b", "c"));
452    }
453}