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