mr_ulid/zeroable.rs
1use std::{
2 fmt,
3 str::FromStr,
4 time::{Duration, SystemTime},
5};
6
7use crate::{Error, RANDOM_BITS, RANDOM_MASK, Ulid, base32, generator, util};
8
9/// A ULID with even the value zero allowed.
10///
11/// This flavour of ULID can be zero. Sometimes ULIDs with zero value are used to
12/// signal the absence of a ULID. While this works, it could be considered a bad practice.
13/// In Rust an [`Option<Ulid>`](Ulid) is a more idiomatic way to handle ULIDs with zero value.
14/// However, if you need to parse zero value ULIDs, this type helps.
15///
16/// # Example
17///
18/// ```
19/// use mr_ulid::ZeroableUlid;
20///
21/// let s = "00000000000000000000000000";
22///
23/// assert!(s.parse::<ZeroableUlid>().is_ok());
24/// ```
25#[allow(clippy::module_name_repetitions)]
26#[derive(Default, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
27#[repr(transparent)]
28pub struct ZeroableUlid(u128);
29
30impl ZeroableUlid {
31 /// Minimum allowed [`ZeroableUlid`]
32 ///
33 /// The smallest value for [`ZeroableUlid`] is 0, because zero *is* explicitly allowed.
34 ///
35 /// # Example
36 ///
37 /// ```
38 /// use mr_ulid::ZeroableUlid;
39 ///
40 /// assert_eq!(ZeroableUlid::MIN.to_u128(), 0);
41 /// ```
42 pub const MIN: Self = Self(0);
43
44 /// Maximum allowed [`ZeroableUlid`]
45 ///
46 /// The largest value for [`ZeroableUlid`] is `u128::MAX`.
47 ///
48 /// # Example
49 ///
50 /// ```
51 /// use mr_ulid::ZeroableUlid;
52 ///
53 /// assert_eq!(ZeroableUlid::MAX.to_u128(), u128::MAX);
54 /// ```
55 pub const MAX: Self = Self(u128::MAX);
56
57 /// Creates a `ZeroableUlid` with the value zero.
58 ///
59 /// Chances are high, you're looking for method [`ZeroableUlid::new()`],
60 /// which creates unique `ZeroableUlid`s which are never zero.
61 ///
62 /// # Example
63 ///
64 /// ```
65 /// use mr_ulid::ZeroableUlid;
66 ///
67 /// let u1 = ZeroableUlid::zeroed();
68 /// let u2 = ZeroableUlid::zeroed();
69 ///
70 /// assert!(u1 == u2); // They are not unique!
71 ///
72 /// assert!(u1.is_zero()); // They are both zero!
73 /// assert!(u2.is_zero());
74 ///
75 /// ```
76 #[must_use]
77 pub const fn zeroed() -> Self {
78 Self(0)
79 }
80
81 /// Generates a new unique `ZeroableUlid`.
82 ///
83 /// The generated `ZeroableUlid`s are guaranteed to be unique and monotonically increasing and never zero.
84 ///
85 /// A lot of care is taken, that the `ZeroableUlid`s cannot overflow. You can create as many
86 /// `ZeroableUlid`s as you like, and they will always be unique and strict monotonically increasing.
87 ///
88 /// # Panics
89 ///
90 /// With the standard entropy source ([`STANDARD_ENTROPY_SOURCE`][generator::STANDARD_ENTROPY_SOURCE]),
91 /// this method will panic if the system date is somewhere after the year 10889 or before the Unix epoch (year 1970).
92 ///
93 /// # Example
94 ///
95 /// ```
96 /// use mr_ulid::ZeroableUlid;
97 ///
98 /// let u1 = ZeroableUlid::new();
99 /// let u2 = ZeroableUlid::new();
100 ///
101 /// assert!(u1 != u2);
102 /// assert!(u1 < u2);
103 ///
104 /// let t1 = u1.timestamp();
105 /// let t2 = u2.timestamp();
106 ///
107 /// let r1 = u1.randomness();
108 /// let r2 = u2.randomness();
109 ///
110 /// assert!((t1 < t2) || (t1 == t2 && r1 < r2));
111 /// ```
112 #[must_use]
113 pub fn new() -> Self {
114 Self(generator::generate().unwrap())
115 }
116
117 /// Tests if a `ZeroableUlid` is zero.
118 ///
119 /// When a `ZeroableUlid` is zero, it returns `true`. Otherwise, it returns `false`.
120 ///
121 /// # Example
122 ///
123 /// ```
124 /// use mr_ulid::ZeroableUlid;
125 ///
126 /// let u1 = ZeroableUlid::zeroed();
127 /// let u2 = ZeroableUlid::new();
128 ///
129 /// assert!(u1.is_zero());
130 /// assert!(!u2.is_zero());
131 /// ```
132 #[must_use]
133 pub const fn is_zero(self) -> bool {
134 self.0 == 0
135 }
136
137 /// Returns the timestamp part of a `ZeroableUlid`.
138 ///
139 /// The timestamp is measured in milliseconds since the Unix epoch (1. January 1970).
140 /// ULID timestamps are limited to 48 bits.
141 ///
142 /// # Example
143 ///
144 /// ```
145 /// use mr_ulid::ZeroableUlid;
146 ///
147 /// let u = ZeroableUlid::new();
148 ///
149 /// assert!(u.timestamp() > 1704067200000); // 1st January 2024
150 /// ```
151 #[must_use]
152 pub const fn timestamp(self) -> u64 {
153 (self.0 >> RANDOM_BITS) as u64
154 }
155
156 /// Returns the random part of a `ZeroableUlid`.
157 ///
158 /// The randomness of a `ZeroableUlid` is limited to 80 bits.
159 ///
160 /// # Example
161 ///
162 /// ```
163 /// use mr_ulid::ZeroableUlid;
164 ///
165 /// let u = ZeroableUlid::new();
166 ///
167 /// assert!(u.randomness() < (1<<80));
168 /// ```
169 ///
170 #[must_use]
171 pub const fn randomness(self) -> u128 {
172 self.0 & RANDOM_MASK
173 }
174
175 /// Returns the timestamp part of a `ZeroableUlid` as a `SystemTime`.
176 ///
177 /// # Panics
178 ///
179 /// In Rust the allowed range for [`SystemTime`] is not defined.
180 /// So this method may panic if the timestamp of the `ZeroableUlid` cannot represented with [`SystemTime`].
181 /// On most common systems that will not happen.
182 ///
183 /// For a variant which never panics, see [`ZeroableUlid::try_datetime`].
184 ///
185 /// # Example
186 ///
187 /// ```
188 /// use std::time::SystemTime;
189 /// use mr_ulid::ZeroableUlid;
190 ///
191 /// let u = ZeroableUlid::new();
192 ///
193 /// assert!(u.datetime() <= SystemTime::now());
194 /// ```
195 #[must_use]
196 pub fn datetime(self) -> SystemTime {
197 SystemTime::UNIX_EPOCH + Duration::from_millis(self.timestamp())
198 }
199
200 /// Converts this `ZeroableUlid` to a [`Ulid`].
201 ///
202 /// When the `ZeroableUlid` is zero, `None` is returned.
203 ///
204 /// # Example
205 ///
206 /// ```
207 /// use mr_ulid::ZeroableUlid;
208 ///
209 /// let u1 = ZeroableUlid::new();
210 ///
211 /// assert!(!u1.is_zero());
212 /// assert!(u1.to_ulid().is_some());
213 ///
214 /// let u2 = ZeroableUlid::zeroed(); // Creates a ZeroableUlid with value zero
215 ///
216 /// assert!(u2.is_zero());
217 /// assert!(u2.to_ulid().is_none());
218 ///
219 /// ```
220 #[must_use]
221 pub const fn to_ulid(self) -> Option<Ulid> {
222 Ulid::from_u128(self.0)
223 }
224
225 /// Creates a `ZeroableUlid` from a [`Ulid`].
226 ///
227 /// This method always succeeds, as every [`Ulid`] is a valid `ZeroableUlid`.
228 ///
229 /// # Example
230 ///
231 /// ```
232 /// use mr_ulid::{Ulid, ZeroableUlid};
233 ///
234 /// let u1 = Ulid::new();
235 /// let u2 = ZeroableUlid::from_ulid(u1);
236 ///
237 /// assert!(!u2.is_zero());
238 /// assert_eq!(u1.to_u128(), u2.to_u128());
239 /// ```
240 #[must_use]
241 pub const fn from_ulid(ulid: Ulid) -> Self {
242 Self::from_u128(ulid.to_u128())
243 }
244
245 /// Returns the timestamp and randomness parts of a `ZeroableUlid` as a pair.
246 ///
247 /// # Example
248 ///
249 /// ```
250 /// use mr_ulid::ZeroableUlid;
251 ///
252 /// let u = ZeroableUlid::new();
253 /// let (timestamp, randomness) = u.to_parts();
254 ///
255 /// assert_eq!(timestamp, u.timestamp());
256 /// assert_eq!(randomness, u.randomness());
257 /// ```
258 #[must_use]
259 pub const fn to_parts(self) -> (u64, u128) {
260 (self.timestamp(), self.randomness())
261 }
262
263 /// Creates a `ZeroableUlid` from a timestamp and randomness parts.
264 ///
265 /// # Errors
266 ///
267 /// Will fail if the timestamp (48 bits) or randomness (80 bits) are out of range.
268 ///
269 /// # Example
270 ///
271 /// ```
272 /// # use std::error::Error;
273 /// # fn main() -> Result<(), Box<dyn Error>> {
274 /// use mr_ulid::ZeroableUlid;
275 ///
276 /// let u1 = ZeroableUlid::zeroed();
277 /// let (timestamp, randomness) = u1.to_parts();
278 /// let u2 = ZeroableUlid::from_parts(timestamp, randomness)?;
279 ///
280 /// assert_eq!(u1, u2);
281 ///
282 /// assert_eq!(ZeroableUlid::from_parts(0, 0)?, ZeroableUlid::zeroed());
283 /// # Ok(()) }
284 /// ```
285 pub const fn from_parts(timestamp: u64, randomness: u128) -> Result<Self, Error> {
286 match util::from_parts(timestamp, randomness) {
287 Ok(n) => Ok(Self::from_u128(n)),
288 Err(err) => Err(err),
289 }
290 }
291
292 /// Converts a `ZeroableUlid` into binary bytes
293 ///
294 /// The bytes are in network byte order (big endian).
295 ///
296 /// # Example
297 ///
298 /// ```
299 /// # use std::error::Error;
300 /// # fn main() -> Result<(), Box<dyn Error>> {
301 /// use mr_ulid::ZeroableUlid;
302 ///
303 // cspell:disable-next-line
304 /// let u: ZeroableUlid = "01JB05JV6H9ZA2YQ6X3K1DAGVA".parse()?;
305 ///
306 /// assert_eq!(u.to_bytes(), [1, 146, 192, 89, 108, 209, 79, 212, 47, 92, 221, 28, 194, 213, 67, 106]);
307 /// # Ok(()) }
308 /// ```
309 #[must_use]
310 pub const fn to_bytes(self) -> [u8; 16] {
311 self.0.to_be_bytes()
312 }
313
314 /// Creates a `ZeroableUlid` from a binary byte array.
315 ///
316 /// The byte array must be in network byte order (big endian).
317 ///
318 /// # Example
319 ///
320 /// ```
321 /// use mr_ulid::ZeroableUlid;
322 ///
323 /// let bytes: [u8; 16] = [1, 146, 192, 89, 108, 209, 79, 212, 47, 92, 221, 28, 194, 213, 67, 106];
324 /// let u = ZeroableUlid::from_bytes(bytes);
325 ///
326 // cspell:disable-next-line
327 /// assert_eq!(u.to_string(), "01JB05JV6H9ZA2YQ6X3K1DAGVA");
328 /// ```
329 #[must_use]
330 pub const fn from_bytes(bytes: [u8; 16]) -> Self {
331 Self::from_u128(u128::from_be_bytes(bytes))
332 }
333
334 /// Converts a `ZeroableUlid` into a `u128` integer.
335 ///
336 /// # Example
337 ///
338 /// ```
339 /// # use std::error::Error;
340 /// # fn main() -> Result<(), Box<dyn Error>> {
341 /// use mr_ulid::ZeroableUlid;
342 ///
343 // cspell:disable-next-line
344 /// let u: ZeroableUlid = "01JB07NQ643XZXVHZDY0JNYR02".parse()?;
345 ///
346 /// assert_eq!(u.to_u128(), 2091207293934528941058695985186693122);
347 /// # Ok(()) }
348 /// ```
349 #[must_use]
350 pub const fn to_u128(self) -> u128 {
351 self.0
352 }
353
354 /// Creates a `ZeroableUlid` from a `u128` integer.
355 ///
356 ///
357 /// # Example
358 ///
359 /// ```
360 /// use mr_ulid::ZeroableUlid;
361 ///
362 /// let n = 2091207293934528941058695985186693122_u128;
363 /// let u = ZeroableUlid::from_u128(n);
364 ///
365 // cspell:disable-next-line
366 /// assert_eq!(u.to_string(), "01JB07NQ643XZXVHZDY0JNYR02");
367 /// ```
368 #[must_use]
369 pub const fn from_u128(n: u128) -> Self {
370 Self(n)
371 }
372
373 /// Generates a new `ZeroableUlid` and never panics.
374 ///
375 /// This is a variant of [`ZeroableUlid::new()`] which never panics (with the [`STANDARD_ENTROPY_SOURCE`](generator::STANDARD_ENTROPY_SOURCE)).
376 ///
377 /// In the case of problems with the ULID-generator, this function returns `None`.
378 ///
379 /// # Example
380 ///
381 /// ```
382 /// # { inner(); fn inner() -> Option<()> {
383 /// use mr_ulid::ZeroableUlid;
384 ///
385 /// let u1 = ZeroableUlid::try_new()?;
386 /// let u2 = ZeroableUlid::try_new()?;
387 ///
388 /// assert!(u1 != u2);
389 /// assert!(u1.timestamp() <= u2.timestamp());
390 /// # Some(()) }}
391 /// ```
392 #[must_use]
393 pub fn try_new() -> Option<Self> {
394 Some(Self(generator::generate()?))
395 }
396
397 /// Returns the timestamp part of a `ZeroableUlid` as a [`SystemTime`] and never panics.
398 ///
399 /// This is a variant of [`ZeroableUlid::datetime()`] which never panics.
400 ///
401 /// In the case that the timestamp of a [`ZeroableUlid`] cannot be encoded in a [`SystemTime`], this method returns `None`.
402 ///
403 /// # Example
404 ///
405 /// ```
406 /// use std::time::SystemTime;
407 /// use mr_ulid::ZeroableUlid;
408 ///
409 /// let u = ZeroableUlid::new();
410 ///
411 /// let datetime: Option<SystemTime> = u.try_datetime();
412 /// ```
413 #[must_use]
414 pub fn try_datetime(self) -> Option<SystemTime> {
415 SystemTime::UNIX_EPOCH.checked_add(Duration::from_millis(self.timestamp()))
416 }
417
418 /// Return the string representation of a `ZeroableUlid` and never panics.
419 ///
420 /// While the blanket implementation of [`std::string::ToString`] for `std::fmt::Display` may
421 /// panic, this method is guaranteed to never panic, but returns `None` if the string representation cannot be created.
422 /// One reason this can happen is if the allocation of memory for the string fails.
423 #[must_use]
424 pub fn try_to_string(self) -> Option<String> {
425 util::try_to_string(self.0)
426 }
427
428 /// Creates a `ZeroableUlid` from timestamp and randomness parts without checking.
429 ///
430 /// This results in undefined behaviour if timestamp or randomness parts are too large.
431 ///
432 /// # Safety
433 ///
434 /// - Timestamp must be less than 2<sup>48</sup>.
435 /// - Randomness must be less than 2<sup>80</sup>.
436 #[must_use]
437 pub const unsafe fn from_parts_unchecked(timestamp: u64, randomness: u128) -> Self {
438 Self(((timestamp as u128) << RANDOM_BITS) | randomness)
439 }
440}
441
442impl fmt::Debug for ZeroableUlid {
443 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
444 util::debug_ulid("ZeroableUlid", self.0, f)
445 }
446}
447
448impl fmt::Display for ZeroableUlid {
449 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
450 let mut buffer = [0; 26];
451 f.write_str(base32::encode(self.0, &mut buffer))
452 }
453}
454
455impl FromStr for ZeroableUlid {
456 type Err = Error;
457 fn from_str(s: &str) -> Result<Self, Self::Err> {
458 let buffer = util::as_array(s.as_bytes())?;
459 Ok(Self::from_u128(base32::decode(buffer)?))
460 }
461}
462
463impl From<Ulid> for ZeroableUlid {
464 fn from(non_zero: Ulid) -> Self {
465 Self::from_u128(non_zero.to_u128())
466 }
467}
468
469impl From<ZeroableUlid> for u128 {
470 fn from(ulid: ZeroableUlid) -> Self {
471 ulid.to_u128()
472 }
473}
474
475impl From<u128> for ZeroableUlid {
476 fn from(n: u128) -> Self {
477 Self::from_u128(n)
478 }
479}
480
481impl From<ZeroableUlid> for [u8; 16] {
482 fn from(ulid: ZeroableUlid) -> Self {
483 ulid.to_bytes()
484 }
485}
486
487impl From<[u8; 16]> for ZeroableUlid {
488 fn from(bytes: [u8; 16]) -> Self {
489 Self::from_bytes(bytes)
490 }
491}
492
493impl From<&[u8; 16]> for ZeroableUlid {
494 fn from(bytes: &[u8; 16]) -> Self {
495 Self::from_bytes(*bytes)
496 }
497}
498
499impl TryFrom<&[u8]> for ZeroableUlid {
500 type Error = Error;
501 fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
502 Ok(Self::from_bytes(*util::as_array(bytes)?))
503 }
504}