//! Random 128-bit identifiers: ChangeId, NodeId, WorkspaceId (spec §6.3, §7.1, §12.2). use rand::RngCore; use std::fmt; macro_rules! id128 { ($name:ident, $doc:literal) => { #[doc = $doc] #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct $name(pub [u8; 16]); impl $name { pub const ZERO: $name = $name([0; 16]); /// A fresh identifier from the OS CSPRNG. pub fn random() -> $name { let mut b = [0u8; 16]; rand::rngs::OsRng.fill_bytes(&mut b); $name(b) } pub fn to_hex(&self) -> String { hex::encode(self.0) } pub fn from_hex(s: &str) -> Option<$name> { let v = hex::decode(s).ok()?; Some($name(v.try_into().ok()?)) } pub fn from_slice(b: &[u8]) -> Option<$name> { Some($name(b.try_into().ok()?)) } pub fn short(&self, n: usize) -> String { let h = self.to_hex(); h[..n.min(h.len())].to_string() } pub fn starts_with_hex(&self, prefix: &str) -> bool { self.to_hex().starts_with(prefix) } } impl fmt::Display for $name { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(&self.to_hex()) } } impl fmt::Debug for $name { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}({})", stringify!($name), self.short(8)) } } }; } id128!( ChangeId, "Stable identity of a change across rewrites (spec §6.2)." ); id128!(NodeId, "Stable identity of a file, directory, or symlink across renames (spec §7.1). `NodeId::ZERO` is the root directory."); id128!( WorkspaceId, "Identity of a checkout directory (spec §12.2)." ); impl NodeId { pub const ROOT: NodeId = NodeId::ZERO; pub fn is_root(&self) -> bool { self.is_zero() } } impl ChangeId { pub fn is_zero(&self) -> bool { *self == Self::ZERO } } impl NodeId { pub fn is_zero(&self) -> bool { *self == Self::ZERO } } impl WorkspaceId { pub fn is_zero(&self) -> bool { *self == Self::ZERO } } #[cfg(test)] mod tests { use super::*; #[test] fn random_ids_differ_and_round_trip() { let a = ChangeId::random(); let b = ChangeId::random(); assert_ne!(a, b); assert_eq!(ChangeId::from_hex(&a.to_hex()), Some(a)); assert!(NodeId::ROOT.is_root()); assert!(!NodeId::random().is_root()); } }