123 lines
3.9 KiB
Rust
123 lines
3.9 KiB
Rust
use std::fmt;
|
|
|
|
use uuid::{Uuid, Variant};
|
|
|
|
use crate::{Julid, COUNTER_BITS};
|
|
|
|
impl Julid {
|
|
/// Convert to UUIDv7, possibly losing counter bits and altering the top
|
|
/// two bits from the lower 64.
|
|
///
|
|
/// UUIDv7s are very similar to Julids, but use 12 bits for a monotonic
|
|
/// counter instead of 16, and only 62 bits of entropy vs Julids' 64. This
|
|
/// means that some bits in the original Julid are overwritten with
|
|
/// UUID-specific values, but only six bits in total are potentially
|
|
/// altered.
|
|
pub fn as_uuid(&self) -> Uuid {
|
|
let counter_mask = (1 << 12) - 1;
|
|
let entropy_mask = (1 << 62) - 1;
|
|
let timestamp = self.timestamp();
|
|
// https://www.ietf.org/rfc/rfc9562.html#name-uuid-version-7 "ver" is 0b0111
|
|
let counter = (self.counter() & counter_mask) | (0b0111 << 12);
|
|
// https://www.ietf.org/rfc/rfc9562.html#name-uuid-version-7 "var" is 0b10
|
|
let entropy = (self.random() & entropy_mask) | (0b10 << 62);
|
|
let top = (timestamp << 16) | counter as u64;
|
|
Uuid::from_u64_pair(top, entropy)
|
|
}
|
|
|
|
/// Create from a UUIDv7; will fail if the UUID is not a valid v7 UUID.
|
|
///
|
|
/// UUIDv7s are very similar to Julids, but use 12 bits for a monotonic
|
|
/// counter instead of 16, and only 62 bits of entropy vs Julids' 64.
|
|
/// Therefore, no bits need to be altered when converting to a Julid.
|
|
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();
|
|
// zero out the high bits of the counter, which are "7" (0b0111) from the uuid
|
|
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() {
|
|
// see example from https://docs.rs/uuid/1.17.0/uuid/struct.Uuid.html#method.new_v7
|
|
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);
|
|
// once we've converted to uuid and then back to julid, we've reached the fixed
|
|
// point
|
|
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)));
|
|
}
|
|
}
|