Compare commits

...

2 commits

Author SHA1 Message Date
Joe Ardent
3329e01c2c even betterer uuid stuff 2025-06-25 23:20:28 -07:00
Joe Ardent
66621bb8c7 add tryfrom impls from uuid 2025-06-24 20:35:58 -07:00
4 changed files with 47 additions and 18 deletions

View file

@ -1,8 +1,8 @@
[package] [package]
name = "julid-rs" name = "julid-rs"
# 1.61803398874989484 # 1.61803398874989484
#--------------^ #---------------^
version = "1.6.18033988749" version = "1.6.180339887498"
authors = ["Joe Ardent <code@ardent.nebcorp.com>"] authors = ["Joe Ardent <code@ardent.nebcorp.com>"]
edition = "2024" edition = "2024"
keywords = ["ulid", "sqlite", "julid", "uuid", "guid"] keywords = ["ulid", "sqlite", "julid", "uuid", "guid"]
@ -41,7 +41,7 @@ uuid = { version = "1.17", default-features = false, optional = true }
[dev-dependencies] [dev-dependencies]
divan = "0.1" divan = "0.1"
uuid = { version = "1", default-features = false, features = ["v7"] } uuid = { version = "1", default-features = false, features = ["v7", "v4"] }
julid-rs = { path = ".", features = ["uuid"] } julid-rs = { path = ".", features = ["uuid"] }
[[bench]] [[bench]]

View file

@ -1 +1 @@
1.618033988749 1.6180339887498

View file

@ -65,7 +65,7 @@ impl fmt::Display for DecodeError {
DecodeError::InvalidLength(len) => format!("invalid length: {len}"), DecodeError::InvalidLength(len) => format!("invalid length: {len}"),
DecodeError::InvalidChar(c) => format!("invalid character: {c}"), DecodeError::InvalidChar(c) => format!("invalid character: {c}"),
}; };
write!(f, "{}", text) write!(f, "{text}")
} }
} }

View file

@ -2,7 +2,7 @@ use std::fmt;
use uuid::{Uuid, Variant}; use uuid::{Uuid, Variant};
use crate::Julid; use crate::{Julid, COUNTER_BITS};
impl Julid { impl Julid {
/// Convert to UUIDv7, possibly losing counter bits and altering the top /// Convert to UUIDv7, possibly losing counter bits and altering the top
@ -40,11 +40,32 @@ impl Julid {
return Err(UuidError::UnsupportedVariant(var)); return Err(UuidError::UnsupportedVariant(var));
} }
Ok(id.as_u64_pair().into()) 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())
} }
} }
#[derive(Debug)] 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 { pub enum UuidError {
UnsupportedVersion(usize), UnsupportedVersion(usize),
UnsupportedVariant(uuid::Variant), UnsupportedVariant(uuid::Variant),
@ -64,7 +85,9 @@ impl fmt::Display for UuidError {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::Julid; use uuid::Uuid;
use crate::{uuid::UuidError, Julid};
#[test] #[test]
fn into_uuid() { fn into_uuid() {
@ -78,17 +101,23 @@ mod test {
#[test] #[test]
fn from_uuid() { fn from_uuid() {
let j1 = Julid::new(); let j1 = Julid::new();
let u1 = j1.as_uuid();
let ju1 = Julid::from_uuid(u1).unwrap();
// casting a julid to a uuid alters the counter and entropy bits slightly, so
// the original julid and one derived from a uuid made from it won't be the
// same.
assert_ne!(j1, ju1);
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 u2 = ju1.as_uuid();
let ju2 = Julid::from_uuid(u2).unwrap(); let ju2 = u2.try_into().unwrap();
// but once we've made that alteration, we've reached the fixed point
assert_eq!(ju1, ju2); assert_eq!(ju1, ju2);
assert_eq!(u1, u2); }
#[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)));
} }
} }