lib.rs

  1//! Provides a type for Jabber IDs.
  2//!
  3//! For usage, check the documentation on the `Jid` struct.
  4
  5use std::fmt;
  6
  7use std::convert::Into;
  8
  9use std::str::FromStr;
 10
 11/// An error that signifies that a `Jid` cannot be parsed from a string.
 12#[derive(Debug, Clone, PartialEq, Eq)]
 13pub enum JidParseError {
 14    NoDomain,
 15}
 16
 17/// A struct representing a Jabber ID.
 18///
 19/// A Jabber ID is composed of 3 components, of which 2 are optional:
 20///
 21///  - A node/name, `node`, which is the optional part before the @.
 22///  - A domain, `domain`, which is the mandatory part after the @ but before the /.
 23///  - A resource, `resource`, which is the optional part after the /.
 24#[derive(Debug, Clone, PartialEq, Eq)]
 25pub struct Jid {
 26    /// The node part of the Jabber ID, if it exists, else None.
 27    pub node: Option<String>,
 28    /// The domain of the Jabber ID.
 29    pub domain: String,
 30    /// The resource of the Jabber ID, if it exists, else None.
 31    pub resource: Option<String>,
 32}
 33
 34impl fmt::Display for Jid {
 35    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
 36        // TODO: may need escaping
 37        if let Some(ref node) = self.node {
 38            write!(fmt, "{}@", node)?;
 39        }
 40        write!(fmt, "{}", self.domain)?;
 41        if let Some(ref resource) = self.resource {
 42            write!(fmt, "/{}", resource)?;
 43        }
 44        Ok(())
 45    }
 46}
 47
 48enum ParserState {
 49    Node,
 50    Domain,
 51    Resource
 52}
 53
 54impl FromStr for Jid {
 55    type Err = JidParseError;
 56
 57    fn from_str(s: &str) -> Result<Jid, JidParseError> {
 58        // TODO: very naive, may need to do it differently
 59        let iter = s.chars();
 60        let mut buf = String::new();
 61        let mut state = ParserState::Node;
 62        let mut node = None;
 63        let mut domain = None;
 64        let mut resource = None;
 65        for c in iter {
 66            match state {
 67                ParserState::Node => {
 68                    match c {
 69                        '@' => {
 70                            state = ParserState::Domain;
 71                            node = Some(buf.clone()); // TODO: performance tweaks, do not need to copy it
 72                            buf.clear();
 73                        },
 74                        '/' => {
 75                            state = ParserState::Resource;
 76                            domain = Some(buf.clone()); // TODO: performance tweaks
 77                            buf.clear();
 78                        },
 79                        c => {
 80                            buf.push(c);
 81                        },
 82                    }
 83                },
 84                ParserState::Domain => {
 85                    match c {
 86                        '/' => {
 87                            state = ParserState::Resource;
 88                            domain = Some(buf.clone()); // TODO: performance tweaks
 89                            buf.clear();
 90                        },
 91                        c => {
 92                            buf.push(c);
 93                        },
 94                    }
 95                },
 96                ParserState::Resource => {
 97                    buf.push(c);
 98                },
 99            }
100        }
101        if !buf.is_empty() {
102            match state {
103                ParserState::Node => {
104                    domain = Some(buf);
105                },
106                ParserState::Domain => {
107                    domain = Some(buf);
108                },
109                ParserState::Resource => {
110                    resource = Some(buf);
111                },
112            }
113        }
114        Ok(Jid {
115            node: node,
116            domain: domain.ok_or(JidParseError::NoDomain)?,
117            resource: resource,
118        })
119    }
120}
121
122impl Jid {
123    /// Constructs a Jabber ID containing all three components.
124    ///
125    /// This is of the form `node`@`domain`/`resource`.
126    ///
127    /// # Examples
128    ///
129    /// ```
130    /// use jid::Jid;
131    ///
132    /// let jid = Jid::full("node", "domain", "resource");
133    ///
134    /// assert_eq!(jid.node, Some("node".to_owned()));
135    /// assert_eq!(jid.domain, "domain".to_owned());
136    /// assert_eq!(jid.resource, Some("resource".to_owned()));
137    /// ```
138    pub fn full<NS, DS, RS>(node: NS, domain: DS, resource: RS) -> Jid
139        where NS: Into<String>
140            , DS: Into<String>
141            , RS: Into<String> {
142        Jid {
143            node: Some(node.into()),
144            domain: domain.into(),
145            resource: Some(resource.into()),
146        }
147    }
148
149    /// Constructs a Jabber ID containing only the `node` and `domain` components.
150    ///
151    /// This is of the form `node`@`domain`.
152    ///
153    /// # Examples
154    ///
155    /// ```
156    /// use jid::Jid;
157    ///
158    /// let jid = Jid::bare("node", "domain");
159    ///
160    /// assert_eq!(jid.node, Some("node".to_owned()));
161    /// assert_eq!(jid.domain, "domain".to_owned());
162    /// assert_eq!(jid.resource, None);
163    /// ```
164    pub fn bare<NS, DS>(node: NS, domain: DS) -> Jid
165        where NS: Into<String>
166            , DS: Into<String> {
167        Jid {
168            node: Some(node.into()),
169            domain: domain.into(),
170            resource: None,
171        }
172    }
173
174    /// Constructs a Jabber ID containing only a `domain`.
175    ///
176    /// This is of the form `domain`.
177    ///
178    /// # Examples
179    ///
180    /// ```
181    /// use jid::Jid;
182    ///
183    /// let jid = Jid::domain("domain");
184    ///
185    /// assert_eq!(jid.node, None);
186    /// assert_eq!(jid.domain, "domain".to_owned());
187    /// assert_eq!(jid.resource, None);
188    /// ```
189    pub fn domain<DS>(domain: DS) -> Jid
190        where DS: Into<String> {
191        Jid {
192            node: None,
193            domain: domain.into(),
194            resource: None,
195        }
196    }
197
198    /// Constructs a Jabber ID containing the `domain` and `resource` components.
199    ///
200    /// This is of the form `domain`/`resource`.
201    ///
202    /// # Examples
203    ///
204    /// ```
205    /// use jid::Jid;
206    ///
207    /// let jid = Jid::domain_with_resource("domain", "resource");
208    ///
209    /// assert_eq!(jid.node, None);
210    /// assert_eq!(jid.domain, "domain".to_owned());
211    /// assert_eq!(jid.resource, Some("resource".to_owned()));
212    /// ```
213    pub fn domain_with_resource<DS, RS>(domain: DS, resource: RS) -> Jid
214        where DS: Into<String>
215            , RS: Into<String> {
216        Jid {
217            node: None,
218            domain: domain.into(),
219            resource: Some(resource.into()),
220        }
221    }
222
223    /// Constructs a new Jabber ID from an existing one, with the node swapped out with a new one.
224    ///
225    /// # Examples
226    ///
227    /// ```
228    /// use jid::Jid;
229    ///
230    /// let jid = Jid::domain("domain");
231    ///
232    /// assert_eq!(jid.node, None);
233    ///
234    /// let new_jid = jid.with_node("node");
235    ///
236    /// assert_eq!(new_jid.node, Some("node".to_owned()));
237    /// ```
238    pub fn with_node<S>(&self, node: S) -> Jid
239        where S: Into<String> {
240        Jid {
241            node: Some(node.into()),
242            domain: self.domain.clone(),
243            resource: self.resource.clone(),
244        }
245    }
246
247    /// Constructs a new Jabber ID from an existing one, with the domain swapped out with a new one.
248    ///
249    /// # Examples
250    ///
251    /// ```
252    /// use jid::Jid;
253    ///
254    /// let jid = Jid::domain("domain");
255    ///
256    /// assert_eq!(jid.domain, "domain");
257    ///
258    /// let new_jid = jid.with_domain("new_domain");
259    ///
260    /// assert_eq!(new_jid.domain, "new_domain");
261    /// ```
262    pub fn with_domain<S>(&self, domain: S) -> Jid
263        where S: Into<String> {
264        Jid {
265            node: self.node.clone(),
266            domain: domain.into(),
267            resource: self.resource.clone(),
268        }
269    }
270
271    /// Constructs a new Jabber ID from an existing one, with the resource swapped out with a new one.
272    ///
273    /// # Examples
274    ///
275    /// ```
276    /// use jid::Jid;
277    ///
278    /// let jid = Jid::domain("domain");
279    ///
280    /// assert_eq!(jid.resource, None);
281    ///
282    /// let new_jid = jid.with_resource("resource");
283    ///
284    /// assert_eq!(new_jid.resource, Some("resource".to_owned()));
285    /// ```
286    pub fn with_resource<S>(&self, resource: S) -> Jid
287        where S: Into<String> {
288        Jid {
289            node: self.node.clone(),
290            domain: self.domain.clone(),
291            resource: Some(resource.into()),
292        }
293    }
294
295}
296
297#[cfg(test)]
298mod tests {
299    use super::*;
300
301    use std::str::FromStr;
302
303    #[test]
304    fn can_parse_jids() {
305        assert_eq!(Jid::from_str("a@b.c/d"), Ok(Jid::full("a", "b.c", "d")));
306        assert_eq!(Jid::from_str("a@b.c"), Ok(Jid::bare("a", "b.c")));
307        assert_eq!(Jid::from_str("b.c"), Ok(Jid::domain("b.c")));
308
309        assert_eq!(Jid::from_str(""), Err(JidParseError::NoDomain));
310
311        assert_eq!(Jid::from_str("a/b@c"), Ok(Jid::domain_with_resource("a", "b@c")));
312    }
313}