Skip to main content

lfsx_server/
oid.rs

1use crate::error::Error;
2
3// The digest a client names an object by, validated once, where it enters.
4// Everything downstream takes the type and cannot be handed anything else,
5// the same shape `Namespace` already has: there is no unvalidated `Oid` to
6// pass, so there is nothing for the eighteenth call site to forget.
7#[derive(Debug, Clone, PartialEq, Eq, Hash)]
8pub struct Oid(String);
9
10impl Oid {
11    pub fn parse(raw: &str) -> Result<Self, Error> {
12        let well_formed = raw.len() == 64
13            && raw
14                .bytes()
15                .all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b));
16
17        well_formed
18            .then(|| Self(raw.to_owned()))
19            .ok_or(Error::MalformedOid)
20    }
21
22    // The two directory levels an object files under, on disk and in a bucket
23    // alike. Safe to slice because parsing guaranteed sixty-four ASCII bytes,
24    // which is the fact this type exists to carry.
25    pub fn fanout(&self) -> (&str, &str) {
26        (&self.0[0..2], &self.0[2..4])
27    }
28
29    pub fn as_str(&self) -> &str {
30        &self.0
31    }
32}
33
34impl std::fmt::Display for Oid {
35    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36        f.write_str(&self.0)
37    }
38}
39
40#[cfg(test)]
41mod tests {
42    use super::*;
43
44    #[test]
45    fn only_a_lowercase_hex_sha256_digest_parses() {
46        let digest = "a".repeat(64);
47
48        assert_eq!(Oid::parse(&digest).unwrap().as_str(), digest);
49
50        for raw in [
51            "",
52            "abc",
53            &"A".repeat(64),
54            &"g".repeat(64),
55            &"a".repeat(63),
56            &"a".repeat(65),
57            &format!("../{}", "a".repeat(61)),
58        ] {
59            assert!(
60                matches!(Oid::parse(raw), Err(Error::MalformedOid)),
61                "{raw:?} must not parse"
62            );
63        }
64    }
65
66    #[test]
67    fn the_fanout_is_the_first_four_characters() {
68        let oid = Oid::parse(&format!("abcd{}", "0".repeat(60))).unwrap();
69
70        assert_eq!(oid.fanout(), ("ab", "cd"));
71    }
72}