Skip to main content

psc_nanoid/
lib.rs

1//! Generate and parse Nano IDs.
2//!
3//! Nano ID is a small, secure, URL-friendly, unique string ID.
4//! Here's an example of a Nano ID:
5//!
6//! ```text
7//! qjH-6uGrFy0QgNJtUh0_c
8//! ```
9//!
10//! This crate is a Rust implementation of the original [Nano ID](https://github.com/ai/nanoid) library written in JavaScript.
11//! Please refer to the original library for the detailed explanation of Nano ID.
12//!
13//! # Getting started
14//!
15//! Add the following to your `Cargo.toml`:
16//!
17//! ```toml
18//! [dependencies]
19//! nid = "3.0.0"
20//! ```
21//!
22//! When you want a new Nano ID, you can generate one using the [`Nanoid::new`] method.
23//!
24//! ```
25//! use nid::Nanoid;
26//! let id: Nanoid = Nanoid::new();
27//! ```
28//!
29//! You can parse a string into a Nano ID using [`Nanoid::try_from_str`], [`std::str::FromStr`] or [`TryFrom<String>`].
30//!
31//! ```
32//! use nid::Nanoid;
33//! let id: Nanoid = Nanoid::try_from_str("K8N4Q7MNmeHJ-OHHoVDcz")?;
34//! let id: Nanoid = "3hYR3muA_xvjMrrrqFWxF".parse()?;
35//! let id: Nanoid = "iH26rJ8CpRz-gfIh7TSRu".to_string().try_into()?;
36//! # Ok::<(), Box<dyn std::error::Error>>(())
37//! ```
38//!
39//! If the Nano ID string is constant, you can also use the [`nanoid`] macro to parse it at compile time.
40//!
41//! ```
42//! use nid::{nanoid, Nanoid};
43//! let id = nanoid!("ClCrhcvy5kviH5ZozARfi");
44//! const ID: Nanoid = nanoid!("9vZZWqFI_rTou3Mutq1LH");
45//! ```
46//!
47//! The length of the Nano ID is 21 by default. You can change it by specifying the generic parameter.
48//!
49//! ```
50//! use nid::Nanoid;
51//! let id: Nanoid<10> = "j1-SOTHHxi".parse()?;
52//! # Ok::<(), Box<dyn std::error::Error>>(())
53//! ```
54//!
55//! You can also use a different alphabet. The list of available alphabets is in the [`alphabet`] module.
56//!
57//! ```
58//! use nid::{alphabet::Base62Alphabet, Nanoid};
59//! let id: Nanoid<10, Base62Alphabet> = Nanoid::new();
60//! ```
61//!
62//! # Examples
63//!
64//! ```
65//! use nid::{alphabet::Base62Alphabet, Nanoid};
66//!
67//! // Generate a new Nano ID and print it.
68//! let id: Nanoid = Nanoid::new();
69//! println!("{}", id);
70//!
71//! // Parse a string into a Nano ID and convert it back to a string.
72//! let id: Nanoid = "abcdefg1234567UVWXYZ_".parse()?;
73//! let s = id.to_string();
74//!
75//! // Parse a string into a Nano ID with a different length and alphabet.
76//! let id: Nanoid<9, Base62Alphabet> = "abc123XYZ".parse()?;
77//! # Ok::<(), Box<dyn std::error::Error>>(())
78//! ```
79//!
80//! # Features
81//!
82//! - `serde`: Add support for serialization and deserialization of [`Nanoid`]. Implement [`serde::Serialize`] and [`serde::Deserialize`] for [`Nanoid`].
83//! - `zeroize`: Add support for zeroizing the memory of [`Nanoid`]. Implement [`zeroize::Zeroize`] for [`Nanoid`].
84//! - `packed`: Add support for packed byte representation of Nano IDs. See [`packed`] module.
85//!
86//! # Comparison with other implementations of Nano ID
87//!
88//! [`nanoid`](https://docs.rs/nanoid) and [`nano-id`](https://docs.rs/nano-id) are other implementations of Nano ID in Rust.
89//! The main difference between `nid` and the other implementations is that `nid` has [`Nanoid`] type to represent Nano IDs.
90//! This type provides a safe way to generate and parse Nano IDs.
91//! This is similar to [`uuid`](https://docs.rs/uuid) crate, which provides [`Uuid`](https://docs.rs/uuid/latest/uuid/struct.Uuid.html) type to represent UUIDs.
92
93#![cfg_attr(doc_auto_cfg, feature(doc_cfg))]
94#![deny(missing_debug_implementations, missing_docs)]
95
96pub mod alphabet;
97#[cfg(feature = "packed")]
98pub mod packed;
99
100#[cfg(feature = "packed")]
101pub use packed::PackedNanoid;
102
103use std::{marker::PhantomData, mem::MaybeUninit};
104
105use alphabet::{Alphabet, AlphabetExt, Base64UrlAlphabet};
106
107/// A Nano ID.
108///
109/// # Generic parameters
110///
111/// - `N`: The length of the Nano ID. The default is `21`.
112/// - `A`: The alphabet used in the Nano ID. The default is [`Base64UrlAlphabet`].
113///
114/// # Generating
115///
116/// When you want a new Nano ID, you can generate one using the [`Nanoid::new`].
117///
118/// ```
119/// use nid::Nanoid;
120/// let id: Nanoid = Nanoid::new();
121/// ```
122///
123/// # Parsing
124///
125/// You can parse a string into a Nano ID using [`Nanoid::try_from_str`], [`std::str::FromStr`] or [`TryFrom<String>`].
126///
127/// ```
128/// use nid::Nanoid;
129/// let id: Nanoid = Nanoid::try_from_str("K8N4Q7MNmeHJ-OHHoVDcz")?;
130/// let id: Nanoid = "3hYR3muA_xvjMrrrqFWxF".parse()?;
131/// let id: Nanoid = "iH26rJ8CpRz-gfIh7TSRu".to_string().try_into()?;
132/// # Ok::<(), Box<dyn std::error::Error>>(())
133/// ```
134///
135/// If you try to parse an invalid Nano ID, you will get an error.
136///
137/// ```
138/// use nid::{Nanoid, ParseError};
139///
140/// let result: Result<Nanoid, _> = "61psxw-too_short".parse();
141/// assert!(matches!(result, Err(ParseError::InvalidLength { .. })));
142///
143/// let result: Result<Nanoid, _> = "6yt_invalid_char#####".to_string().try_into();
144/// assert!(matches!(result, Err(ParseError::InvalidCharacter(_))));
145/// ```
146///
147/// # Converting to a string
148///
149/// You can get the string representation of the Nano ID using [`Nanoid::as_str`], [`AsRef<str>`] or [`Display`](std::fmt::Display).
150///
151/// ```
152/// use nid::Nanoid;
153/// let id: Nanoid = "Z9ifKfmBL7j69naN7hthu".parse()?;
154///
155/// // Convert to &str
156/// assert_eq!(id.as_str(), "Z9ifKfmBL7j69naN7hthu");
157/// assert_eq!(id.as_ref(), "Z9ifKfmBL7j69naN7hthu");
158///
159/// // Convert to String
160/// assert_eq!(id.to_string(), "Z9ifKfmBL7j69naN7hthu");
161/// # Ok::<(), Box<dyn std::error::Error>>(())
162/// ```
163///
164/// # Examples
165///
166/// ```
167/// use nid::{alphabet::Base62Alphabet, Nanoid};
168///
169/// // Generate a new Nano ID and print it.
170/// let id: Nanoid = Nanoid::new();
171/// println!("{}", id);
172///
173/// // Parse a string into a Nano ID and convert it back to a string.
174/// let id: Nanoid = "abcdefg1234567UVWXYZ_".parse()?;
175/// let s = id.to_string();
176///
177/// // Parse a string into a Nano ID with a different length and alphabet.
178/// let id: Nanoid<9, Base62Alphabet> = "abc123XYZ".parse()?;
179/// # Ok::<(), Box<dyn std::error::Error>>(())
180/// ```
181#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize))]
182pub struct Nanoid<const N: usize = 21, A: Alphabet = Base64UrlAlphabet> {
183    /// The Nano ID string. All characters are ASCII.
184    inner: [u8; N],
185
186    _marker: PhantomData<fn() -> A>,
187}
188
189/// An error that can occur when parsing a string into a Nano ID.
190#[derive(Debug, Clone, PartialEq, Eq, Hash, thiserror::Error)]
191pub enum ParseError {
192    /// The length of the provided value is not equal to the expected length.
193    #[error("Invalid length: expected {expected} bytes, but got {actual} bytes")]
194    InvalidLength {
195        /// The expected length.
196        expected: usize,
197        /// The actual length.
198        actual: usize,
199    },
200
201    /// The provided value contains a character that is not in the alphabet.
202    #[error("Invalid character: {0:x}")]
203    InvalidCharacter(u8),
204}
205
206impl<const N: usize, A: Alphabet> Nanoid<N, A> {
207    /// Generate a new Nano ID using random number generator seeded by the system.
208    ///
209    /// # Panics
210    ///
211    /// The function will panic if the random number generator is not able to generate random numbers.
212    /// This function also panics if the provided [`Alphabet`] produces non-ascii characters, but this
213    /// never happens unless the alphabet is implemented incorrectly.
214    ///
215    /// # Examples
216    ///
217    /// ```
218    /// use nid::Nanoid;
219    /// let id: Nanoid = Nanoid::new();
220    /// ```
221    #[allow(clippy::new_without_default)]
222    #[must_use]
223    pub fn new() -> Self {
224        Self::new_with(rand::thread_rng())
225    }
226
227    /// Generate a new Nano ID using the provided random number generator.
228    ///
229    /// # Panics
230    ///
231    /// The function will panic if the provided random number generator is not able to generate random numbers.
232    /// This function also panics if the provided [`Alphabet`] produces non-ascii characters, but this
233    /// never happens unless the alphabet is implemented incorrectly.
234    ///
235    /// # Examples
236    ///
237    /// ```
238    /// use nid::Nanoid;
239    /// let id: Nanoid = Nanoid::new_with(rand::thread_rng());
240    /// ```
241    #[must_use]
242    #[inline]
243    pub fn new_with(mut rng: impl rand::Rng) -> Self {
244        // SAFETY: The `assume_init` is safe because the type we are claiming to have initialized
245        // here is a bunch of `MaybeUninit`s, which do not require initialization.
246        // cf. https://doc.rust-lang.org/std/mem/union.MaybeUninit.html#initializing-an-array-element-by-element
247        let mut buf: [MaybeUninit<u8>; N] = unsafe { MaybeUninit::uninit().assume_init() };
248
249        let distr = rand::distributions::Uniform::from(0..A::VALID_SYMBOL_LIST.len());
250        for b in &mut buf {
251            b.write(A::VALID_SYMBOL_LIST[rng.sample(distr)]);
252        }
253
254        // Convert `MaybeUninit<u8>` to `u8`. `MaybeUninit::assume_init` doesn't work due to the limitation of the compiler.
255        // cf. https://github.com/rust-lang/rust/issues/61956
256        let buf = {
257            let ptr = &mut buf as *mut _ as *mut [u8; N];
258            // SAFETY: The `MaybeUninit` array is fully initialized and can be read as an array of `u8`.
259            unsafe { ptr.read() }
260        };
261
262        Self {
263            inner: buf,
264            _marker: PhantomData,
265        }
266    }
267
268    /// Parse a string into a [`Nanoid`].
269    ///
270    /// # Errors
271    ///
272    /// - If the length of the string is not equal to the expected length, this method returns [`ParseError::InvalidLength`].
273    /// - If the string contains a character that is not in the alphabet, this method returns [`ParseError::InvalidCharacter`].
274    ///
275    /// # Examples
276    ///
277    /// ```
278    /// use nid::Nanoid;
279    /// let id: Nanoid = Nanoid::try_from_str("r9p_QLd_9CD63JqQaGQ9I")?;
280    /// # Ok::<(), Box<dyn std::error::Error>>(())
281    /// ```
282    pub const fn try_from_str(s: &str) -> Result<Self, ParseError> {
283        let s = s.as_bytes();
284
285        // This conversion is copied from the `TryFrom` implementation. We can't call `try_from` here because it's not const.
286        // https://github.com/rust-lang/rust/blob/7cf61ebde7b22796c69757901dd346d0fe70bd97/library/core/src/array/mod.rs#L250-L264
287        let buf = if s.len() == N {
288            let ptr = s.as_ptr() as *const [u8; N];
289            // SAFETY: ok because we just checked that the length fits
290            unsafe { &*ptr }
291        } else {
292            return Err(ParseError::InvalidLength {
293                expected: N,
294                actual: s.len(),
295            });
296        };
297
298        Self::try_from_bytes(buf)
299    }
300
301    /// Parse a byte array into a [`Nanoid`].
302    ///
303    /// # Errors
304    ///
305    /// If the byte array contains a character that is not in the alphabet, this method returns [`ParseError::InvalidCharacter`].
306    ///
307    /// # Examples
308    ///
309    /// ```
310    /// use nid::Nanoid;
311    /// let id: Nanoid = Nanoid::try_from_bytes(b"0tY_GxufiwmAxvmHR7G0R")?;
312    /// # Ok::<(), Box<dyn std::error::Error>>(())
313    /// ```
314    #[inline]
315    pub const fn try_from_bytes(buf: &[u8; N]) -> Result<Self, ParseError> {
316        let mut i = 0;
317        while i < N {
318            if buf[i] >= A::VALID_SYMBOL_MAP.len() as u8 || !A::VALID_SYMBOL_MAP[buf[i] as usize] {
319                return Err(ParseError::InvalidCharacter(buf[i]));
320            }
321            i += 1;
322        }
323
324        Ok(Nanoid {
325            inner: *buf,
326            _marker: PhantomData,
327        })
328    }
329
330    /// Get the string representation of the [`Nanoid`].
331    ///
332    /// # Examples
333    ///
334    /// ```
335    /// use nid::Nanoid;
336    /// let id: Nanoid = "vsB2sq2PfhCdU6WCXk37s".parse()?;
337    /// assert_eq!(id.as_str(), "vsB2sq2PfhCdU6WCXk37s");
338    /// # Ok::<(), Box<dyn std::error::Error>>(())
339    /// ```
340    #[must_use]
341    #[inline]
342    pub const fn as_str(&self) -> &str {
343        // SAFETY: all characters are ASCII.
344        unsafe { std::str::from_utf8_unchecked(&self.inner) }
345    }
346}
347
348#[cfg(feature = "rkyv")]
349impl<const N: usize, A: Alphabet> rkyv::Archive for Nanoid<N, A> {
350    type Archived = [u8; N];
351    type Resolver = [(); N];
352
353    fn resolve(&self, _: Self::Resolver, out: rkyv::Place<Self::Archived>) {
354        out.write(self.inner);
355    }
356}
357
358#[cfg(feature = "rkyv")]
359impl<const N: usize, A: Alphabet, S> rkyv::Serialize<S> for Nanoid<N, A>
360where
361    S: rkyv::rancor::Fallible + ?Sized,
362{
363    fn serialize(&self, serializer: &mut S) -> Result<Self::Resolver, S::Error> {
364        self.inner.serialize(serializer)
365    }
366}
367
368#[cfg(feature = "rkyv")]
369impl<const N: usize, A: Alphabet, D: rkyv::rancor::Fallible + ?Sized>
370    rkyv::Deserialize<Nanoid<N, A>, D> for [u8; N]
371{
372    fn deserialize(&self, _: &mut D) -> Result<Nanoid<N, A>, D::Error> {
373        Ok(Nanoid {
374            inner: *self,
375            _marker: PhantomData,
376        })
377    }
378}
379
380// `Copy` cannot be derived due to a limitation of the compiler.
381// https://github.com/rust-lang/rust/issues/26925
382impl<const N: usize, A: Alphabet> Copy for Nanoid<N, A> {}
383
384// `Clone` cannot be derived as well.
385impl<const N: usize, A: Alphabet> Clone for Nanoid<N, A> {
386    fn clone(&self) -> Self {
387        *self
388    }
389}
390
391// `PartialEq` cannot be derived as well.
392impl<const N: usize, A: Alphabet> PartialEq for Nanoid<N, A> {
393    fn eq(&self, other: &Self) -> bool {
394        self.inner == other.inner
395    }
396}
397
398// `Eq` cannot be derived as well.
399impl<const N: usize, A: Alphabet> Eq for Nanoid<N, A> {}
400
401// `Hash` cannot be derived as well.
402impl<const N: usize, A: Alphabet> std::hash::Hash for Nanoid<N, A> {
403    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
404        self.inner.hash(state);
405    }
406}
407
408// `PartialOrd` cannot be derived as well.
409impl<const N: usize, A: Alphabet> PartialOrd for Nanoid<N, A> {
410    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
411        Some(self.cmp(other))
412    }
413}
414
415// `Ord` cannot be derived as well.
416impl<const N: usize, A: Alphabet> Ord for Nanoid<N, A> {
417    #[inline]
418    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
419        self.inner.cmp(&other.inner)
420    }
421}
422
423impl<const N: usize, A: Alphabet> std::fmt::Debug for Nanoid<N, A> {
424    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
425        f.debug_tuple("Nanoid").field(&self.as_str()).finish()
426    }
427}
428
429impl<const N: usize, A: Alphabet> std::fmt::Display for Nanoid<N, A> {
430    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
431        f.write_str(self.as_str())
432    }
433}
434
435impl<const N: usize, A: Alphabet> From<Nanoid<N, A>> for String {
436    fn from(id: Nanoid<N, A>) -> Self {
437        id.as_str().to_owned()
438    }
439}
440
441impl<const N: usize, A: Alphabet> AsRef<str> for Nanoid<N, A> {
442    fn as_ref(&self) -> &str {
443        self.as_str()
444    }
445}
446
447impl<const N: usize, A: Alphabet> TryFrom<String> for Nanoid<N, A> {
448    type Error = ParseError;
449
450    fn try_from(s: String) -> Result<Self, Self::Error> {
451        Self::try_from_str(&s)
452    }
453}
454
455impl<const N: usize, A: Alphabet> std::str::FromStr for Nanoid<N, A> {
456    type Err = ParseError;
457
458    fn from_str(s: &str) -> Result<Self, Self::Err> {
459        Self::try_from_str(s)
460    }
461}
462
463#[cfg(feature = "serde")]
464impl<const N: usize, A: Alphabet> serde::Serialize for Nanoid<N, A> {
465    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
466    where
467        S: serde::Serializer,
468    {
469        self.as_str().serialize(serializer)
470    }
471}
472
473#[cfg(feature = "serde")]
474impl<'de, const N: usize, A: Alphabet> serde::Deserialize<'de> for Nanoid<N, A> {
475    fn deserialize<D>(deserializer: D) -> Result<Nanoid<N, A>, D::Error>
476    where
477        D: serde::Deserializer<'de>,
478    {
479        let s = String::deserialize(deserializer)?;
480        Self::try_from_str(&s).map_err(serde::de::Error::custom)
481    }
482}
483
484/// Parse [`Nanoid`]s from strings at compile time.
485///
486/// This macro transforms a constant string into [`Nanoid`] at compile time.
487/// If the provided string is not a valid Nano ID, the program will not compile.
488///
489/// # Arguments
490///
491/// - `$id`: The Nano ID string.
492/// - `$alphabet`: The alphabet used in the Nano ID. The default is [`Base64UrlAlphabet`].
493///
494/// # Examples
495///
496/// ```
497/// use nid::{alphabet::Base62Alphabet, nanoid, Nanoid};
498///
499/// let id1 = nanoid!("F6JA-LPEbPpz71qxDjaId");
500/// const ID1: Nanoid = nanoid!("F6JA-LPEbPpz71qxDjaId");
501///
502/// // With a different length.
503/// let id2 = nanoid!("P2_LONIp4S");
504/// const ID2: Nanoid<10> = nanoid!("P2_LONIp4S");
505///
506/// // With a different alphabet.
507/// let id3 = nanoid!("F6JAzLPEbPpz71qxDjaId", Base62Alphabet);
508/// const ID3: Nanoid<21, Base62Alphabet> = nanoid!("F6JAzLPEbPpz71qxDjaId", Base62Alphabet);
509/// ```
510///
511/// # Compilation errors
512///
513/// If the provided string is not a valid Nano ID, the program will not compile.
514///
515/// ```compile_fail
516/// use nid::nanoid;
517/// let id = nanoid!("abc###"); // Compilation error: the provided string has invalid character
518/// ```
519#[macro_export]
520macro_rules! nanoid {
521    ($id:expr $(, $alphabet:ty)? $(,)?) => {{
522        const ID: $crate::Nanoid<{ $crate::std::primitive::str::as_bytes($id).len() }$(, $alphabet)?> = match $crate::Nanoid::try_from_str($id) {
523            $crate::std::result::Result::Ok(id) => id,
524            $crate::std::result::Result::Err($crate::ParseError::InvalidLength { .. }) => {
525                $crate::std::unreachable!()
526            }
527            $crate::std::result::Result::Err($crate::ParseError::InvalidCharacter(_)) => {
528                $crate::std::panic!("the provided string has invalid character")
529            }
530        };
531        ID
532    }};
533}
534
535#[doc(hidden)]
536pub use std;
537
538#[cfg(test)]
539mod tests {
540    use std::collections::HashMap;
541
542    use pretty_assertions::{assert_eq, assert_ne};
543
544    use super::*;
545    use crate::alphabet::{Base16Alphabet, Base58Alphabet, Base62Alphabet};
546
547    #[test]
548    fn test_new_unique() {
549        fn inner<const N: usize, A: Alphabet>() {
550            let id1: Nanoid<N, A> = Nanoid::new();
551            let id2: Nanoid<N, A> = Nanoid::new();
552            assert_ne!(id1, id2);
553        }
554
555        inner::<21, Base64UrlAlphabet>();
556        inner::<21, Base62Alphabet>();
557        inner::<21, Base58Alphabet>();
558        inner::<6, Base64UrlAlphabet>();
559        inner::<10, Base62Alphabet>();
560        inner::<12, Base58Alphabet>();
561    }
562
563    #[test]
564    fn test_new_uniformity() {
565        fn inner<const N: usize, A: Alphabet>(iterations: usize) {
566            let mut counts = HashMap::new();
567
568            for _ in 0..iterations {
569                let id: Nanoid<N, A> = Nanoid::new();
570                for c in id.as_str().chars() {
571                    *counts.entry(c).or_insert(0) += 1;
572                }
573            }
574
575            assert_eq!(counts.len(), A::VALID_SYMBOL_LIST.len());
576
577            let max_count = counts.values().max().unwrap();
578            let min_count = counts.values().min().unwrap();
579            let expected_count = counts.values().sum::<usize>() as f64 / counts.len() as f64;
580            assert!((max_count - min_count) as f64 / expected_count < 0.05);
581        }
582
583        inner::<21, Base64UrlAlphabet>(100_000);
584        inner::<21, Base62Alphabet>(100_000);
585        inner::<21, Base58Alphabet>(100_000);
586        inner::<6, Base64UrlAlphabet>(400_000);
587        inner::<10, Base62Alphabet>(200_000);
588        inner::<12, Base58Alphabet>(200_000);
589    }
590
591    #[test]
592    fn test_copy() {
593        fn inner<const N: usize, A: Alphabet>() {
594            let id: Nanoid<N, A> = Nanoid::new();
595            let copied = id;
596            assert_eq!(id, copied);
597        }
598
599        inner::<21, Base64UrlAlphabet>();
600        inner::<21, Base62Alphabet>();
601        inner::<21, Base58Alphabet>();
602        inner::<6, Base64UrlAlphabet>();
603        inner::<10, Base62Alphabet>();
604        inner::<12, Base58Alphabet>();
605    }
606
607    #[test]
608    fn test_clone() {
609        fn inner<const N: usize, A: Alphabet>() {
610            let id: Nanoid<N, A> = Nanoid::new();
611            let cloned = Clone::clone(&id);
612            assert_eq!(id, cloned);
613        }
614
615        inner::<21, Base64UrlAlphabet>();
616        inner::<21, Base62Alphabet>();
617        inner::<21, Base58Alphabet>();
618        inner::<6, Base64UrlAlphabet>();
619        inner::<10, Base62Alphabet>();
620        inner::<12, Base58Alphabet>();
621    }
622
623    #[test]
624    fn test_eq() {
625        fn inner<const N: usize, A: Alphabet>(s: &str) {
626            let id1: Nanoid<N, A> = s.parse().unwrap();
627            let id2: Nanoid<N, A> = s.parse().unwrap();
628            assert_eq!(id1, id2);
629        }
630
631        inner::<21, Base64UrlAlphabet>("ABCDEFGHIJKLMNOPQ123_");
632        inner::<21, Base62Alphabet>("ABCDEFGHIJKLMNOPQ1234");
633        inner::<21, Base58Alphabet>("ABCDEFGHJKLMNPQ123456");
634        inner::<6, Base64UrlAlphabet>("abc12-");
635        inner::<10, Base62Alphabet>("abc1234XYZ");
636        inner::<12, Base58Alphabet>("abc123XYZ123");
637    }
638
639    #[test]
640    fn test_hash() {
641        fn inner<const N: usize, A: Alphabet>(s: &str) {
642            let id1: Nanoid<N, A> = s.parse().unwrap();
643            let id2: Nanoid<N, A> = s.parse().unwrap();
644
645            let mut map = HashMap::new();
646            map.insert(id1, ());
647            assert!(map.contains_key(&id2));
648        }
649
650        inner::<21, Base64UrlAlphabet>("ABCDEFGHIJKLMNOPQ123_");
651        inner::<21, Base62Alphabet>("ABCDEFGHIJKLMNOPQ1234");
652        inner::<21, Base58Alphabet>("ABCDEFGHJKLMNPQ123456");
653        inner::<6, Base64UrlAlphabet>("abc12-");
654        inner::<10, Base62Alphabet>("abc1234XYZ");
655        inner::<12, Base58Alphabet>("abc123XYZ123");
656    }
657
658    #[test]
659    fn test_partial_cmp() {
660        fn inner<const N: usize, A: Alphabet>(s1: &str, s2: &str) {
661            let id1: Nanoid<N, A> = s1.parse().unwrap();
662            let id2: Nanoid<N, A> = s2.parse().unwrap();
663            assert_eq!(id1.partial_cmp(&id2), Some(std::cmp::Ordering::Less));
664        }
665
666        inner::<21, Base64UrlAlphabet>("ABCDEFGHIJKLMNOPQRSTU", "ABCDEFGHIJKLMNOPQRSTV");
667        inner::<21, Base62Alphabet>("ABCDEFGHIJKLMNOPQ1234", "ABCDEFGHIJKLMNOPQ5678");
668        inner::<21, Base58Alphabet>("ABCDEFGHJKLMNPQ123456", "ZBCDEFGHJKLMNPQ123456");
669        inner::<6, Base64UrlAlphabet>("abc12-", "abc12_");
670        inner::<10, Base62Alphabet>("aBc1234XYZ", "abc1234XYZ");
671        inner::<12, Base58Alphabet>("abc123XYZ123", "abc123XYZ124");
672    }
673
674    #[test]
675    fn test_cmp() {
676        fn inner<const N: usize, A: Alphabet>(s1: &str, s2: &str) {
677            let id1: Nanoid<N, A> = s1.parse().unwrap();
678            let id2: Nanoid<N, A> = s2.parse().unwrap();
679            assert_eq!(id1.cmp(&id2), std::cmp::Ordering::Greater);
680        }
681
682        inner::<21, Base64UrlAlphabet>("ABCDEFGHIJKLMNOPQRSTV", "ABCDEFGHIJKLMNOPQRSTU");
683        inner::<21, Base62Alphabet>("ABCDEFGHIJKLMNOPQ5678", "ABCDEFGHIJKLMNOPQ1234");
684        inner::<21, Base58Alphabet>("ZBCDEFGHJKLMNPQ123456", "ABCDEFGHJKLMNPQ123456");
685        inner::<6, Base64UrlAlphabet>("abc12_", "abc12-");
686        inner::<10, Base62Alphabet>("abc1234XYZ", "aBc1234XYZ");
687        inner::<12, Base58Alphabet>("abc123XYZ124", "abc123XYZ123");
688    }
689
690    #[test]
691    fn test_debug_format() {
692        fn inner<const N: usize, A: Alphabet>(s: &str, f: &str) {
693            let id: Nanoid<N, A> = s.parse().unwrap();
694            assert_eq!(format!("{:?}", id), f);
695        }
696
697        inner::<21, Base64UrlAlphabet>(
698            "ABCDEFGHIJKLMNOPQ123_",
699            "Nanoid(\"ABCDEFGHIJKLMNOPQ123_\")",
700        );
701        inner::<21, Base62Alphabet>("ABCDEFGHIJKLMNOPQ1234", "Nanoid(\"ABCDEFGHIJKLMNOPQ1234\")");
702        inner::<21, Base58Alphabet>("ABCDEFGHJKLMNPQ123456", "Nanoid(\"ABCDEFGHJKLMNPQ123456\")");
703        inner::<6, Base64UrlAlphabet>("abc12-", "Nanoid(\"abc12-\")");
704        inner::<10, Base62Alphabet>("abc1234XYZ", "Nanoid(\"abc1234XYZ\")");
705        inner::<12, Base58Alphabet>("abc123XYZ123", "Nanoid(\"abc123XYZ123\")");
706    }
707
708    #[test]
709    fn test_convert_to_string() {
710        fn inner<const N: usize, A: Alphabet>(s: &str) {
711            let id: Nanoid<N, A> = s.parse().unwrap();
712
713            // Test `as_str` method
714            assert_eq!(id.as_str(), s);
715
716            // Test `Display` trait
717            assert_eq!(format!("{}", id), s);
718
719            // Test `From<Nanoid>` trait
720            assert_eq!(String::from(id), s);
721
722            // Test `AsRef<str>` trait
723            assert_eq!(id.as_ref(), s);
724        }
725
726        inner::<21, Base64UrlAlphabet>("ABCDEFGHIJKLMNOPQ123_");
727        inner::<21, Base62Alphabet>("ABCDEFGHIJKLMNOPQ1234");
728        inner::<21, Base58Alphabet>("ABCDEFGHJKLMNPQ123456");
729        inner::<6, Base64UrlAlphabet>("abc12-");
730        inner::<10, Base62Alphabet>("abc1234XYZ");
731        inner::<12, Base58Alphabet>("abc123XYZ123");
732    }
733
734    #[test]
735    fn test_parse_valid() {
736        fn inner<const N: usize, A: Alphabet>(s: &str) {
737            let id: Nanoid<N, A> = Nanoid::try_from_str(s).unwrap();
738            assert_eq!(id.as_str(), s);
739
740            let id: Nanoid<N, A> = s.to_string().try_into().unwrap();
741            assert_eq!(id.as_str(), s);
742
743            let id: Nanoid<N, A> = s.parse().unwrap();
744            assert_eq!(id.as_str(), s);
745        }
746
747        inner::<21, Base64UrlAlphabet>("ABCDEFGHIJKLMNOPQ123_");
748        inner::<21, Base62Alphabet>("ABCDEFGHIJKLMNOPQ1234");
749        inner::<21, Base58Alphabet>("ABCDEFGHJKLMNPQ123456");
750        inner::<6, Base64UrlAlphabet>("abc12-");
751        inner::<10, Base62Alphabet>("abc1234XYZ");
752        inner::<12, Base58Alphabet>("abc123XYZ123");
753    }
754
755    #[test]
756    fn test_parse_invalid_length() {
757        fn inner<const N: usize, A: Alphabet>(s: &str, expected: usize, actual: usize) {
758            let result: Result<Nanoid<N, A>, _> = Nanoid::try_from_str(s);
759            assert_eq!(result, Err(ParseError::InvalidLength { expected, actual }));
760
761            let result: Result<Nanoid<N, A>, _> = s.to_string().try_into();
762            assert_eq!(result, Err(ParseError::InvalidLength { expected, actual }));
763
764            let result: Result<Nanoid<N, A>, _> = s.parse();
765            assert_eq!(result, Err(ParseError::InvalidLength { expected, actual }));
766        }
767
768        inner::<21, Base64UrlAlphabet>("ABCDEF123!!", 21, 11);
769        inner::<21, Base62Alphabet>("#1234567890123456789012345", 21, 26);
770        inner::<21, Base58Alphabet>("あいうえお", 21, 15);
771        inner::<6, Base64UrlAlphabet>("abcdefg", 6, 7);
772        inner::<10, Base62Alphabet>("-_-_", 10, 4);
773        inner::<12, Base58Alphabet>("###", 12, 3);
774    }
775
776    #[test]
777    fn test_parse_invalid_character() {
778        fn inner<const N: usize, A: Alphabet>(s: &str, character: u8) {
779            let result: Result<Nanoid<N, A>, _> = Nanoid::try_from_str(s);
780            assert_eq!(result, Err(ParseError::InvalidCharacter(character)));
781
782            let result: Result<Nanoid<N, A>, _> = s.to_string().try_into();
783            assert_eq!(result, Err(ParseError::InvalidCharacter(character)));
784
785            let result: Result<Nanoid<N, A>, _> = s.parse();
786            assert_eq!(result, Err(ParseError::InvalidCharacter(character)));
787        }
788
789        inner::<21, Base64UrlAlphabet>("$TQBHLT47zhMMxee2LRSo", b'$');
790        inner::<21, Base62Alphabet>("1234567890-1234567890", b'-');
791        inner::<21, Base58Alphabet>("AtDQpkiYrFufeIGWbcSRk", b'I');
792        inner::<6, Base64UrlAlphabet>("アイ", 0xe3);
793        inner::<10, Base62Alphabet>(" \n \n \n \n \n", b' ');
794        inner::<12, Base58Alphabet>("abcdefghijkl", b'l');
795    }
796
797    #[cfg(feature = "serde")]
798    #[test]
799    fn test_serialize() {
800        fn inner<const N: usize, A: Alphabet>(s: &str, expected_serialized: &str) {
801            let id: Nanoid<N, A> = s.parse().unwrap();
802            let serialized = serde_json::to_string(&id).unwrap();
803            assert_eq!(serialized, expected_serialized);
804        }
805
806        inner::<21, Base64UrlAlphabet>("ABCDEFGHIJKLMNOPQ123_", "\"ABCDEFGHIJKLMNOPQ123_\"");
807        inner::<21, Base62Alphabet>("ABCDEFGHIJKLMNOPQ1234", "\"ABCDEFGHIJKLMNOPQ1234\"");
808        inner::<21, Base58Alphabet>("ABCDEFGHJKLMNPQ123456", "\"ABCDEFGHJKLMNPQ123456\"");
809        inner::<6, Base64UrlAlphabet>("abc12-", "\"abc12-\"");
810        inner::<10, Base62Alphabet>("abc1234XYZ", "\"abc1234XYZ\"");
811        inner::<12, Base58Alphabet>("abc123XYZ123", "\"abc123XYZ123\"");
812    }
813
814    #[cfg(feature = "serde")]
815    #[test]
816    fn test_deserialize_valid() {
817        fn inner<const N: usize, A: Alphabet>(serialized: &str, expected_id: &str) {
818            let id: Nanoid<N, A> = serde_json::from_str(serialized).unwrap();
819            assert_eq!(id.as_str(), expected_id);
820        }
821
822        inner::<21, Base64UrlAlphabet>("\"ABCDEFGHIJKLMNOPQ123_\"", "ABCDEFGHIJKLMNOPQ123_");
823        inner::<21, Base62Alphabet>("\"ABCDEFGHIJKLMNOPQ1234\"", "ABCDEFGHIJKLMNOPQ1234");
824        inner::<21, Base58Alphabet>("\"ABCDEFGHJKLMNPQ123456\"", "ABCDEFGHJKLMNPQ123456");
825        inner::<6, Base64UrlAlphabet>("\"abc12-\"", "abc12-");
826        inner::<10, Base62Alphabet>("\"abc1234XYZ\"", "abc1234XYZ");
827        inner::<12, Base58Alphabet>("\"abc123XYZ123\"", "abc123XYZ123");
828    }
829
830    #[cfg(feature = "serde")]
831    #[test]
832    fn test_deserialize_invalid() {
833        fn inner<const N: usize, A: Alphabet>(serialized: &str) {
834            let result: Result<Nanoid<N, A>, _> = serde_json::from_str(serialized);
835            assert!(result.is_err());
836        }
837
838        inner::<21, Base64UrlAlphabet>("\"ABCDEF123!!\"");
839        inner::<21, Base62Alphabet>("\"#1234567890123456789012345\"");
840        inner::<21, Base58Alphabet>("\"あいうえお\"");
841        inner::<6, Base64UrlAlphabet>("\"アイ\"");
842        inner::<10, Base62Alphabet>("\" \\n \\n \\n \\n \\n\"");
843        inner::<12, Base58Alphabet>("\"abcdefghijkl\"");
844    }
845
846    #[cfg(feature = "zeroize")]
847    #[test]
848    fn test_zeroize() {
849        use zeroize::Zeroize;
850
851        fn inner<const N: usize, A: Alphabet>(s: &str) {
852            let mut id: Nanoid<N, A> = s.parse().unwrap();
853            id.zeroize();
854        }
855
856        inner::<21, Base64UrlAlphabet>("ABCDEFGHIJKLMNOPQ123_");
857        inner::<21, Base62Alphabet>("ABCDEFGHIJKLMNOPQ1234");
858        inner::<21, Base58Alphabet>("ABCDEFGHJKLMNPQ123456");
859        inner::<6, Base64UrlAlphabet>("abc12-");
860        inner::<10, Base62Alphabet>("abc1234XYZ");
861        inner::<12, Base58Alphabet>("abc123XYZ123");
862    }
863
864    #[test]
865    fn test_nanoid_macro() {
866        {
867            let id = nanoid!("vj-JewhEyrcoWbaLEXTp-");
868            const ID: Nanoid = nanoid!("vj-JewhEyrcoWbaLEXTp-");
869            assert_eq!(id.as_str(), "vj-JewhEyrcoWbaLEXTp-");
870            assert_eq!(ID.as_str(), "vj-JewhEyrcoWbaLEXTp-");
871        }
872
873        {
874            let id = nanoid!("4KC9zU3v_8mLJokZ");
875            const ID: Nanoid<16> = nanoid!("4KC9zU3v_8mLJokZ");
876            assert_eq!(id.as_str(), "4KC9zU3v_8mLJokZ");
877            assert_eq!(ID.as_str(), "4KC9zU3v_8mLJokZ");
878        }
879
880        {
881            let id = nanoid!("5B0AD0A10D", Base16Alphabet);
882            const ID: Nanoid<10, Base16Alphabet> = nanoid!("5B0AD0A10D", Base16Alphabet);
883            assert_eq!(id.as_str(), "5B0AD0A10D");
884            assert_eq!(ID.as_str(), "5B0AD0A10D");
885        }
886
887        nanoid!("vj-JewhEyrcoWbaLEXTp-",);
888        nanoid!("5B0AD0A10D", Base16Alphabet,);
889    }
890}