nanoid/
lib.rs

1//! A tiny, secure, URL-friendly, unique string ID generator
2//!
3//! **Safe.** It uses cryptographically strong random APIs
4//! and guarantees a proper distribution of symbols.
5//!
6//! **Compact.** It uses a larger alphabet than UUID (`A-Za-z0-9_~`)
7//! and has a similar number of unique IDs in just 21 symbols instead of 36.
8//!
9//! ```toml
10//! [dependencies]
11//! nanoid = "0.4.0"
12//! ```
13//!
14//! ```rust
15//! use nanoid::nanoid;
16//!
17//! fn main() {
18//!    let id = nanoid!(); //=> "Yo1Tr9F3iF-LFHX9i9GvA"
19//! }
20//! ```
21//!
22//! ## Usage
23//!
24//! ### Simple
25//!
26//! The main module uses URL-friendly symbols (`A-Za-z0-9_~`) and returns an ID
27//! with 21 characters.
28//!
29//! ```rust
30//! use nanoid::nanoid;
31//!
32//! fn main() {
33//!    let id = nanoid!(); //=> "Yo1Tr9F3iF-LFHX9i9GvA"
34//! }
35//! ```
36//!
37//! Symbols `-,.()` are not encoded in the URL. If used at the end of a link
38//! they could be identified as a punctuation symbol.
39//!
40//! ### Custom length
41//!
42//! If you want to reduce ID length (and increase collisions probability),
43//! you can pass the length as an argument generate function:
44//!
45//! ```rust
46//! use nanoid::nanoid;
47//!
48//! fn main() {
49//!    let id = nanoid!(10); //=> "IRFa~VaY2b"
50//! }
51//! ```
52//!
53//! ### Custom Alphabet or Length
54//!
55//! If you want to change the ID's alphabet or length
56//! you can use the low-level `custom` module.
57//!
58//! ```rust
59//! use nanoid::nanoid;
60//!
61//! fn main() {
62//!     let alphabet: [char; 16] = [
63//!         '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'a', 'b', 'c', 'd', 'e', 'f'
64//!     ];
65//!
66//!    let id = nanoid!(10, &alphabet); //=> "4f90d13a42"
67//! }
68//! ```
69//!
70//! Alphabet must contain 256 symbols or less.
71//! Otherwise, the generator will not be secure.
72//!
73//! ### Custom Random Bytes Generator
74//!
75//! You can replace the default safe random generator using the `complex` module.
76//! For instance, to use a seed-based generator.
77//!
78//! ```rust
79//! use nanoid::nanoid;
80//!
81//! fn random_byte () -> u8 {
82//!     0
83//! }
84//!
85//! fn main() {
86//!     fn random (size: usize) -> Vec<u8> {
87//!         let mut bytes: Vec<u8> = vec![0; size];
88//!
89//!         for i in 0..size {
90//!             bytes[i] = random_byte();
91//!         }
92//!
93//!         bytes
94//!     }
95//!
96//!     nanoid!(10, &['a', 'b', 'c', 'd', 'e', 'f'], random); //=> "fbaefaadeb"
97//! }
98//! ```
99//!
100//! `random` function must accept the array size and return an vector
101//! with random numbers.
102//!
103//! If you want to use the same URL-friendly symbols with `format`,
104//! you can get the default alphabet from the `url` module:
105//!
106//! ```rust
107//! use nanoid::nanoid;
108//!
109//! fn random (size: usize) -> Vec<u8> {
110//!     let result: Vec<u8> = vec![0; size];
111//!
112//!     result
113//! }
114//!
115//! fn main() {
116//!     nanoid!(10, &nanoid::alphabet::SAFE, random); //=> "93ce_Ltuub"
117//! }
118//! ```
119//!
120
121#![doc(
122    html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk.png",
123    html_favicon_url = "https://www.rust-lang.org/favicon.ico",
124    html_root_url = "https://docs.rs/nanoid"
125)]
126
127pub mod alphabet;
128pub mod rngs;
129
130pub fn format(random: fn(usize) -> Vec<u8>, alphabet: &[char], size: usize) -> String {
131    assert!(
132        alphabet.len() <= u8::max_value() as usize,
133        "The alphabet cannot be longer than a `u8` (to comply with the `random` function)"
134    );
135
136    let mask = alphabet.len().next_power_of_two() - 1;
137    let step: usize = 8 * size / 5;
138
139    // Assert that the masking does not truncate the alphabet. (See #9)
140    debug_assert!(alphabet.len() <= mask + 1);
141
142    let mut id = String::with_capacity(size);
143
144    loop {
145        let bytes = random(step);
146
147        for &byte in &bytes {
148            let byte = byte as usize & mask;
149
150            if alphabet.len() > byte {
151                id.push(alphabet[byte]);
152
153                if id.len() == size {
154                    return id;
155                }
156            }
157        }
158    }
159}
160
161#[cfg(test)]
162mod test_format {
163    use super::*;
164
165    #[test]
166    fn generates_random_string() {
167        fn random(size: usize) -> Vec<u8> {
168            [2, 255, 0, 1].iter().cloned().cycle().take(size).collect()
169        }
170
171        assert_eq!(format(random, &['a', 'b', 'c'], 4), "cabc");
172    }
173
174    #[test]
175    #[should_panic]
176    fn bad_alphabet() {
177        let alphabet: Vec<char> = (0..32_u8).cycle().map(|i| i as char).take(1000).collect();
178        nanoid!(21, &alphabet);
179    }
180
181    #[test]
182    fn non_power_2() {
183        let id: String = nanoid!(42, &alphabet::SAFE[0..62]);
184
185        assert_eq!(id.len(), 42);
186    }
187}
188
189#[macro_export]
190macro_rules! nanoid {
191    // simple
192    () => {
193        $crate::format($crate::rngs::default, &$crate::alphabet::SAFE, 21)
194    };
195
196    // generate
197    ($size:tt) => {
198        $crate::format($crate::rngs::default, &$crate::alphabet::SAFE, $size)
199    };
200
201    // custom
202    ($size:tt, $alphabet:expr) => {
203        $crate::format($crate::rngs::default, $alphabet, $size)
204    };
205
206    // complex
207    ($size:tt, $alphabet:expr, $random:expr) => {
208        $crate::format($random, $alphabet, $size)
209    };
210}
211
212#[cfg(test)]
213mod test_macros {
214    use super::*;
215
216    #[test]
217    fn simple() {
218        let id: String = nanoid!();
219
220        assert_eq!(id.len(), 21);
221    }
222
223    #[test]
224    fn generate() {
225        let id: String = nanoid!(42);
226
227        assert_eq!(id.len(), 42);
228    }
229
230    #[test]
231    fn custom() {
232        let id: String = nanoid!(42, &alphabet::SAFE);
233
234        assert_eq!(id.len(), 42);
235    }
236
237    #[test]
238    fn complex() {
239        let id: String = nanoid!(4, &alphabet::SAFE, rngs::default);
240
241        assert_eq!(id.len(), 4);
242    }
243}
244
245#[cfg(doctest)]
246doc_comment::doctest!("../README.md");