OXYGENHUB
# a405d4c99440 oxygen / crates / oxy-core / src / ids.rs
2.7 KiB · Rust · last changed Unbound hosted repositories adopt the first pusher's id 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!(
54
    ChangeId,
55
    "Stable identity of a change across rewrites (spec §6.2)."
56
);
57
id128!(NodeId, "Stable identity of a file, directory, or symlink across renames (spec §7.1). `NodeId::ZERO` is the root directory.");
58
id128!(
59
    WorkspaceId,
60
    "Identity of a checkout directory (spec §12.2)."
61
);
62
63
impl NodeId {
64
    pub const ROOT: NodeId = NodeId::ZERO;
65
    pub fn is_root(&self) -> bool {
66
        self.is_zero()
67
    }
68
}
69
70
impl ChangeId {
71
    pub fn is_zero(&self) -> bool {
72
        *self == Self::ZERO
73
    }
74
}
75
impl NodeId {
76
    pub fn is_zero(&self) -> bool {
77
        *self == Self::ZERO
78
    }
79
}
80
impl WorkspaceId {
81
    pub fn is_zero(&self) -> bool {
82
        *self == Self::ZERO
83
    }
84
}
85
86
#[cfg(test)]
87
mod tests {
88
    use super::*;
89
90
    #[test]
91
    fn random_ids_differ_and_round_trip() {
92
        let a = ChangeId::random();
93
        let b = ChangeId::random();
94
        assert_ne!(a, b);
95
        assert_eq!(ChangeId::from_hex(&a.to_hex()), Some(a));
96
        assert!(NodeId::ROOT.is_root());
97
        assert!(!NodeId::random().is_root());
98
    }
99
}