1// Copyright (c) 2017, 2018 lumi <lumi@pew.im>
2// Copyright (c) 2017, 2018, 2019 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
3// Copyright (c) 2017, 2018, 2019 Maxime “pep” Buquet <pep@bouah.net>
4// Copyright (c) 2017, 2018 Astro <astro@spaceboyz.net>
5// Copyright (c) 2017 Bastien Orivel <eijebong@bananium.fr>
6//
7// This Source Code Form is subject to the terms of the Mozilla Public
8// License, v. 2.0. If a copy of the MPL was not distributed with this
9// file, You can obtain one at http://mozilla.org/MPL/2.0/.
10
11#![no_std]
12#![deny(missing_docs)]
13#![cfg_attr(docsrs, feature(doc_auto_cfg))]
14
15//! Represents XMPP addresses, also known as JabberIDs (JIDs) for the [XMPP](https://xmpp.org/)
16//! protocol. A [`Jid`] can have between one and three parts in the form `node@domain/resource`:
17//! - the (optional) node part designates a specific account/service on a server, for example
18//! `username@server.com`
19//! - the domain part designates a server, for example `irc.jabberfr.org`
20//! - the (optional) resource part designates a more specific client, such as a participant in a
21//! groupchat (`jabberfr@chat.jabberfr.org/user`) or a specific client device associated with an
22//! account (`user@example.com/dino`)
23//!
24//! The [`Jid`] enum can be one of two variants, containing a more specific type:
25//! - [`BareJid`] (`Jid::Bare` variant): a JID without a resource
26//! - [`FullJid`] (`Jid::Full` variant): a JID with a resource
27//!
28//! Jids as per the XMPP protocol only ever contain valid UTF-8. However, creating any form of Jid
29//! can fail in one of the following cases:
30//! - wrong syntax: creating a Jid with an empty (yet declared) node or resource part, such as
31//! `@example.com` or `user@example.com/`
32//! - stringprep error: some characters were invalid according to the stringprep algorithm, such as
33//! mixing left-to-write and right-to-left characters
34
35extern crate alloc;
36
37#[cfg(any(feature = "std", test))]
38extern crate std;
39
40use alloc::borrow::Cow;
41use alloc::format;
42use alloc::string::{String, ToString};
43use core::borrow::Borrow;
44use core::cmp::Ordering;
45use core::fmt;
46use core::hash::{Hash, Hasher};
47use core::mem;
48use core::num::NonZeroU16;
49use core::ops::Deref;
50use core::str::FromStr;
51
52use memchr::memchr;
53
54use stringprep::{nameprep, nodeprep, resourceprep};
55
56#[cfg(feature = "serde")]
57use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
58
59#[cfg(feature = "quote")]
60use proc_macro2::TokenStream;
61#[cfg(feature = "quote")]
62use quote::{quote, ToTokens};
63
64#[cfg(feature = "minidom")]
65use minidom::{IntoAttributeValue, Node};
66
67mod error;
68pub use crate::error::Error;
69
70mod parts;
71pub use parts::{DomainPart, DomainRef, NodePart, NodeRef, ResourcePart, ResourceRef};
72
73fn length_check(len: usize, error_empty: Error, error_too_long: Error) -> Result<(), Error> {
74 if len == 0 {
75 Err(error_empty)
76 } else if len > 1023 {
77 Err(error_too_long)
78 } else {
79 Ok(())
80 }
81}
82
83/// A struct representing a Jabber ID (JID).
84///
85/// This JID can either be "bare" (without a `/resource` suffix) or full (with
86/// a resource suffix).
87///
88/// In many APIs, it is appropriate to use the more specific types
89/// ([`BareJid`] or [`FullJid`]) instead, as these two JID types are generally
90/// used in different contexts within XMPP.
91///
92/// This dynamic type on the other hand can be used in contexts where it is
93/// not known, at compile-time, whether a JID is full or bare.
94#[derive(Clone, Eq)]
95pub struct Jid {
96 normalized: String,
97 at: Option<NonZeroU16>,
98 slash: Option<NonZeroU16>,
99}
100
101impl PartialEq for Jid {
102 fn eq(&self, other: &Jid) -> bool {
103 self.normalized == other.normalized
104 }
105}
106
107impl PartialOrd for Jid {
108 fn partial_cmp(&self, other: &Jid) -> Option<Ordering> {
109 Some(self.cmp(other))
110 }
111}
112
113impl Ord for Jid {
114 fn cmp(&self, other: &Jid) -> Ordering {
115 self.normalized.cmp(&other.normalized)
116 }
117}
118
119impl Hash for Jid {
120 fn hash<H: Hasher>(&self, state: &mut H) {
121 self.normalized.hash(state)
122 }
123}
124
125impl FromStr for Jid {
126 type Err = Error;
127
128 fn from_str(s: &str) -> Result<Self, Self::Err> {
129 Self::new(s)
130 }
131}
132
133impl From<BareJid> for Jid {
134 fn from(other: BareJid) -> Self {
135 other.inner
136 }
137}
138
139impl From<FullJid> for Jid {
140 fn from(other: FullJid) -> Self {
141 other.inner
142 }
143}
144
145impl fmt::Display for Jid {
146 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
147 fmt.write_str(&self.normalized)
148 }
149}
150
151impl Jid {
152 /// Constructs a Jabber ID from a string. This is of the form
153 /// `node`@`domain`/`resource`, where node and resource parts are optional.
154 /// If you want a non-fallible version, use [`Jid::from_parts`] instead.
155 ///
156 /// # Examples
157 ///
158 /// ```
159 /// use jid::Jid;
160 /// # use jid::Error;
161 ///
162 /// # fn main() -> Result<(), Error> {
163 /// let jid = Jid::new("node@domain/resource")?;
164 ///
165 /// assert_eq!(jid.node().map(|x| x.as_str()), Some("node"));
166 /// assert_eq!(jid.domain().as_str(), "domain");
167 /// assert_eq!(jid.resource().map(|x| x.as_str()), Some("resource"));
168 /// # Ok(())
169 /// # }
170 /// ```
171 pub fn new(unnormalized: &str) -> Result<Jid, Error> {
172 let bytes = unnormalized.as_bytes();
173 let mut orig_at = memchr(b'@', bytes);
174 let mut orig_slash = memchr(b'/', bytes);
175 if orig_at.is_some() && orig_slash.is_some() && orig_at > orig_slash {
176 // This is part of the resource, not a node@domain separator.
177 orig_at = None;
178 }
179
180 let normalized = match (orig_at, orig_slash) {
181 (Some(at), Some(slash)) => {
182 let node = nodeprep(&unnormalized[..at]).map_err(|_| Error::NodePrep)?;
183 length_check(node.len(), Error::NodeEmpty, Error::NodeTooLong)?;
184
185 let domain = nameprep(&unnormalized[at + 1..slash]).map_err(|_| Error::NamePrep)?;
186 length_check(domain.len(), Error::DomainEmpty, Error::DomainTooLong)?;
187
188 let resource =
189 resourceprep(&unnormalized[slash + 1..]).map_err(|_| Error::ResourcePrep)?;
190 length_check(resource.len(), Error::ResourceEmpty, Error::ResourceTooLong)?;
191
192 orig_at = Some(node.len());
193 orig_slash = Some(node.len() + domain.len() + 1);
194 match (node, domain, resource) {
195 (Cow::Borrowed(_), Cow::Borrowed(_), Cow::Borrowed(_)) => {
196 unnormalized.to_string()
197 }
198 (node, domain, resource) => format!("{node}@{domain}/{resource}"),
199 }
200 }
201 (Some(at), None) => {
202 let node = nodeprep(&unnormalized[..at]).map_err(|_| Error::NodePrep)?;
203 length_check(node.len(), Error::NodeEmpty, Error::NodeTooLong)?;
204
205 let domain = nameprep(&unnormalized[at + 1..]).map_err(|_| Error::NamePrep)?;
206 length_check(domain.len(), Error::DomainEmpty, Error::DomainTooLong)?;
207
208 orig_at = Some(node.len());
209 match (node, domain) {
210 (Cow::Borrowed(_), Cow::Borrowed(_)) => unnormalized.to_string(),
211 (node, domain) => format!("{node}@{domain}"),
212 }
213 }
214 (None, Some(slash)) => {
215 let domain = nameprep(&unnormalized[..slash]).map_err(|_| Error::NamePrep)?;
216 length_check(domain.len(), Error::DomainEmpty, Error::DomainTooLong)?;
217
218 let resource =
219 resourceprep(&unnormalized[slash + 1..]).map_err(|_| Error::ResourcePrep)?;
220 length_check(resource.len(), Error::ResourceEmpty, Error::ResourceTooLong)?;
221
222 orig_slash = Some(domain.len());
223 match (domain, resource) {
224 (Cow::Borrowed(_), Cow::Borrowed(_)) => unnormalized.to_string(),
225 (domain, resource) => format!("{domain}/{resource}"),
226 }
227 }
228 (None, None) => {
229 let domain = nameprep(unnormalized).map_err(|_| Error::NamePrep)?;
230 length_check(domain.len(), Error::DomainEmpty, Error::DomainTooLong)?;
231
232 domain.into_owned()
233 }
234 };
235
236 Ok(Self {
237 normalized,
238 at: orig_at.and_then(|x| NonZeroU16::new(x as u16)),
239 slash: orig_slash.and_then(|x| NonZeroU16::new(x as u16)),
240 })
241 }
242
243 /// Returns the inner String of this JID.
244 pub fn into_inner(self) -> String {
245 self.normalized
246 }
247
248 /// Build a [`Jid`] from typed parts. This method cannot fail because it uses parts that have
249 /// already been parsed and stringprepped into [`NodePart`], [`DomainPart`], and [`ResourcePart`].
250 ///
251 /// This method allocates and does not consume the typed parts. To avoid
252 /// allocation if both `node` and `resource` are known to be `None` and
253 /// `domain` is owned, you can use `domain.into()`.
254 pub fn from_parts(
255 node: Option<&NodeRef>,
256 domain: &DomainRef,
257 resource: Option<&ResourceRef>,
258 ) -> Self {
259 match resource {
260 Some(resource) => FullJid::from_parts(node, domain, resource).into(),
261 None => BareJid::from_parts(node, domain).into(),
262 }
263 }
264
265 /// The optional node part of the JID as reference.
266 pub fn node(&self) -> Option<&NodeRef> {
267 self.at.map(|at| {
268 let at = u16::from(at) as usize;
269 NodeRef::from_str_unchecked(&self.normalized[..at])
270 })
271 }
272
273 /// The domain part of the JID as reference
274 pub fn domain(&self) -> &DomainRef {
275 match (self.at, self.slash) {
276 (Some(at), Some(slash)) => {
277 let at = u16::from(at) as usize;
278 let slash = u16::from(slash) as usize;
279 DomainRef::from_str_unchecked(&self.normalized[at + 1..slash])
280 }
281 (Some(at), None) => {
282 let at = u16::from(at) as usize;
283 DomainRef::from_str_unchecked(&self.normalized[at + 1..])
284 }
285 (None, Some(slash)) => {
286 let slash = u16::from(slash) as usize;
287 DomainRef::from_str_unchecked(&self.normalized[..slash])
288 }
289 (None, None) => DomainRef::from_str_unchecked(&self.normalized),
290 }
291 }
292
293 /// The optional resource of the Jabber ID. It is guaranteed to be present when the JID is
294 /// a Full variant, which you can check with [`Jid::is_full`].
295 pub fn resource(&self) -> Option<&ResourceRef> {
296 self.slash.map(|slash| {
297 let slash = u16::from(slash) as usize;
298 ResourceRef::from_str_unchecked(&self.normalized[slash + 1..])
299 })
300 }
301
302 /// Allocate a new [`BareJid`] from this JID, discarding the resource.
303 pub fn to_bare(&self) -> BareJid {
304 BareJid::from_parts(self.node(), self.domain())
305 }
306
307 /// Transforms this JID into a [`BareJid`], throwing away the resource.
308 ///
309 /// ```
310 /// # use jid::{BareJid, Jid};
311 /// let jid: Jid = "foo@bar/baz".parse().unwrap();
312 /// let bare = jid.into_bare();
313 /// assert_eq!(bare.to_string(), "foo@bar");
314 /// ```
315 pub fn into_bare(mut self) -> BareJid {
316 if let Some(slash) = self.slash {
317 // truncate the string
318 self.normalized.truncate(slash.get() as usize);
319 self.slash = None;
320 }
321 BareJid { inner: self }
322 }
323
324 /// Checks if the JID is a full JID.
325 pub fn is_full(&self) -> bool {
326 self.slash.is_some()
327 }
328
329 /// Checks if the JID is a bare JID.
330 pub fn is_bare(&self) -> bool {
331 self.slash.is_none()
332 }
333
334 /// Return a reference to the canonical string representation of the JID.
335 pub fn as_str(&self) -> &str {
336 &self.normalized
337 }
338
339 /// Try to convert this Jid to a [`FullJid`] if it contains a resource
340 /// and return a [`BareJid`] otherwise.
341 ///
342 /// This is useful for match blocks:
343 ///
344 /// ```
345 /// # use jid::Jid;
346 /// let jid: Jid = "foo@bar".parse().unwrap();
347 /// match jid.try_into_full() {
348 /// Ok(full) => println!("it is full: {:?}", full),
349 /// Err(bare) => println!("it is bare: {:?}", bare),
350 /// }
351 /// ```
352 pub fn try_into_full(self) -> Result<FullJid, BareJid> {
353 if self.slash.is_some() {
354 Ok(FullJid { inner: self })
355 } else {
356 Err(BareJid { inner: self })
357 }
358 }
359
360 /// Try to convert this Jid reference to a [`&FullJid`][`FullJid`] if it
361 /// contains a resource and return a [`&BareJid`][`BareJid`] otherwise.
362 ///
363 /// This is useful for match blocks:
364 ///
365 /// ```
366 /// # use jid::Jid;
367 /// let jid: Jid = "foo@bar".parse().unwrap();
368 /// match jid.try_as_full() {
369 /// Ok(full) => println!("it is full: {:?}", full),
370 /// Err(bare) => println!("it is bare: {:?}", bare),
371 /// }
372 /// ```
373 pub fn try_as_full(&self) -> Result<&FullJid, &BareJid> {
374 if self.slash.is_some() {
375 Ok(unsafe {
376 // SAFETY: FullJid is #[repr(transparent)] of Jid
377 // SOUNDNESS: we asserted that self.slash is set above
378 mem::transmute::<&Jid, &FullJid>(self)
379 })
380 } else {
381 Err(unsafe {
382 // SAFETY: BareJid is #[repr(transparent)] of Jid
383 // SOUNDNESS: we asserted that self.slash is unset above
384 mem::transmute::<&Jid, &BareJid>(self)
385 })
386 }
387 }
388
389 /// Try to convert this mutable Jid reference to a
390 /// [`&mut FullJid`][`FullJid`] if it contains a resource and return a
391 /// [`&mut BareJid`][`BareJid`] otherwise.
392 pub fn try_as_full_mut(&mut self) -> Result<&mut FullJid, &mut BareJid> {
393 if self.slash.is_some() {
394 Ok(unsafe {
395 // SAFETY: FullJid is #[repr(transparent)] of Jid
396 // SOUNDNESS: we asserted that self.slash is set above
397 mem::transmute::<&mut Jid, &mut FullJid>(self)
398 })
399 } else {
400 Err(unsafe {
401 // SAFETY: BareJid is #[repr(transparent)] of Jid
402 // SOUNDNESS: we asserted that self.slash is unset above
403 mem::transmute::<&mut Jid, &mut BareJid>(self)
404 })
405 }
406 }
407
408 #[doc(hidden)]
409 #[allow(non_snake_case)]
410 #[deprecated(
411 since = "0.11.0",
412 note = "use Jid::from (for construction of Jid values) or Jid::try_into_full/Jid::try_as_full (for match blocks) instead"
413 )]
414 pub fn Bare(other: BareJid) -> Self {
415 Self::from(other)
416 }
417
418 #[doc(hidden)]
419 #[allow(non_snake_case)]
420 #[deprecated(
421 since = "0.11.0",
422 note = "use Jid::from (for construction of Jid values) or Jid::try_into_full/Jid::try_as_full (for match blocks) instead"
423 )]
424 pub fn Full(other: FullJid) -> Self {
425 Self::from(other)
426 }
427}
428
429impl TryFrom<Jid> for FullJid {
430 type Error = Error;
431
432 fn try_from(inner: Jid) -> Result<Self, Self::Error> {
433 if inner.slash.is_none() {
434 return Err(Error::ResourceMissingInFullJid);
435 }
436 Ok(Self { inner })
437 }
438}
439
440impl TryFrom<Jid> for BareJid {
441 type Error = Error;
442
443 fn try_from(inner: Jid) -> Result<Self, Self::Error> {
444 if inner.slash.is_some() {
445 return Err(Error::ResourceInBareJid);
446 }
447 Ok(Self { inner })
448 }
449}
450
451impl PartialEq<Jid> for FullJid {
452 fn eq(&self, other: &Jid) -> bool {
453 &self.inner == other
454 }
455}
456
457impl PartialEq<Jid> for BareJid {
458 fn eq(&self, other: &Jid) -> bool {
459 &self.inner == other
460 }
461}
462
463impl PartialEq<FullJid> for Jid {
464 fn eq(&self, other: &FullJid) -> bool {
465 self == &other.inner
466 }
467}
468
469impl PartialEq<BareJid> for Jid {
470 fn eq(&self, other: &BareJid) -> bool {
471 self == &other.inner
472 }
473}
474
475/// A struct representing a full Jabber ID, with a resource part.
476///
477/// A full JID is composed of 3 components, of which only the node is optional:
478///
479/// - the (optional) node part is the part before the (optional) `@`.
480/// - the domain part is the mandatory part between the (optional) `@` and before the `/`.
481/// - the resource part after the `/`.
482///
483/// Unlike a [`BareJid`], it always contains a resource, and should only be used when you are
484/// certain there is no case where a resource can be missing. Otherwise, use a [`Jid`] or
485/// [`BareJid`].
486#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
487#[repr(transparent)] // WARNING: Jid::try_as_* relies on this for safety!
488pub struct FullJid {
489 inner: Jid,
490}
491
492/// A struct representing a bare Jabber ID, without a resource part.
493///
494/// A bare JID is composed of 2 components, of which only the node is optional:
495/// - the (optional) node part is the part before the (optional) `@`.
496/// - the domain part is the mandatory part between the (optional) `@` and before the `/`.
497///
498/// Unlike a [`FullJid`], it can’t contain a resource, and should only be used when you are certain
499/// there is no case where a resource can be set. Otherwise, use a [`Jid`] or [`FullJid`].
500#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
501#[repr(transparent)] // WARNING: Jid::try_as_* relies on this for safety!
502pub struct BareJid {
503 inner: Jid,
504}
505
506impl Deref for FullJid {
507 type Target = Jid;
508
509 fn deref(&self) -> &Self::Target {
510 &self.inner
511 }
512}
513
514impl Deref for BareJid {
515 type Target = Jid;
516
517 fn deref(&self) -> &Self::Target {
518 &self.inner
519 }
520}
521
522impl Borrow<Jid> for FullJid {
523 fn borrow(&self) -> &Jid {
524 &self.inner
525 }
526}
527
528impl Borrow<Jid> for BareJid {
529 fn borrow(&self) -> &Jid {
530 &self.inner
531 }
532}
533
534impl fmt::Debug for Jid {
535 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
536 fmt.debug_tuple("Jid").field(&self.normalized).finish()
537 }
538}
539
540impl fmt::Debug for FullJid {
541 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
542 fmt.debug_tuple("FullJid")
543 .field(&self.inner.normalized)
544 .finish()
545 }
546}
547
548impl fmt::Debug for BareJid {
549 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
550 fmt.debug_tuple("BareJid")
551 .field(&self.inner.normalized)
552 .finish()
553 }
554}
555
556impl fmt::Display for FullJid {
557 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
558 fmt::Display::fmt(&self.inner, fmt)
559 }
560}
561
562impl fmt::Display for BareJid {
563 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
564 fmt::Display::fmt(&self.inner, fmt)
565 }
566}
567
568#[cfg(feature = "serde")]
569impl Serialize for Jid {
570 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
571 where
572 S: Serializer,
573 {
574 serializer.serialize_str(&self.normalized)
575 }
576}
577
578#[cfg(feature = "serde")]
579impl Serialize for FullJid {
580 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
581 where
582 S: Serializer,
583 {
584 self.inner.serialize(serializer)
585 }
586}
587
588#[cfg(feature = "serde")]
589impl Serialize for BareJid {
590 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
591 where
592 S: Serializer,
593 {
594 self.inner.serialize(serializer)
595 }
596}
597
598impl FromStr for FullJid {
599 type Err = Error;
600
601 fn from_str(s: &str) -> Result<Self, Self::Err> {
602 Self::new(s)
603 }
604}
605
606#[cfg(feature = "serde")]
607impl<'de> Deserialize<'de> for Jid {
608 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
609 where
610 D: Deserializer<'de>,
611 {
612 let s = String::deserialize(deserializer)?;
613 Jid::new(&s).map_err(de::Error::custom)
614 }
615}
616
617#[cfg(feature = "serde")]
618impl<'de> Deserialize<'de> for FullJid {
619 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
620 where
621 D: Deserializer<'de>,
622 {
623 let jid = Jid::deserialize(deserializer)?;
624 jid.try_into().map_err(de::Error::custom)
625 }
626}
627
628#[cfg(feature = "serde")]
629impl<'de> Deserialize<'de> for BareJid {
630 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
631 where
632 D: Deserializer<'de>,
633 {
634 let jid = Jid::deserialize(deserializer)?;
635 jid.try_into().map_err(de::Error::custom)
636 }
637}
638
639#[cfg(feature = "quote")]
640impl ToTokens for Jid {
641 fn to_tokens(&self, tokens: &mut TokenStream) {
642 let s = &self.normalized;
643 tokens.extend(quote! {
644 ::jid::Jid::new(#s).unwrap()
645 });
646 }
647}
648
649#[cfg(feature = "quote")]
650impl ToTokens for FullJid {
651 fn to_tokens(&self, tokens: &mut TokenStream) {
652 let s = &self.inner.normalized;
653 tokens.extend(quote! {
654 ::jid::FullJid::new(#s).unwrap()
655 });
656 }
657}
658
659#[cfg(feature = "quote")]
660impl ToTokens for BareJid {
661 fn to_tokens(&self, tokens: &mut TokenStream) {
662 let s = &self.inner.normalized;
663 tokens.extend(quote! {
664 ::jid::BareJid::new(#s).unwrap()
665 });
666 }
667}
668
669impl FullJid {
670 /// Constructs a full Jabber ID containing all three components. This is of the form
671 /// `node@domain/resource`, where node part is optional.
672 /// If you want a non-fallible version, use [`FullJid::from_parts`] instead.
673 ///
674 /// # Examples
675 ///
676 /// ```
677 /// use jid::FullJid;
678 /// # use jid::Error;
679 ///
680 /// # fn main() -> Result<(), Error> {
681 /// let jid = FullJid::new("node@domain/resource")?;
682 ///
683 /// assert_eq!(jid.node().map(|x| x.as_str()), Some("node"));
684 /// assert_eq!(jid.domain().as_str(), "domain");
685 /// assert_eq!(jid.resource().as_str(), "resource");
686 /// # Ok(())
687 /// # }
688 /// ```
689 pub fn new(unnormalized: &str) -> Result<Self, Error> {
690 Jid::new(unnormalized)?.try_into()
691 }
692
693 /// Build a [`FullJid`] from typed parts. This method cannot fail because it uses parts that have
694 /// already been parsed and stringprepped into [`NodePart`], [`DomainPart`], and [`ResourcePart`].
695 /// This method allocates and does not consume the typed parts.
696 pub fn from_parts(
697 node: Option<&NodeRef>,
698 domain: &DomainRef,
699 resource: &ResourceRef,
700 ) -> FullJid {
701 let (at, slash, normalized);
702
703 if let Some(node) = node {
704 // Parts are never empty so len > 0 for NonZeroU16::new is always Some
705 at = NonZeroU16::new(node.len() as u16);
706 slash = NonZeroU16::new((node.len() + 1 + domain.len()) as u16);
707 normalized = format!("{node}@{domain}/{resource}");
708 } else {
709 at = None;
710 slash = NonZeroU16::new(domain.len() as u16);
711 normalized = format!("{domain}/{resource}");
712 }
713
714 let inner = Jid {
715 normalized,
716 at,
717 slash,
718 };
719
720 Self { inner }
721 }
722
723 /// The optional resource of the Jabber ID. Since this is a full JID it is always present.
724 pub fn resource(&self) -> &ResourceRef {
725 self.inner.resource().unwrap()
726 }
727
728 /// Return the inner String of this full JID.
729 pub fn into_inner(self) -> String {
730 self.inner.into_inner()
731 }
732
733 /// Transforms this full JID into a [`BareJid`], throwing away the
734 /// resource.
735 ///
736 /// ```
737 /// # use jid::{BareJid, FullJid};
738 /// let jid: FullJid = "foo@bar/baz".parse().unwrap();
739 /// let bare = jid.into_bare();
740 /// assert_eq!(bare.to_string(), "foo@bar");
741 /// ```
742 pub fn into_bare(self) -> BareJid {
743 self.inner.into_bare()
744 }
745}
746
747impl FromStr for BareJid {
748 type Err = Error;
749
750 fn from_str(s: &str) -> Result<Self, Self::Err> {
751 Self::new(s)
752 }
753}
754
755impl BareJid {
756 /// Constructs a bare Jabber ID, containing two components. This is of the form
757 /// `node`@`domain`, where node part is optional.
758 /// If you want a non-fallible version, use [`BareJid::from_parts`] instead.
759 ///
760 /// # Examples
761 ///
762 /// ```
763 /// use jid::BareJid;
764 /// # use jid::Error;
765 ///
766 /// # fn main() -> Result<(), Error> {
767 /// let jid = BareJid::new("node@domain")?;
768 ///
769 /// assert_eq!(jid.node().map(|x| x.as_str()), Some("node"));
770 /// assert_eq!(jid.domain().as_str(), "domain");
771 /// # Ok(())
772 /// # }
773 /// ```
774 pub fn new(unnormalized: &str) -> Result<Self, Error> {
775 Jid::new(unnormalized)?.try_into()
776 }
777
778 /// Build a [`BareJid`] from typed parts. This method cannot fail because it uses parts that have
779 /// already been parsed and stringprepped into [`NodePart`] and [`DomainPart`].
780 ///
781 /// This method allocates and does not consume the typed parts. To avoid
782 /// allocation if `node` is known to be `None` and `domain` is owned, you
783 /// can use `domain.into()`.
784 pub fn from_parts(node: Option<&NodeRef>, domain: &DomainRef) -> Self {
785 let (at, normalized);
786
787 if let Some(node) = node {
788 // Parts are never empty so len > 0 for NonZeroU16::new is always Some
789 at = NonZeroU16::new(node.len() as u16);
790 normalized = format!("{node}@{domain}");
791 } else {
792 at = None;
793 normalized = domain.to_string();
794 }
795
796 let inner = Jid {
797 normalized,
798 at,
799 slash: None,
800 };
801
802 Self { inner }
803 }
804
805 /// Constructs a [`BareJid`] from the bare JID, by specifying a [`ResourcePart`].
806 /// If you'd like to specify a stringy resource, use [`BareJid::with_resource_str`] instead.
807 ///
808 /// # Examples
809 ///
810 /// ```
811 /// use jid::{BareJid, ResourcePart};
812 ///
813 /// let resource = ResourcePart::new("resource").unwrap();
814 /// let bare = BareJid::new("node@domain").unwrap();
815 /// let full = bare.with_resource(&resource);
816 ///
817 /// assert_eq!(full.node().map(|x| x.as_str()), Some("node"));
818 /// assert_eq!(full.domain().as_str(), "domain");
819 /// assert_eq!(full.resource().as_str(), "resource");
820 /// ```
821 pub fn with_resource(&self, resource: &ResourceRef) -> FullJid {
822 let slash = NonZeroU16::new(self.inner.normalized.len() as u16);
823 let normalized = format!("{}/{resource}", self.inner.normalized);
824 let inner = Jid {
825 normalized,
826 at: self.inner.at,
827 slash,
828 };
829
830 FullJid { inner }
831 }
832
833 /// Constructs a [`FullJid`] from the bare JID, by specifying a stringy `resource`.
834 /// If your resource has already been parsed into a [`ResourcePart`], use [`BareJid::with_resource`].
835 ///
836 /// # Examples
837 ///
838 /// ```
839 /// use jid::BareJid;
840 ///
841 /// let bare = BareJid::new("node@domain").unwrap();
842 /// let full = bare.with_resource_str("resource").unwrap();
843 ///
844 /// assert_eq!(full.node().map(|x| x.as_str()), Some("node"));
845 /// assert_eq!(full.domain().as_str(), "domain");
846 /// assert_eq!(full.resource().as_str(), "resource");
847 /// ```
848 pub fn with_resource_str(&self, resource: &str) -> Result<FullJid, Error> {
849 let resource = ResourcePart::new(resource)?;
850 Ok(self.with_resource(&resource))
851 }
852
853 /// Return the inner String of this bare JID.
854 pub fn into_inner(self) -> String {
855 self.inner.into_inner()
856 }
857}
858
859#[cfg(feature = "minidom")]
860impl IntoAttributeValue for Jid {
861 fn into_attribute_value(self) -> Option<String> {
862 Some(self.to_string())
863 }
864}
865
866#[cfg(feature = "minidom")]
867impl From<Jid> for Node {
868 fn from(jid: Jid) -> Self {
869 Node::Text(jid.to_string())
870 }
871}
872
873#[cfg(feature = "minidom")]
874impl IntoAttributeValue for FullJid {
875 fn into_attribute_value(self) -> Option<String> {
876 self.inner.into_attribute_value()
877 }
878}
879
880#[cfg(feature = "minidom")]
881impl From<FullJid> for Node {
882 fn from(jid: FullJid) -> Self {
883 jid.inner.into()
884 }
885}
886
887#[cfg(feature = "minidom")]
888impl IntoAttributeValue for BareJid {
889 fn into_attribute_value(self) -> Option<String> {
890 self.inner.into_attribute_value()
891 }
892}
893
894#[cfg(feature = "minidom")]
895impl From<BareJid> for Node {
896 fn from(other: BareJid) -> Self {
897 other.inner.into()
898 }
899}
900
901#[cfg(test)]
902mod tests {
903 use super::*;
904
905 use std::collections::{HashMap, HashSet};
906 use std::vec::Vec;
907
908 macro_rules! assert_size (
909 ($t:ty, $sz:expr) => (
910 assert_eq!(::core::mem::size_of::<$t>(), $sz);
911 );
912 );
913
914 #[cfg(target_pointer_width = "32")]
915 #[test]
916 fn test_size() {
917 assert_size!(BareJid, 16);
918 assert_size!(FullJid, 16);
919 assert_size!(Jid, 16);
920 }
921
922 #[cfg(target_pointer_width = "64")]
923 #[test]
924 fn test_size() {
925 assert_size!(BareJid, 32);
926 assert_size!(FullJid, 32);
927 assert_size!(Jid, 32);
928 }
929
930 #[test]
931 fn can_parse_full_jids() {
932 assert_eq!(
933 FullJid::from_str("a@b.c/d"),
934 Ok(FullJid::new("a@b.c/d").unwrap())
935 );
936 assert_eq!(
937 FullJid::from_str("b.c/d"),
938 Ok(FullJid::new("b.c/d").unwrap())
939 );
940
941 assert_eq!(
942 FullJid::from_str("a@b.c"),
943 Err(Error::ResourceMissingInFullJid)
944 );
945 assert_eq!(
946 FullJid::from_str("b.c"),
947 Err(Error::ResourceMissingInFullJid)
948 );
949 }
950
951 #[test]
952 fn can_parse_bare_jids() {
953 assert_eq!(
954 BareJid::from_str("a@b.c"),
955 Ok(BareJid::new("a@b.c").unwrap())
956 );
957 assert_eq!(BareJid::from_str("b.c"), Ok(BareJid::new("b.c").unwrap()));
958 }
959
960 #[test]
961 fn can_parse_jids() {
962 let full = FullJid::from_str("a@b.c/d").unwrap();
963 let bare = BareJid::from_str("e@f.g").unwrap();
964
965 assert_eq!(Jid::from_str("a@b.c/d").unwrap(), full);
966 assert_eq!(Jid::from_str("e@f.g").unwrap(), bare);
967 }
968
969 #[test]
970 fn full_to_bare_jid() {
971 let bare: BareJid = FullJid::new("a@b.c/d").unwrap().to_bare();
972 assert_eq!(bare, BareJid::new("a@b.c").unwrap());
973 }
974
975 #[test]
976 fn bare_to_full_jid_str() {
977 assert_eq!(
978 BareJid::new("a@b.c")
979 .unwrap()
980 .with_resource_str("d")
981 .unwrap(),
982 FullJid::new("a@b.c/d").unwrap()
983 );
984 }
985
986 #[test]
987 fn bare_to_full_jid() {
988 assert_eq!(
989 BareJid::new("a@b.c")
990 .unwrap()
991 .with_resource(&ResourcePart::new("d").unwrap()),
992 FullJid::new("a@b.c/d").unwrap()
993 )
994 }
995
996 #[test]
997 fn node_from_jid() {
998 let jid = Jid::new("a@b.c/d").unwrap();
999
1000 assert_eq!(jid.node().map(|x| x.as_str()), Some("a"),);
1001 }
1002
1003 #[test]
1004 fn domain_from_jid() {
1005 let jid = Jid::new("a@b.c").unwrap();
1006
1007 assert_eq!(jid.domain().as_str(), "b.c");
1008 }
1009
1010 #[test]
1011 fn resource_from_jid() {
1012 let jid = Jid::new("a@b.c/d").unwrap();
1013
1014 assert_eq!(jid.resource().map(|x| x.as_str()), Some("d"),);
1015 }
1016
1017 #[test]
1018 fn jid_to_full_bare() {
1019 let full = FullJid::new("a@b.c/d").unwrap();
1020 let bare = BareJid::new("a@b.c").unwrap();
1021
1022 assert_eq!(FullJid::try_from(Jid::from(full.clone())), Ok(full.clone()));
1023 assert_eq!(
1024 FullJid::try_from(Jid::from(bare.clone())),
1025 Err(Error::ResourceMissingInFullJid),
1026 );
1027 assert_eq!(Jid::from(full.clone().to_bare()), bare.clone());
1028 assert_eq!(Jid::from(bare.clone()), bare);
1029 }
1030
1031 #[test]
1032 fn serialise() {
1033 assert_eq!(FullJid::new("a@b/c").unwrap().to_string(), "a@b/c");
1034 assert_eq!(BareJid::new("a@b").unwrap().to_string(), "a@b");
1035 }
1036
1037 #[test]
1038 fn hash() {
1039 let _map: HashMap<Jid, String> = HashMap::new();
1040 }
1041
1042 #[test]
1043 fn invalid_jids() {
1044 assert_eq!(BareJid::from_str(""), Err(Error::DomainEmpty));
1045 assert_eq!(BareJid::from_str("/c"), Err(Error::DomainEmpty));
1046 assert_eq!(BareJid::from_str("a@/c"), Err(Error::DomainEmpty));
1047 assert_eq!(BareJid::from_str("@b"), Err(Error::NodeEmpty));
1048 assert_eq!(BareJid::from_str("b/"), Err(Error::ResourceEmpty));
1049
1050 assert_eq!(FullJid::from_str(""), Err(Error::DomainEmpty));
1051 assert_eq!(FullJid::from_str("/c"), Err(Error::DomainEmpty));
1052 assert_eq!(FullJid::from_str("a@/c"), Err(Error::DomainEmpty));
1053 assert_eq!(FullJid::from_str("@b"), Err(Error::NodeEmpty));
1054 assert_eq!(FullJid::from_str("b/"), Err(Error::ResourceEmpty));
1055 assert_eq!(
1056 FullJid::from_str("a@b"),
1057 Err(Error::ResourceMissingInFullJid)
1058 );
1059 assert_eq!(BareJid::from_str("a@b/c"), Err(Error::ResourceInBareJid));
1060 }
1061
1062 #[test]
1063 fn display_jids() {
1064 assert_eq!(FullJid::new("a@b/c").unwrap().to_string(), "a@b/c");
1065 assert_eq!(BareJid::new("a@b").unwrap().to_string(), "a@b");
1066 assert_eq!(
1067 Jid::from(FullJid::new("a@b/c").unwrap()).to_string(),
1068 "a@b/c"
1069 );
1070 assert_eq!(Jid::from(BareJid::new("a@b").unwrap()).to_string(), "a@b");
1071 }
1072
1073 #[cfg(feature = "minidom")]
1074 #[test]
1075 fn minidom() {
1076 let elem: minidom::Element = "<message xmlns='ns1' from='a@b/c'/>".parse().unwrap();
1077 let to: Jid = elem.attr("from").unwrap().parse().unwrap();
1078 assert_eq!(to, Jid::from(FullJid::new("a@b/c").unwrap()));
1079
1080 let elem: minidom::Element = "<message xmlns='ns1' from='a@b'/>".parse().unwrap();
1081 let to: Jid = elem.attr("from").unwrap().parse().unwrap();
1082 assert_eq!(to, Jid::from(BareJid::new("a@b").unwrap()));
1083
1084 let elem: minidom::Element = "<message xmlns='ns1' from='a@b/c'/>".parse().unwrap();
1085 let to: FullJid = elem.attr("from").unwrap().parse().unwrap();
1086 assert_eq!(to, FullJid::new("a@b/c").unwrap());
1087
1088 let elem: minidom::Element = "<message xmlns='ns1' from='a@b'/>".parse().unwrap();
1089 let to: BareJid = elem.attr("from").unwrap().parse().unwrap();
1090 assert_eq!(to, BareJid::new("a@b").unwrap());
1091 }
1092
1093 #[cfg(feature = "minidom")]
1094 #[test]
1095 fn minidom_into_attr() {
1096 let full = FullJid::new("a@b/c").unwrap();
1097 let elem = minidom::Element::builder("message", "jabber:client")
1098 .attr("from", full.clone())
1099 .build();
1100 assert_eq!(elem.attr("from"), Some(full.to_string().as_str()));
1101
1102 let bare = BareJid::new("a@b").unwrap();
1103 let elem = minidom::Element::builder("message", "jabber:client")
1104 .attr("from", bare.clone())
1105 .build();
1106 assert_eq!(elem.attr("from"), Some(bare.to_string().as_str()));
1107
1108 let jid = Jid::from(bare.clone());
1109 let _elem = minidom::Element::builder("message", "jabber:client")
1110 .attr("from", jid)
1111 .build();
1112 assert_eq!(elem.attr("from"), Some(bare.to_string().as_str()));
1113 }
1114
1115 #[test]
1116 fn stringprep() {
1117 let full = FullJid::from_str("Test@☃.coM/Test™").unwrap();
1118 let equiv = FullJid::new("test@☃.com/TestTM").unwrap();
1119 assert_eq!(full, equiv);
1120 }
1121
1122 #[test]
1123 fn invalid_stringprep() {
1124 FullJid::from_str("a@b/🎉").unwrap_err();
1125 }
1126
1127 #[test]
1128 fn jid_from_parts() {
1129 let node = NodePart::new("node").unwrap();
1130 let domain = DomainPart::new("domain").unwrap();
1131 let resource = ResourcePart::new("resource").unwrap();
1132
1133 let jid = Jid::from_parts(Some(&node), &domain, Some(&resource));
1134 assert_eq!(jid, Jid::new("node@domain/resource").unwrap());
1135
1136 let barejid = BareJid::from_parts(Some(&node), &domain);
1137 assert_eq!(barejid, BareJid::new("node@domain").unwrap());
1138
1139 let fulljid = FullJid::from_parts(Some(&node), &domain, &resource);
1140 assert_eq!(fulljid, FullJid::new("node@domain/resource").unwrap());
1141 }
1142
1143 #[test]
1144 #[cfg(feature = "serde")]
1145 fn jid_ser_de() {
1146 let jid: Jid = Jid::new("node@domain").unwrap();
1147 serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain")]);
1148
1149 let jid: Jid = Jid::new("node@domain/resource").unwrap();
1150 serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain/resource")]);
1151
1152 let jid: BareJid = BareJid::new("node@domain").unwrap();
1153 serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain")]);
1154
1155 let jid: FullJid = FullJid::new("node@domain/resource").unwrap();
1156 serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain/resource")]);
1157 }
1158
1159 #[test]
1160 fn jid_into_parts_and_from_parts() {
1161 let node = NodePart::new("node").unwrap();
1162 let domain = DomainPart::new("domain").unwrap();
1163
1164 let jid1 = domain.with_node(&node);
1165 let jid2 = node.with_domain(&domain);
1166 let jid3 = BareJid::new("node@domain").unwrap();
1167 assert_eq!(jid1, jid2);
1168 assert_eq!(jid2, jid3);
1169 }
1170
1171 #[test]
1172 fn jid_match_replacement_try_as() {
1173 let jid1 = Jid::new("foo@bar").unwrap();
1174 let jid2 = Jid::new("foo@bar/baz").unwrap();
1175
1176 match jid1.try_as_full() {
1177 Err(_) => (),
1178 other => panic!("unexpected result: {:?}", other),
1179 };
1180
1181 match jid2.try_as_full() {
1182 Ok(_) => (),
1183 other => panic!("unexpected result: {:?}", other),
1184 };
1185 }
1186
1187 #[test]
1188 fn jid_match_replacement_try_as_mut() {
1189 let mut jid1 = Jid::new("foo@bar").unwrap();
1190 let mut jid2 = Jid::new("foo@bar/baz").unwrap();
1191
1192 match jid1.try_as_full_mut() {
1193 Err(_) => (),
1194 other => panic!("unexpected result: {:?}", other),
1195 };
1196
1197 match jid2.try_as_full_mut() {
1198 Ok(_) => (),
1199 other => panic!("unexpected result: {:?}", other),
1200 };
1201 }
1202
1203 #[test]
1204 fn jid_match_replacement_try_into() {
1205 let jid1 = Jid::new("foo@bar").unwrap();
1206 let jid2 = Jid::new("foo@bar/baz").unwrap();
1207
1208 match jid1.try_as_full() {
1209 Err(_) => (),
1210 other => panic!("unexpected result: {:?}", other),
1211 };
1212
1213 match jid2.try_as_full() {
1214 Ok(_) => (),
1215 other => panic!("unexpected result: {:?}", other),
1216 };
1217 }
1218
1219 #[test]
1220 fn lookup_jid_by_full_jid() {
1221 let mut map: HashSet<Jid> = HashSet::new();
1222 let jid1 = Jid::new("foo@bar").unwrap();
1223 let jid2 = Jid::new("foo@bar/baz").unwrap();
1224 let jid3 = FullJid::new("foo@bar/baz").unwrap();
1225
1226 map.insert(jid1);
1227 assert!(!map.contains(&jid2));
1228 assert!(!map.contains(&jid3));
1229 map.insert(jid2);
1230 assert!(map.contains(&jid3));
1231 }
1232
1233 #[test]
1234 fn lookup_full_jid_by_jid() {
1235 let mut map: HashSet<FullJid> = HashSet::new();
1236 let jid1 = FullJid::new("foo@bar/baz").unwrap();
1237 let jid2 = FullJid::new("foo@bar/fnord").unwrap();
1238 let jid3 = Jid::new("foo@bar/fnord").unwrap();
1239
1240 map.insert(jid1);
1241 assert!(!map.contains(&jid2));
1242 assert!(!map.contains(&jid3));
1243 map.insert(jid2);
1244 assert!(map.contains(&jid3));
1245 }
1246
1247 #[test]
1248 fn lookup_bare_jid_by_jid() {
1249 let mut map: HashSet<BareJid> = HashSet::new();
1250 let jid1 = BareJid::new("foo@bar").unwrap();
1251 let jid2 = BareJid::new("foo@baz").unwrap();
1252 let jid3 = Jid::new("foo@baz").unwrap();
1253
1254 map.insert(jid1);
1255 assert!(!map.contains(&jid2));
1256 assert!(!map.contains(&jid3));
1257 map.insert(jid2);
1258 assert!(map.contains(&jid3));
1259 }
1260
1261 #[test]
1262 fn normalizes_all_parts() {
1263 assert_eq!(
1264 Jid::new("ßA@IX.test/\u{2168}").unwrap().as_str(),
1265 "ssa@ix.test/IX"
1266 );
1267 }
1268
1269 #[test]
1270 fn rejects_unassigned_codepoints() {
1271 match Jid::new("\u{01f601}@example.com") {
1272 Err(Error::NodePrep) => (),
1273 other => panic!("unexpected result: {:?}", other),
1274 };
1275
1276 match Jid::new("foo@\u{01f601}.example.com") {
1277 Err(Error::NamePrep) => (),
1278 other => panic!("unexpected result: {:?}", other),
1279 };
1280
1281 match Jid::new("foo@example.com/\u{01f601}") {
1282 Err(Error::ResourcePrep) => (),
1283 other => panic!("unexpected result: {:?}", other),
1284 };
1285 }
1286
1287 #[test]
1288 fn accepts_domain_only_jid() {
1289 match Jid::new("example.com") {
1290 Ok(_) => (),
1291 other => panic!("unexpected result: {:?}", other),
1292 };
1293
1294 match BareJid::new("example.com") {
1295 Ok(_) => (),
1296 other => panic!("unexpected result: {:?}", other),
1297 };
1298
1299 match FullJid::new("example.com/x") {
1300 Ok(_) => (),
1301 other => panic!("unexpected result: {:?}", other),
1302 };
1303 }
1304
1305 #[test]
1306 fn is_bare_returns_true_iff_bare() {
1307 let bare = Jid::new("foo@bar").unwrap();
1308 let full = Jid::new("foo@bar/baz").unwrap();
1309
1310 assert!(bare.is_bare());
1311 assert!(!full.is_bare());
1312 }
1313
1314 #[test]
1315 fn is_full_returns_true_iff_full() {
1316 let bare = Jid::new("foo@bar").unwrap();
1317 let full = Jid::new("foo@bar/baz").unwrap();
1318
1319 assert!(!bare.is_full());
1320 assert!(full.is_full());
1321 }
1322
1323 #[test]
1324 fn reject_long_localpart() {
1325 let mut long = Vec::with_capacity(1028);
1326 long.resize(1024, b'a');
1327 let mut long = String::from_utf8(long).unwrap();
1328 long.push_str("@foo");
1329
1330 match Jid::new(&long) {
1331 Err(Error::NodeTooLong) => (),
1332 other => panic!("unexpected result: {:?}", other),
1333 }
1334
1335 match BareJid::new(&long) {
1336 Err(Error::NodeTooLong) => (),
1337 other => panic!("unexpected result: {:?}", other),
1338 }
1339 }
1340
1341 #[test]
1342 fn reject_long_domainpart() {
1343 let mut long = Vec::with_capacity(1028);
1344 long.push(b'x');
1345 long.push(b'@');
1346 long.resize(1026, b'a');
1347 let long = String::from_utf8(long).unwrap();
1348
1349 match Jid::new(&long) {
1350 Err(Error::DomainTooLong) => (),
1351 other => panic!("unexpected result: {:?}", other),
1352 }
1353
1354 match BareJid::new(&long) {
1355 Err(Error::DomainTooLong) => (),
1356 other => panic!("unexpected result: {:?}", other),
1357 }
1358 }
1359
1360 #[test]
1361 fn reject_long_resourcepart() {
1362 let mut long = Vec::with_capacity(1028);
1363 long.push(b'x');
1364 long.push(b'@');
1365 long.push(b'y');
1366 long.push(b'/');
1367 long.resize(1028, b'a');
1368 let long = String::from_utf8(long).unwrap();
1369
1370 match Jid::new(&long) {
1371 Err(Error::ResourceTooLong) => (),
1372 other => panic!("unexpected result: {:?}", other),
1373 }
1374
1375 match FullJid::new(&long) {
1376 Err(Error::ResourceTooLong) => (),
1377 other => panic!("unexpected result: {:?}", other),
1378 }
1379 }
1380}