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