1use std::fmt;
2use std::str::FromStr;
3use std::sync::Mutex;
4use std::time::{SystemTime, UNIX_EPOCH};
5
6use rand::RngCore;
7use serde::{Deserialize, Serialize};
8
9use crate::IdError;
10
11const CROCKFORD: &[u8; 32] = b"0123456789ABCDEFGHJKMNPQRSTVWXYZ";
12const CROCKFORD_DECODE: [i8; 128] = build_decode_table();
13
14const fn build_decode_table() -> [i8; 128] {
15 let mut table = [-1i8; 128];
16 let alpha = b"0123456789ABCDEFGHJKMNPQRSTVWXYZ";
17 let mut i = 0usize;
18 while i < 32 {
19 table[alpha[i] as usize] = i as i8;
20 if alpha[i].is_ascii_uppercase() {
22 table[(alpha[i] + 32) as usize] = i as i8;
23 }
24 i += 1;
25 }
26 table
27}
28
29fn encode_ulid(ts_ms: u64, random: &[u8; 10]) -> [u8; 26] {
30 let mut chars = [0u8; 26];
31
32 chars[0] = CROCKFORD[((ts_ms >> 45) & 0x1F) as usize];
34 chars[1] = CROCKFORD[((ts_ms >> 40) & 0x1F) as usize];
35 chars[2] = CROCKFORD[((ts_ms >> 35) & 0x1F) as usize];
36 chars[3] = CROCKFORD[((ts_ms >> 30) & 0x1F) as usize];
37 chars[4] = CROCKFORD[((ts_ms >> 25) & 0x1F) as usize];
38 chars[5] = CROCKFORD[((ts_ms >> 20) & 0x1F) as usize];
39 chars[6] = CROCKFORD[((ts_ms >> 15) & 0x1F) as usize];
40 chars[7] = CROCKFORD[((ts_ms >> 10) & 0x1F) as usize];
41 chars[8] = CROCKFORD[((ts_ms >> 5) & 0x1F) as usize];
42 chars[9] = CROCKFORD[(ts_ms & 0x1F) as usize];
43
44 let mut r: u128 = 0;
46 for &b in random.iter() {
47 r = (r << 8) | b as u128;
48 }
49 for i in 0..16usize {
50 chars[25 - i] = CROCKFORD[((r >> (5 * i)) & 0x1F) as usize];
51 }
52
53 chars
54}
55
56struct MonotonicState {
57 last_ms: u64,
58 last_random: [u8; 10],
59}
60
61static MONOTONIC: Mutex<Option<MonotonicState>> = Mutex::new(None);
62
63#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
65pub struct Ulid(String);
66
67impl Ulid {
68 pub fn generate() -> Self {
70 let ts = SystemTime::now()
71 .duration_since(UNIX_EPOCH)
72 .expect("time went backwards")
73 .as_millis() as u64;
74
75 let mut rnd = [0u8; 10];
76 rand::thread_rng().fill_bytes(&mut rnd);
77
78 let chars = encode_ulid(ts, &rnd);
79 Self(String::from_utf8(chars.to_vec()).unwrap())
80 }
81
82 pub fn monotonic() -> Self {
85 let ts = SystemTime::now()
86 .duration_since(UNIX_EPOCH)
87 .expect("time went backwards")
88 .as_millis() as u64;
89
90 let mut guard = MONOTONIC.lock().unwrap();
91 let random = match &mut *guard {
92 Some(state) if state.last_ms == ts => {
93 let mut i = 9usize;
95 loop {
96 let (val, overflow) = state.last_random[i].overflowing_add(1);
97 state.last_random[i] = val;
98 if !overflow {
99 break;
100 }
101 if i == 0 {
102 rand::thread_rng().fill_bytes(&mut state.last_random);
104 break;
105 }
106 i -= 1;
107 }
108 state.last_random
109 }
110 _ => {
111 let mut rnd = [0u8; 10];
112 rand::thread_rng().fill_bytes(&mut rnd);
113 *guard = Some(MonotonicState {
114 last_ms: ts,
115 last_random: rnd,
116 });
117 rnd
118 }
119 };
120
121 let chars = encode_ulid(ts, &random);
122 Self(String::from_utf8(chars.to_vec()).unwrap())
123 }
124
125 pub fn as_str(&self) -> &str {
126 &self.0
127 }
128
129 pub fn timestamp_ms(&self) -> u64 {
131 let bytes = self.0.as_bytes();
132 let mut ts: u64 = 0;
133 for &byte in bytes.iter().take(10) {
134 let ch = byte as usize;
135 let v = if ch < 128 { CROCKFORD_DECODE[ch] } else { -1 };
136 ts = (ts << 5) | v as u64;
137 }
138 ts
139 }
140}
141
142impl fmt::Display for Ulid {
143 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144 f.write_str(&self.0)
145 }
146}
147
148impl FromStr for Ulid {
149 type Err = IdError;
150
151 fn from_str(s: &str) -> Result<Self, Self::Err> {
152 if s.len() != 26 {
153 return Err(IdError::InvalidFormat("ulid", "expected 26 chars"));
154 }
155 for ch in s.chars() {
156 let idx = ch as usize;
157 if idx >= 128 || CROCKFORD_DECODE[idx] < 0 {
158 return Err(IdError::InvalidFormat(
159 "ulid",
160 "invalid Crockford base32 char",
161 ));
162 }
163 }
164 Ok(Self(s.to_ascii_uppercase()))
165 }
166}
167
168impl AsRef<str> for Ulid {
169 fn as_ref(&self) -> &str {
170 &self.0
171 }
172}
173
174#[cfg(feature = "sqlx-postgres")]
175mod sqlx_impl {
176 use super::Ulid;
177 use sqlx::{
178 encode::IsNull,
179 error::BoxDynError,
180 postgres::{PgArgumentBuffer, PgTypeInfo, PgValueRef},
181 };
182
183 impl sqlx::Type<sqlx::Postgres> for Ulid {
184 fn type_info() -> PgTypeInfo {
185 <String as sqlx::Type<sqlx::Postgres>>::type_info()
186 }
187 }
188
189 impl<'q> sqlx::Encode<'q, sqlx::Postgres> for Ulid {
190 fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
191 <String as sqlx::Encode<'q, sqlx::Postgres>>::encode_by_ref(&self.0, buf)
192 }
193 }
194
195 impl<'r> sqlx::Decode<'r, sqlx::Postgres> for Ulid {
196 fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> {
197 let s = <String as sqlx::Decode<sqlx::Postgres>>::decode(value)?;
198 Ok(Self(s))
199 }
200 }
201}