ulid/
lib.rs

1#![warn(missing_docs)]
2//! # ulid-rs
3//!
4//! This is a Rust implementation of the [ulid][ulid] project which provides
5//! Universally Unique Lexicographically Sortable Identifiers.
6//!
7//! [ulid]: https://github.com/ulid/spec
8//!
9//!
10//! ## Quickstart
11//!
12//! ```rust
13//! # use ulid::Ulid;
14//! // Generate a ulid
15//! # #[cfg(not(feature = "std"))]
16//! # let ulid = Ulid::default();
17//! # #[cfg(feature = "std")]
18//! let ulid = Ulid::new();
19//!
20//! // Generate a string for a ulid
21//! let s = ulid.to_string();
22//!
23//! // Create from a String
24//! let res = Ulid::from_string(&s);
25//! assert_eq!(ulid, res.unwrap());
26//!
27//! // Or using FromStr
28//! let res = s.parse();
29//! assert_eq!(ulid, res.unwrap());
30//!
31//! ```
32#![cfg_attr(not(feature = "std"), no_std)]
33
34#[doc = include_str!("../README.md")]
35#[cfg(all(doctest, feature = "std"))]
36struct ReadMeDoctest;
37
38mod base32;
39#[cfg(feature = "std")]
40mod generator;
41#[cfg(feature = "postgres")]
42mod postgres;
43#[cfg(feature = "rkyv")]
44mod rkyv;
45#[cfg(feature = "serde")]
46pub mod serde;
47#[cfg(feature = "std")]
48mod time;
49#[cfg(feature = "std")]
50mod time_utils;
51#[cfg(feature = "uuid")]
52mod uuid;
53
54use core::convert::TryFrom;
55use core::fmt;
56use core::str::FromStr;
57
58pub use crate::base32::{DecodeError, EncodeError, ULID_LEN};
59#[cfg(feature = "std")]
60pub use crate::generator::{Generator, MonotonicError};
61
62/// Create a right-aligned bitmask of $len bits
63macro_rules! bitmask {
64    ($len:expr) => {
65        ((1 << $len) - 1)
66    };
67}
68// Allow other modules to use the macro
69pub(crate) use bitmask;
70
71/// A Ulid is a unique 128-bit lexicographically sortable identifier
72///
73/// Canonically, it is represented as a 26 character Crockford Base32 encoded
74/// string.
75///
76/// Of the 128-bits, the first 48 are a unix timestamp in milliseconds. The
77/// remaining 80 are random. The first 48 provide for lexicographic sorting and
78/// the remaining 80 ensure that the identifier is unique.
79#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Hash, Clone, Copy)]
80#[cfg_attr(
81    feature = "rkyv",
82    derive(::rkyv::Archive, ::rkyv::Serialize, ::rkyv::Deserialize)
83)]
84pub struct Ulid(pub u128);
85
86impl Ulid {
87    /// The number of bits in a Ulid's time portion
88    pub const TIME_BITS: u8 = 48;
89    /// The number of bits in a Ulid's random portion
90    pub const RAND_BITS: u8 = 80;
91
92    /// Create a Ulid from separated parts.
93    ///
94    /// NOTE: Any overflow bits in the given args are discarded
95    ///
96    /// # Example
97    /// ```rust
98    /// use ulid::Ulid;
99    ///
100    /// let ulid = Ulid::from_string("01D39ZY06FGSCTVN4T2V9PKHFZ").unwrap();
101    ///
102    /// let ulid2 = Ulid::from_parts(ulid.timestamp_ms(), ulid.random());
103    ///
104    /// assert_eq!(ulid, ulid2);
105    /// ```
106    pub const fn from_parts(timestamp_ms: u64, random: u128) -> Ulid {
107        let time_part = (timestamp_ms & bitmask!(Self::TIME_BITS)) as u128;
108        let rand_part = random & bitmask!(Self::RAND_BITS);
109        Ulid((time_part << Self::RAND_BITS) | rand_part)
110    }
111
112    /// Creates a Ulid from a Crockford Base32 encoded string
113    ///
114    /// An DecodeError will be returned when the given string is not formatted
115    /// properly.
116    ///
117    /// # Example
118    /// ```rust
119    /// use ulid::Ulid;
120    ///
121    /// let text = "01D39ZY06FGSCTVN4T2V9PKHFZ";
122    /// let result = Ulid::from_string(text);
123    ///
124    /// assert!(result.is_ok());
125    /// assert_eq!(&result.unwrap().to_string(), text);
126    /// ```
127    pub const fn from_string(encoded: &str) -> Result<Ulid, DecodeError> {
128        match base32::decode(encoded) {
129            Ok(int_val) => Ok(Ulid(int_val)),
130            Err(err) => Err(err),
131        }
132    }
133
134    /// The 'nil Ulid'.
135    ///
136    /// The nil Ulid is special form of Ulid that is specified to have
137    /// all 128 bits set to zero.
138    ///
139    /// # Example
140    /// ```rust
141    /// use ulid::Ulid;
142    ///
143    /// let ulid = Ulid::nil();
144    ///
145    /// assert_eq!(
146    ///     ulid.to_string(),
147    ///     "00000000000000000000000000"
148    /// );
149    /// ```
150    pub const fn nil() -> Ulid {
151        Ulid(0)
152    }
153
154    /// Gets the timestamp section of this ulid
155    ///
156    /// # Example
157    /// ```rust
158    /// # #[cfg(feature = "std")] {
159    /// use std::time::{SystemTime, Duration};
160    /// use ulid::Ulid;
161    ///
162    /// let dt = SystemTime::now();
163    /// let ulid = Ulid::from_datetime(dt);
164    ///
165    /// assert_eq!(u128::from(ulid.timestamp_ms()), dt.duration_since(SystemTime::UNIX_EPOCH).unwrap_or(Duration::ZERO).as_millis());
166    /// # }
167    /// ```
168    pub const fn timestamp_ms(&self) -> u64 {
169        (self.0 >> Self::RAND_BITS) as u64
170    }
171
172    /// Gets the random section of this ulid
173    ///
174    /// # Example
175    /// ```rust
176    /// use ulid::Ulid;
177    ///
178    /// let text = "01D39ZY06FGSCTVN4T2V9PKHFZ";
179    /// let ulid = Ulid::from_string(text).unwrap();
180    /// let ulid_next = ulid.increment().unwrap();
181    ///
182    /// assert_eq!(ulid.random() + 1, ulid_next.random());
183    /// ```
184    pub const fn random(&self) -> u128 {
185        self.0 & bitmask!(Self::RAND_BITS)
186    }
187
188    /// Creates a Crockford Base32 encoded string that represents this Ulid
189    ///
190    /// # Example
191    /// ```rust
192    /// use ulid::Ulid;
193    ///
194    /// let text = "01D39ZY06FGSCTVN4T2V9PKHFZ";
195    /// let ulid = Ulid::from_string(text).unwrap();
196    ///
197    /// let mut buf = [0; ulid::ULID_LEN];
198    /// let new_text = ulid.to_str(&mut buf).unwrap();
199    ///
200    /// assert_eq!(new_text, text);
201    /// ```
202    #[deprecated(since = "1.2.0", note = "Use the infallible `array_to_str` instead.")]
203    pub fn to_str<'buf>(&self, buf: &'buf mut [u8]) -> Result<&'buf mut str, EncodeError> {
204        #[allow(deprecated)]
205        let len = base32::encode_to(self.0, buf)?;
206        Ok(unsafe { core::str::from_utf8_unchecked_mut(&mut buf[..len]) })
207    }
208
209    /// Creates a Crockford Base32 encoded string that represents this Ulid
210    ///
211    /// # Example
212    /// ```rust
213    /// use ulid::Ulid;
214    ///
215    /// let text = "01D39ZY06FGSCTVN4T2V9PKHFZ";
216    /// let ulid = Ulid::from_string(text).unwrap();
217    ///
218    /// let mut buf = [0; ulid::ULID_LEN];
219    /// let new_text = ulid.array_to_str(&mut buf);
220    ///
221    /// assert_eq!(new_text, text);
222    /// ```
223    pub fn array_to_str<'buf>(&self, buf: &'buf mut [u8; ULID_LEN]) -> &'buf mut str {
224        base32::encode_to_array(self.0, buf);
225        unsafe { core::str::from_utf8_unchecked_mut(buf) }
226    }
227
228    /// Creates a Crockford Base32 encoded string that represents this Ulid
229    ///
230    /// # Example
231    /// ```rust
232    /// use ulid::Ulid;
233    ///
234    /// let text = "01D39ZY06FGSCTVN4T2V9PKHFZ";
235    /// let ulid = Ulid::from_string(text).unwrap();
236    ///
237    /// assert_eq!(&ulid.to_string(), text);
238    /// ```
239    #[allow(clippy::inherent_to_string_shadow_display)] // Significantly faster than Display::to_string
240    #[cfg(feature = "std")]
241    pub fn to_string(&self) -> String {
242        base32::encode(self.0)
243    }
244
245    /// Test if the Ulid is nil
246    ///
247    /// # Example
248    /// ```rust
249    /// use ulid::Ulid;
250    ///
251    /// # #[cfg(not(feature = "std"))]
252    /// # let ulid = Ulid(1);
253    /// # #[cfg(feature = "std")]
254    /// let ulid = Ulid::new();
255    /// assert!(!ulid.is_nil());
256    ///
257    /// let nil = Ulid::nil();
258    /// assert!(nil.is_nil());
259    /// ```
260    pub const fn is_nil(&self) -> bool {
261        self.0 == 0u128
262    }
263
264    /// Increment the random number, make sure that the ts millis stays the same
265    pub const fn increment(&self) -> Option<Ulid> {
266        const MAX_RANDOM: u128 = bitmask!(Ulid::RAND_BITS);
267
268        if (self.0 & MAX_RANDOM) == MAX_RANDOM {
269            None
270        } else {
271            Some(Ulid(self.0 + 1))
272        }
273    }
274
275    /// Creates a Ulid using the provided bytes array.
276    ///
277    /// # Example
278    /// ```
279    /// use ulid::Ulid;
280    /// let bytes = [0xFF; 16];
281    ///
282    /// let ulid = Ulid::from_bytes(bytes);
283    ///
284    /// assert_eq!(
285    ///     ulid.to_string(),
286    ///     "7ZZZZZZZZZZZZZZZZZZZZZZZZZ"
287    /// );
288    /// ```
289    pub const fn from_bytes(bytes: [u8; 16]) -> Ulid {
290        Self(u128::from_be_bytes(bytes))
291    }
292
293    /// Returns the bytes of the Ulid in big-endian order.
294    ///
295    /// # Example
296    /// ```
297    /// use ulid::Ulid;
298    ///
299    /// let text = "7ZZZZZZZZZZZZZZZZZZZZZZZZZ";
300    /// let ulid = Ulid::from_string(text).unwrap();
301    ///
302    /// assert_eq!(ulid.to_bytes(), [0xFF; 16]);
303    /// ```
304    pub const fn to_bytes(&self) -> [u8; 16] {
305        self.0.to_be_bytes()
306    }
307}
308
309impl Default for Ulid {
310    fn default() -> Self {
311        Ulid::nil()
312    }
313}
314
315#[cfg(feature = "std")]
316impl From<Ulid> for String {
317    fn from(ulid: Ulid) -> String {
318        ulid.to_string()
319    }
320}
321
322impl From<(u64, u64)> for Ulid {
323    fn from((msb, lsb): (u64, u64)) -> Self {
324        Ulid(u128::from(msb) << 64 | u128::from(lsb))
325    }
326}
327
328impl From<Ulid> for (u64, u64) {
329    fn from(ulid: Ulid) -> (u64, u64) {
330        ((ulid.0 >> 64) as u64, (ulid.0 & bitmask!(64)) as u64)
331    }
332}
333
334impl From<u128> for Ulid {
335    fn from(value: u128) -> Ulid {
336        Ulid(value)
337    }
338}
339
340impl From<Ulid> for u128 {
341    fn from(ulid: Ulid) -> u128 {
342        ulid.0
343    }
344}
345
346impl From<[u8; 16]> for Ulid {
347    fn from(bytes: [u8; 16]) -> Self {
348        Self(u128::from_be_bytes(bytes))
349    }
350}
351
352impl From<Ulid> for [u8; 16] {
353    fn from(ulid: Ulid) -> Self {
354        ulid.0.to_be_bytes()
355    }
356}
357
358impl FromStr for Ulid {
359    type Err = DecodeError;
360
361    fn from_str(s: &str) -> Result<Self, Self::Err> {
362        Ulid::from_string(s)
363    }
364}
365
366impl TryFrom<&'_ str> for Ulid {
367    type Error = DecodeError;
368
369    fn try_from(value: &'_ str) -> Result<Self, Self::Error> {
370        Ulid::from_string(value)
371    }
372}
373
374impl fmt::Display for Ulid {
375    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
376        let mut buffer = [0; ULID_LEN];
377        write!(f, "{}", self.array_to_str(&mut buffer))
378    }
379}
380
381#[cfg(all(test, feature = "std"))]
382mod tests {
383    use super::*;
384
385    #[test]
386    fn test_static() {
387        let s = Ulid(0x41414141414141414141414141414141).to_string();
388        let u = Ulid::from_string(&s).unwrap();
389        assert_eq!(&s, "21850M2GA1850M2GA1850M2GA1");
390        assert_eq!(u.0, 0x41414141414141414141414141414141);
391    }
392
393    #[test]
394    fn test_increment() {
395        let ulid = Ulid::from_string("01BX5ZZKBKAZZZZZZZZZZZZZZZ").unwrap();
396        let ulid = ulid.increment().unwrap();
397        assert_eq!("01BX5ZZKBKB000000000000000", ulid.to_string());
398
399        let ulid = Ulid::from_string("01BX5ZZKBKZZZZZZZZZZZZZZZX").unwrap();
400        let ulid = ulid.increment().unwrap();
401        assert_eq!("01BX5ZZKBKZZZZZZZZZZZZZZZY", ulid.to_string());
402        let ulid = ulid.increment().unwrap();
403        assert_eq!("01BX5ZZKBKZZZZZZZZZZZZZZZZ", ulid.to_string());
404        assert!(ulid.increment().is_none());
405    }
406
407    #[test]
408    fn test_increment_overflow() {
409        let ulid = Ulid(u128::max_value());
410        assert!(ulid.increment().is_none());
411    }
412
413    #[test]
414    fn can_into_thing() {
415        let ulid = Ulid::from_str("01FKMG6GAG0PJANMWFN84TNXCD").unwrap();
416        let s: String = ulid.into();
417        let u: u128 = ulid.into();
418        let uu: (u64, u64) = ulid.into();
419        let bytes: [u8; 16] = ulid.into();
420
421        assert_eq!(Ulid::from_str(&s).unwrap(), ulid);
422        assert_eq!(Ulid::from(u), ulid);
423        assert_eq!(Ulid::from(uu), ulid);
424        assert_eq!(Ulid::from(bytes), ulid);
425    }
426
427    #[test]
428    fn default_is_nil() {
429        assert_eq!(Ulid::default(), Ulid::nil());
430    }
431
432    #[test]
433    fn can_display_things() {
434        println!("{}", Ulid::nil());
435        println!("{}", EncodeError::BufferTooSmall);
436        println!("{}", DecodeError::InvalidLength);
437        println!("{}", DecodeError::InvalidChar);
438    }
439}