rig_core/id.rs
1//! Lightweight generation of short, unique, URL-safe identifiers.
2//!
3//! These IDs are used purely to disambiguate things like in-flight tool calls
4//! and request headers — they are *not* security-sensitive and do not need a
5//! cryptographic source of randomness. We therefore generate them with
6//! [`fastrand`] (already a hard dependency of this crate) rather than pulling in
7//! `nanoid` → `rand` → `getrandom`, which would add a cryptographic RNG (and a
8//! `getrandom/js` shim on wasm) for no benefit here.
9
10/// The URL-safe alphabet used by `nanoid` (`A-Za-z0-9_-`), preserved so the
11/// shape of generated IDs is unchanged from the previous implementation.
12const ALPHABET: &[u8; 64] = b"_-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
13
14/// Default ID length, matching `nanoid`'s default.
15const DEFAULT_LEN: usize = 21;
16
17/// Generate a 21-character, URL-safe, non-cryptographic identifier.
18///
19/// This is a drop-in replacement for the previous `nanoid!()` usage for
20/// internal, non-security-sensitive use.
21pub fn generate() -> String {
22 generate_with_len(DEFAULT_LEN)
23}
24
25/// Generate a `len`-character, URL-safe, non-cryptographic identifier.
26pub fn generate_with_len(len: usize) -> String {
27 std::iter::repeat_with(|| {
28 let idx = fastrand::usize(..ALPHABET.len());
29 // `idx` is always in bounds, but use `get` to avoid the `indexing_slicing` lint.
30 ALPHABET.get(idx).copied().unwrap_or(b'_') as char
31 })
32 .take(len)
33 .collect()
34}
35
36#[cfg(test)]
37mod tests {
38 use super::*;
39
40 #[test]
41 fn default_length_and_alphabet() {
42 let id = generate();
43 assert_eq!(id.len(), DEFAULT_LEN);
44 assert!(id.bytes().all(|b| ALPHABET.contains(&b)));
45 }
46
47 #[test]
48 fn ids_are_unique() {
49 let a = generate();
50 let b = generate();
51 assert_ne!(a, b);
52 }
53
54 #[test]
55 fn custom_length() {
56 assert_eq!(generate_with_len(8).len(), 8);
57 }
58}