julid-rs/src/julid.rs

281 lines
7.9 KiB
Rust

use core::{fmt, str::FromStr};
use std::{
sync::atomic::{AtomicU64, Ordering},
time::Duration,
};
use rand::{random, thread_rng, Rng};
use crate::base32::{self, DecodeError};
/// This is used to ensure monotonicity for new IDs.
static LAST_SORTABLE: AtomicU64 = AtomicU64::new(0);
/// The number of bits in a Julid's time portion
pub const TIME_BITS: u8 = 48;
/// The number of bits in the monotonic counter for intra-millisecond IDs
pub const COUNTER_BITS: u8 = 16;
/// The number of random bits + bits in the monotonic counter
pub const UNIQUE_BITS: u8 = 80;
pub const RANDOM_BITS: u8 = 64;
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 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 https://github.com/ahawker/ulid/issues/306#issuecomment-451850395
pub fn new() -> Self {
let lsb: u64 = random();
loop {
let ts = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or(Duration::ZERO)
.as_millis() as u64;
let last = LAST_SORTABLE.load(Ordering::SeqCst);
let ots = last >> COUNTER_BITS;
if ots < ts {
let msb = ts << COUNTER_BITS;
if LAST_SORTABLE
.compare_exchange(last, msb, Ordering::SeqCst, Ordering::Relaxed)
.is_ok()
{
break (msb, lsb).into();
}
} else {
let counter = ((last & bitmask!(COUNTER_BITS) as u64) as u16).saturating_add(1);
let msb = (ots << COUNTER_BITS) + counter as u64;
if LAST_SORTABLE
.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));
}
}
/// Creates a Julid from a Crockford Base32 encoded string
///
/// An DecodeError will be returned when the given string is not formated
/// properly.
///
/// # Example
/// ```rust
/// use julid::julid::Julid;
/// let text = "01D39ZY06FGSCTVN4T2V9PKHFZ";
/// let result = Julid::from_string(text);
///
/// assert!(result.is_ok());
/// assert_eq!(&result.unwrap().to_string(), text);
/// ```
pub const fn from_string(encoded: &str) -> Result<Julid, DecodeError> {
match base32::decode(encoded) {
Ok(int_val) => Ok(Julid(int_val)),
Err(err) => Err(err),
}
}
/// 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 ulid
pub const fn timestamp(&self) -> u64 {
(self.0 >> UNIQUE_BITS) as u64
}
pub const fn counter(&self) -> u16 {
let mask = bitmask!(COUNTER_BITS);
((self.0 >> RANDOM_BITS) & mask) as u16
}
pub const fn sortable(&self) -> u64 {
let mask = bitmask!(TIME_BITS + COUNTER_BITS);
((self.0 >> RANDOM_BITS) & mask) as u64
}
pub const fn random(&self) -> u128 {
self.0 & bitmask!(RANDOM_BITS)
}
/// Gets the non-timestamp section of this Julid (random + counter bits).
pub const fn unique(&self) -> u128 {
self.0 & bitmask!(UNIQUE_BITS)
}
/// Creates a Crockford Base32 encoded string that represents this Julid
///
/// # Example
/// ```rust
/// use julid::julid::Julid;
/// let text = "01D39ZY06FGSCTVN4T2V9PKHFZ";
/// let id = Julid::from_string(text).unwrap();
///
/// assert_eq!(&id.to_string(), text);
/// ```
pub fn as_string(self) -> String {
base32::encode(self.0)
}
/// Test if the Julid is Alpha
pub const fn is_alpha(&self) -> bool {
self.0 == 0u128
}
/// Creates a Julid using the provided bytes array, assumed big-endian.
pub const fn from_bytes(bytes: [u8; 16]) -> Julid {
Self(u128::from_be_bytes(bytes))
}
/// Returns the bytes of the Julid in big-endian order.
pub const fn to_bytes(self) -> [u8; 16] {
self.0.to_be_bytes()
}
}
impl Default for Julid {
fn default() -> Self {
Julid::alpha()
}
}
impl From<Julid> 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<Julid> for (u64, u64) {
fn from(ulid: Julid) -> (u64, u64) {
((ulid.0 >> 64) as u64, (ulid.0 & bitmask!(64)) as u64)
}
}
impl From<u128> for Julid {
fn from(value: u128) -> Julid {
Julid(value)
}
}
impl From<Julid> 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<Julid> 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<Self, Self::Err> {
Julid::from_string(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_string(&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);
}
}