Skip to main content

id_forge/
ulid.rs

1//! # ULID generation
2//!
3//! Universally Unique Lexicographically Sortable Identifier.
4//! 128 bits: 48-bit timestamp + 80-bit randomness. Sorts naturally
5//! by creation time.
6//!
7//! In `0.1.0` this is a placeholder implementation.
8
9use core::fmt;
10
11/// A 128-bit ULID.
12///
13/// # Example
14///
15/// ```
16/// use id_forge::ulid::Ulid;
17///
18/// let id = Ulid::new();
19/// assert_eq!(id.to_string().len(), 26);
20/// ```
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22pub struct Ulid([u8; 16]);
23
24impl Ulid {
25    /// Construct a new ULID with the current time.
26    ///
27    /// In `0.1.0` this is a placeholder. The real implementation
28    /// lands in `0.9.x`.
29    pub fn new() -> Self {
30        use std::sync::atomic::{AtomicU64, Ordering};
31        use std::time::{SystemTime, UNIX_EPOCH};
32        static COUNTER: AtomicU64 = AtomicU64::new(0);
33        let ms = SystemTime::now()
34            .duration_since(UNIX_EPOCH)
35            .map(|d| d.as_millis() as u64)
36            .unwrap_or(0);
37        let counter = COUNTER.fetch_add(1, Ordering::Relaxed);
38        let mut bytes = [0u8; 16];
39        // First 6 bytes: 48-bit ms timestamp.
40        let ms_bytes = ms.to_be_bytes();
41        bytes[0..6].copy_from_slice(&ms_bytes[2..8]);
42        // Remaining 10 bytes: placeholder "randomness" derived from counter.
43        bytes[6..14].copy_from_slice(&counter.to_be_bytes());
44        Self(bytes)
45    }
46
47    /// Return the raw 16-byte representation.
48    pub fn as_bytes(&self) -> &[u8; 16] {
49        &self.0
50    }
51}
52
53impl Default for Ulid {
54    fn default() -> Self {
55        Self::new()
56    }
57}
58
59impl fmt::Display for Ulid {
60    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61        // Crockford base32 encoding of 128 bits = 26 chars.
62        const ALPHABET: &[u8] = b"0123456789ABCDEFGHJKMNPQRSTVWXYZ";
63        // Treat the bytes as a 128-bit big-endian integer.
64        let mut n: u128 = 0;
65        for &b in &self.0 {
66            n = (n << 8) | (b as u128);
67        }
68        let mut out = [0u8; 26];
69        for i in (0..26).rev() {
70            out[i] = ALPHABET[(n & 31) as usize];
71            n >>= 5;
72        }
73        f.write_str(core::str::from_utf8(&out).unwrap_or(""))
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[test]
82    fn display_length_26() {
83        let id = Ulid::new();
84        assert_eq!(id.to_string().len(), 26);
85    }
86
87    #[test]
88    fn unique() {
89        let a = Ulid::new();
90        let b = Ulid::new();
91        assert_ne!(a, b);
92    }
93
94    #[test]
95    fn time_ordered() {
96        let a = Ulid::new();
97        let b = Ulid::new();
98        // Same millisecond may produce a==b on timestamp prefix; counter
99        // ensures b > a overall.
100        assert!(b.as_bytes() >= a.as_bytes());
101    }
102}