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::new();
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").get_bare();
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 pub fn get_bare(self) -> Jid {
208 Jid {
209 node: self.node.clone(),
210 domain: self.domain.clone(),
211 resource: None,
212 }
213 }
214
215 /// Constructs a Jabber ID containing only a `domain`.
216 ///
217 /// This is of the form `domain`.
218 ///
219 /// # Examples
220 ///
221 /// ```
222 /// use jid::Jid;
223 ///
224 /// let jid = Jid::domain("domain");
225 ///
226 /// assert_eq!(jid.node, None);
227 /// assert_eq!(jid.domain, "domain".to_owned());
228 /// assert_eq!(jid.resource, None);
229 /// ```
230 pub fn domain<DS>(domain: DS) -> Jid
231 where DS: Into<String> {
232 Jid {
233 node: None,
234 domain: domain.into(),
235 resource: None,
236 }
237 }
238
239 /// Returns a new Jabber ID from the current one with only domain.
240 ///
241 /// This is of the form `domain`.
242 ///
243 /// # Examples
244 ///
245 /// ```
246 /// use jid::Jid;
247 ///
248 /// let jid = Jid::full("node", "domain", "resource").get_domain();
249 ///
250 /// assert_eq!(jid.node, None);
251 /// assert_eq!(jid.domain, "domain".to_owned());
252 /// assert_eq!(jid.resource, None);
253 pub fn get_domain(self) -> Jid {
254 Jid {
255 node: None,
256 domain: self.domain.clone(),
257 resource: None,
258 }
259 }
260
261 /// Constructs a Jabber ID containing the `domain` and `resource` components.
262 ///
263 /// This is of the form `domain`/`resource`.
264 ///
265 /// # Examples
266 ///
267 /// ```
268 /// use jid::Jid;
269 ///
270 /// let jid = Jid::domain_with_resource("domain", "resource");
271 ///
272 /// assert_eq!(jid.node, None);
273 /// assert_eq!(jid.domain, "domain".to_owned());
274 /// assert_eq!(jid.resource, Some("resource".to_owned()));
275 /// ```
276 pub fn domain_with_resource<DS, RS>(domain: DS, resource: RS) -> Jid
277 where DS: Into<String>
278 , RS: Into<String> {
279 Jid {
280 node: None,
281 domain: domain.into(),
282 resource: Some(resource.into()),
283 }
284 }
285
286 /// Constructs a new Jabber ID from an existing one, with the node swapped out with a new one.
287 ///
288 /// # Examples
289 ///
290 /// ```
291 /// use jid::Jid;
292 ///
293 /// let jid = Jid::domain("domain");
294 ///
295 /// assert_eq!(jid.node, None);
296 ///
297 /// let new_jid = jid.with_node("node");
298 ///
299 /// assert_eq!(new_jid.node, Some("node".to_owned()));
300 /// ```
301 pub fn with_node<S>(&self, node: S) -> Jid
302 where S: Into<String> {
303 Jid {
304 node: Some(node.into()),
305 domain: self.domain.clone(),
306 resource: self.resource.clone(),
307 }
308 }
309
310 /// Constructs a new Jabber ID from an existing one, with the domain 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.domain, "domain");
320 ///
321 /// let new_jid = jid.with_domain("new_domain");
322 ///
323 /// assert_eq!(new_jid.domain, "new_domain");
324 /// ```
325 pub fn with_domain<S>(&self, domain: S) -> Jid
326 where S: Into<String> {
327 Jid {
328 node: self.node.clone(),
329 domain: domain.into(),
330 resource: self.resource.clone(),
331 }
332 }
333
334 /// Constructs a new Jabber ID from an existing one, with the resource 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.resource, None);
344 ///
345 /// let new_jid = jid.with_resource("resource");
346 ///
347 /// assert_eq!(new_jid.resource, Some("resource".to_owned()));
348 /// ```
349 pub fn with_resource<S>(&self, resource: S) -> Jid
350 where S: Into<String> {
351 Jid {
352 node: self.node.clone(),
353 domain: self.domain.clone(),
354 resource: Some(resource.into()),
355 }
356 }
357
358}
359
360#[cfg(test)]
361mod tests {
362 use super::*;
363
364 use std::str::FromStr;
365
366 #[test]
367 fn can_parse_jids() {
368 assert_eq!(Jid::from_str("a@b.c/d"), Ok(Jid::full("a", "b.c", "d")));
369 assert_eq!(Jid::from_str("a@b.c"), Ok(Jid::bare("a", "b.c")));
370 assert_eq!(Jid::from_str("b.c"), Ok(Jid::domain("b.c")));
371
372 assert_eq!(Jid::from_str(""), Err(JidParseError::NoDomain));
373
374 assert_eq!(Jid::from_str("a/b@c"), Ok(Jid::domain_with_resource("a", "b@c")));
375 }
376
377 #[test]
378 fn serialise() {
379 assert_eq!(String::from(Jid::full("a", "b", "c")), String::from("a@b/c"));
380 }
381}