g1t/crates/contracts/src/ids.rs
Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| API and MCP server, Rust identity service, registration, site redesign | 1 | use std::cell::Cell; |
| 2 | ||
| 3 | const ALPHABET: &[u8; 32] = b"0123456789abcdefghjkmnpqrstvwxyz"; | |
| 4 | ||
| 5 | thread_local! { | |
| 6 | /// The millisecond and counter of the last id made, to keep ids made in | |
| 7 | /// the same millisecond in order. | |
| 8 | static LAST: Cell<(u64, u16)> = const { Cell::new((0, 0)) }; | |
| 9 | } | |
| 10 | ||
| 11 | /// A new id in [TypeID](https://github.com/jetify-com/typeid) format: a type | |
| 12 | /// prefix, then a UUIDv7 in lowercase Crockford base32, such as | |
| 13 | /// `att_01jb2k7x9hfq0b3zj0f5s2m8ra`. | |
| 14 | /// | |
| 15 | /// - The prefix says what the id refers to, so ids cannot be mixed up. | |
| 16 | /// - Sorting ids as strings sorts them by creation time, which also keeps | |
| 17 | /// inserts at the end of the primary-key index. | |
| 18 | /// - The suffix decodes to a standard UUIDv7 for systems that want one. | |
| 19 | /// | |
| 20 | /// Ids made in the same millisecond by one process increase monotonically: | |
| 21 | /// the UUID's 12-bit `rand_a` field is used as a counter. | |
| 22 | pub fn new_id(prefix: &str, now_ms: u64) -> String { | |
| 23 | let mut bytes = [0u8; 16]; | |
| 24 | getrandom::getrandom(&mut bytes).expect("no source of randomness"); | |
| 25 | ||
| 26 | let counter = LAST.with(|last| { | |
| 27 | let (last_ms, last_counter) = last.get(); | |
| 28 | let counter = if now_ms == last_ms { | |
| 29 | last_counter.wrapping_add(1) & 0x0fff | |
| 30 | } else { | |
| 31 | // Start in the lower half so there is room to count up. | |
| 32 | u16::from_be_bytes([bytes[6], bytes[7]]) & 0x07ff | |
| 33 | }; | |
| 34 | last.set((now_ms, counter)); | |
| 35 | counter | |
| 36 | }); | |
| 37 | ||
| 38 | bytes[..6].copy_from_slice(&now_ms.to_be_bytes()[2..]); | |
| 39 | bytes[6] = 0x70 | (counter >> 8) as u8; // version 7 | |
| 40 | bytes[7] = counter as u8; | |
| 41 | bytes[8] = 0x80 | (bytes[8] & 0x3f); // RFC 9562 variant | |
| 42 | ||
| 43 | let value = u128::from_be_bytes(bytes); | |
| 44 | let mut id = String::with_capacity(prefix.len() + 27); | |
| 45 | id.push_str(prefix); | |
| 46 | id.push('_'); | |
| 47 | // 128 bits in 26 characters of 5 bits; the first carries only 3. | |
| 48 | for index in 0..26 { | |
| 49 | let shift = 125 - 5 * index; | |
| 50 | id.push(ALPHABET[((value >> shift) & 31) as usize] as char); | |
| 51 | } | |
| 52 | id | |
| 53 | } | |
| 54 | ||
| 55 | #[cfg(test)] | |
| 56 | mod tests { | |
| 57 | use super::*; | |
| 58 | ||
| 59 | #[test] | |
| 60 | fn has_prefix_and_26_character_suffix() { | |
| 61 | let id = new_id("att", 1_790_000_000_000); | |
| 62 | let (prefix, suffix) = id.split_once('_').unwrap(); | |
| 63 | assert_eq!(prefix, "att"); | |
| 64 | assert_eq!(suffix.len(), 26); | |
| 65 | assert!(suffix.bytes().all(|byte| ALPHABET.contains(&byte))); | |
| 66 | // A 128-bit value never needs more than 3 bits in the first character. | |
| 67 | assert!(suffix.as_bytes()[0] <= b'7'); | |
| 68 | } | |
| 69 | ||
| 70 | #[test] | |
| 71 | fn sorts_by_time_then_by_order_made() { | |
| 72 | let earlier = new_id("evt", 1_790_000_000_000); | |
| 73 | let later = new_id("evt", 1_790_000_000_001); | |
| 74 | assert!(earlier < later); | |
| 75 | ||
| 76 | let same_ms: Vec<String> = (0..100).map(|_| new_id("evt", 1_790_000_000_002)).collect(); | |
| 77 | assert!(same_ms.windows(2).all(|pair| pair[0] < pair[1])); | |
| 78 | } | |
| 79 | } |