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}