1//! Provides a type for Jabber IDs.
2//!
3//! For usage, check the documentation on the `Jid` struct.
4
5use std::fmt;
6
7use std::convert::Into;
8
9use std::str::FromStr;
10
11/// An error that signifies that a `Jid` cannot be parsed from a string.
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum JidParseError {
14 NoDomain,
15}
16
17/// A struct representing a Jabber ID.
18///
19/// A Jabber ID is composed of 3 components, of which 2 are optional:
20///
21/// - A node/name, `node`, which is the optional part before the @.
22/// - A domain, `domain`, which is the mandatory part after the @ but before the /.
23/// - A resource, `resource`, which is the optional part after the /.
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct Jid {
26 /// The node part of the Jabber ID, if it exists, else None.
27 pub node: Option<String>,
28 /// The domain of the Jabber ID.
29 pub domain: String,
30 /// The resource of the Jabber ID, if it exists, else None.
31 pub resource: Option<String>,
32}
33
34impl fmt::Display for Jid {
35 fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
36 // TODO: may need escaping
37 if let Some(ref node) = self.node {
38 write!(fmt, "{}@", node)?;
39 }
40 write!(fmt, "{}", self.domain)?;
41 if let Some(ref resource) = self.resource {
42 write!(fmt, "/{}", resource)?;
43 }
44 Ok(())
45 }
46}
47
48enum ParserState {
49 Node,
50 Domain,
51 Resource
52}
53
54impl FromStr for Jid {
55 type Err = JidParseError;
56
57 fn from_str(s: &str) -> Result<Jid, JidParseError> {
58 // TODO: very naive, may need to do it differently
59 let iter = s.chars();
60 let mut buf = String::new();
61 let mut state = ParserState::Node;
62 let mut node = None;
63 let mut domain = None;
64 let mut resource = None;
65 for c in iter {
66 match state {
67 ParserState::Node => {
68 match c {
69 '@' => {
70 state = ParserState::Domain;
71 node = Some(buf.clone()); // TODO: performance tweaks, do not need to copy it
72 buf.clear();
73 },
74 '/' => {
75 state = ParserState::Resource;
76 domain = Some(buf.clone()); // TODO: performance tweaks
77 buf.clear();
78 },
79 c => {
80 buf.push(c);
81 },
82 }
83 },
84 ParserState::Domain => {
85 match c {
86 '/' => {
87 state = ParserState::Resource;
88 domain = Some(buf.clone()); // TODO: performance tweaks
89 buf.clear();
90 },
91 c => {
92 buf.push(c);
93 },
94 }
95 },
96 ParserState::Resource => {
97 buf.push(c);
98 },
99 }
100 }
101 if !buf.is_empty() {
102 match state {
103 ParserState::Node => {
104 domain = Some(buf);
105 },
106 ParserState::Domain => {
107 domain = Some(buf);
108 },
109 ParserState::Resource => {
110 resource = Some(buf);
111 },
112 }
113 }
114 Ok(Jid {
115 node: node,
116 domain: domain.ok_or(JidParseError::NoDomain)?,
117 resource: resource,
118 })
119 }
120}
121
122impl Jid {
123 /// Constructs a Jabber ID containing all three components.
124 ///
125 /// This is of the form `node`@`domain`/`resource`.
126 ///
127 /// # Examples
128 ///
129 /// ```
130 /// use jid::Jid;
131 ///
132 /// let jid = Jid::full("node", "domain", "resource");
133 ///
134 /// assert_eq!(jid.node, Some("node".to_owned()));
135 /// assert_eq!(jid.domain, "domain".to_owned());
136 /// assert_eq!(jid.resource, Some("resource".to_owned()));
137 /// ```
138 pub fn full<NS, DS, RS>(node: NS, domain: DS, resource: RS) -> Jid
139 where NS: Into<String>
140 , DS: Into<String>
141 , RS: Into<String> {
142 Jid {
143 node: Some(node.into()),
144 domain: domain.into(),
145 resource: Some(resource.into()),
146 }
147 }
148
149 /// Constructs a Jabber ID containing only the `node` and `domain` components.
150 ///
151 /// This is of the form `node`@`domain`.
152 ///
153 /// # Examples
154 ///
155 /// ```
156 /// use jid::Jid;
157 ///
158 /// let jid = Jid::bare("node", "domain");
159 ///
160 /// assert_eq!(jid.node, Some("node".to_owned()));
161 /// assert_eq!(jid.domain, "domain".to_owned());
162 /// assert_eq!(jid.resource, None);
163 /// ```
164 pub fn bare<NS, DS>(node: NS, domain: DS) -> Jid
165 where NS: Into<String>
166 , DS: Into<String> {
167 Jid {
168 node: Some(node.into()),
169 domain: domain.into(),
170 resource: None,
171 }
172 }
173
174 /// Constructs a Jabber ID containing only a `domain`.
175 ///
176 /// This is of the form `domain`.
177 ///
178 /// # Examples
179 ///
180 /// ```
181 /// use jid::Jid;
182 ///
183 /// let jid = Jid::domain("domain");
184 ///
185 /// assert_eq!(jid.node, None);
186 /// assert_eq!(jid.domain, "domain".to_owned());
187 /// assert_eq!(jid.resource, None);
188 /// ```
189 pub fn domain<DS>(domain: DS) -> Jid
190 where DS: Into<String> {
191 Jid {
192 node: None,
193 domain: domain.into(),
194 resource: None,
195 }
196 }
197
198 /// Constructs a Jabber ID containing the `domain` and `resource` components.
199 ///
200 /// This is of the form `domain`/`resource`.
201 ///
202 /// # Examples
203 ///
204 /// ```
205 /// use jid::Jid;
206 ///
207 /// let jid = Jid::domain_with_resource("domain", "resource");
208 ///
209 /// assert_eq!(jid.node, None);
210 /// assert_eq!(jid.domain, "domain".to_owned());
211 /// assert_eq!(jid.resource, Some("resource".to_owned()));
212 /// ```
213 pub fn domain_with_resource<DS, RS>(domain: DS, resource: RS) -> Jid
214 where DS: Into<String>
215 , RS: Into<String> {
216 Jid {
217 node: None,
218 domain: domain.into(),
219 resource: Some(resource.into()),
220 }
221 }
222
223 /// Constructs a new Jabber ID from an existing one, with the node swapped out with a new one.
224 ///
225 /// # Examples
226 ///
227 /// ```
228 /// use jid::Jid;
229 ///
230 /// let jid = Jid::domain("domain");
231 ///
232 /// assert_eq!(jid.node, None);
233 ///
234 /// let new_jid = jid.with_node("node");
235 ///
236 /// assert_eq!(new_jid.node, Some("node".to_owned()));
237 /// ```
238 pub fn with_node<S>(&self, node: S) -> Jid
239 where S: Into<String> {
240 Jid {
241 node: Some(node.into()),
242 domain: self.domain.clone(),
243 resource: self.resource.clone(),
244 }
245 }
246
247 /// Constructs a new Jabber ID from an existing one, with the domain swapped out with a new one.
248 ///
249 /// # Examples
250 ///
251 /// ```
252 /// use jid::Jid;
253 ///
254 /// let jid = Jid::domain("domain");
255 ///
256 /// assert_eq!(jid.domain, "domain");
257 ///
258 /// let new_jid = jid.with_domain("new_domain");
259 ///
260 /// assert_eq!(new_jid.domain, "new_domain");
261 /// ```
262 pub fn with_domain<S>(&self, domain: S) -> Jid
263 where S: Into<String> {
264 Jid {
265 node: self.node.clone(),
266 domain: domain.into(),
267 resource: self.resource.clone(),
268 }
269 }
270
271 /// Constructs a new Jabber ID from an existing one, with the resource swapped out with a new one.
272 ///
273 /// # Examples
274 ///
275 /// ```
276 /// use jid::Jid;
277 ///
278 /// let jid = Jid::domain("domain");
279 ///
280 /// assert_eq!(jid.resource, None);
281 ///
282 /// let new_jid = jid.with_resource("resource");
283 ///
284 /// assert_eq!(new_jid.resource, Some("resource".to_owned()));
285 /// ```
286 pub fn with_resource<S>(&self, resource: S) -> Jid
287 where S: Into<String> {
288 Jid {
289 node: self.node.clone(),
290 domain: self.domain.clone(),
291 resource: Some(resource.into()),
292 }
293 }
294
295}
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300
301 use std::str::FromStr;
302
303 #[test]
304 fn can_parse_jids() {
305 assert_eq!(Jid::from_str("a@b.c/d"), Ok(Jid::full("a", "b.c", "d")));
306 assert_eq!(Jid::from_str("a@b.c"), Ok(Jid::bare("a", "b.c")));
307 assert_eq!(Jid::from_str("b.c"), Ok(Jid::domain("b.c")));
308
309 assert_eq!(Jid::from_str(""), Err(JidParseError::NoDomain));
310
311 assert_eq!(Jid::from_str("a/b@c"), Ok(Jid::domain_with_resource("a", "b@c")));
312 }
313}