1use crate::error::Error;
2
3#[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 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}