use std::fmt;
use uuid::{Uuid, Variant};
use crate::{Julid, COUNTER_BITS};
impl Julid {
pub const fn as_uuid(&self) -> Uuid {
let counter_mask = (1 << 12) - 1;
let entropy_mask = (1 << 62) - 1;
let timestamp = self.timestamp();
let counter = (self.counter() & counter_mask) | (0b0111 << 12);
let entropy = (self.random() & entropy_mask) | (0b10 << 62);
let top = (timestamp << 16) | counter as u64;
Uuid::from_u64_pair(top, entropy)
}
pub fn from_uuid(id: Uuid) -> Result<Self, UuidError> {
let ver = id.get_version_num();
if ver != 7 {
return Err(UuidError::UnsupportedVersion(ver));
}
let var = id.get_variant();
if var != Variant::RFC4122 {
return Err(UuidError::UnsupportedVariant(var));
}
let (hi, lo) = id.as_u64_pair();
let mask = (1 << 12) - 1;
let counter = hi & mask;
let ts = hi >> COUNTER_BITS;
let hi = (ts << COUNTER_BITS) | counter;
Ok((hi, lo).into())
}
}
impl From<Julid> for Uuid {
fn from(value: Julid) -> Self {
value.as_uuid()
}
}
impl TryFrom<Uuid> for Julid {
type Error = UuidError;
fn try_from(value: Uuid) -> Result<Self, Self::Error> {
Julid::from_uuid(value)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum UuidError {
UnsupportedVersion(usize),
UnsupportedVariant(uuid::Variant),
}
impl std::error::Error for UuidError {}
impl fmt::Display for UuidError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
let text = match *self {
UuidError::UnsupportedVersion(v) => format!("unsupported version {v}"),
UuidError::UnsupportedVariant(v) => format!("unsupported variant: {v:?}"),
};
write!(f, "{text}")
}
}
#[cfg(test)]
mod test {
use uuid::Uuid;
use crate::{uuid::UuidError, Julid};
#[test]
fn into_uuid() {
let ts = 1497624119 * 1000;
let j = Julid::at(ts);
let u = j.as_uuid().hyphenated().to_string();
assert!(u.starts_with("015cb15a-86d8-7"));
}
#[test]
fn from_uuid() {
let j1 = Julid::new();
let u1: Uuid = j1.into();
let ju1: Julid = u1.try_into().unwrap();
assert_eq!(j1.timestamp(), ju1.timestamp());
assert_eq!(j1.counter(), ju1.counter());
assert_eq!(j1.random() << 2, ju1.random() << 2);
assert_eq!(ju1.random() >> 62, 2);
let u2 = ju1.as_uuid();
let ju2 = u2.try_into().unwrap();
assert_eq!(ju1, ju2);
}
#[test]
fn cant_even_from_uuid_non_v7() {
let u = uuid::Uuid::new_v4();
let jr: Result<Julid, UuidError> = u.try_into();
assert_eq!(jr, Err(UuidError::UnsupportedVersion(4)));
}
}