jid/
parts.rs

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        // useful for use in hashmaps
151        impl Borrow<String> for $name {
152            fn borrow(&self) -> &String {
153                &self.0
154            }
155        }
156
157        // useful for use in hashmaps
158        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                // SAFETY: repr(transparent) thing can be transmuted to/from
192                // its inner.
193                unsafe { mem::transmute(s) }
194            }
195
196            /// Access the contents as [`str`] slice.
197            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    /// The [`NodePart`] is the optional part before the (optional) `@` in any
234    /// [`Jid`][crate::Jid], whether [`BareJid`][crate::BareJid] or
235    /// [`FullJid`][crate::FullJid].
236    ///
237    /// The corresponding slice type is [`NodeRef`].
238    #[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    /// `str`-like type which conforms to the requirements of [`NodePart`].
243    ///
244    /// See [`NodePart`] for details.
245    #[cfg_attr(feature = "serde", derive(Serialize))]
246    pub struct ref NodeRef(str);
247}
248
249def_part_types! {
250    /// The [`DomainPart`] is the part between the (optional) `@` and the
251    /// (optional) `/` in any [`Jid`][crate::Jid], whether
252    /// [`BareJid`][crate::BareJid] or [`FullJid`][crate::FullJid].
253    #[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    /// `str`-like type which conforms to the requirements of [`DomainPart`].
258    ///
259    /// See [`DomainPart`] for details.
260    #[cfg_attr(feature = "serde", derive(Serialize))]
261    pub struct ref DomainRef(str);
262}
263
264def_part_types! {
265    /// The [`ResourcePart`] is the optional part after the `/` in a
266    /// [`Jid`][crate::Jid]. It is mandatory in [`FullJid`][crate::FullJid].
267    #[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    /// `str`-like type which conforms to the requirements of
272    /// [`ResourcePart`].
273    ///
274    /// See [`ResourcePart`] for details.
275    #[cfg_attr(feature = "serde", derive(Serialize))]
276    pub struct ref ResourceRef(str);
277}
278
279impl DomainRef {
280    /// Construct a bare JID (a JID without a resource) from this domain and
281    /// the given node (local part).
282    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    /// Construct a bare JID (a JID without a resource) from this node (the
313    /// local part) and the given domain.
314    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}