1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
//! This module defines the [`Alphabet`] trait and provides implementations for the most common alphabets used in Nano ID.
//!
//! An alphabet is a set of symbols that can be used in Nano ID. In this crate, only ASCII characters can be used as symbols.
//!
//! The default alphabet used in Nano ID is [`Base64UrlAlphabet`], which contains `A-Za-z0-9_-` symbols.
//!
//! # Implementing a custom alphabet
//!
//! To implement a custom alphabet, you need to create a new type that implements the [`Alphabet`] trait.
//!
//! # Examples
//!
//! ```rust
//! use nid::{alphabet::{Base36Alphabet, Base58Alphabet}, Nanoid};
//!
//! // Use the default Base64URL alphabet, which contains `A-Za-z0-9_-` symbols.
//! type ShopId = Nanoid<9>;
//! let id: ShopId = Nanoid::new();
//! let id: ShopId = "kP_IH1DPM".parse()?;
//!
//! // Use Base36 alphabet, which contains `A-Z0-9` symbols.
//! type UserId = Nanoid<21, Base36Alphabet>;
//! let id: UserId = Nanoid::new();
//! let id: UserId = "NDBIZRQSB6OGXJS06AN5L".parse()?;
//!
//! // Use Base58 alphabet, which contains `A-Za-z0-9` symbols excluding `0OlI`.
//! type ItemId = Nanoid<16, Base58Alphabet>;
//! let id: ItemId = Nanoid::new();
//! let id: ItemId = "96MrjhHuWXJMLCKh".parse()?;
//! # Ok::<(), Box<dyn std::error::Error>>(())
//! ```
// NOTE: Currently symbols are represented as [`u8`] values. We may change this to [`std::ascii::Char`] when it becomes stable.
/// A set of symbols that can be used in Nano ID. In this crate, only ASCII characters can be used as symbols.
///
/// For the list of available alphabets, see the [`alphabet`](crate::alphabet) module.
///
/// # Implementing a custom alphabet
///
/// To implement a custom alphabet, you need to create a new type that implements the [`Alphabet`] trait.
///
/// ```rust
/// use nid::{alphabet::Alphabet, Nanoid};
///
/// struct CustomAlphabet;
///
/// impl Alphabet for CustomAlphabet {
/// const SYMBOL_LIST: &'static [u8] = b"(){}[]<>";
/// }
///
/// let id: Nanoid<21, CustomAlphabet> = Nanoid::new();
/// let id: Nanoid<21, CustomAlphabet> = "{{)((})>]<)}(>)(<)<){".parse()?;
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// Note that the alphabet must contain only ASCII characters. If you use an alphabet with non-ASCII characters, the compilation error will occur.
///
/// ```compile_fail
/// use nid::{alphabet::Alphabet, Nanoid};
///
/// struct CustomAlphabet;
///
/// impl Alphabet for CustomAlphabet {
/// const SYMBOL_LIST: &'static [u8] = b"abc012\xa0\xa1";
/// }
///
/// let id: Nanoid<21, CustomAlphabet> = Nanoid::new(); // Compilation error: found non-ascii symbol in alphabet
/// ```
pub trait Alphabet {
/// The symbols that can be used in Nano ID. Symbols are represented as [`u8`] values.
const SYMBOL_LIST: &'static [u8];
}
/// An extension trait for [`Alphabet`] that provides additional constants.
pub(crate) trait AlphabetExt {
/// The symbols that can be used in Nano ID.
///
/// This is the same as [`Alphabet::SYMBOL_LIST`], but with the guarantee that all elements are ASCII characters.
/// If [`Alphabet::SYMBOL_LIST`] contains non-ASCII characters, reading this constant will result in a compilation error.
const VALID_SYMBOL_LIST: &'static [u8];
/// A map that indicates whether a symbol is in the alphabet.
///
/// If [`Alphabet::SYMBOL_LIST`] contains non-ASCII characters, reading this constant will result in a compilation error.
const VALID_SYMBOL_MAP: [bool; 128];
}
impl<A: Alphabet> AlphabetExt for A {
const VALID_SYMBOL_LIST: &'static [u8] = {
assert_all_ascii(A::SYMBOL_LIST);
A::SYMBOL_LIST
};
const VALID_SYMBOL_MAP: [bool; 128] = {
let mut symbols_map = [false; 128];
let mut i = 0;
while i < A::VALID_SYMBOL_LIST.len() {
symbols_map[A::VALID_SYMBOL_LIST[i] as usize] = true;
i += 1;
}
symbols_map
};
}
/// Assert that all elements are ASCII characters.
const fn assert_all_ascii(s: &[u8]) {
let mut i = 0;
while i < s.len() {
assert!(s[i].is_ascii(), "found non-ascii symbol in alphabet");
i += 1;
}
}
macro_rules! define_and_impl_alphabet {
($name:ident, $symbols:expr, $description:expr $(,)?) => {
#[doc = concat!(" ", $description, "
# Example
```rust
use nid::{alphabet::", stringify!($name), ", Nanoid};
let id: Nanoid<21, ", stringify!($name), "> = Nanoid::new();
```")]
#[derive(Debug)]
pub struct $name;
impl Alphabet for $name {
const SYMBOL_LIST: &'static [u8] = $symbols;
}
};
}
define_and_impl_alphabet!(
Base64UrlAlphabet,
b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-",
"Alphabet with `A-Za-z0-9_-` symbols. This is the default alphabet used in Nano ID.",
);
define_and_impl_alphabet!(
Base62Alphabet,
b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
"Alphabet with `A-Za-z0-9` symbols.",
);
define_and_impl_alphabet!(
Base58Alphabet,
b"ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz123456789",
"Alphabet with `A-Za-z0-9` symbols excluding `0OlI`.",
);
define_and_impl_alphabet!(
Base36Alphabet,
b"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",
"Alphabet with `A-Z0-9` symbols.",
);
define_and_impl_alphabet!(
Base36LowercaseAlphabet,
b"abcdefghijklmnopqrstuvwxyz0123456789",
"Alphabet with `a-z0-9` symbols.",
);
define_and_impl_alphabet!(
Base32Alphabet,
b"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567",
"Alphabet with `A-Z2-7` symbols.",
);
define_and_impl_alphabet!(
Base32LowercaseAlphabet,
b"abcdefghijklmnopqrstuvwxyz234567",
"Alphabet with `a-z2-7` symbols.",
);
define_and_impl_alphabet!(
Base16Alphabet,
b"ABCDEF0123456789",
"Alphabet with `A-F0-9` symbols.",
);
define_and_impl_alphabet!(
Base16LowercaseAlphabet,
b"abcdef0123456789",
"Alphabet with `a-f0-9` symbols.",
);
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use super::*;
#[test]
fn test_alphabet_len() {
assert_eq!(Base64UrlAlphabet::SYMBOL_LIST.len(), 64);
assert_eq!(Base62Alphabet::SYMBOL_LIST.len(), 62);
assert_eq!(Base58Alphabet::SYMBOL_LIST.len(), 58);
assert_eq!(Base36Alphabet::SYMBOL_LIST.len(), 36);
assert_eq!(Base36LowercaseAlphabet::SYMBOL_LIST.len(), 36);
assert_eq!(Base32Alphabet::SYMBOL_LIST.len(), 32);
assert_eq!(Base32LowercaseAlphabet::SYMBOL_LIST.len(), 32);
assert_eq!(Base16Alphabet::SYMBOL_LIST.len(), 16);
assert_eq!(Base16LowercaseAlphabet::SYMBOL_LIST.len(), 16);
}
#[test]
fn test_alphabet_symbol_map() {
assert!(Base64UrlAlphabet::VALID_SYMBOL_MAP[b'A' as usize]);
assert!(!Base64UrlAlphabet::VALID_SYMBOL_MAP[b':' as usize]);
assert!(Base62Alphabet::VALID_SYMBOL_MAP[b'A' as usize]);
assert!(!Base62Alphabet::VALID_SYMBOL_MAP[b'-' as usize]);
assert!(Base58Alphabet::VALID_SYMBOL_MAP[b'A' as usize]);
assert!(!Base58Alphabet::VALID_SYMBOL_MAP[b'0' as usize]);
assert!(Base36Alphabet::VALID_SYMBOL_MAP[b'A' as usize]);
assert!(!Base36Alphabet::VALID_SYMBOL_MAP[b'a' as usize]);
assert!(Base36LowercaseAlphabet::VALID_SYMBOL_MAP[b'a' as usize]);
assert!(!Base36LowercaseAlphabet::VALID_SYMBOL_MAP[b'A' as usize]);
assert!(Base32Alphabet::VALID_SYMBOL_MAP[b'A' as usize]);
assert!(!Base32Alphabet::VALID_SYMBOL_MAP[b'8' as usize]);
assert!(Base32LowercaseAlphabet::VALID_SYMBOL_MAP[b'a' as usize]);
assert!(!Base32LowercaseAlphabet::VALID_SYMBOL_MAP[b'8' as usize]);
assert!(Base16Alphabet::VALID_SYMBOL_MAP[b'A' as usize]);
assert!(!Base16Alphabet::VALID_SYMBOL_MAP[b'Z' as usize]);
assert!(Base16LowercaseAlphabet::VALID_SYMBOL_MAP[b'a' as usize]);
assert!(!Base16LowercaseAlphabet::VALID_SYMBOL_MAP[b'z' as usize]);
}
}