Coverage Report

Created: 2025-11-28 06:44

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/ulid-1.2.1/src/lib.rs
Line
Count
Source
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"))]
36
struct ReadMeDoctest;
37
38
mod base32;
39
#[cfg(feature = "std")]
40
mod generator;
41
#[cfg(feature = "postgres")]
42
mod postgres;
43
#[cfg(feature = "rkyv")]
44
mod rkyv;
45
#[cfg(feature = "serde")]
46
pub mod serde;
47
#[cfg(feature = "std")]
48
mod time;
49
#[cfg(feature = "std")]
50
mod time_utils;
51
#[cfg(feature = "uuid")]
52
mod uuid;
53
54
use core::convert::TryFrom;
55
use core::fmt;
56
use core::str::FromStr;
57
58
pub use crate::base32::{DecodeError, EncodeError, ULID_LEN};
59
#[cfg(feature = "std")]
60
pub use crate::generator::{Generator, MonotonicError};
61
62
/// Create a right-aligned bitmask of $len bits
63
macro_rules! bitmask {
64
    ($len:expr) => {
65
        ((1 << $len) - 1)
66
    };
67
}
68
// Allow other modules to use the macro
69
pub(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
)]
84
pub struct Ulid(pub u128);
85
86
impl 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
0
    pub const fn from_parts(timestamp_ms: u64, random: u128) -> Ulid {
107
0
        let time_part = (timestamp_ms & bitmask!(Self::TIME_BITS)) as u128;
108
0
        let rand_part = random & bitmask!(Self::RAND_BITS);
109
0
        Ulid((time_part << Self::RAND_BITS) | rand_part)
110
0
    }
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
0
    pub const fn from_string(encoded: &str) -> Result<Ulid, DecodeError> {
128
0
        match base32::decode(encoded) {
129
0
            Ok(int_val) => Ok(Ulid(int_val)),
130
0
            Err(err) => Err(err),
131
        }
132
0
    }
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
0
    pub const fn nil() -> Ulid {
151
0
        Ulid(0)
152
0
    }
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
0
    pub const fn timestamp_ms(&self) -> u64 {
169
0
        (self.0 >> Self::RAND_BITS) as u64
170
0
    }
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
0
    pub const fn random(&self) -> u128 {
185
0
        self.0 & bitmask!(Self::RAND_BITS)
186
0
    }
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
0
    pub fn to_str<'buf>(&self, buf: &'buf mut [u8]) -> Result<&'buf mut str, EncodeError> {
204
        #[allow(deprecated)]
205
0
        let len = base32::encode_to(self.0, buf)?;
206
0
        Ok(unsafe { core::str::from_utf8_unchecked_mut(&mut buf[..len]) })
207
0
    }
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
0
    pub fn array_to_str<'buf>(&self, buf: &'buf mut [u8; ULID_LEN]) -> &'buf mut str {
224
0
        base32::encode_to_array(self.0, buf);
225
0
        unsafe { core::str::from_utf8_unchecked_mut(buf) }
226
0
    }
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
0
    pub fn to_string(&self) -> String {
242
0
        base32::encode(self.0)
243
0
    }
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
0
    pub const fn is_nil(&self) -> bool {
261
0
        self.0 == 0u128
262
0
    }
263
264
    /// Increment the random number, make sure that the ts millis stays the same
265
0
    pub const fn increment(&self) -> Option<Ulid> {
266
        const MAX_RANDOM: u128 = bitmask!(Ulid::RAND_BITS);
267
268
0
        if (self.0 & MAX_RANDOM) == MAX_RANDOM {
269
0
            None
270
        } else {
271
0
            Some(Ulid(self.0 + 1))
272
        }
273
0
    }
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
0
    pub const fn from_bytes(bytes: [u8; 16]) -> Ulid {
290
0
        Self(u128::from_be_bytes(bytes))
291
0
    }
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
0
    pub const fn to_bytes(&self) -> [u8; 16] {
305
0
        self.0.to_be_bytes()
306
0
    }
307
}
308
309
impl Default for Ulid {
310
0
    fn default() -> Self {
311
0
        Ulid::nil()
312
0
    }
313
}
314
315
#[cfg(feature = "std")]
316
impl From<Ulid> for String {
317
0
    fn from(ulid: Ulid) -> String {
318
0
        ulid.to_string()
319
0
    }
320
}
321
322
impl From<(u64, u64)> for Ulid {
323
0
    fn from((msb, lsb): (u64, u64)) -> Self {
324
0
        Ulid(u128::from(msb) << 64 | u128::from(lsb))
325
0
    }
326
}
327
328
impl From<Ulid> for (u64, u64) {
329
0
    fn from(ulid: Ulid) -> (u64, u64) {
330
0
        ((ulid.0 >> 64) as u64, (ulid.0 & bitmask!(64)) as u64)
331
0
    }
332
}
333
334
impl From<u128> for Ulid {
335
0
    fn from(value: u128) -> Ulid {
336
0
        Ulid(value)
337
0
    }
338
}
339
340
impl From<Ulid> for u128 {
341
0
    fn from(ulid: Ulid) -> u128 {
342
0
        ulid.0
343
0
    }
344
}
345
346
impl From<[u8; 16]> for Ulid {
347
0
    fn from(bytes: [u8; 16]) -> Self {
348
0
        Self(u128::from_be_bytes(bytes))
349
0
    }
350
}
351
352
impl From<Ulid> for [u8; 16] {
353
0
    fn from(ulid: Ulid) -> Self {
354
0
        ulid.0.to_be_bytes()
355
0
    }
356
}
357
358
impl FromStr for Ulid {
359
    type Err = DecodeError;
360
361
0
    fn from_str(s: &str) -> Result<Self, Self::Err> {
362
0
        Ulid::from_string(s)
363
0
    }
364
}
365
366
impl TryFrom<&'_ str> for Ulid {
367
    type Error = DecodeError;
368
369
0
    fn try_from(value: &'_ str) -> Result<Self, Self::Error> {
370
0
        Ulid::from_string(value)
371
0
    }
372
}
373
374
impl fmt::Display for Ulid {
375
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
376
0
        let mut buffer = [0; ULID_LEN];
377
0
        write!(f, "{}", self.array_to_str(&mut buffer))
378
0
    }
379
}
380
381
#[cfg(all(test, feature = "std"))]
382
mod 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
}