julid-rs 1.6.1

A library and loadable extension for SQLite that uses it, that provides Joe's ULIDs.
Documentation
use core::{fmt, str::FromStr};
use std::{
    sync::atomic::{AtomicU64, Ordering},
    time::{Duration, SystemTime},
};

use rand::{random, thread_rng, Rng};

use crate::{base32, DecodeError};

/// This is used to ensure monotonicity for new IDs.
static LAST_MSB: AtomicU64 = AtomicU64::new(0);

/// The number of bits in a Julid's time portion
const TIME_BITS: u8 = 48;
/// The number of bits in the monotonic counter for intra-millisecond IDs
const COUNTER_BITS: u8 = 16;
/// The number of random bits + bits in the monotonic counter
const UNIQUE_BITS: u8 = 80;
/// The number of fully random bits
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(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 <https://github.com/ahawker/ulid/issues/306#issuecomment-451850395>
    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;
            if ots < ts {
                let msb = ts << COUNTER_BITS;
                if LAST_MSB
                    .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_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));
        }
    }

    /// 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
    }

    #[cfg(feature = "chrono")]
    /// Returns the timestamp as a `chrono::DateTime<chrono::Utc>` (feature
    /// `chrono` (default))
    pub fn created_at(&self) -> chrono::DateTime<chrono::Utc> {
        (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_string(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_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),
        }
    }

    /// 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]) -> Julid {
        Self(u128::from_be_bytes(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);
    }
}