Skip to main content

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}