Skip to main content

mr_ulid/
lib.rs

1//! # Robust and Hassle-Free ULIDs
2//!
3//! This crate provides an implementation of ULIDs (Universally Unique Lexicographically Sortable Identifiers)
4//! with an emphasis on correctness, resilience, and hassle-free usability, in that order.
5//! It enables the generation and manipulation of ULIDs that are guaranteed to be unique
6//! and strictly monotonically increasing in any circumstances.
7//!
8//! ## Generating ULIDs
9//!
10//! ULIDs are generated using the [`Ulid::new()`] method:
11//!
12//! ```
13//! use mr_ulid::Ulid;
14//!
15//! let u = Ulid::new();
16//! ```
17//!
18//! Each ULID generated is guaranteed to be unique and strictly monotonically increasing.
19//! The generation is thread-safe, maintaining all guarantees even when ULIDs are produced
20//! concurrently across multiple threads.
21//!
22//! ## Printing ULIDs and converting to Strings
23//!
24//! ULIDs implement the [`std::fmt::Display`] trait:
25//!
26//! ```
27//! use mr_ulid::Ulid;
28//!
29//! let u = Ulid::new();
30//!
31//! println!("Generated ULID: {u}");
32//!
33//! let s = u.to_string();
34//!
35//! ```
36//!
37//! ## Parsing ULIDs from Strings:
38//!
39//! ULIDs implements the [`std::str::FromStr`] trait and can be parsed with [`str::parse()`] method:
40//!
41//! ```
42//! # use std::error::Error;
43//! # fn main() -> Result<(), Box<dyn Error>> {
44//! use mr_ulid::Ulid;
45//!
46//! // Method A
47// cspell:disable-next-line
48//! let u1: Ulid = "01JB5C84ZBM8QVBE5QRZW6HY89".parse()?;
49//!
50//! // Method B
51// cspell:disable-next-line
52//! let u2 = "01JB5C84ZBM8QVBE5QRZW6HY89".parse::<Ulid>()?;
53//! # Ok(()) }
54//! ```
55//!
56//! ## Serializing and Deserializing using `Serde` (JSON)
57//!
58//! For serializing/deserializing the feature flag `serde` needs to be enabled:
59//!
60//! ```bash
61//! cargo add mr-ulid -F serde
62//! ```
63//!
64//! Once the `serde` feature is enabled, ULIDs implement the `Serialize` and `Deserialize` traits:
65//!
66//! ```
67//! # use std::error::Error;
68//! # fn main() -> Result<(), Box<dyn Error>> {
69//! # #[cfg(feature = "serde")]
70//! # {
71//! use mr_ulid::Ulid;
72//! # use serde_derive as serde;
73//! use serde::{Deserialize, Serialize};
74//!
75//! #[derive(Serialize, Deserialize, PartialEq, Debug)]
76//! struct Example {
77//!     id: Ulid,
78//!     data: String,
79//! }
80//!
81//! let e1 = Example {
82//!     id: Ulid::new(),
83//!     data: "Hello, World!".to_string(),
84//! };
85//!
86//! let s = serde_json::to_string(&e1)?;
87//!
88//! println!("JSON: {s}");
89//!
90//! let e2: Example = serde_json::from_str(&s)?;
91//!
92//! assert_eq!(e1, e2);
93//! # }
94//! # Ok(()) }
95//! ```
96//!
97//! ## Guarantees
98//!
99//! A notable feature of this crate is the guarantee that a sufficient number
100//! of ULIDs can be generated at any time without the random part overflowing
101//! and the guarantees of uniqueness and strict monotonicity is maintained
102//! under all circumstances.
103//!
104//! The 80-bit random component of a ULID is slightly reduced by 10<sup>10</sup> values,
105//! resulting in a negligible reduction in entropy of approximately 0.000000000001%.
106//! This ensures that at least 10<sup>10</sup> ULIDs can be generated per _millisecond_,
107//! equating to 10<sup>13</sup> ULIDs per _second_.
108//! Such capacity exceeds the capabilities of current systems by magnitudes.
109//!
110//! In the very unlikely event that a system could generate more than 10<sup>13</sup> ULIDs
111//! per second, any overflowing random part is projected into the next millisecond.
112//! There, the full range of 2<sup>80</sup> (ca. 10<sup>24</sup>) is available.
113//!
114//! ## ULID Types
115//!
116//! - [`Ulid`]: This is the preferred type for most use cases and represents a ULID that can never be zero.
117//! - [`ZeroableUlid`]: This alternative type allows for zero values ULIDs (e.g., `"00000000000000000000000000"`).
118//!
119//! In idiomatic Rust code, if an absent ULID is needed, it is best represented as [`Option<Ulid>`](Ulid).
120//! However, for use cases that may represent absent ULIDs with a zero ULID,
121//! the [`ZeroableUlid`] may be an easier choice.
122//!
123//! ## Feature Flags
124//!
125//! - **`rand`**: Utilizes the `rand` crate as the source for random numbers, enabled by default.
126//! - **`serde`**: Provides support for serialization and deserialization via `Serde`, optional.
127//!
128
129mod base32;
130mod error;
131mod generator;
132mod nonzero;
133#[cfg(feature = "serde")]
134mod serde;
135mod util;
136mod zeroable;
137
138use std::borrow::Cow;
139
140pub use error::Error;
141#[cfg(feature = "rand")]
142pub use generator::STANDARD_ENTROPY_SOURCE;
143pub use generator::{EntropySource, EntropySourceHandle, NO_ENTROPY_SOURCE, set_entropy_source};
144pub use nonzero::Ulid;
145pub use zeroable::ZeroableUlid;
146
147const RESERVED: u128 = 10_000_000_000;
148
149const RANDOM_BITS: u32 = 80;
150const RANDOM_MASK: u128 = (1 << RANDOM_BITS) - 1;
151const RANDOM_GEN_MAX: u128 = RANDOM_MASK - RESERVED;
152
153const TIMESTAMP_BITS: u32 = 48;
154const TIMESTAMP_MAX: u64 = (1 << TIMESTAMP_BITS) - 1;
155const TIMESTAMP_MASK: u128 = ((1 << TIMESTAMP_BITS) - 1) << RANDOM_BITS;
156
157/// Canonicalizes a ULID string by converting it to a standard format.
158///
159/// Takes a ULID string and returns the canonicalized version:
160/// Letters 'i', 'l', and 'o' are replaced by their corresponding digits '1' and `0`,
161/// and all characters are converted into uppercase.
162///
163/// If the input is already in canonical form, it returns a borrowed version of the input string
164/// without allocating a new `String`.
165///
166/// # Errors
167///
168/// The string must be a valid ULID. It must have the correct length (26) and contain only valid characters,
169/// and it is not to be overflowed. If not, an error is returned.
170///
171/// # Example
172///
173/// ```
174// cspell:disable-next-line
175/// let s = "olixjazthsfjzt7wd6j8ir92vn";
176///
177// cspell:disable-next-line
178/// assert_eq!(mr_ulid::canonicalize(s), Ok("011XJAZTHSFJZT7WD6J81R92VN".into()));
179/// ```
180///
181pub fn canonicalize(ulid: &str) -> Result<Cow<'_, str>, Error> {
182    let mut buffer = *util::as_array(ulid.as_bytes())?;
183    let cleaned = base32::canonicalize(&mut buffer)?;
184
185    if cleaned == ulid {
186        Ok(ulid.into())
187    } else {
188        Ok(cleaned.to_string().into())
189    }
190}
191/// Checks a ULID string for validity.
192///
193/// To be valid, a ULID must have the correct length (26) and contain only valid characters,
194/// and not overflowed.
195///
196/// It is not checked, if the ULID has a zero value or if the ULID is in its canonical form.
197///
198/// # Errors
199///
200/// If the ULID string is not valid, an appropriate error is returned.
201///
202/// # Example
203///
204/// ```
205// cspell:disable-next-line
206/// assert!(mr_ulid::validate("olixjazthsfjzt7wd6j8ir92vn").is_ok());
207// cspell:disable-next-line
208/// assert!(mr_ulid::validate("011XJAZTHSFJZT7WD6J81R92VN").is_ok());
209///
210/// assert!(mr_ulid::validate("00000000000000000000000000").is_ok());
211/// assert!(mr_ulid::validate("7FFFFFFFFFFFFFFFFFFFFFFFFF").is_ok());
212/// assert_eq!(mr_ulid::validate("80000000000000000000000000"), Err(mr_ulid::Error::InvalidChar));
213///
214/// assert_eq!(mr_ulid::validate("0000000000000000000000u89$"), Err(mr_ulid::Error::InvalidChar));
215/// assert_eq!(mr_ulid::validate("xxxxxxxxxxxxxxxxxxxxxx"), Err(mr_ulid::Error::TooShort));
216/// assert_eq!(mr_ulid::validate("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"), Err(mr_ulid::Error::TooLong));
217/// ```
218pub fn validate(ulid: &str) -> Result<(), Error> {
219    let buffer = util::as_array(ulid.as_bytes())?;
220    base32::validate(buffer)
221}
222
223#[cfg(test)]
224mod tests;