use core::{fmt, str::FromStr}; use std::{ sync::atomic::{AtomicU64, Ordering}, time::{Duration, SystemTime}, }; use rand::{random, thread_rng, Rng}; use crate::{base32, DecodeError, COUNTER_BITS, RANDOM_BITS, TIME_BITS, UNIQUE_BITS}; /// This is used to ensure monotonicity for new IDs. static LAST_MSB: AtomicU64 = AtomicU64::new(0); macro_rules! bitmask { ($len:expr) => { ((1 << $len) - 1) }; } /// A Julid is a unique 128-bit lexicographically sortable identifier, /// compatible with ULIDs. /// /// Canonically, it is represented as a 26 character Crockford Base32 encoded /// string, or as a sequence of 16 bytes in big-endian order. /// /// Of the 128-bits, the 48 most-significant are a unix timestamp in /// milliseconds. The next 16 bits are a monotonic counter for IDs created in /// the same millisecond. The remaining 64 least-significant bits are fully /// random. #[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Hash, Clone, Copy)] pub struct Julid(pub(crate) u128); impl Julid { /// Return a new Julid. If a previous ID was generated in the same /// millisecond, increment the monotonic counter, up to u16::MAX. The random /// bits are always fresh, so once the monotonic counter is saturated, /// subsequent IDs from the current millisecond will not have an /// inherent ordering. See discussion at pub fn new() -> Self { let lsb: u64 = random(); loop { let ts = SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap_or(Duration::ZERO) .as_millis() as u64; let last = LAST_MSB.load(Ordering::SeqCst); let ots = last >> COUNTER_BITS; let msb = if ots < ts { ts << COUNTER_BITS } else { let counter = ((last & bitmask!(COUNTER_BITS) as u64) as u16).saturating_add(1); (ots << COUNTER_BITS) + counter as u64 }; if LAST_MSB .compare_exchange(last, msb, Ordering::SeqCst, Ordering::Relaxed) .is_ok() { break (msb, lsb).into(); } // we didn't update the global counter, try again let micros = thread_rng().gen_range(10..50); std::thread::sleep(Duration::from_micros(micros)); } } /// Return a new Julid with the given timestamp (in milliseconds), no /// counter bits set, and 64 random lower bits. pub fn at(ts_ms: u64) -> Self { let hi = ts_ms << COUNTER_BITS; let lo = random(); (hi, lo).into() } /// The 'Alpha Julid'. /// /// The Alpha Julid is special form of Julid that is specified to have /// all 128 bits set to zero. pub const fn alpha() -> Julid { Julid(0) } /// The 'Omega Julid'. /// /// The Omega Julid is special form of Julid that is specified to have /// all 128 bits set to one. pub const fn omega() -> Self { Julid(u128::MAX) } /// Gets the timestamp section of this julid pub const fn timestamp(&self) -> u64 { (self.0 >> UNIQUE_BITS) as u64 } /// Gets the value of this julid's monotonic counter pub const fn counter(&self) -> u16 { let mask = bitmask!(COUNTER_BITS); ((self.0 >> RANDOM_BITS) & mask) as u16 } /// Gets the 64-bit concatenation of the timestamp and counter pub const fn sortable(&self) -> u64 { let mask = bitmask!(TIME_BITS + COUNTER_BITS); ((self.0 >> RANDOM_BITS) & mask) as u64 } /// Gets the 64-bit random value pub const fn random(&self) -> u64 { (self.0 & bitmask!(RANDOM_BITS)) as u64 } /// Gets the non-timestamp section of this Julid (random + counter bits). pub const fn unique(&self) -> u64 { (self.0 & bitmask!(UNIQUE_BITS)) as u64 } /// Returns the timestamp as a `chrono::DateTime` (feature /// `chrono` (default)) pub fn created_at(&self) -> chrono::DateTime { (SystemTime::UNIX_EPOCH + Duration::from_millis(self.timestamp())).into() } /// Test if the Julid is Alpha pub const fn is_alpha(&self) -> bool { self.0 == 0u128 } /// Test if the Julid is Omega pub const fn is_omega(&self) -> bool { self.0 == u128::MAX } /// Creates a Crockford Base32 encoded string that represents this Julid /// /// # Example /// ```rust /// use julid::julid::Julid; /// let text = "01D39ZY06FGSCTVN4T2V9PKHFZ"; /// let id = Julid::from_str(text).unwrap(); /// /// assert_eq!(&id.to_string(), text); /// ``` pub fn as_string(self) -> String { base32::encode(self.0) } /// Creates a Julid from a Crockford Base32 encoded string /// /// A [`DecodeError`] will be returned if the given string is not formated /// properly. /// /// # Example /// ```rust /// use julid::julid::Julid; /// let text = "01D39ZY06FGSCTVN4T2V9PKHFZ"; /// let result = Julid::from_str(text); /// /// assert!(result.is_ok()); /// assert_eq!(&result.unwrap().to_string(), text); /// ``` pub const fn from_str(encoded: &str) -> Result { match base32::decode(encoded) { Ok(int_val) => Ok(Julid(int_val)), Err(err) => Err(err), } } /// Returns the bytes of the Julid in big-endian order. pub const fn as_bytes(self) -> [u8; 16] { self.0.to_be_bytes() } /// Creates a Julid using the provided bytes array, assumed big-endian. pub const fn from_bytes(bytes: [u8; 16]) -> Self { Self(u128::from_be_bytes(bytes)) } } impl Default for Julid { fn default() -> Self { Julid::alpha() } } impl From for String { fn from(ulid: Julid) -> String { ulid.as_string() } } impl From<(u64, u64)> for Julid { fn from((msb, lsb): (u64, u64)) -> Self { Julid(u128::from(msb) << 64 | u128::from(lsb)) } } impl From for (u64, u64) { fn from(ulid: Julid) -> (u64, u64) { ((ulid.0 >> 64) as u64, (ulid.0 & bitmask!(64)) as u64) } } impl From for Julid { fn from(value: u128) -> Julid { Julid(value) } } impl From for u128 { fn from(ulid: Julid) -> u128 { ulid.0 } } impl From<[u8; 16]> for Julid { fn from(bytes: [u8; 16]) -> Self { Self(u128::from_be_bytes(bytes)) } } impl From for [u8; 16] { fn from(ulid: Julid) -> Self { ulid.0.to_be_bytes() } } impl FromStr for Julid { type Err = DecodeError; fn from_str(s: &str) -> Result { Julid::from_str(s) } } impl fmt::Display for Julid { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { write!(f, "{}", self.as_string()) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_static() { let s = Julid(0x41414141414141414141414141414141).as_string(); let u = Julid::from_str(&s).unwrap(); assert_eq!(&s, "21850M2GA1850M2GA1850M2GA1"); assert_eq!(u.0, 0x41414141414141414141414141414141); } #[test] fn can_into_thing() { let ulid = Julid::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!(Julid::from_str(&s).unwrap(), ulid); assert_eq!(Julid::from(u), ulid); assert_eq!(Julid::from(uu), ulid); assert_eq!(Julid::from(bytes), ulid); } #[test] fn default_is_nil() { assert_eq!(Julid::default(), Julid::alpha()); } #[test] fn can_display_things() { println!("{}", Julid::alpha()); println!("{}", DecodeError::InvalidLength(0)); println!("{}", DecodeError::InvalidChar('^')); } #[test] fn can_increment() { let mut max = 0; for i in 0..100 { let id = Julid::new(); max = id.counter().max(max); assert!(max <= i); } assert!(max > 49); } }