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 /// Constructs a Jabber ID containing only a `domain`.
194 ///
195 /// This is of the form `domain`.
196 ///
197 /// # Examples
198 ///
199 /// ```
200 /// use jid::Jid;
201 ///
202 /// let jid = Jid::domain("domain");
203 ///
204 /// assert_eq!(jid.node, None);
205 /// assert_eq!(jid.domain, "domain".to_owned());
206 /// assert_eq!(jid.resource, None);
207 /// ```
208 pub fn domain<DS>(domain: DS) -> Jid
209 where DS: Into<String> {
210 Jid {
211 node: None,
212 domain: domain.into(),
213 resource: None,
214 }
215 }
216
217 /// Constructs a Jabber ID containing the `domain` and `resource` components.
218 ///
219 /// This is of the form `domain`/`resource`.
220 ///
221 /// # Examples
222 ///
223 /// ```
224 /// use jid::Jid;
225 ///
226 /// let jid = Jid::domain_with_resource("domain", "resource");
227 ///
228 /// assert_eq!(jid.node, None);
229 /// assert_eq!(jid.domain, "domain".to_owned());
230 /// assert_eq!(jid.resource, Some("resource".to_owned()));
231 /// ```
232 pub fn domain_with_resource<DS, RS>(domain: DS, resource: RS) -> Jid
233 where DS: Into<String>
234 , RS: Into<String> {
235 Jid {
236 node: None,
237 domain: domain.into(),
238 resource: Some(resource.into()),
239 }
240 }
241
242 /// Constructs a new Jabber ID from an existing one, with the node swapped out with a new one.
243 ///
244 /// # Examples
245 ///
246 /// ```
247 /// use jid::Jid;
248 ///
249 /// let jid = Jid::domain("domain");
250 ///
251 /// assert_eq!(jid.node, None);
252 ///
253 /// let new_jid = jid.with_node("node");
254 ///
255 /// assert_eq!(new_jid.node, Some("node".to_owned()));
256 /// ```
257 pub fn with_node<S>(&self, node: S) -> Jid
258 where S: Into<String> {
259 Jid {
260 node: Some(node.into()),
261 domain: self.domain.clone(),
262 resource: self.resource.clone(),
263 }
264 }
265
266 /// Constructs a new Jabber ID from an existing one, with the domain swapped out with a new one.
267 ///
268 /// # Examples
269 ///
270 /// ```
271 /// use jid::Jid;
272 ///
273 /// let jid = Jid::domain("domain");
274 ///
275 /// assert_eq!(jid.domain, "domain");
276 ///
277 /// let new_jid = jid.with_domain("new_domain");
278 ///
279 /// assert_eq!(new_jid.domain, "new_domain");
280 /// ```
281 pub fn with_domain<S>(&self, domain: S) -> Jid
282 where S: Into<String> {
283 Jid {
284 node: self.node.clone(),
285 domain: domain.into(),
286 resource: self.resource.clone(),
287 }
288 }
289
290 /// Constructs a new Jabber ID from an existing one, with the resource swapped out with a new one.
291 ///
292 /// # Examples
293 ///
294 /// ```
295 /// use jid::Jid;
296 ///
297 /// let jid = Jid::domain("domain");
298 ///
299 /// assert_eq!(jid.resource, None);
300 ///
301 /// let new_jid = jid.with_resource("resource");
302 ///
303 /// assert_eq!(new_jid.resource, Some("resource".to_owned()));
304 /// ```
305 pub fn with_resource<S>(&self, resource: S) -> Jid
306 where S: Into<String> {
307 Jid {
308 node: self.node.clone(),
309 domain: self.domain.clone(),
310 resource: Some(resource.into()),
311 }
312 }
313
314}
315
316#[cfg(test)]
317mod tests {
318 use super::*;
319
320 use std::str::FromStr;
321
322 #[test]
323 fn can_parse_jids() {
324 assert_eq!(Jid::from_str("a@b.c/d"), Ok(Jid::full("a", "b.c", "d")));
325 assert_eq!(Jid::from_str("a@b.c"), Ok(Jid::bare("a", "b.c")));
326 assert_eq!(Jid::from_str("b.c"), Ok(Jid::domain("b.c")));
327
328 assert_eq!(Jid::from_str(""), Err(JidParseError::NoDomain));
329
330 assert_eq!(Jid::from_str("a/b@c"), Ok(Jid::domain_with_resource("a", "b@c")));
331 }
332
333 #[test]
334 fn serialise() {
335 assert_eq!(String::from(Jid::full("a", "b", "c")), String::from("a@b/c"));
336 }
337}