OXYGENHUB
# 55e5a16a2b9c oxygen / crates / oxy-core / src / ids.rs
2.6 KiB · Rust · last changed Import the Oxygen specification and the first Rust implementation (root change) 1 month ago source blame history raw
1
//! Random 128-bit identifiers: ChangeId, NodeId, WorkspaceId (spec §6.3, §7.1, §12.2).
2
3
use rand::RngCore;
4
use std::fmt;
5
6
macro_rules! id128 {
7
    ($name:ident, $doc:literal) => {
8
        #[doc = $doc]
9
        #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
10
        pub struct $name(pub [u8; 16]);
11
12
        impl $name {
13
            pub const ZERO: $name = $name([0; 16]);
14
15
            /// A fresh identifier from the OS CSPRNG.
16
            pub fn random() -> $name {
17
                let mut b = [0u8; 16];
18
                rand::rngs::OsRng.fill_bytes(&mut b);
19
                $name(b)
20
            }
21
            pub fn to_hex(&self) -> String {
22
                hex::encode(self.0)
23
            }
24
            pub fn from_hex(s: &str) -> Option<$name> {
25
                let v = hex::decode(s).ok()?;
26
                Some($name(v.try_into().ok()?))
27
            }
28
            pub fn from_slice(b: &[u8]) -> Option<$name> {
29
                Some($name(b.try_into().ok()?))
30
            }
31
            pub fn short(&self, n: usize) -> String {
32
                let h = self.to_hex();
33
                h[..n.min(h.len())].to_string()
34
            }
35
            pub fn starts_with_hex(&self, prefix: &str) -> bool {
36
                self.to_hex().starts_with(prefix)
37
            }
38
        }
39
40
        impl fmt::Display for $name {
41
            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42
                f.write_str(&self.to_hex())
43
            }
44
        }
45
        impl fmt::Debug for $name {
46
            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47
                write!(f, "{}({})", stringify!($name), self.short(8))
48
            }
49
        }
50
    };
51
}
52
53
id128!(ChangeId, "Stable identity of a change across rewrites (spec §6.2).");
54
id128!(NodeId, "Stable identity of a file, directory, or symlink across renames (spec §7.1). `NodeId::ZERO` is the root directory.");
55
id128!(WorkspaceId, "Identity of a checkout directory (spec §12.2).");
56
57
impl NodeId {
58
    pub const ROOT: NodeId = NodeId::ZERO;
59
    pub fn is_root(&self) -> bool {
60
        self.is_zero()
61
    }
62
}
63
64
impl ChangeId {
65
    pub fn is_zero(&self) -> bool {
66
        *self == Self::ZERO
67
    }
68
}
69
impl NodeId {
70
    pub fn is_zero(&self) -> bool {
71
        *self == Self::ZERO
72
    }
73
}
74
impl WorkspaceId {
75
    pub fn is_zero(&self) -> bool {
76
        *self == Self::ZERO
77
    }
78
}
79
80
#[cfg(test)]
81
mod tests {
82
    use super::*;
83
84
    #[test]
85
    fn random_ids_differ_and_round_trip() {
86
        let a = ChangeId::random();
87
        let b = ChangeId::random();
88
        assert_ne!(a, b);
89
        assert_eq!(ChangeId::from_hex(&a.to_hex()), Some(a));
90
        assert!(NodeId::ROOT.is_root());
91
        assert!(!NodeId::random().is_root());
92
    }
93
}