1use std::cmp::Ordering;
2use std::fmt::{Debug, Display, Formatter};
3use std::str::FromStr;
4
5pub use label::Label;
6use sid_encode::{base32_encode, base32_decode, SHORT_LENGTH};
7pub use sid_encode::DecodeError;
8
9mod label;
10mod monotonic;
11#[cfg(feature = "sqlx")]
12mod sqlx;
13#[cfg(feature = "serde")]
14mod serde;
15
16pub use monotonic::MonotonicGenerator;
17
18#[cfg(target_arch = "wasm32")]
19fn unix_epoch_millis() -> u64 {
20 js_sys::Date::now() as u64
21}
22
23#[cfg(not(target_arch = "wasm32"))]
24fn unix_epoch_millis() -> u64 {
25 use std::time::SystemTime;
26 SystemTime::now()
27 .duration_since(SystemTime::UNIX_EPOCH)
28 .unwrap()
29 .as_millis() as u64
30}
31
32#[derive(Copy, Clone)]
33pub struct NoLabel;
34
35impl NoLabel {
36 pub fn sid() -> Sid {
37 Sid::<NoLabel>::new()
38 }
39}
40
41pub struct Sid<T = NoLabel> {
42 data: [u8; 16],
43 marker: std::marker::PhantomData<T>,
44}
45
46impl<T: Label> std::hash::Hash for Sid<T> {
47 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
48 self.data.hash(state)
49 }
50}
51
52impl<T> PartialEq<Self> for Sid<T> {
53 fn eq(&self, other: &Self) -> bool {
54 self.data == other.data
55 }
56}
57
58impl<T> PartialOrd<Self> for Sid<T> {
59 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
60 Some(self.cmp(other))
61 }
62}
63
64impl<T> Eq for Sid<T> {}
65
66impl<T> Ord for Sid<T> {
67 fn cmp(&self, other: &Self) -> Ordering {
68 self.data.cmp(&other.data)
69 }
70}
71
72impl<T> Clone for Sid<T> {
73 fn clone(&self) -> Self {
74 Self {
75 data: self.data,
76 marker: Default::default(),
77 }
78 }
79}
80
81impl<T> Copy for Sid<T> {}
82
83pub fn sid<T: Label>() -> Sid<T> {
84 Sid::<T>::new()
85}
86
87impl<T> Sid<T> {
88 #[cfg(feature = "uuid")]
89 pub fn uuid(&self) -> uuid::Uuid {
90 uuid::Uuid::from_bytes(self.data)
91 }
92}
93
94impl<T: Label> Sid<T> {
95 pub fn null() -> Self {
96 Self {
97 data: [0; 16],
98 marker: Default::default(),
99 }
100 }
101
102 pub fn new() -> Self {
103 Self::from_timestamp_with_rng(unix_epoch_millis(), &mut rand::thread_rng())
104 }
105
106 #[cfg(feature = "rand")]
107 pub fn from_timestamp_with_rng<R>(timestamp: u64, rng: &mut R) -> Self
108 where
109 R: rand::Rng,
110 {
111 if (timestamp >> 48) != 0 {
112 panic!("sid does not support timestamps after +10889-08-02T05:31:50.655Z");
113 }
114 let rand_high = rng.gen::<u32>() as u64 & ((1 << 16) - 1);
115 let high = timestamp << 16 | rand_high;
116 let low = rng.gen::<u64>();
117 let high = high.to_be_bytes();
118 let low = low.to_be_bytes();
119
120 let mut data: [u8; 16] = [0; 16];
121 data[..8].copy_from_slice(&high);
122 data[8..].copy_from_slice(&low);
123
124 Self {
125 data,
126 marker: Default::default(),
127 }
128 }
129
130 pub fn short(&self) -> String {
132 let encoded = base32_encode(self.data);
133 let label = T::label();
134 let separator = if label.is_empty() { "" } else { "_" };
135 format!("{}{}{}", label, separator, &encoded[SHORT_LENGTH + 1..])
136 }
137
138 pub fn is_null(&self) -> bool {
139 self.data.iter().all(|&b| b == 0)
140 }
141
142 pub fn data(&self) -> &[u8; 16] {
143 &self.data
144 }
145
146 pub fn timestamp(&self) -> u64 {
147 u64::from_be_bytes(self.data[0..8].try_into().unwrap())
148 }
149
150 pub(crate) fn increment(&self) -> Self {
153 let mut data = self.data;
154 let mut i = 15;
155 loop {
156 let (value, overflow) = data[i].overflowing_add(1);
157 data[i] = value;
158 if !overflow {
159 break;
160 }
161 if i == 0 {
162 panic!("sid overflow");
163 }
164 i -= 1;
165 }
166 Self {
167 data,
168 marker: Default::default(),
169 }
170 }
171}
172
173#[cfg(feature = "uuid")]
174impl<T> Into<uuid::Uuid> for Sid<T> {
175 fn into(self) -> uuid::Uuid {
176 uuid::Uuid::from_bytes(self.data)
177 }
178}
179
180#[cfg(feature = "uuid")]
181impl<T> From<uuid::Uuid> for Sid<T> {
182 fn from(value: uuid::Uuid) -> Self {
183 let bytes = value.as_ref();
184 let mut data: [u8; 16] = [0; 16];
185 data.copy_from_slice(bytes);
186 Self {
187 data,
188 marker: Default::default(),
189 }
190 }
191}
192
193impl<T> Sid<T> {
194 pub fn unlabel(self) -> Sid<NoLabel> {
195 Sid {
196 data: self.data,
197 marker: Default::default(),
198 }
199 }
200}
201
202impl Sid<NoLabel> {
203 pub fn into_labeled<U>(self) -> Sid<U> {
204 Sid {
205 data: self.data,
206 marker: Default::default(),
207 }
208 }
209}
210
211impl<T> From<[u8; 16]> for Sid<T> {
212 fn from(data: [u8; 16]) -> Self {
213 Self {
214 data,
215 marker: Default::default(),
216 }
217 }
218}
219
220impl<T> FromStr for Sid<T> {
221 type Err = DecodeError;
222
223 fn from_str(s: &str) -> Result<Self, Self::Err> {
224 let data = base32_decode(s)?;
225 Ok(Sid::<T>::from(data))
226 }
227}
228
229impl<T: Label> Debug for Sid<T> {
230 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
231 let encoded = base32_encode(self.data);
232 let label = T::label();
233 let sep = if label.is_empty() { "" } else { "_" };
234 f.write_str(label)?;
235 f.write_str(sep)?;
236 f.write_str(&encoded)
237 }
238}
239
240impl<T> Display for Sid<T> {
241 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
242 let encoded = base32_encode(self.data);
243 f.write_str(&encoded)
244 }
245}
246
247#[macro_export]
248macro_rules! sid {
249 ($value:ident) => {
250 sid!(stringify!($value))
251 };
252 ($value:expr) => {{
253 let value = $value;
254 let count = value.matches('_').count();
255 match count {
256 1 => Sid::from_str(value).unwrap(),
257 2 => {
258 let value = value.splitn(2, '_').nth(1).unwrap();
259 Sid::from_str(value).unwrap()
260 },
261 _ => panic!("sid must have 1 or 2 underscores."),
262 }
263 }};
264}
265
266
267#[cfg(test)]
268mod tests {
269 use label::Label;
270
271 use super::*;
272
273 label!(Team, "team");
274
275 #[test]
276 fn test_struct_sid_can_reference_itself() {
277 struct Team {
278 id: Sid<Self>,
279 }
280
281 impl Label for Team {
282 fn label() -> &'static str {
283 "tea_"
284 }
285 }
286
287 let f = Team { id: sid() };
288 let s = format!("{:?}", f.id);
289 assert!(s.starts_with("tea_"));
290 }
291
292 #[test]
293 fn it_works() {
294 let bytes = [1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
295 let sid = Sid::<Team>::from(bytes);
296 println!("{}", sid.short());
297 println!("{}", sid);
298 assert_eq!(sid.to_string(), "01081g81860w40j2gb1g6g_w3rg");
299 assert_eq!(sid.short(), "team_w3rg");
300 }
301
302 #[test]
303 fn test_null() {
304 let sid = Sid::<Team>::null();
305 println!("{}", sid.short());
306 println!("{}", sid);
307 assert_eq!(sid.to_string(), "0000000000000000000000_0000");
308 assert_eq!(sid.short(), "team_0000");
309 let sid = Sid::<NoLabel>::null();
310 assert_eq!(sid.to_string(), "0000000000000000000000_0000");
311 }
312
313 #[test]
314 #[cfg(feature = "uuid")]
315 fn test_uuid() {
316 let bytes = [1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
317 let sid = Sid::<Team>::from(bytes);
318 let uuid: uuid::Uuid = sid.clone().into();
319 assert_eq!(uuid.to_string(), "01020304-0506-0708-090a-0b0c0d0e0f10");
320 let uuid2 = sid.uuid();
321 assert_eq!(uuid, uuid2);
322 }
323
324 #[test]
325 fn test_macro() {
326 let sid: Sid<Team> = sid!("team_0000000000000000000000_0000");
327 assert!(sid.is_null(), "{}", sid);
328 let sid: Sid<Team> = sid!(team_0000000000000000000000_0000);
329 assert!(sid.is_null(), "{}", sid);
330 let sid: Sid<Team> = sid!(team_0da0fa0e02cssbhkanf04c_srb0);
331 assert_eq!(sid.to_string(), "0da0fa0e02cssbhkanf04c_srb0");
332 }
333
334 #[test]
335 fn test_size() {
336 assert_eq!(std::mem::size_of::<Sid<Team>>(), 16);
337 }
338
339 #[test]
340 fn test_sort() {
341 let ts = unix_epoch_millis();
342 let ts2 = ts + 1;
343 let ts3 = ts + 2;
344 let rng = &mut rand::thread_rng();
345 let sid1 = Sid::<NoLabel>::from_timestamp_with_rng(ts, rng);
346 let sid2 = Sid::from_timestamp_with_rng(ts2, rng);
347 let sid3 = Sid::from_timestamp_with_rng(ts3, rng);
348 let mut sids = vec![sid3.clone(), sid1.clone(), sid2.clone()];
349 sids.sort();
350 assert_eq!(sids, vec![sid1, sid2, sid3]);
351 }
352}