1use base64::Engine;
7use base64::engine::general_purpose::URL_SAFE_NO_PAD;
8use rand::Rng;
9
10const TOKEN_ENTROPY_BYTES: usize = 32;
12
13pub fn generate_token() -> String {
19 let mut bytes = [0u8; TOKEN_ENTROPY_BYTES];
20 rand::rng().fill(&mut bytes);
21 URL_SAFE_NO_PAD.encode(bytes)
22}
23
24#[cfg(test)]
25mod tests {
26 use super::*;
27
28 #[test]
29 fn generated_token_has_expected_length() {
30 let token = generate_token();
31 assert_eq!(token.len(), 43);
33 }
34
35 #[test]
36 fn generated_token_is_url_safe() {
37 let token = generate_token();
38 for ch in token.chars() {
39 assert!(
40 ch.is_ascii_alphanumeric() || ch == '-' || ch == '_',
41 "unexpected character in token: {ch}"
42 );
43 }
44 }
45
46 #[test]
47 fn two_tokens_are_distinct() {
48 let a = generate_token();
49 let b = generate_token();
50 assert_ne!(a, b, "two generated tokens should differ");
51 }
52
53 #[test]
54 fn url_safe_base64_roundtrip_known_value() {
55 let zeros = [0u8; 32];
57 let encoded = URL_SAFE_NO_PAD.encode(zeros);
58 assert_eq!(encoded.len(), 43);
59 assert!(encoded.chars().all(|c| c == 'A'));
60 }
61
62 #[test]
63 fn url_safe_base64_encode_known_vector() {
64 let encoded = URL_SAFE_NO_PAD.encode(b"Hello");
66 assert_eq!(encoded, "SGVsbG8");
67 }
68}