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}