use crate::{Error, Result};
use core::cmp::Ordering;
use core::fmt;
use core::str::FromStr;
use core::time::Duration;
use rand::Rng;
use crate::time::{SystemTime, UNIX_EPOCH};
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(
feature = "rkyv",
derive(::rkyv::Archive, ::rkyv::Serialize, ::rkyv::Deserialize)
)]
#[repr(transparent)]
pub struct Nulid(u128);
impl Nulid {
pub const TIMESTAMP_BITS: u32 = 68;
pub const RANDOM_BITS: u32 = 60;
const TIMESTAMP_SHIFT: u32 = Self::RANDOM_BITS;
const RANDOM_MASK: u128 = (1u128 << Self::RANDOM_BITS) - 1;
const TIMESTAMP_MASK: u128 = (1u128 << Self::TIMESTAMP_BITS) - 1;
pub const MIN: Self = Self(0);
pub const MAX: Self = Self(u128::MAX);
pub const ZERO: Self = Self::MIN;
#[must_use]
pub const fn nil() -> Self {
Self::ZERO
}
#[must_use]
pub const fn is_nil(self) -> bool {
self.0 == 0
}
#[must_use]
pub const fn min() -> Self {
Self::MIN
}
#[must_use]
pub const fn max() -> Self {
Self::MAX
}
pub fn new() -> Result<Self> {
Self::now()
}
pub fn now() -> Result<Self> {
let timestamp_nanos = crate::time::now_nanos()?;
let random = rand::rng().random::<u64>() & ((1u64 << Self::RANDOM_BITS) - 1);
Ok(Self::from_nanos(timestamp_nanos, random))
}
pub fn from_datetime(time: SystemTime) -> Result<Self> {
let duration = time
.duration_since(UNIX_EPOCH)
.map_err(|_| Error::SystemTimeError)?;
let timestamp_nanos =
u128::from(duration.as_secs()) * 1_000_000_000 + u128::from(duration.subsec_nanos());
let random = rand::rng().random::<u64>() & ((1u64 << Self::RANDOM_BITS) - 1);
Ok(Self::from_nanos(timestamp_nanos, random))
}
#[must_use]
pub const fn from_nanos(timestamp_nanos: u128, random: u64) -> Self {
let ts = timestamp_nanos & Self::TIMESTAMP_MASK;
let rand = (random as u128) & Self::RANDOM_MASK;
let value = (ts << Self::TIMESTAMP_SHIFT) | rand;
Self(value)
}
#[must_use]
pub const fn from_u128(value: u128) -> Self {
Self(value)
}
#[must_use]
pub const fn from_bytes(bytes: [u8; 16]) -> Self {
Self(u128::from_be_bytes(bytes))
}
#[must_use]
pub const fn nanos(self) -> u128 {
self.0 >> Self::TIMESTAMP_SHIFT
}
#[must_use]
pub const fn micros(self) -> u128 {
self.nanos() / 1_000
}
#[must_use]
pub const fn millis(self) -> u128 {
self.nanos() / 1_000_000
}
#[must_use]
pub const fn random(self) -> u64 {
(self.0 & Self::RANDOM_MASK) as u64
}
#[must_use]
pub const fn parts(self) -> (u128, u64) {
(self.nanos(), self.random())
}
#[must_use]
#[allow(clippy::cast_possible_truncation)]
pub const fn seconds(self) -> u64 {
let seconds = self.nanos() / 1_000_000_000;
debug_assert!(seconds <= u64::MAX as u128);
seconds as u64
}
#[must_use]
#[allow(clippy::cast_possible_truncation)]
pub const fn subsec_nanos(self) -> u32 {
let subsec = self.nanos() % 1_000_000_000;
debug_assert!(subsec < 1_000_000_000);
subsec as u32
}
#[must_use]
pub const fn as_u128(self) -> u128 {
self.0
}
#[must_use]
pub const fn to_bytes(self) -> [u8; 16] {
self.0.to_be_bytes()
}
#[must_use]
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::missing_const_for_fn)]
pub fn datetime(self) -> SystemTime {
let nanos = self.nanos();
let secs = (nanos / 1_000_000_000) as u64;
let subsec_nanos = (nanos % 1_000_000_000) as u32;
UNIX_EPOCH + Duration::new(secs, subsec_nanos)
}
#[must_use]
#[allow(clippy::cast_possible_truncation)]
pub const fn duration_since_epoch(self) -> Duration {
let nanos = self.nanos();
let secs = (nanos / 1_000_000_000) as u64;
let subsec_nanos = (nanos % 1_000_000_000) as u32;
Duration::new(secs, subsec_nanos)
}
#[must_use]
pub const fn increment(self) -> Option<Self> {
match self.0.checked_add(1) {
Some(value) => Some(Self(value)),
None => None,
}
}
pub fn encode(self, buf: &mut [u8; 26]) -> Result<&str> {
crate::base32::encode_u128(self.0, buf)
}
}
impl fmt::Debug for Nulid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut buf = [0u8; 26];
let s = self.encode(&mut buf).map_err(|_| fmt::Error)?;
f.debug_tuple("Nulid").field(&s).finish()
}
}
impl fmt::Display for Nulid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut buf = [0u8; 26];
let s = self.encode(&mut buf).map_err(|_| fmt::Error)?;
f.write_str(s)
}
}
impl FromStr for Nulid {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
let value = crate::base32::decode_u128(s)?;
Ok(Self::from_u128(value))
}
}
impl Ord for Nulid {
fn cmp(&self, other: &Self) -> Ordering {
self.0.cmp(&other.0)
}
}
impl PartialOrd for Nulid {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Default for Nulid {
fn default() -> Self {
Self::ZERO
}
}
impl From<u128> for Nulid {
fn from(value: u128) -> Self {
Self::from_u128(value)
}
}
impl From<Nulid> for u128 {
fn from(nulid: Nulid) -> Self {
nulid.as_u128()
}
}
impl From<[u8; 16]> for Nulid {
fn from(bytes: [u8; 16]) -> Self {
Self::from_bytes(bytes)
}
}
impl From<Nulid> for [u8; 16] {
fn from(nulid: Nulid) -> Self {
nulid.to_bytes()
}
}
impl AsRef<u128> for Nulid {
fn as_ref(&self) -> &u128 {
&self.0
}
}
impl TryFrom<&[u8]> for Nulid {
type Error = Error;
fn try_from(bytes: &[u8]) -> Result<Self> {
if bytes.len() != 16 {
return Err(Error::InvalidLength {
expected: 16,
found: bytes.len(),
});
}
let mut arr = [0u8; 16];
arr.copy_from_slice(bytes);
Ok(Self::from_bytes(arr))
}
}
impl TryFrom<Vec<u8>> for Nulid {
type Error = Error;
fn try_from(bytes: Vec<u8>) -> Result<Self> {
bytes.as_slice().try_into()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_nil() {
let nil = Nulid::nil();
assert!(nil.is_nil());
assert_eq!(nil.nanos(), 0);
assert_eq!(nil.random(), 0);
assert_eq!(nil.as_u128(), 0);
}
#[test]
fn test_from_nanos() {
let timestamp = 1_234_567_890_123_456_789u128;
let random = 987_654_321u64;
let id = Nulid::from_nanos(timestamp, random);
assert_eq!(id.nanos(), timestamp);
assert_eq!(id.random(), random);
}
#[test]
fn test_nanos() {
let timestamp = 1_234_567_890_123_456_789u128;
let id = Nulid::from_nanos(timestamp, 0);
assert_eq!(id.nanos(), timestamp);
}
#[test]
fn test_micros() {
let nanos = 1_234_567_890_000u128; let id = Nulid::from_nanos(nanos, 0);
assert_eq!(id.micros(), 1_234_567_890);
let nanos_with_remainder = 1_234_567_890_999u128;
let id2 = Nulid::from_nanos(nanos_with_remainder, 0);
assert_eq!(id2.micros(), 1_234_567_890);
let id_zero = Nulid::from_nanos(0, 0);
assert_eq!(id_zero.micros(), 0);
}
#[test]
fn test_millis() {
let nanos = 1_234_567_000_000u128; let id = Nulid::from_nanos(nanos, 0);
assert_eq!(id.millis(), 1_234_567);
let nanos_with_remainder = 1_234_567_999_999u128;
let id2 = Nulid::from_nanos(nanos_with_remainder, 0);
assert_eq!(id2.millis(), 1_234_567);
let id_zero = Nulid::from_nanos(0, 0);
assert_eq!(id_zero.millis(), 0);
}
#[test]
fn test_timestamp_conversions() {
let nanos = 1_234_567_890_123_456_789u128;
let id = Nulid::from_nanos(nanos, 0);
let micros_from_nanos = id.nanos() / 1_000;
let millis_from_nanos = id.nanos() / 1_000_000;
assert_eq!(id.micros(), micros_from_nanos);
assert_eq!(id.millis(), millis_from_nanos);
}
#[test]
fn test_parts() {
let timestamp = 5_000_000_000u128;
let random = 12345u64;
let id = Nulid::from_nanos(timestamp, random);
let (ts, rand) = id.parts();
assert_eq!(ts, timestamp);
assert_eq!(rand, random);
}
#[test]
fn test_seconds_and_subsec_nanos() {
let timestamp = 1_234_567_890_123_456_789u128;
let id = Nulid::from_nanos(timestamp, 0);
assert_eq!(id.seconds(), 1_234_567_890);
assert_eq!(id.subsec_nanos(), 123_456_789);
}
#[test]
fn test_seconds_maximum_timestamp() {
let max_68bit = (1u128 << 68) - 1; let id = Nulid::from_nanos(max_68bit, 0);
let seconds = id.seconds();
assert_eq!(seconds, 295_147_905_179);
assert!(seconds < u64::MAX);
let subsec = id.subsec_nanos();
assert_eq!(subsec, 352_825_855);
assert_eq!(id.nanos(), max_68bit);
}
#[test]
fn test_subsec_nanos_invariants() {
let test_cases = [
0u128,
999_999_999, 1_000_000_000, 1_000_000_001, 1_234_567_890_123_456_789, 999_999_999_999_999_999, (1u128 << 68) - 1, ];
for timestamp in test_cases {
let id = Nulid::from_nanos(timestamp, 0);
let subsec = id.subsec_nanos();
assert!(
subsec < 1_000_000_000,
"subsec_nanos() returned {subsec}, which is >= 1 billion for timestamp {timestamp}"
);
let expected = (timestamp % 1_000_000_000) as u32;
assert_eq!(
subsec, expected,
"subsec_nanos() mismatch for timestamp {timestamp}"
);
}
}
#[test]
fn test_seconds_and_subsec_nanos_reconstruction() {
let test_timestamps = [
1_234_567_890_123_456_789u128,
5_000_000_000_000_000_000,
999_999_999,
1_000_000_000_000_000_000,
];
for original_ts in test_timestamps {
let id = Nulid::from_nanos(original_ts, 0);
let seconds = id.seconds();
let subsec = id.subsec_nanos();
let reconstructed = u128::from(seconds) * 1_000_000_000 + u128::from(subsec);
assert_eq!(
reconstructed, original_ts,
"Failed to reconstruct timestamp {original_ts} from seconds {seconds} and subsec_nanos {subsec}"
);
}
}
#[test]
fn test_from_to_bytes() {
let id = Nulid::from_u128(0x0123_4567_89AB_CDEF_FEDC_BA98_7654_3210);
let bytes = id.to_bytes();
let id2 = Nulid::from_bytes(bytes);
assert_eq!(id, id2);
}
#[test]
fn test_ordering() {
let id1 = Nulid::from_u128(100);
let id2 = Nulid::from_u128(200);
let id3 = Nulid::from_u128(200);
assert!(id1 < id2);
assert!(id2 > id1);
assert_eq!(id2, id3);
}
#[test]
fn test_increment() {
let id = Nulid::from_u128(100);
let next = id.increment().unwrap();
assert_eq!(next.as_u128(), 101);
let max = Nulid::MAX;
assert!(max.increment().is_none());
}
#[test]
fn test_timestamp_ordering() {
let id1 = Nulid::from_nanos(1000, 500);
let id2 = Nulid::from_nanos(2000, 100);
assert!(id1 < id2);
}
#[test]
fn test_random_ordering_same_timestamp() {
let id1 = Nulid::from_nanos(1000, 100);
let id2 = Nulid::from_nanos(1000, 200);
assert!(id1 < id2);
}
#[test]
fn test_constants() {
assert_eq!(Nulid::MIN.as_u128(), 0);
assert_eq!(Nulid::MAX.as_u128(), u128::MAX);
assert_eq!(Nulid::ZERO.as_u128(), 0);
}
#[test]
fn test_bit_masks() {
assert_eq!(Nulid::TIMESTAMP_MASK, (1u128 << 68) - 1);
assert_eq!(Nulid::RANDOM_MASK, (1u128 << 60) - 1);
}
#[test]
fn test_masking() {
let large_ts = u128::MAX;
let large_rand = u64::MAX;
let id = Nulid::from_nanos(large_ts, large_rand);
assert!(id.nanos() <= Nulid::TIMESTAMP_MASK);
assert!(id.random() < (1u64 << Nulid::RANDOM_BITS));
}
#[test]
fn test_from_u128() {
let value = 0x0123_4567_89AB_CDEF_FEDC_BA98_7654_3210u128;
let id: Nulid = value.into();
assert_eq!(id.as_u128(), value);
}
#[test]
fn test_into_u128() {
let id = Nulid::from_u128(0x0123_4567_89AB_CDEF_FEDC_BA98_7654_3210);
let value: u128 = id.into();
assert_eq!(value, 0x0123_4567_89AB_CDEF_FEDC_BA98_7654_3210);
}
#[test]
fn test_from_bytes_trait() {
let bytes = [
0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54,
0x32, 0x10,
];
let id: Nulid = bytes.into();
assert_eq!(id.to_bytes(), bytes);
}
#[test]
fn test_into_bytes() {
let id = Nulid::from_u128(0x0123_4567_89AB_CDEF_FEDC_BA98_7654_3210);
let bytes: [u8; 16] = id.into();
assert_eq!(bytes, id.to_bytes());
}
#[test]
fn test_as_ref_u128() {
let id = Nulid::from_u128(12345);
let value_ref: &u128 = id.as_ref();
assert_eq!(*value_ref, 12345);
}
#[test]
fn test_try_from_slice_valid() {
let bytes = [
0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54,
0x32, 0x10,
];
let slice: &[u8] = &bytes;
let id = Nulid::try_from(slice).unwrap();
assert_eq!(id.to_bytes(), bytes);
}
#[test]
fn test_try_from_slice_invalid_length() {
let bytes = [0u8; 15]; let slice: &[u8] = &bytes;
let result = Nulid::try_from(slice);
assert!(result.is_err());
match result {
Err(Error::InvalidLength { expected, found }) => {
assert_eq!(expected, 16);
assert_eq!(found, 15);
}
_ => panic!("Expected InvalidLength error"),
}
}
#[test]
fn test_try_from_slice_too_long() {
let bytes = [0u8; 20]; let slice: &[u8] = &bytes;
let result = Nulid::try_from(slice);
assert!(result.is_err());
match result {
Err(Error::InvalidLength { expected, found }) => {
assert_eq!(expected, 16);
assert_eq!(found, 20);
}
_ => panic!("Expected InvalidLength error"),
}
}
#[test]
fn test_try_from_empty_slice() {
let bytes: &[u8] = &[];
let result = Nulid::try_from(bytes);
assert!(result.is_err());
match result {
Err(Error::InvalidLength { expected, found }) => {
assert_eq!(expected, 16);
assert_eq!(found, 0);
}
_ => panic!("Expected InvalidLength error"),
}
}
}