idnano/
lib.rs

1extern crate core;
2
3use rand::{RngCore};
4use rand::thread_rng;
5
6pub const DEFAULT_ALPHABET: &[u8] = "-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz".as_bytes();
7const DEFAULT_SIZE: usize = 21;
8
9const LEN8TAB: &[u8] = "\x00\x01\x02\x02\x03\x03\x03\x03\x04\x04\x04\x04\x04\x04\x04\x04\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08".as_bytes();
10
11fn len_32(mut x: u32) -> i32 {
12    let mut n = 0;
13    if x >= 1 << 16 {
14        x >>= 16;
15        n = 16;
16    }
17    if x >= 1 << 8 {
18        x >>= 8;
19        n += 8;
20    }
21    return n + LEN8TAB[x as usize] as i32;
22}
23
24fn leading_zeros_32(x: u32) -> i32 {
25    return 32 - len_32(x);
26}
27
28
29type BytesGenerator = fn(size: usize) -> Vec<u8>;
30
31fn generate_random_buffer(size: usize) -> Vec<u8> {
32    let mut rng = thread_rng();
33    let mut buffer: Vec<u8> = Vec::with_capacity(size);
34    buffer.resize(size, 0);
35    rng.fill_bytes(buffer.as_mut_slice());
36    return buffer;
37}
38
39fn format_string(generate_random_buffer: BytesGenerator, alphabet: &[u8], size: usize) -> String {
40    let mask = (2 << (31 - leading_zeros_32((alphabet.len() - 1 | 1) as u32)) as u32) - 1;
41    let step = ((1.6 * (mask * size) as f64 / (alphabet.len()) as f64).ceil()) as usize;
42    let mut id = String::new();
43    loop {
44        let random_buffer = generate_random_buffer(step);
45        for i in 0..step {
46            let current_index = (random_buffer[i] & mask as u8) as usize;
47            if current_index < alphabet.len() {
48                id.push(char::from(alphabet[current_index]));
49                if id.len() == size as usize {
50                    return id;
51                }
52            }
53        }
54    }
55}
56
57
58pub fn generate_string(alphabet: &[u8], size: usize) -> String {
59    return format_string(generate_random_buffer, alphabet, size);
60}
61
62pub fn new() -> String {
63    return generate_string(DEFAULT_ALPHABET, DEFAULT_SIZE);
64}
65
66#[macro_export]
67macro_rules! id {
68    () => {
69        new()
70    };
71    ($size: expr) => {
72        generate_string(DEFAULT_ALPHABET, $size)
73    };
74    ($size: expr, $alphabet: expr) => {
75        generate_string($alphabet, $size)
76    };
77
78}
79
80#[cfg(test)]
81mod tests {
82    use std::collections::HashMap;
83    use crate::{DEFAULT_ALPHABET, DEFAULT_SIZE, format_string, generate_random_buffer, generate_string, LEN8TAB, len_32, new};
84
85    #[test]
86    fn test_len_32() {
87        assert_eq!(0, len_32(0));
88        assert_eq!(3, len_32(4));
89        assert_eq!(4, len_32(8));
90        assert_eq!(5, len_32(21));
91    }
92
93    #[test]
94    fn default_alphabet() {
95        assert_eq!("-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz".as_bytes(), DEFAULT_ALPHABET);
96    }
97
98    #[test]
99    fn default_size_is_21() {
100        assert_eq!(21, DEFAULT_SIZE);
101    }
102
103    #[test]
104    fn test_generate_url_friendly_ids() {
105        let alphabet = DEFAULT_ALPHABET;
106        let size = DEFAULT_SIZE;
107        for _i in 0..100 {
108            let id = generate_string(alphabet, size);
109            for ch in id.bytes() {
110                if !alphabet.contains(&ch) {
111                    assert_eq!("contains url not frinedly chars", "");
112                }
113            }
114        }
115    }
116
117    #[test]
118    fn test_change_id_length() {
119        let alphabet = DEFAULT_ALPHABET;
120        let size = 10;
121        let id = generate_string(alphabet, size);
122        assert_eq!(size, id.len());
123    }
124
125    #[test]
126    fn test_no_collisions() {
127        let count = 100 * 1000;
128        let mut used: HashMap<String, usize> = HashMap::new();
129        for _i in 0..count {
130            let id = new();
131            if used.contains_key(&id) {
132                assert_eq!("repeated id has been generated", "");
133            }
134            used.insert(id, 1);
135        }
136    }
137
138    #[test]
139    fn test_has_flat_distribution() {
140        let alphabet = "abcdefghijklmnopqrstuvwxyz".as_bytes();
141        let size = 5;
142
143        let count = 100 * 1000;
144        let mut chars: HashMap<u8, i32> = HashMap::new();
145        for _i in 0..count {
146            let id = generate_string(alphabet, size);
147            let id_bytes = id.as_bytes();
148            for j in 0..id_bytes.len() {
149                let ch = id_bytes[j];
150                if !chars.contains_key(&ch) {
151                    chars.insert(ch, 0);
152                } else {
153                    *chars.get_mut(&ch).unwrap() += 1;
154                }
155            }
156        }
157
158        assert_eq!(chars.len(), alphabet.len());
159
160        let mut max = 0.0;
161        let mut min = i32::MAX as f64;
162        for v in chars {
163            let distribution = (v.1 * alphabet.len() as i32) as f64 / (count * size) as f64;
164            if distribution > max {
165                max = distribution;
166            }
167            if distribution < min {
168                min = distribution;
169            }
170        }
171        let distribution = max - min;
172        assert_eq!(true, distribution < 0.05);
173    }
174
175    #[test]
176    fn test_has_options() {
177        let id = generate_string("a".as_bytes(), 5);
178        let target = "aaaaa".to_string();
179        assert_eq!(target, id);
180    }
181
182    const SEQUENCE: [u8; 10] = [2, 255, 3, 7, 7, 7, 7, 7, 0, 1];
183
184    fn generate_bytes_buffer(step: usize) -> Vec<u8> {
185        let mut buffer: Vec<u8> = Vec::new();
186        let mut i = 0;
187        loop {
188            if i < step {
189                for j in 0..step {
190                    buffer.push(SEQUENCE[j]);
191                }
192            } else {
193                break;
194            }
195            i += SEQUENCE.len();
196        }
197        return buffer;
198    }
199
200    #[test]
201    fn test_generate_random_string() {
202        let id = format_string(generate_bytes_buffer, "abcde".as_bytes(), 4);
203        let target = "cdac".to_string();
204        assert_eq!(target, id);
205    }
206
207    #[test]
208    fn len8tab_is_256_len() {
209        assert_eq!(256, LEN8TAB.len());
210        assert_eq!(0, LEN8TAB[0] as i32);
211        assert_eq!(8, LEN8TAB[255] as i32);
212    }
213
214    #[test]
215    fn gen_random_buffer_len() {
216        let buf = generate_random_buffer(DEFAULT_SIZE);
217        assert_eq!(DEFAULT_SIZE, buf.len());
218    }
219
220    #[test]
221    fn generate_id() {
222        let _id = new();
223    }
224
225    #[test]
226    fn test_macros() {
227        //default id
228        let id = id!();
229        assert_eq!(id.len(), DEFAULT_SIZE);
230
231        //set size
232        let id_10 = id!(10);
233        assert_eq!(id_10.len(), 10);
234
235        //set size and alphabet
236        let id_alphabet_10 = id!(10, "01234567890".as_bytes());
237        assert_eq!(id_alphabet_10.len(), 10);
238    }
239}