#![warn(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]
#[doc = include_str!("../README.md")]
#[cfg(all(doctest, feature = "std"))]
struct ReadMeDoctest;
mod base32;
#[cfg(feature = "std")]
mod generator;
#[cfg(feature = "postgres")]
mod postgres;
#[cfg(feature = "rkyv")]
mod rkyv;
#[cfg(feature = "serde")]
pub mod serde;
#[cfg(feature = "std")]
mod time;
#[cfg(feature = "std")]
mod time_utils;
#[cfg(feature = "uuid")]
mod uuid;
use core::convert::TryFrom;
use core::fmt;
use core::str::FromStr;
pub use crate::base32::{DecodeError, EncodeError, ULID_LEN};
#[cfg(feature = "std")]
pub use crate::generator::{Generator, MonotonicError};
macro_rules! bitmask {
($len:expr) => {
((1 << $len) - 1)
};
}
pub(crate) use bitmask;
#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Hash, Clone, Copy)]
#[cfg_attr(
feature = "rkyv",
derive(::rkyv::Archive, ::rkyv::Serialize, ::rkyv::Deserialize)
)]
pub struct Ulid(pub u128);
impl Ulid {
pub const TIME_BITS: u8 = 48;
pub const RAND_BITS: u8 = 80;
pub const fn from_parts(timestamp_ms: u64, random: u128) -> Ulid {
let time_part = (timestamp_ms & bitmask!(Self::TIME_BITS)) as u128;
let rand_part = random & bitmask!(Self::RAND_BITS);
Ulid((time_part << Self::RAND_BITS) | rand_part)
}
pub const fn from_string(encoded: &str) -> Result<Ulid, DecodeError> {
match base32::decode(encoded) {
Ok(int_val) => Ok(Ulid(int_val)),
Err(err) => Err(err),
}
}
pub const fn nil() -> Ulid {
Ulid(0)
}
pub const fn timestamp_ms(&self) -> u64 {
(self.0 >> Self::RAND_BITS) as u64
}
pub const fn random(&self) -> u128 {
self.0 & bitmask!(Self::RAND_BITS)
}
#[deprecated(since = "1.2.0", note = "Use the infallible `array_to_str` instead.")]
pub fn to_str<'buf>(&self, buf: &'buf mut [u8]) -> Result<&'buf mut str, EncodeError> {
#[allow(deprecated)]
let len = base32::encode_to(self.0, buf)?;
Ok(unsafe { core::str::from_utf8_unchecked_mut(&mut buf[..len]) })
}
pub fn array_to_str<'buf>(&self, buf: &'buf mut [u8; ULID_LEN]) -> &'buf mut str {
base32::encode_to_array(self.0, buf);
unsafe { core::str::from_utf8_unchecked_mut(buf) }
}
#[allow(clippy::inherent_to_string_shadow_display)] #[cfg(feature = "std")]
pub fn to_string(&self) -> String {
base32::encode(self.0)
}
pub const fn is_nil(&self) -> bool {
self.0 == 0u128
}
pub const fn increment(&self) -> Option<Ulid> {
const MAX_RANDOM: u128 = bitmask!(Ulid::RAND_BITS);
if (self.0 & MAX_RANDOM) == MAX_RANDOM {
None
} else {
Some(Ulid(self.0 + 1))
}
}
pub const fn from_bytes(bytes: [u8; 16]) -> Ulid {
Self(u128::from_be_bytes(bytes))
}
pub const fn to_bytes(&self) -> [u8; 16] {
self.0.to_be_bytes()
}
}
impl Default for Ulid {
fn default() -> Self {
Ulid::nil()
}
}
#[cfg(feature = "std")]
impl From<Ulid> for String {
fn from(ulid: Ulid) -> String {
ulid.to_string()
}
}
impl From<(u64, u64)> for Ulid {
fn from((msb, lsb): (u64, u64)) -> Self {
Ulid(u128::from(msb) << 64 | u128::from(lsb))
}
}
impl From<Ulid> for (u64, u64) {
fn from(ulid: Ulid) -> (u64, u64) {
((ulid.0 >> 64) as u64, (ulid.0 & bitmask!(64)) as u64)
}
}
impl From<u128> for Ulid {
fn from(value: u128) -> Ulid {
Ulid(value)
}
}
impl From<Ulid> for u128 {
fn from(ulid: Ulid) -> u128 {
ulid.0
}
}
impl From<[u8; 16]> for Ulid {
fn from(bytes: [u8; 16]) -> Self {
Self(u128::from_be_bytes(bytes))
}
}
impl From<Ulid> for [u8; 16] {
fn from(ulid: Ulid) -> Self {
ulid.0.to_be_bytes()
}
}
impl FromStr for Ulid {
type Err = DecodeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ulid::from_string(s)
}
}
impl TryFrom<&'_ str> for Ulid {
type Error = DecodeError;
fn try_from(value: &'_ str) -> Result<Self, Self::Error> {
Ulid::from_string(value)
}
}
impl fmt::Display for Ulid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
let mut buffer = [0; ULID_LEN];
write!(f, "{}", self.array_to_str(&mut buffer))
}
}
#[cfg(all(test, feature = "std"))]
mod tests {
use super::*;
#[test]
fn test_static() {
let s = Ulid(0x41414141414141414141414141414141).to_string();
let u = Ulid::from_string(&s).unwrap();
assert_eq!(&s, "21850M2GA1850M2GA1850M2GA1");
assert_eq!(u.0, 0x41414141414141414141414141414141);
}
#[test]
fn test_increment() {
let ulid = Ulid::from_string("01BX5ZZKBKAZZZZZZZZZZZZZZZ").unwrap();
let ulid = ulid.increment().unwrap();
assert_eq!("01BX5ZZKBKB000000000000000", ulid.to_string());
let ulid = Ulid::from_string("01BX5ZZKBKZZZZZZZZZZZZZZZX").unwrap();
let ulid = ulid.increment().unwrap();
assert_eq!("01BX5ZZKBKZZZZZZZZZZZZZZZY", ulid.to_string());
let ulid = ulid.increment().unwrap();
assert_eq!("01BX5ZZKBKZZZZZZZZZZZZZZZZ", ulid.to_string());
assert!(ulid.increment().is_none());
}
#[test]
fn test_increment_overflow() {
let ulid = Ulid(u128::max_value());
assert!(ulid.increment().is_none());
}
#[test]
fn can_into_thing() {
let ulid = Ulid::from_str("01FKMG6GAG0PJANMWFN84TNXCD").unwrap();
let s: String = ulid.into();
let u: u128 = ulid.into();
let uu: (u64, u64) = ulid.into();
let bytes: [u8; 16] = ulid.into();
assert_eq!(Ulid::from_str(&s).unwrap(), ulid);
assert_eq!(Ulid::from(u), ulid);
assert_eq!(Ulid::from(uu), ulid);
assert_eq!(Ulid::from(bytes), ulid);
}
#[test]
fn default_is_nil() {
assert_eq!(Ulid::default(), Ulid::nil());
}
#[test]
fn can_display_things() {
println!("{}", Ulid::nil());
println!("{}", EncodeError::BufferTooSmall);
println!("{}", DecodeError::InvalidLength);
println!("{}", DecodeError::InvalidChar);
}
}