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}