use core::cmp::Ordering;
use core::fmt;
use core::hash::{Hash, Hasher};
use core::ops::Deref;
use core::str::FromStr;
use nexus_ascii::AsciiString;
use crate::parse::{self, DecodeError, ParseError, UuidParseError};
#[derive(Clone, Copy, PartialEq, Eq)]
#[repr(transparent)]
pub struct Uuid(pub(crate) AsciiString<40>);
impl Uuid {
#[inline]
pub fn from_raw(hi: u64, lo: u64) -> Self {
Self(crate::encode::uuid_dashed(hi, lo))
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, ParseError> {
if bytes.len() != 16 {
return Err(ParseError::InvalidLength {
expected: 16,
got: bytes.len(),
});
}
let hi = u64::from_be_bytes(bytes[0..8].try_into().unwrap());
let lo = u64::from_be_bytes(bytes[8..16].try_into().unwrap());
Ok(Self::from_raw(hi, lo))
}
#[inline]
pub unsafe fn from_bytes_unchecked(bytes: &[u8]) -> Self {
debug_assert!(bytes.len() >= 16);
unsafe {
let hi = u64::from_be_bytes(bytes.get_unchecked(0..8).try_into().unwrap_unchecked());
let lo = u64::from_be_bytes(bytes.get_unchecked(8..16).try_into().unwrap_unchecked());
Self::from_raw(hi, lo)
}
}
#[inline]
pub fn as_str(&self) -> &str {
self.0.as_str()
}
#[inline]
pub fn as_bytes(&self) -> &[u8] {
self.0.as_bytes()
}
pub fn decode(&self) -> (u64, u64) {
let bytes = self.0.as_bytes();
let mut hi: u64 = 0;
let mut lo: u64 = 0;
for &b in &bytes[0..8] {
hi = (hi << 4) | hex_digit(b) as u64;
}
for &b in &bytes[9..13] {
hi = (hi << 4) | hex_digit(b) as u64;
}
for &b in &bytes[14..18] {
hi = (hi << 4) | hex_digit(b) as u64;
}
for &b in &bytes[19..23] {
lo = (lo << 4) | hex_digit(b) as u64;
}
for &b in &bytes[24..36] {
lo = (lo << 4) | hex_digit(b) as u64;
}
(hi, lo)
}
#[inline]
pub fn version(&self) -> u8 {
hex_digit(self.0.as_bytes()[14])
}
pub fn parse(s: &str) -> Result<Self, UuidParseError> {
let bytes = s.as_bytes();
if bytes.len() != 36 {
return Err(UuidParseError::InvalidLength {
expected: 36,
got: bytes.len(),
});
}
if bytes[8] != b'-' || bytes[13] != b'-' || bytes[18] != b'-' || bytes[23] != b'-' {
return Err(UuidParseError::InvalidFormat);
}
let input: &[u8; 36] = unsafe { &*(bytes.as_ptr().cast::<[u8; 36]>()) };
let (hi, lo) = crate::simd::uuid_parse_dashed(input).map_err(|pos| {
let input_pos = match pos {
0..=7 => pos, 8..=11 => pos + 1, 12..=15 => pos + 2, 16..=19 => pos + 3, _ => pos + 4, };
UuidParseError::InvalidChar {
position: input_pos,
byte: bytes[input_pos],
}
})?;
Ok(Self::from_raw(hi, lo))
}
#[inline]
pub fn to_compact(&self) -> UuidCompact {
let (hi, lo) = self.decode();
UuidCompact::from_raw(hi, lo)
}
#[inline]
pub fn is_nil(&self) -> bool {
let (hi, lo) = self.decode();
hi == 0 && lo == 0
}
#[inline]
pub fn timestamp_ms(&self) -> Option<u64> {
if self.version() != 7 {
return None;
}
let (hi, _) = self.decode();
Some(hi >> 16)
}
pub fn to_bytes(&self) -> [u8; 16] {
let (hi, lo) = self.decode();
let mut out = [0u8; 16];
out[..8].copy_from_slice(&hi.to_be_bytes());
out[8..].copy_from_slice(&lo.to_be_bytes());
out
}
}
impl Deref for Uuid {
type Target = str;
#[inline]
fn deref(&self) -> &str {
self.0.as_str()
}
}
impl AsRef<str> for Uuid {
#[inline]
fn as_ref(&self) -> &str {
self.0.as_str()
}
}
impl fmt::Display for Uuid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.0.as_str())
}
}
impl fmt::Debug for Uuid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Uuid({})", self.0.as_str())
}
}
impl Hash for Uuid {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
impl Ord for Uuid {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
self.0.cmp(&other.0)
}
}
impl PartialOrd for Uuid {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl FromStr for Uuid {
type Err = UuidParseError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse(s)
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
#[repr(transparent)]
pub struct UuidCompact(pub(crate) AsciiString<32>);
impl UuidCompact {
#[inline]
pub fn from_raw(hi: u64, lo: u64) -> Self {
Self(crate::encode::hex_u128(hi, lo))
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, ParseError> {
if bytes.len() != 16 {
return Err(ParseError::InvalidLength {
expected: 16,
got: bytes.len(),
});
}
let hi = u64::from_be_bytes(bytes[0..8].try_into().unwrap());
let lo = u64::from_be_bytes(bytes[8..16].try_into().unwrap());
Ok(Self::from_raw(hi, lo))
}
#[inline]
pub unsafe fn from_bytes_unchecked(bytes: &[u8]) -> Self {
debug_assert!(bytes.len() >= 16);
unsafe {
let hi = u64::from_be_bytes(bytes.get_unchecked(0..8).try_into().unwrap_unchecked());
let lo = u64::from_be_bytes(bytes.get_unchecked(8..16).try_into().unwrap_unchecked());
Self::from_raw(hi, lo)
}
}
#[inline]
pub fn as_str(&self) -> &str {
self.0.as_str()
}
#[inline]
pub fn as_bytes(&self) -> &[u8] {
self.0.as_bytes()
}
pub fn decode(&self) -> (u64, u64) {
let bytes: &[u8; 32] = self.0.as_bytes().try_into().unwrap();
unsafe { crate::simd::hex_decode_32(bytes).unwrap_unchecked() }
}
pub fn parse(s: &str) -> Result<Self, ParseError> {
let bytes = s.as_bytes();
if bytes.len() != 32 {
return Err(ParseError::InvalidLength {
expected: 32,
got: bytes.len(),
});
}
let hex_bytes: &[u8; 32] = bytes.try_into().unwrap();
let (hi, lo) =
crate::simd::hex_decode_32(hex_bytes).map_err(|pos| ParseError::InvalidChar {
position: pos,
byte: bytes[pos],
})?;
Ok(Self::from_raw(hi, lo))
}
#[inline]
pub fn to_dashed(&self) -> Uuid {
let (hi, lo) = self.decode();
Uuid::from_raw(hi, lo)
}
#[inline]
pub fn is_nil(&self) -> bool {
let (hi, lo) = self.decode();
hi == 0 && lo == 0
}
pub fn to_bytes(&self) -> [u8; 16] {
let (hi, lo) = self.decode();
let mut out = [0u8; 16];
out[..8].copy_from_slice(&hi.to_be_bytes());
out[8..].copy_from_slice(&lo.to_be_bytes());
out
}
}
impl Deref for UuidCompact {
type Target = str;
#[inline]
fn deref(&self) -> &str {
self.0.as_str()
}
}
impl AsRef<str> for UuidCompact {
#[inline]
fn as_ref(&self) -> &str {
self.0.as_str()
}
}
impl fmt::Display for UuidCompact {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.0.as_str())
}
}
impl fmt::Debug for UuidCompact {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "UuidCompact({})", self.0.as_str())
}
}
impl Hash for UuidCompact {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
impl Ord for UuidCompact {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
self.0.cmp(&other.0)
}
}
impl PartialOrd for UuidCompact {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl FromStr for UuidCompact {
type Err = ParseError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse(s)
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
#[repr(transparent)]
pub struct HexId64(pub(crate) AsciiString<16>);
impl HexId64 {
#[inline]
pub fn encode(value: u64) -> Self {
Self(crate::encode::hex_u64(value))
}
#[inline]
pub fn as_str(&self) -> &str {
self.0.as_str()
}
#[inline]
pub fn as_bytes(&self) -> &[u8] {
self.0.as_bytes()
}
pub fn decode(&self) -> u64 {
let bytes: &[u8; 16] = self.0.as_bytes().try_into().unwrap();
unsafe { crate::simd::hex_decode_16(bytes).unwrap_unchecked() }
}
pub fn parse(s: &str) -> Result<Self, ParseError> {
let bytes = s.as_bytes();
if bytes.len() != 16 {
return Err(ParseError::InvalidLength {
expected: 16,
got: bytes.len(),
});
}
let hex_bytes: &[u8; 16] = bytes.try_into().unwrap();
let value =
crate::simd::hex_decode_16(hex_bytes).map_err(|pos| ParseError::InvalidChar {
position: pos,
byte: bytes[pos],
})?;
Ok(Self::encode(value))
}
}
impl Deref for HexId64 {
type Target = str;
#[inline]
fn deref(&self) -> &str {
self.0.as_str()
}
}
impl AsRef<str> for HexId64 {
#[inline]
fn as_ref(&self) -> &str {
self.0.as_str()
}
}
impl fmt::Display for HexId64 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.0.as_str())
}
}
impl fmt::Debug for HexId64 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "HexId64({})", self.0.as_str())
}
}
impl Hash for HexId64 {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
impl FromStr for HexId64 {
type Err = ParseError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse(s)
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
#[repr(transparent)]
pub struct Base62Id(pub(crate) AsciiString<16>);
impl Base62Id {
#[inline]
pub fn encode(value: u64) -> Self {
Self(crate::encode::base62_u64(value))
}
#[inline]
pub fn as_str(&self) -> &str {
self.0.as_str()
}
#[inline]
pub fn as_bytes(&self) -> &[u8] {
self.0.as_bytes()
}
pub fn decode(&self) -> u64 {
let bytes = self.0.as_bytes();
let mut value: u64 = 0;
for &b in bytes {
value = value * 62 + base62_digit(b) as u64;
}
value
}
pub fn parse(s: &str) -> Result<Self, DecodeError> {
let bytes = s.as_bytes();
if bytes.len() != 11 {
return Err(DecodeError::InvalidLength {
expected: 11,
got: bytes.len(),
});
}
let mut value: u64 = 0;
let mut i = 0;
while i < 11 {
let d = parse::validate_base62(bytes[i], i)?;
value = value
.checked_mul(62)
.and_then(|v| v.checked_add(d as u64))
.ok_or(DecodeError::Overflow)?;
i += 1;
}
Ok(Self::encode(value))
}
}
impl Deref for Base62Id {
type Target = str;
#[inline]
fn deref(&self) -> &str {
self.0.as_str()
}
}
impl AsRef<str> for Base62Id {
#[inline]
fn as_ref(&self) -> &str {
self.0.as_str()
}
}
impl fmt::Display for Base62Id {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.0.as_str())
}
}
impl fmt::Debug for Base62Id {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Base62Id({})", self.0.as_str())
}
}
impl Hash for Base62Id {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
impl FromStr for Base62Id {
type Err = DecodeError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse(s)
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
#[repr(transparent)]
pub struct Base36Id(pub(crate) AsciiString<16>);
impl Base36Id {
#[inline]
pub fn encode(value: u64) -> Self {
Self(crate::encode::base36_u64(value))
}
#[inline]
pub fn as_str(&self) -> &str {
self.0.as_str()
}
#[inline]
pub fn as_bytes(&self) -> &[u8] {
self.0.as_bytes()
}
pub fn decode(&self) -> u64 {
let bytes = self.0.as_bytes();
let mut value: u64 = 0;
for &b in bytes {
value = value * 36 + base36_digit(b) as u64;
}
value
}
pub fn parse(s: &str) -> Result<Self, DecodeError> {
let bytes = s.as_bytes();
if bytes.len() != 13 {
return Err(DecodeError::InvalidLength {
expected: 13,
got: bytes.len(),
});
}
let mut value: u64 = 0;
let mut i = 0;
while i < 13 {
let d = parse::validate_base36(bytes[i], i)?;
value = value
.checked_mul(36)
.and_then(|v| v.checked_add(d as u64))
.ok_or(DecodeError::Overflow)?;
i += 1;
}
Ok(Self::encode(value))
}
}
impl Deref for Base36Id {
type Target = str;
#[inline]
fn deref(&self) -> &str {
self.0.as_str()
}
}
impl AsRef<str> for Base36Id {
#[inline]
fn as_ref(&self) -> &str {
self.0.as_str()
}
}
impl fmt::Display for Base36Id {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.0.as_str())
}
}
impl fmt::Debug for Base36Id {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Base36Id({})", self.0.as_str())
}
}
impl Hash for Base36Id {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
impl FromStr for Base36Id {
type Err = DecodeError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse(s)
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
#[repr(transparent)]
pub struct Ulid(pub(crate) AsciiString<32>);
impl Ulid {
#[inline]
pub fn from_raw(timestamp_ms: u64, rand_hi: u16, rand_lo: u64) -> Self {
Self(crate::encode::ulid_encode(timestamp_ms, rand_hi, rand_lo))
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, ParseError> {
if bytes.len() != 16 {
return Err(ParseError::InvalidLength {
expected: 16,
got: bytes.len(),
});
}
let mut ts_buf = [0u8; 8];
ts_buf[2..8].copy_from_slice(&bytes[0..6]);
let timestamp_ms = u64::from_be_bytes(ts_buf);
let rand_hi = u16::from_be_bytes(bytes[6..8].try_into().unwrap());
let rand_lo = u64::from_be_bytes(bytes[8..16].try_into().unwrap());
Ok(Self::from_raw(timestamp_ms, rand_hi, rand_lo))
}
#[inline]
pub unsafe fn from_bytes_unchecked(bytes: &[u8]) -> Self {
debug_assert!(bytes.len() >= 16);
unsafe {
let mut ts_buf = [0u8; 8];
core::ptr::copy_nonoverlapping(bytes.as_ptr(), ts_buf.as_mut_ptr().add(2), 6);
let timestamp_ms = u64::from_be_bytes(ts_buf);
let rand_hi =
u16::from_be_bytes(bytes.get_unchecked(6..8).try_into().unwrap_unchecked());
let rand_lo =
u64::from_be_bytes(bytes.get_unchecked(8..16).try_into().unwrap_unchecked());
Self::from_raw(timestamp_ms, rand_hi, rand_lo)
}
}
#[inline]
pub fn as_str(&self) -> &str {
self.0.as_str()
}
#[inline]
pub fn as_bytes(&self) -> &[u8] {
self.0.as_bytes()
}
pub fn timestamp_ms(&self) -> u64 {
let bytes = self.0.as_bytes();
let mut ts: u64 = 0;
ts = (ts << 3) | crockford32_digit(bytes[0]) as u64;
for &b in &bytes[1..10] {
ts = (ts << 5) | crockford32_digit(b) as u64;
}
ts
}
pub fn parse(s: &str) -> Result<Self, ParseError> {
let bytes = s.as_bytes();
if bytes.len() != 26 {
return Err(ParseError::InvalidLength {
expected: 26,
got: bytes.len(),
});
}
let first = parse::validate_crockford32(bytes[0], 0)?;
if first > 7 {
return Err(ParseError::InvalidChar {
position: 0,
byte: bytes[0],
});
}
let mut ts: u64 = first as u64;
let mut i = 1;
while i < 10 {
let d = parse::validate_crockford32(bytes[i], i)? as u64;
ts = (ts << 5) | d;
i += 1;
}
let c10 = parse::validate_crockford32(bytes[10], 10)? as u16;
let c11 = parse::validate_crockford32(bytes[11], 11)? as u16;
let c12 = parse::validate_crockford32(bytes[12], 12)? as u16;
let c13 = parse::validate_crockford32(bytes[13], 13)? as u64;
let rand_hi = (c10 << 11) | (c11 << 6) | (c12 << 1) | ((c13 >> 4) as u16);
let mut rand_lo: u64 = c13 & 0x0F;
i = 14;
while i < 26 {
let d = parse::validate_crockford32(bytes[i], i)? as u64;
rand_lo = (rand_lo << 5) | d;
i += 1;
}
Ok(Self::from_raw(ts, rand_hi, rand_lo))
}
#[inline]
pub fn is_nil(&self) -> bool {
self.timestamp_ms() == 0 && {
let (hi, lo) = self.random();
hi == 0 && lo == 0
}
}
pub fn to_uuid(&self) -> Uuid {
let ts = self.timestamp_ms();
let (rand_hi, rand_lo) = self.random();
let rand_a = (rand_hi >> 4) as u64; let hi = (ts << 16) | (0x7 << 12) | (rand_a & 0xFFF);
let remaining = ((rand_hi as u64 & 0x0F) << 58) | (rand_lo >> 6);
let lo = (0b10u64 << 62) | (remaining & 0x3FFF_FFFF_FFFF_FFFF);
Uuid::from_raw(hi, lo)
}
pub fn to_bytes(&self) -> [u8; 16] {
let ts = self.timestamp_ms();
let (rand_hi, rand_lo) = self.random();
let mut out = [0u8; 16];
let ts_bytes = ts.to_be_bytes();
out[0..6].copy_from_slice(&ts_bytes[2..8]);
out[6..8].copy_from_slice(&rand_hi.to_be_bytes());
out[8..16].copy_from_slice(&rand_lo.to_be_bytes());
out
}
pub fn random(&self) -> (u16, u64) {
let bytes = self.0.as_bytes();
let c10 = crockford32_digit(bytes[10]) as u16;
let c11 = crockford32_digit(bytes[11]) as u16;
let c12 = crockford32_digit(bytes[12]) as u16;
let c13 = crockford32_digit(bytes[13]) as u64;
let rand_hi = (c10 << 11) | (c11 << 6) | (c12 << 1) | ((c13 >> 4) as u16);
let mut rand_lo: u64 = c13 & 0x0F;
for &b in &bytes[14..26] {
rand_lo = (rand_lo << 5) | crockford32_digit(b) as u64;
}
(rand_hi, rand_lo)
}
}
impl Deref for Ulid {
type Target = str;
#[inline]
fn deref(&self) -> &str {
self.0.as_str()
}
}
impl AsRef<str> for Ulid {
#[inline]
fn as_ref(&self) -> &str {
self.0.as_str()
}
}
impl fmt::Display for Ulid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.0.as_str())
}
}
impl fmt::Debug for Ulid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Ulid({})", self.0.as_str())
}
}
impl Hash for Ulid {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
impl Ord for Ulid {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
self.0.cmp(&other.0)
}
}
impl PartialOrd for Ulid {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl FromStr for Ulid {
type Err = ParseError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse(s)
}
}
impl From<Uuid> for UuidCompact {
#[inline]
fn from(u: Uuid) -> Self {
u.to_compact()
}
}
impl From<UuidCompact> for Uuid {
#[inline]
fn from(u: UuidCompact) -> Self {
u.to_dashed()
}
}
impl From<Ulid> for Uuid {
#[inline]
fn from(u: Ulid) -> Self {
u.to_uuid()
}
}
#[inline]
fn crockford32_digit(b: u8) -> u8 {
parse::CROCKFORD32_DECODE[b as usize]
}
#[inline]
const fn hex_digit(b: u8) -> u8 {
match b {
b'0'..=b'9' => b - b'0',
b'a'..=b'f' => b - b'a' + 10,
b'A'..=b'F' => b - b'A' + 10,
_ => 0, }
}
#[inline]
const fn base62_digit(b: u8) -> u8 {
match b {
b'0'..=b'9' => b - b'0',
b'A'..=b'Z' => b - b'A' + 10,
b'a'..=b'z' => b - b'a' + 36,
_ => 0,
}
}
#[inline]
const fn base36_digit(b: u8) -> u8 {
match b {
b'0'..=b'9' => b - b'0',
b'a'..=b'z' => b - b'a' + 10,
b'A'..=b'Z' => b - b'A' + 10, _ => 0,
}
}
#[cfg(all(test, feature = "std"))]
mod tests {
use super::*;
#[test]
fn uuid_decode_roundtrip() {
let hi = 0x0123_4567_89AB_CDEF_u64;
let lo = 0xFEDC_BA98_7654_3210_u64;
let uuid = Uuid::from_raw(hi, lo);
let (decoded_hi, decoded_lo) = uuid.decode();
assert_eq!(hi, decoded_hi);
assert_eq!(lo, decoded_lo);
}
#[test]
fn uuid_compact_decode_roundtrip() {
let hi = 0x0123_4567_89AB_CDEF_u64;
let lo = 0xFEDC_BA98_7654_3210_u64;
let uuid = UuidCompact::from_raw(hi, lo);
let (decoded_hi, decoded_lo) = uuid.decode();
assert_eq!(hi, decoded_hi);
assert_eq!(lo, decoded_lo);
}
#[test]
fn hex_id64_decode_roundtrip() {
for value in [0, 1, 12345, u64::MAX, 0xDEAD_BEEF_CAFE_BABE] {
let id = HexId64::encode(value);
assert_eq!(id.decode(), value);
}
}
#[test]
fn base62_id_decode_roundtrip() {
for value in [0, 1, 12345, u64::MAX] {
let id = Base62Id::encode(value);
assert_eq!(id.decode(), value);
}
}
#[test]
fn base36_id_decode_roundtrip() {
for value in [0, 1, 12345, u64::MAX] {
let id = Base36Id::encode(value);
assert_eq!(id.decode(), value);
}
}
#[test]
fn uuid_version() {
let hi = 0x0123_4567_89AB_4DEF_u64; let lo = 0x8EDC_BA98_7654_3210_u64;
let uuid = Uuid::from_raw(hi, lo);
assert_eq!(uuid.version(), 4);
let hi = 0x0123_4567_89AB_7DEF_u64; let uuid = Uuid::from_raw(hi, lo);
assert_eq!(uuid.version(), 7);
}
#[test]
fn display_works() {
let uuid = Uuid::from_raw(0x0123_4567_89AB_CDEF, 0xFEDC_BA98_7654_3210);
let s = format!("{}", uuid);
assert_eq!(s, "01234567-89ab-cdef-fedc-ba9876543210");
}
#[test]
fn deref_works() {
let uuid = Uuid::from_raw(0x0123_4567_89AB_CDEF, 0xFEDC_BA98_7654_3210);
let s: &str = &uuid;
assert_eq!(s, "01234567-89ab-cdef-fedc-ba9876543210");
}
#[test]
fn uuid_from_bytes_roundtrip() {
let original = Uuid::from_raw(0x0123_4567_89AB_CDEF, 0xFEDC_BA98_7654_3210);
let bytes = original.to_bytes();
let recovered = Uuid::from_bytes(&bytes).unwrap();
assert_eq!(original, recovered);
}
#[test]
fn uuid_from_bytes_wrong_length() {
assert!(Uuid::from_bytes(&[0u8; 15]).is_err());
assert!(Uuid::from_bytes(&[0u8; 17]).is_err());
assert!(Uuid::from_bytes(&[]).is_err());
}
#[test]
fn uuid_from_bytes_unchecked_roundtrip() {
let original = Uuid::from_raw(0xDEAD_BEEF_CAFE_BABE, 0x0123_4567_89AB_CDEF);
let bytes = original.to_bytes();
let recovered = unsafe { Uuid::from_bytes_unchecked(&bytes) };
assert_eq!(original, recovered);
}
#[test]
fn uuid_compact_from_bytes_roundtrip() {
let original = UuidCompact::from_raw(0x0123_4567_89AB_CDEF, 0xFEDC_BA98_7654_3210);
let bytes = original.to_bytes();
let recovered = UuidCompact::from_bytes(&bytes).unwrap();
assert_eq!(original, recovered);
}
#[test]
fn uuid_compact_from_bytes_wrong_length() {
assert!(UuidCompact::from_bytes(&[0u8; 15]).is_err());
assert!(UuidCompact::from_bytes(&[0u8; 17]).is_err());
}
#[test]
fn ulid_from_bytes_roundtrip() {
let original = Ulid::from_raw(1_700_000_000_000, 0xABCD, 0xDEAD_BEEF_CAFE_BABE);
let bytes = original.to_bytes();
let recovered = Ulid::from_bytes(&bytes).unwrap();
assert_eq!(original.timestamp_ms(), recovered.timestamp_ms());
assert_eq!(original.random(), recovered.random());
assert_eq!(original, recovered);
}
#[test]
fn ulid_from_bytes_wrong_length() {
assert!(Ulid::from_bytes(&[0u8; 15]).is_err());
assert!(Ulid::from_bytes(&[0u8; 17]).is_err());
}
#[test]
fn ulid_from_bytes_unchecked_roundtrip() {
let original = Ulid::from_raw(1_700_000_000_000, 0x1234, 0x0123_4567_89AB_CDEF);
let bytes = original.to_bytes();
let recovered = unsafe { Ulid::from_bytes_unchecked(&bytes) };
assert_eq!(original, recovered);
}
#[test]
fn ulid_parse_rejects_overflow_first_char() {
let overflow = "80000000000000000000000000";
assert!(Ulid::parse(overflow).is_err());
let z_first = "Z0000000000000000000000000";
assert!(Ulid::parse(z_first).is_err());
let max_valid = "70000000000000000000000000";
assert!(Ulid::parse(max_valid).is_ok());
}
#[test]
fn base62_parse_overflow() {
use crate::parse::DecodeError;
let result = Base62Id::parse("zzzzzzzzzzz");
assert_eq!(result, Err(DecodeError::Overflow));
let max_id = Base62Id::encode(u64::MAX);
let parsed = Base62Id::parse(max_id.as_str()).unwrap();
assert_eq!(parsed.decode(), u64::MAX);
}
#[test]
fn base36_parse_overflow() {
use crate::parse::DecodeError;
let result = Base36Id::parse("zzzzzzzzzzzzz");
assert_eq!(result, Err(DecodeError::Overflow));
let max_id = Base36Id::encode(u64::MAX);
let parsed = Base36Id::parse(max_id.as_str()).unwrap();
assert_eq!(parsed.decode(), u64::MAX);
}
#[test]
fn uuid_parse_wrong_length() {
use crate::parse::UuidParseError;
let result = Uuid::parse("01234567-89ab-cdef-fedc-ba987654321"); assert!(matches!(result, Err(UuidParseError::InvalidLength { .. })));
let result = Uuid::parse("01234567-89ab-cdef-fedc-ba98765432100"); assert!(matches!(result, Err(UuidParseError::InvalidLength { .. })));
let result = Uuid::parse("");
assert!(matches!(result, Err(UuidParseError::InvalidLength { .. })));
}
#[test]
fn uuid_parse_bad_dashes() {
use crate::parse::UuidParseError;
let result = Uuid::parse("01234567089ab-cdef-fedc-ba9876543210");
assert!(matches!(result, Err(UuidParseError::InvalidFormat)));
let result = Uuid::parse("01234567-89ab0cdef-fedc-ba9876543210");
assert!(matches!(result, Err(UuidParseError::InvalidFormat)));
let result = Uuid::parse("01234567-89ab-cdef0fedc-ba9876543210");
assert!(matches!(result, Err(UuidParseError::InvalidFormat)));
let result = Uuid::parse("01234567-89ab-cdef-fedc0ba9876543210");
assert!(matches!(result, Err(UuidParseError::InvalidFormat)));
}
#[test]
fn uuid_parse_invalid_hex_char() {
use crate::parse::UuidParseError;
let result = Uuid::parse("g1234567-89ab-cdef-fedc-ba9876543210");
assert!(matches!(
result,
Err(UuidParseError::InvalidChar { position: 0, .. })
));
let result = Uuid::parse("01234567-89xb-cdef-fedc-ba9876543210");
assert!(matches!(
result,
Err(UuidParseError::InvalidChar { position: 11, .. })
));
}
#[test]
fn uuid_is_nil() {
let nil = Uuid::from_raw(0, 0);
assert!(nil.is_nil());
let not_nil = Uuid::from_raw(0, 1);
assert!(!not_nil.is_nil());
let not_nil = Uuid::from_raw(1, 0);
assert!(!not_nil.is_nil());
}
#[test]
fn uuid_compact_is_nil() {
let nil = UuidCompact::from_raw(0, 0);
assert!(nil.is_nil());
let not_nil = UuidCompact::from_raw(0, 1);
assert!(!not_nil.is_nil());
}
#[test]
fn ulid_is_nil() {
let nil = Ulid::from_raw(0, 0, 0);
assert!(nil.is_nil());
let not_nil = Ulid::from_raw(1, 0, 0);
assert!(!not_nil.is_nil());
let not_nil = Ulid::from_raw(0, 1, 0);
assert!(!not_nil.is_nil());
let not_nil = Ulid::from_raw(0, 0, 1);
assert!(!not_nil.is_nil());
}
#[test]
fn ulid_parse_crockford_aliases() {
let canonical = Ulid::parse("01000000000000000000000000").unwrap();
let with_o = Ulid::parse("O1000000000000000000000000").unwrap();
assert_eq!(canonical, with_o);
let with_i = Ulid::parse("0I000000000000000000000000").unwrap();
assert_eq!(canonical, with_i);
let with_l = Ulid::parse("0L000000000000000000000000").unwrap();
assert_eq!(canonical, with_l);
let with_i_lower = Ulid::parse("0i000000000000000000000000").unwrap();
assert_eq!(canonical, with_i_lower);
let with_o_lower = Ulid::parse("o1000000000000000000000000").unwrap();
assert_eq!(canonical, with_o_lower);
let with_l_lower = Ulid::parse("0l000000000000000000000000").unwrap();
assert_eq!(canonical, with_l_lower);
}
#[test]
fn ulid_to_uuid_preserves_timestamp() {
let ts = 1_700_000_000_000u64;
let ulid = Ulid::from_raw(ts, 0x1234, 0xDEAD_BEEF_CAFE_BABE);
let uuid = ulid.to_uuid();
assert_eq!(uuid.version(), 7);
let (hi, _) = uuid.decode();
let extracted_ts = hi >> 16;
assert_eq!(extracted_ts, ts);
}
#[test]
fn ulid_to_uuid_is_lossy() {
let ulid_a = Ulid::from_raw(1_700_000_000_000, 0x1234, 0xDEAD_BEEF_CAFE_BA00);
let ulid_b = Ulid::from_raw(1_700_000_000_000, 0x1234, 0xDEAD_BEEF_CAFE_BA3F);
assert_ne!(ulid_a, ulid_b);
assert_eq!(ulid_a.to_uuid(), ulid_b.to_uuid());
}
#[test]
fn ulid_to_uuid_sets_variant_bits() {
let ulid = Ulid::from_raw(1_700_000_000_000, 0xFFFF, u64::MAX);
let uuid = ulid.to_uuid();
let (_, lo) = uuid.decode();
assert_eq!(lo >> 62, 0b10);
}
}