pub use uuid::Uuid;
#[must_use]
pub fn new_v7() -> Uuid {
Uuid::now_v7()
}
pub const PROVENANCE_NAMESPACE: Uuid = Uuid::from_bytes([
0x9f, 0x6c, 0x2a, 0x1e, 0x7b, 0x42, 0x4d, 0x88, 0xa3, 0x10, 0x5e, 0x0c, 0x91, 0x33, 0x77, 0xd2,
]);
#[must_use]
pub fn new_v5(namespace: &Uuid, name: &[u8]) -> Uuid {
Uuid::new_v5(namespace, name)
}
#[must_use]
pub fn portable_import_generation(operation: &Uuid) -> Uuid {
new_v5(operation, b"graphforge-portable-import-generation/1")
}
#[must_use]
pub fn portable_v2_import_generation(operation: &Uuid) -> Uuid {
new_v5(operation, b"graphforge-portable-v2-import-generation/1")
}
#[must_use]
pub fn portable_v2_bootstrap_generation(operation: &Uuid) -> Uuid {
new_v5(operation, b"graphforge-portable-v2-bootstrap-generation/1")
}
#[must_use]
pub fn portable_v2_bootstrap_transaction(operation: &Uuid) -> Uuid {
new_v5(operation, b"graphforge-portable-v2-bootstrap-transaction/1")
}
#[must_use]
pub fn composite_delta_operation(operation: &Uuid, index: usize) -> Uuid {
new_v5(
operation,
format!("graphforge-composite-delta-operation/1/{index}").as_bytes(),
)
}
#[must_use]
pub fn composite_delta_run(transaction: &Uuid) -> Uuid {
new_v5(transaction, b"graphforge-composite-delta-run/1")
}
#[must_use]
pub fn hub_clone_operation(repository: &str, immutable_version: &str) -> Uuid {
new_v5(
&Uuid::NAMESPACE_URL,
format!("{repository}:{immutable_version}").as_bytes(),
)
}
#[must_use]
pub fn hub_research_clone_operation(
repository: &str,
immutable_version: &str,
version_uuid: &str,
identity_digest: &str,
) -> Uuid {
new_v5(
&hub_clone_operation(repository, immutable_version),
format!("graphforge-hub-research-clone/1/{version_uuid}/{identity_digest}").as_bytes(),
)
}
#[must_use]
pub fn hub_publish_operation(
repository: &str,
git_ref: Option<&str>,
version_uuid: &str,
project_content: &str,
) -> Uuid {
new_v5(
&Uuid::NAMESPACE_URL,
format!(
"graphforge-hub-publish/1\0{repository}\0{}\0{version_uuid}\0{project_content}",
git_ref.unwrap_or("")
)
.as_bytes(),
)
}
#[must_use]
pub fn to_bytes(uuid: &Uuid) -> [u8; 16] {
*uuid.as_bytes()
}
#[must_use]
pub fn from_bytes(bytes: &[u8; 16]) -> Uuid {
Uuid::from_bytes(*bytes)
}
#[must_use]
pub fn to_string(uuid: &Uuid) -> String {
uuid.hyphenated().to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn uuids_are_unique() {
let mut seen = std::collections::HashSet::new();
for _ in 0..1000 {
assert!(seen.insert(new_v7()), "generated a duplicate UUID");
}
assert_eq!(seen.len(), 1000);
}
#[test]
fn uuids_are_time_monotone() {
let mut prev = new_v7();
for _ in 0..1000 {
let next = new_v7();
assert!(next > prev, "UUIDv7 ordering violated: {next} !> {prev}");
prev = next;
}
}
#[test]
fn byte_round_trip_preserves_value() {
let original = new_v7();
let bytes = to_bytes(&original);
let restored = from_bytes(&bytes);
assert_eq!(original, restored);
}
#[test]
fn v5_is_namespace_and_name_deterministic() {
let namespace = Uuid::from_bytes([7; 16]);
let first = new_v5(&namespace, b"graphforge");
assert_eq!(first, new_v5(&namespace, b"graphforge"));
assert_ne!(first, new_v5(&namespace, b"GraphForge"));
assert_eq!(first.get_version_num(), 5);
}
#[test]
fn persisted_derivation_golden_vectors() {
let operation = Uuid::parse_str("00112233-4455-6677-8899-aabbccddeeff").unwrap();
let version_a = format!("sha256:{}", "a".repeat(64));
let version_b = format!("sha256:{}", "b".repeat(64));
let vectors = [
(
portable_import_generation(&operation),
"02abd337-e93c-56a3-bf8f-dadbb5b1e8d5",
),
(
portable_v2_import_generation(&operation),
"167a58d3-52f2-533e-b3c4-01d743d7537d",
),
(
portable_v2_bootstrap_generation(&operation),
"d633da3b-0a2e-543e-bcdd-d383b3c52794",
),
(
portable_v2_bootstrap_transaction(&operation),
"7f6e6309-9103-5c77-837a-0efffec19b70",
),
(
composite_delta_operation(&operation, 0),
"bf613aba-38a4-5ae7-8982-1675caa13e7c",
),
(
composite_delta_operation(&operation, 1),
"dcffd77e-a011-5480-8fb3-8493d22ff8be",
),
(
composite_delta_operation(&operation, 10),
"de381822-c256-5bf9-904f-25692a6b6c7e",
),
(
composite_delta_run(&operation),
"d230eba6-b8a4-549e-b032-888112a9fc62",
),
(
hub_clone_operation("curatelabs/demo", &version_a),
"e6caa879-f226-55fc-9d67-41c15b8f9658",
),
(
hub_clone_operation("curatelabs/demo", &version_b),
"db3c36b4-97ee-57bb-ac63-995c79fb577b",
),
(
hub_clone_operation("curatelabs/other", &version_a),
"29ea96be-d350-5026-a85b-32cbf5b835cc",
),
(
hub_research_clone_operation(
"curatelabs/demo",
&version_a,
"01900000-0000-7000-8000-000000000021",
&format!("sha256:{}", "c".repeat(64)),
),
"a53b2fd9-7de0-5826-9fc3-cbd8f7b07c4c",
),
(
hub_research_clone_operation(
"curatelabs/demo",
&version_a,
"01900000-0000-7000-8000-000000000022",
&format!("sha256:{}", "c".repeat(64)),
),
"c4bb0970-3473-5822-a82a-516833d8fde8",
),
(
hub_publish_operation(
"curatelabs/demo",
Some("main"),
"01900000-0000-7000-8000-000000000021",
&format!("sha256:{}", "e".repeat(64)),
),
"d93f3756-aea3-568b-b2b6-984835743c81",
),
(
hub_publish_operation(
"curatelabs/demo",
None,
"01900000-0000-7000-8000-000000000021",
&format!("sha256:{}", "e".repeat(64)),
),
"b8c6df3d-28dc-589b-bf90-2378b9a388fc",
),
(
hub_publish_operation(
"curatelabs/demo",
Some("main"),
"01900000-0000-7000-8000-000000000022",
&format!("sha256:{}", "e".repeat(64)),
),
"7f331a69-7ea4-5ceb-9280-9f44b8db5a2d",
),
(
hub_publish_operation(
"curatelabs/demo",
Some("main"),
"01900000-0000-7000-8000-000000000021",
&format!("sha256:{}", "f".repeat(64)),
),
"e0a6d8fd-b812-5603-ba77-b694e25648c9",
),
(
new_v5(&PROVENANCE_NAMESPACE, b"graphforge"),
"59a64d29-3fe8-5c2c-ad21-1f285e53d758",
),
];
let mut unique = std::collections::HashSet::new();
for (actual, expected) in vectors {
assert_eq!(
actual.as_bytes(),
Uuid::parse_str(expected).unwrap().as_bytes()
);
assert!(unique.insert(actual), "derivation domains collided");
}
let other_operation = Uuid::from_bytes([7; 16]);
assert_ne!(
portable_import_generation(&operation),
portable_import_generation(&other_operation)
);
assert_ne!(
portable_v2_import_generation(&operation),
portable_v2_import_generation(&other_operation)
);
assert_ne!(
portable_v2_bootstrap_generation(&operation),
portable_v2_bootstrap_generation(&other_operation)
);
assert_ne!(
portable_v2_bootstrap_transaction(&operation),
portable_v2_bootstrap_transaction(&other_operation)
);
assert_ne!(
composite_delta_operation(&operation, 0),
composite_delta_operation(&other_operation, 0)
);
assert_ne!(
composite_delta_run(&operation),
composite_delta_run(&other_operation)
);
}
#[test]
fn string_matches_rfc_format() {
let s = to_string(&new_v7());
assert_eq!(s.len(), 36);
let groups: Vec<&str> = s.split('-').collect();
assert_eq!(groups.len(), 5);
assert_eq!(
groups.iter().map(|g| g.len()).collect::<Vec<_>>(),
vec![8, 4, 4, 4, 12]
);
assert!(s.chars().all(|c| c.is_ascii_hexdigit() || c == '-'));
assert_eq!(groups[2].chars().next(), Some('7'));
}
}