1use std::fmt;
4use std::hash::Hash;
5use std::str::FromStr;
6
7use super::{DbValue, FromDb, ToDbValue};
8
9#[diagnostic::on_unimplemented(
37 message = "`{Self}` can't be a model's primary key",
38 label = "the `id` field's type",
39 note = "use `i64`, `renox::db::Ulid`, `uuid::Uuid` (renox's `uuid` feature) or `String`"
40)]
41pub trait ModelKey:
42 sealed::Sealed
43 + ToDbValue
44 + FromDb
45 + Clone
46 + Eq
47 + Ord
48 + Hash
49 + fmt::Debug
50 + fmt::Display
51 + FromStr
52 + Default
53 + Send
54 + Sync
55 + Unpin
56 + 'static
57{
58 fn is_unsaved(&self) -> bool;
60
61 fn generate() -> Option<Self>;
64
65 const AUTO_INCREMENT: bool = false;
67}
68
69mod sealed {
70 pub trait Sealed {}
72 impl Sealed for i64 {}
73 impl Sealed for String {}
74 impl Sealed for super::Ulid {}
75 #[cfg(feature = "uuid")]
76 impl Sealed for uuid::Uuid {}
77}
78
79impl ModelKey for i64 {
80 const AUTO_INCREMENT: bool = true;
81
82 fn is_unsaved(&self) -> bool {
83 *self == 0
84 }
85
86 fn generate() -> Option<Self> {
87 None
88 }
89}
90
91impl ModelKey for String {
92 fn is_unsaved(&self) -> bool {
93 self.is_empty()
94 }
95
96 fn generate() -> Option<Self> {
97 None
98 }
99}
100
101#[cfg(feature = "uuid")]
102impl ModelKey for uuid::Uuid {
103 fn is_unsaved(&self) -> bool {
104 self.is_nil()
105 }
106
107 fn generate() -> Option<Self> {
110 Some(uuid::Uuid::now_v7())
111 }
112}
113
114impl ModelKey for Ulid {
115 fn is_unsaved(&self) -> bool {
116 self.0.is_empty()
117 }
118
119 fn generate() -> Option<Self> {
120 Some(Ulid::new())
121 }
122}
123
124#[derive(Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
129pub struct Ulid(String);
130
131const CROCKFORD: &[u8; 32] = b"0123456789ABCDEFGHJKMNPQRSTVWXYZ";
132
133impl Ulid {
134 pub fn new() -> Self {
139 static LAST: std::sync::Mutex<(u128, u128)> = std::sync::Mutex::new((0, 0));
140 const RANDOM_BITS: u128 = (1 << 80) - 1;
141 let now = u128::try_from(crate::clock::unix_millis()).unwrap_or(0) & ((1 << 48) - 1);
142 let mut last = LAST.lock().unwrap_or_else(|e| e.into_inner());
143 let (millis, random) = if now <= last.0 && last.1 < RANDOM_BITS {
144 (last.0, last.1 + 1)
145 } else {
146 let mut bytes = [0u8; 10];
147 rand::fill(&mut bytes);
148 let random = bytes
149 .iter()
150 .fold(0u128, |acc, byte| (acc << 8) | u128::from(*byte));
151 (now.max(last.0), random)
152 };
153 *last = (millis, random);
154 drop(last);
155 let value = (millis << 80) | random;
156 let text = (0..26)
157 .rev()
158 .map(|i| CROCKFORD[((value >> (i * 5)) & 31) as usize] as char)
159 .collect();
160 Self(text)
161 }
162
163 pub fn as_str(&self) -> &str {
165 &self.0
166 }
167}
168
169#[derive(Debug)]
171#[non_exhaustive]
172pub struct InvalidUlid;
173
174impl fmt::Display for InvalidUlid {
175 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176 f.write_str("not a ULID")
177 }
178}
179
180impl std::error::Error for InvalidUlid {}
181
182impl FromStr for Ulid {
183 type Err = InvalidUlid;
184
185 fn from_str(text: &str) -> Result<Self, Self::Err> {
188 if text.is_empty() {
189 return Ok(Self::default());
190 }
191 let upper = text.to_ascii_uppercase();
192 let valid = upper.len() == 26
193 && upper.as_bytes()[0] <= b'7'
194 && upper.bytes().all(|b| CROCKFORD.contains(&b));
195 if valid {
196 Ok(Self(upper))
197 } else {
198 Err(InvalidUlid)
199 }
200 }
201}
202
203impl fmt::Display for Ulid {
204 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205 f.write_str(&self.0)
206 }
207}
208
209impl fmt::Debug for Ulid {
210 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
211 write!(f, "Ulid({})", self.0)
212 }
213}
214
215impl serde::Serialize for Ulid {
216 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
217 serializer.serialize_str(&self.0)
218 }
219}
220
221impl<'de> serde::Deserialize<'de> for Ulid {
222 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
225 let text = String::deserialize(deserializer)?;
226 text.parse().map_err(serde::de::Error::custom)
227 }
228}
229
230impl ToDbValue for Ulid {
231 fn to_db_value(&self) -> DbValue {
232 DbValue::Text(self.0.clone())
233 }
234}
235
236crate::__db_text_type_for!(Ulid, sqlx::sqlite::Sqlite);
237#[cfg(feature = "postgres")]
238crate::__db_text_type_for!(Ulid, sqlx::postgres::Postgres);
239
240#[cfg(test)]
241mod tests {
242 use super::*;
243
244 #[test]
245 fn ulids_are_26_sortable_characters() {
246 let first = Ulid::new();
247 assert_eq!(first.as_str().len(), 26);
248 assert!(first.as_str().bytes().all(|b| CROCKFORD.contains(&b)));
249 let second = Ulid::new(); assert!(second > first, "{second} after {first}");
251 let many: Vec<Ulid> = (0..1000).map(|_| Ulid::new()).collect();
252 assert!(many.windows(2).all(|w| w[0] < w[1]), "monotonic");
253 assert_eq!(first.to_string().parse::<Ulid>().unwrap(), first);
254 assert_eq!(
255 first.as_str().to_lowercase().parse::<Ulid>().unwrap(),
256 first
257 );
258 assert!("not-a-ulid".parse::<Ulid>().is_err());
259 assert!(
260 "81J9Z3ABCDEFGHJKMNPQRSTVWX".parse::<Ulid>().is_err(),
261 "past 2^48 ms"
262 );
263 assert!(Ulid::default().is_unsaved());
264 assert!(!first.is_unsaved());
265 }
266
267 #[test]
269 fn empty_text_is_the_unsaved_ulid() {
270 let empty: Ulid = "".parse().unwrap();
271 assert_eq!(empty, Ulid::default());
272 let id: Ulid = "01arz3ndektsv4rrffq69g5fav".parse().unwrap();
273 assert_eq!(id.to_string(), "01ARZ3NDEKTSV4RRFFQ69G5FAV");
274 }
275}