1#![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#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize))]
182pub struct Nanoid<const N: usize = 21, A: Alphabet = Base64UrlAlphabet> {
183 inner: [u8; N],
185
186 _marker: PhantomData<fn() -> A>,
187}
188
189#[derive(Debug, Clone, PartialEq, Eq, Hash, thiserror::Error)]
191pub enum ParseError {
192 #[error("Invalid length: expected {expected} bytes, but got {actual} bytes")]
194 InvalidLength {
195 expected: usize,
197 actual: usize,
199 },
200
201 #[error("Invalid character: {0:x}")]
203 InvalidCharacter(u8),
204}
205
206impl<const N: usize, A: Alphabet> Nanoid<N, A> {
207 #[allow(clippy::new_without_default)]
222 #[must_use]
223 pub fn new() -> Self {
224 Self::new_with(rand::thread_rng())
225 }
226
227 #[must_use]
242 #[inline]
243 pub fn new_with(mut rng: impl rand::Rng) -> Self {
244 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 let buf = {
257 let ptr = &mut buf as *mut _ as *mut [u8; N];
258 unsafe { ptr.read() }
260 };
261
262 Self {
263 inner: buf,
264 _marker: PhantomData,
265 }
266 }
267
268 pub const fn try_from_str(s: &str) -> Result<Self, ParseError> {
283 let s = s.as_bytes();
284
285 let buf = if s.len() == N {
288 let ptr = s.as_ptr() as *const [u8; N];
289 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 #[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 #[must_use]
341 #[inline]
342 pub const fn as_str(&self) -> &str {
343 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
380impl<const N: usize, A: Alphabet> Copy for Nanoid<N, A> {}
383
384impl<const N: usize, A: Alphabet> Clone for Nanoid<N, A> {
386 fn clone(&self) -> Self {
387 *self
388 }
389}
390
391impl<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
398impl<const N: usize, A: Alphabet> Eq for Nanoid<N, A> {}
400
401impl<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
408impl<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
415impl<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#[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 assert_eq!(id.as_str(), s);
715
716 assert_eq!(format!("{}", id), s);
718
719 assert_eq!(String::from(id), s);
721
722 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}