1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
/// Sha256 hash, represented as bytes.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Sha256Hash(pub [u8; 32]);

impl Sha256Hash {
    pub fn as_bytes(&self) -> &[u8; 32] {
        &self.0
    }

    pub fn from_bytes(bytes: [u8; 32]) -> Self {
        Self(bytes)
    }
}

impl std::str::FromStr for Sha256Hash {
    type Err = Sha256HashParseError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if s.len() != 64 {
            return Err(Sha256HashParseError {
                value: s.to_string(),
                message: "invalid hash length - hash must have 64 hex-encoded characters "
                    .to_string(),
            });
        }

        let bytes = hex::decode(s).map_err(|e| Sha256HashParseError {
            value: s.to_string(),
            message: e.to_string(),
        })?;

        Ok(Sha256Hash(bytes.try_into().unwrap()))
    }
}

impl std::fmt::Display for Sha256Hash {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", hex::encode(self.0))
    }
}

impl schemars::JsonSchema for Sha256Hash {
    fn schema_name() -> String {
        "Sha256Hash".to_string()
    }

    fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
        String::json_schema(gen)
    }
}

#[derive(Clone, Debug)]
pub struct Sha256HashParseError {
    value: String,
    message: String,
}

impl std::fmt::Display for Sha256HashParseError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "could not parse value as sha256 hash: {} (value: '{}')",
            self.message, self.value
        )
    }
}

impl std::error::Error for Sha256HashParseError {}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn hash_sha256_parse_roundtrip() {
        let input = "c355cd53795b9b481f7eb2b5f4f6c8cf73631bdc343723a579d671e32db70b3c";
        let h1 = input
            .parse::<Sha256Hash>()
            .expect("string should parse to hash");

        assert_eq!(
            h1.0,
            [
                195, 85, 205, 83, 121, 91, 155, 72, 31, 126, 178, 181, 244, 246, 200, 207, 115, 99,
                27, 220, 52, 55, 35, 165, 121, 214, 113, 227, 45, 183, 11, 60
            ],
        );

        assert_eq!(h1.to_string(), input);
    }

    #[test]
    fn hash_sha256_parse_fails() {
        let res1 =
            "c355cd53795b9b481f7eb2b5f4f6c8cf73631bdc343723a579d671e32db70b3".parse::<Sha256Hash>();
        assert!(matches!(res1, Err(_)));

        let res2 = "".parse::<Sha256Hash>();
        assert!(matches!(res2, Err(_)));

        let res3 = "öööööööööööööööööööööööööööööööööööööööööööööööööööööööööööööööö"
            .parse::<Sha256Hash>();
        assert!(matches!(res3, Err(_)));
    }
}