Skip to main content

pawkit_crockford/
lib.rs

1#![feature(decl_macro)]
2
3use std::{
4    fmt::Debug,
5    time::{SystemTime, UNIX_EPOCH},
6};
7
8use rand::Rng;
9use serde::{Deserialize, Deserializer, Serialize};
10
11pub const CROCKFORD_DIGITS: &[u8] = b"0123456789ABCDEFGHJKMNPQRSTVWXYZ";
12
13pub fn crockford_char_to_digit(c: char) -> Option<u8> {
14    return Some(match c.to_ascii_uppercase() {
15        '0' | 'O' => 0,
16        '1' | 'I' | 'L' => 1,
17        '2' => 2,
18        '3' => 3,
19        '4' => 4,
20        '5' => 5,
21        '6' => 6,
22        '7' => 7,
23        '8' => 8,
24        '9' => 9,
25        'A' => 10,
26        'B' => 11,
27        'C' => 12,
28        'D' => 13,
29        'E' => 14,
30        'F' => 15,
31        'G' => 16,
32        'H' => 17,
33        'J' => 18,
34        'K' => 19,
35        'M' => 20,
36        'N' => 21,
37        'P' => 22,
38        'Q' => 23,
39        'R' => 24,
40        'S' => 25,
41        'T' => 26,
42        'V' => 27,
43        'W' => 28,
44        'X' => 29,
45        'Y' => 30,
46        'Z' => 31,
47        _ => return None,
48    });
49}
50
51pub trait IntoCrockford {
52    fn into_crockford(&self, padding: usize) -> String;
53}
54
55pub trait FromCrockford {
56    fn from_crockford(value: &str) -> Option<Self>
57    where
58        Self: Sized;
59}
60
61macro impl_crockford($t:ty) {
62    impl IntoCrockford for $t {
63        fn into_crockford(&self, padding: usize) -> String {
64            if *self == 0 {
65                return "0".repeat(padding.max(1));
66            }
67
68            let mut value = *self;
69            let mut result = Vec::new();
70
71            while value > 0 {
72                let rem = (value % 32) as usize;
73                result.push(CROCKFORD_DIGITS[rem] as char);
74                value >>= 5;
75            }
76
77            let mut encoded: String = result.iter().rev().collect();
78            if encoded.len() < padding {
79                let pad_len = padding - encoded.len();
80                encoded = "0".repeat(pad_len) + &encoded;
81            }
82
83            encoded
84        }
85    }
86
87    impl FromCrockford for $t {
88        fn from_crockford(value: &str) -> Option<Self> {
89            let mut result: $t = 0;
90
91            for c in value.chars() {
92                let digit = crockford_char_to_digit(c)?;
93                result = result.checked_mul(32)?.checked_add(digit as $t)?;
94            }
95
96            Some(result)
97        }
98    }
99}
100
101impl_crockford!(u8);
102impl_crockford!(u16);
103impl_crockford!(u32);
104impl_crockford!(u64);
105impl_crockford!(u128);
106
107#[repr(C)]
108#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
109pub struct Ulid([u8; 16]);
110
111impl Ulid {
112    pub fn from_raw_parts(timestamp: u128, random: u128) -> Self {
113        let timestamp = timestamp.to_be_bytes();
114        let random = random.to_be_bytes();
115
116        let mut bytes = [0u8; 16];
117
118        bytes[..6].copy_from_slice(&timestamp[10..]);
119        bytes[6..].copy_from_slice(&random[6..]);
120
121        return Self(bytes);
122    }
123
124    pub fn into_raw_parts(&self) -> (u128, u128) {
125        let mut timestamp_bytes = [0u8; 16];
126        timestamp_bytes[10..].copy_from_slice(self.timestamp_bytes());
127
128        let mut random_bytes = [0u8; 16];
129        random_bytes[6..].copy_from_slice(self.random_bytes());
130
131        let timestamp = u128::from_be_bytes(timestamp_bytes);
132        let random = u128::from_be_bytes(random_bytes);
133
134        return (timestamp, random);
135    }
136
137    pub fn new() -> Self {
138        let timestamp = SystemTime::now()
139            .duration_since(UNIX_EPOCH)
140            .unwrap()
141            .as_millis();
142
143        let timestamp = timestamp.to_be_bytes();
144
145        let mut bytes = [0u8; 16];
146
147        bytes[..6].copy_from_slice(&timestamp[10..]);
148
149        rand::rng().fill_bytes(&mut bytes[6..]);
150
151        return Self(bytes);
152    }
153
154    pub fn timestamp_bytes(&self) -> &[u8] {
155        return &self.0[..6];
156    }
157
158    pub fn random_bytes(&self) -> &[u8] {
159        return &self.0[6..];
160    }
161}
162
163impl Debug for Ulid {
164    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
165        return f
166            .debug_tuple("Ulid")
167            .field(&self.into_crockford(26))
168            .finish();
169    }
170}
171
172impl From<Ulid> for u128 {
173    fn from(value: Ulid) -> Self {
174        return Self::from_be_bytes(value.0);
175    }
176}
177
178impl From<u128> for Ulid {
179    fn from(value: u128) -> Self {
180        return Self(value.to_be_bytes());
181    }
182}
183
184impl From<Ulid> for [u8; 16] {
185    fn from(value: Ulid) -> [u8; 16] {
186        return value.0;
187    }
188}
189
190impl From<[u8; 16]> for Ulid {
191    fn from(bytes: [u8; 16]) -> Ulid {
192        return Ulid(bytes);
193    }
194}
195
196impl IntoCrockford for Ulid {
197    fn into_crockford(&self, _: usize) -> String {
198        let (timestamp, random) = self.into_raw_parts();
199
200        return timestamp.into_crockford(10) + &random.into_crockford(16);
201    }
202}
203
204impl FromCrockford for Ulid {
205    fn from_crockford(value: &str) -> Option<Self>
206    where
207        Self: Sized,
208    {
209        let (timestamp, random) = value.split_at_checked(10)?;
210
211        let timestamp = u128::from_crockford(timestamp)?;
212        let random = u128::from_crockford(random)?;
213
214        return Some(Self::from_raw_parts(timestamp, random));
215    }
216}
217
218impl Serialize for Ulid {
219    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
220    where
221        S: serde::Serializer,
222    {
223        if serializer.is_human_readable() {
224            return self.into_crockford(0).serialize(serializer);
225        }
226
227        return self.into_raw_parts().serialize(serializer);
228    }
229}
230
231impl<'de> Deserialize<'de> for Ulid {
232    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
233    where
234        D: Deserializer<'de>,
235    {
236        if deserializer.is_human_readable() {
237            let value = String::deserialize(deserializer)?;
238
239            return Ulid::from_crockford(&value)
240                .ok_or_else(|| serde::de::Error::custom("invalid ULID string"));
241        }
242
243        let (timestamp, random) = <(u128, u128)>::deserialize(deserializer)?;
244
245        return Ok(Ulid::from_raw_parts(timestamp, random));
246    }
247}