1use alloc::borrow::{Cow, ToOwned};
2use alloc::string::{String, ToString};
3use core::borrow::Borrow;
4use core::fmt;
5use core::mem;
6use core::ops::Deref;
7use core::str::FromStr;
8
9#[cfg(feature = "serde")]
10use serde::{Deserialize, Serialize};
11
12use crate::{domain_check, node_check, resource_check};
13use crate::{BareJid, Error, Jid};
14
15macro_rules! def_part_parse_doc {
16 ($name:ident, $other:ident, $more:expr) => {
17 concat!(
18 "Parse a [`",
19 stringify!($name),
20 "`] from a `",
21 stringify!($other),
22 "`, copying its contents.\n",
23 "\n",
24 "If the given `",
25 stringify!($other),
26 "` does not conform to the restrictions imposed by `",
27 stringify!($name),
28 "`, an error is returned.\n",
29 $more,
30 )
31 };
32}
33
34macro_rules! def_part_into_inner_doc {
35 ($name:ident, $other:ident, $more:expr) => {
36 concat!(
37 "Consume the `",
38 stringify!($name),
39 "` and return the inner `",
40 stringify!($other),
41 "`.\n",
42 $more,
43 )
44 };
45}
46
47#[derive(Deserialize)]
48struct NodeDeserializer<'a>(&'a str);
49
50impl TryFrom<NodeDeserializer<'_>> for NodePart {
51 type Error = Error;
52
53 fn try_from(deserializer: NodeDeserializer) -> Result<NodePart, Self::Error> {
54 Ok(NodePart::new(deserializer.0)?.into_owned())
55 }
56}
57
58#[derive(Deserialize)]
59struct DomainDeserializer<'a>(&'a str);
60
61impl TryFrom<DomainDeserializer<'_>> for DomainPart {
62 type Error = Error;
63
64 fn try_from(deserializer: DomainDeserializer) -> Result<DomainPart, Self::Error> {
65 Ok(DomainPart::new(deserializer.0)?.into_owned())
66 }
67}
68
69#[derive(Deserialize)]
70struct ResourceDeserializer<'a>(&'a str);
71
72impl TryFrom<ResourceDeserializer<'_>> for ResourcePart {
73 type Error = Error;
74
75 fn try_from(deserializer: ResourceDeserializer) -> Result<ResourcePart, Self::Error> {
76 Ok(ResourcePart::new(deserializer.0)?.into_owned())
77 }
78}
79
80macro_rules! def_part_types {
81 (
82 $(#[$mainmeta:meta])*
83 pub struct $name:ident(String) use $check_fn:ident();
84
85 $(#[$refmeta:meta])*
86 pub struct ref $borrowed:ident(str);
87 ) => {
88 $(#[$mainmeta])*
89 #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
90 #[repr(transparent)]
91 pub struct $name(pub(crate) String);
92
93 impl $name {
94 #[doc = def_part_parse_doc!($name, str, "Depending on whether the contents are changed by normalisation operations, this function either returns a copy or a reference to the original data.")]
95 #[allow(clippy::new_ret_no_self)]
96 pub fn new(s: &str) -> Result<Cow<'_, $borrowed>, Error> {
97 let part = $check_fn(s)?;
98 match part {
99 Cow::Borrowed(v) => Ok(Cow::Borrowed($borrowed::from_str_unchecked(v))),
100 Cow::Owned(v) => Ok(Cow::Owned(Self(v))),
101 }
102 }
103
104 #[doc = def_part_into_inner_doc!($name, String, "")]
105 pub fn into_inner(self) -> String {
106 self.0
107 }
108 }
109
110 impl FromStr for $name {
111 type Err = Error;
112
113 fn from_str(s: &str) -> Result<Self, Error> {
114 Ok(Self::new(s)?.into_owned())
115 }
116 }
117
118 impl fmt::Display for $name {
119 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120 <$borrowed as fmt::Display>::fmt(Borrow::<$borrowed>::borrow(self), f)
121 }
122 }
123
124 impl Deref for $name {
125 type Target = $borrowed;
126
127 fn deref(&self) -> &Self::Target {
128 Borrow::<$borrowed>::borrow(self)
129 }
130 }
131
132 impl AsRef<$borrowed> for $name {
133 fn as_ref(&self) -> &$borrowed {
134 Borrow::<$borrowed>::borrow(self)
135 }
136 }
137
138 impl AsRef<String> for $name {
139 fn as_ref(&self) -> &String {
140 &self.0
141 }
142 }
143
144 impl Borrow<$borrowed> for $name {
145 fn borrow(&self) -> &$borrowed {
146 $borrowed::from_str_unchecked(self.0.as_str())
147 }
148 }
149
150 impl Borrow<String> for $name {
152 fn borrow(&self) -> &String {
153 &self.0
154 }
155 }
156
157 impl Borrow<str> for $name {
159 fn borrow(&self) -> &str {
160 self.0.as_str()
161 }
162 }
163
164 impl<'x> TryFrom<&'x str> for $name {
165 type Error = Error;
166
167 fn try_from(s: &str) -> Result<Self, Error> {
168 Self::from_str(s)
169 }
170 }
171
172 impl From<&$borrowed> for $name {
173 fn from(other: &$borrowed) -> Self {
174 other.to_owned()
175 }
176 }
177
178 impl<'x> From<Cow<'x, $borrowed>> for $name {
179 fn from(other: Cow<'x, $borrowed>) -> Self {
180 other.into_owned()
181 }
182 }
183
184 $(#[$refmeta])*
185 #[repr(transparent)]
186 #[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
187 pub struct $borrowed(pub(crate) str);
188
189 impl $borrowed {
190 pub(crate) fn from_str_unchecked(s: &str) -> &Self {
191 unsafe { mem::transmute(s) }
194 }
195
196 pub fn as_str(&self) -> &str {
198 &self.0
199 }
200 }
201
202 impl Deref for $borrowed {
203 type Target = str;
204
205 fn deref(&self) -> &Self::Target {
206 &self.0
207 }
208 }
209
210 impl ToOwned for $borrowed {
211 type Owned = $name;
212
213 fn to_owned(&self) -> Self::Owned {
214 $name(self.0.to_string())
215 }
216 }
217
218 impl AsRef<str> for $borrowed {
219 fn as_ref(&self) -> &str {
220 &self.0
221 }
222 }
223
224 impl fmt::Display for $borrowed {
225 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226 write!(f, "{}", &self.0)
227 }
228 }
229 }
230}
231
232def_part_types! {
233 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
239 #[cfg_attr(feature = "serde", serde(try_from = "NodeDeserializer"))]
240 pub struct NodePart(String) use node_check();
241
242 #[cfg_attr(feature = "serde", derive(Serialize))]
246 pub struct ref NodeRef(str);
247}
248
249def_part_types! {
250 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
254 #[cfg_attr(feature = "serde", serde(try_from = "DomainDeserializer"))]
255 pub struct DomainPart(String) use domain_check();
256
257 #[cfg_attr(feature = "serde", derive(Serialize))]
261 pub struct ref DomainRef(str);
262}
263
264def_part_types! {
265 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
268 #[cfg_attr(feature = "serde", serde(try_from = "ResourceDeserializer"))]
269 pub struct ResourcePart(String) use resource_check();
270
271 #[cfg_attr(feature = "serde", derive(Serialize))]
276 pub struct ref ResourceRef(str);
277}
278
279impl DomainRef {
280 pub fn with_node(&self, node: &NodeRef) -> BareJid {
283 BareJid::from_parts(Some(node), self)
284 }
285}
286
287impl From<DomainPart> for BareJid {
288 fn from(other: DomainPart) -> Self {
289 BareJid {
290 inner: other.into(),
291 }
292 }
293}
294
295impl From<DomainPart> for Jid {
296 fn from(other: DomainPart) -> Self {
297 Jid {
298 normalized: other.0,
299 at: None,
300 slash: None,
301 }
302 }
303}
304
305impl<'x> From<&'x DomainRef> for BareJid {
306 fn from(other: &'x DomainRef) -> Self {
307 Self::from_parts(None, other)
308 }
309}
310
311impl NodeRef {
312 pub fn with_domain(&self, domain: &DomainRef) -> BareJid {
315 BareJid::from_parts(Some(self), domain)
316 }
317}
318
319#[cfg(test)]
320mod tests {
321 use super::*;
322
323 #[test]
324 fn nodepart_comparison() {
325 let n1 = NodePart::new("foo").unwrap();
326 let n2 = NodePart::new("bar").unwrap();
327 let n3 = NodePart::new("foo").unwrap();
328 assert_eq!(n1, n3);
329 assert_ne!(n1, n2);
330 }
331
332 #[cfg(feature = "serde")]
333 #[test]
334 fn nodepart_serde() {
335 serde_test::assert_de_tokens(
336 &NodePart(String::from("test")),
337 &[
338 serde_test::Token::TupleStruct {
339 name: "NodePart",
340 len: 1,
341 },
342 serde_test::Token::BorrowedStr("test"),
343 serde_test::Token::TupleStructEnd,
344 ],
345 );
346
347 serde_test::assert_de_tokens_error::<NodePart>(
348 &[
349 serde_test::Token::TupleStruct {
350 name: "NodePart",
351 len: 1,
352 },
353 serde_test::Token::BorrowedStr("invalid@domain"),
354 serde_test::Token::TupleStructEnd,
355 ],
356 "localpart doesn’t pass nodeprep validation",
357 );
358 }
359
360 #[cfg(feature = "serde")]
361 #[test]
362 fn domainpart_serde() {
363 serde_test::assert_de_tokens(
364 &DomainPart(String::from("[::1]")),
365 &[
366 serde_test::Token::TupleStruct {
367 name: "DomainPart",
368 len: 1,
369 },
370 serde_test::Token::BorrowedStr("[::1]"),
371 serde_test::Token::TupleStructEnd,
372 ],
373 );
374
375 serde_test::assert_de_tokens(
376 &DomainPart(String::from("domain.example")),
377 &[
378 serde_test::Token::TupleStruct {
379 name: "DomainPart",
380 len: 1,
381 },
382 serde_test::Token::BorrowedStr("domain.example"),
383 serde_test::Token::TupleStructEnd,
384 ],
385 );
386
387 serde_test::assert_de_tokens_error::<DomainPart>(
388 &[
389 serde_test::Token::TupleStruct {
390 name: "DomainPart",
391 len: 1,
392 },
393 serde_test::Token::BorrowedStr("invalid@domain"),
394 serde_test::Token::TupleStructEnd,
395 ],
396 "domain doesn’t pass idna validation",
397 );
398 }
399
400 #[cfg(feature = "serde")]
401 #[test]
402 fn resourcepart_serde() {
403 serde_test::assert_de_tokens(
404 &ResourcePart(String::from("test")),
405 &[
406 serde_test::Token::TupleStruct {
407 name: "ResourcePart",
408 len: 1,
409 },
410 serde_test::Token::BorrowedStr("test"),
411 serde_test::Token::TupleStructEnd,
412 ],
413 );
414
415 serde_test::assert_de_tokens_error::<ResourcePart>(
416 &[
417 serde_test::Token::TupleStruct {
418 name: "ResourcePart",
419 len: 1,
420 },
421 serde_test::Token::BorrowedStr("🤖"),
422 serde_test::Token::TupleStructEnd,
423 ],
424 "resource doesn’t pass resourceprep validation",
425 );
426 }
427}