1use std::borrow::Cow;
2
3#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
5pub struct Sha256Hash(pub [u8; 32]);
6
7impl Sha256Hash {
8 pub fn as_bytes(&self) -> &[u8; 32] {
9 &self.0
10 }
11
12 pub fn from_bytes(bytes: [u8; 32]) -> Self {
13 Self(bytes)
14 }
15}
16
17impl std::str::FromStr for Sha256Hash {
18 type Err = Sha256HashParseError;
19
20 fn from_str(s: &str) -> Result<Self, Self::Err> {
21 if s.len() != 64 {
22 return Err(Sha256HashParseError {
23 value: s.to_string(),
24 message: "invalid hash length - hash must have 64 hex-encoded characters "
25 .to_string(),
26 });
27 }
28
29 let bytes = hex::decode(s).map_err(|e| Sha256HashParseError {
30 value: s.to_string(),
31 message: e.to_string(),
32 })?;
33
34 Ok(Sha256Hash(bytes.try_into().unwrap()))
35 }
36}
37
38impl std::fmt::Debug for Sha256Hash {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 write!(f, "Sha256({})", hex::encode(self.0))
41 }
42}
43
44impl std::fmt::Display for Sha256Hash {
45 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46 write!(f, "{}", hex::encode(self.0))
47 }
48}
49
50impl schemars::JsonSchema for Sha256Hash {
51 fn schema_name() -> Cow<'static, str> {
52 Cow::Borrowed("Sha256Hash")
53 }
54
55 fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
56 String::json_schema(generator)
57 }
58
59 fn inline_schema() -> bool {
60 false
61 }
62
63 fn schema_id() -> std::borrow::Cow<'static, str> {
64 Self::schema_name()
65 }
66}
67
68#[derive(Clone, Debug)]
69pub struct Sha256HashParseError {
70 value: String,
71 message: String,
72}
73
74impl std::fmt::Display for Sha256HashParseError {
75 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76 write!(
77 f,
78 "could not parse value as sha256 hash: {} (value: '{}')",
79 self.message, self.value
80 )
81 }
82}
83
84impl std::error::Error for Sha256HashParseError {}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 #[test]
91 fn hash_sha256_parse_roundtrip() {
92 let input = "c355cd53795b9b481f7eb2b5f4f6c8cf73631bdc343723a579d671e32db70b3c";
93 let h1 = input
94 .parse::<Sha256Hash>()
95 .expect("string should parse to hash");
96
97 assert_eq!(
98 h1.0,
99 [
100 195, 85, 205, 83, 121, 91, 155, 72, 31, 126, 178, 181, 244, 246, 200, 207, 115, 99,
101 27, 220, 52, 55, 35, 165, 121, 214, 113, 227, 45, 183, 11, 60
102 ],
103 );
104
105 assert_eq!(h1.to_string(), input);
106 }
107
108 #[test]
109 fn hash_sha256_parse_fails() {
110 let res1 =
111 "c355cd53795b9b481f7eb2b5f4f6c8cf73631bdc343723a579d671e32db70b3".parse::<Sha256Hash>();
112 assert!(res1.is_err());
113
114 let res2 = "".parse::<Sha256Hash>();
115 assert!(res2.is_err());
116
117 let res3 = "öööööööööööööööööööööööööööööööööööööööööööööööööööööööööööööööö"
118 .parse::<Sha256Hash>();
119 assert!(res3.is_err());
120 }
121}