1use crate::nixbase32;
2use crate::nixhash::NixHash;
3#[cfg(feature = "serde")]
4use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Unexpected, ser::SerializeMap};
5#[cfg(feature = "serde")]
6use serde_json::{Map, Value};
7use std::borrow::Cow;
8
9#[derive(Clone, Debug, Eq, PartialEq)]
16pub enum CAHash {
17 Flat(NixHash), Nar(NixHash), Text([u8; 32]), }
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum HashMode {
25 Flat,
26 Nar,
27 Text,
28}
29
30impl CAHash {
31 pub fn hash(&self) -> Cow<NixHash> {
32 match *self {
33 CAHash::Flat(ref digest) => Cow::Borrowed(digest),
34 CAHash::Nar(ref digest) => Cow::Borrowed(digest),
35 CAHash::Text(digest) => Cow::Owned(NixHash::Sha256(digest)),
36 }
37 }
38
39 pub fn mode(&self) -> HashMode {
40 match self {
41 CAHash::Flat(_) => HashMode::Flat,
42 CAHash::Nar(_) => HashMode::Nar,
43 CAHash::Text(_) => HashMode::Text,
44 }
45 }
46
47 pub fn from_nix_hex_str(s: &str) -> Option<Self> {
55 let (tag, s) = s.split_once(':')?;
56
57 match tag {
58 "text" => {
59 let digest = s.strip_prefix("sha256:")?;
60 let digest = nixbase32::decode_fixed(digest).ok()?;
61 Some(CAHash::Text(digest))
62 }
63 "fixed" => {
64 if let Some(s) = s.strip_prefix("r:") {
65 NixHash::from_nix_nixbase32(s).map(CAHash::Nar)
66 } else {
67 NixHash::from_nix_nixbase32(s).map(CAHash::Flat)
68 }
69 }
70 _ => None,
71 }
72 }
73
74 pub fn to_nix_nixbase32_string(&self) -> String {
77 let (algo, hash) = match self {
78 CAHash::Flat(h) => match h {
79 NixHash::Md5(h) => ("fixed:md5", &h[..]),
80 NixHash::Sha1(h) => ("fixed:sha1", &h[..]),
81 NixHash::Sha256(h) => ("fixed:sha256", &h[..]),
82 NixHash::Sha512(h) => ("fixed:sha512", &h[..]),
83 },
84 CAHash::Nar(h) => match h {
85 NixHash::Md5(h) => ("fixed:r:md5", &h[..]),
86 NixHash::Sha1(h) => ("fixed:r:sha1", &h[..]),
87 NixHash::Sha256(h) => ("fixed:r:sha256", &h[..]),
88 NixHash::Sha512(h) => ("fixed:r:sha512", &h[..]),
89 },
90 CAHash::Text(h) => ("text:sha256", &h[..]),
91 };
92
93 format!("{}:{}", algo, nixbase32::encode(hash))
94 }
95
96 #[cfg(feature = "serde")]
120 pub(crate) fn from_map<'de, D>(map: &Map<String, Value>) -> Result<Option<Self>, D::Error>
121 where
122 D: Deserializer<'de>,
123 {
124 use super::algos::SUPPORTED_ALGOS;
125 use super::decode_digest;
126 use crate::nixhash::HashAlgo;
127
128 if !map.contains_key("hash") && !map.contains_key("hashAlgo") {
130 return Ok(None);
131 }
132
133 let hash_algo_v = map.get("hashAlgo").ok_or_else(|| {
134 serde::de::Error::missing_field(
135 "couldn't extract `hashAlgo` key, but `hash` key present",
136 )
137 })?;
138 let hash_algo = hash_algo_v.as_str().ok_or_else(|| {
139 serde::de::Error::invalid_type(Unexpected::Other(&hash_algo_v.to_string()), &"a string")
140 })?;
141 let (mode_is_nar, hash_algo) = if let Some(s) = hash_algo.strip_prefix("r:") {
142 (true, s)
143 } else {
144 (false, hash_algo)
145 };
146 let hash_algo = HashAlgo::try_from(hash_algo).map_err(|e| {
147 serde::de::Error::invalid_value(
148 Unexpected::Other(&e.to_string()),
149 &format!("one of {}", SUPPORTED_ALGOS.join(",")).as_str(),
150 )
151 })?;
152
153 let hash_v = map.get("hash").ok_or_else(|| {
154 serde::de::Error::missing_field(
155 "couldn't extract `hash` key but `hashAlgo` key present",
156 )
157 })?;
158 let hash = hash_v.as_str().ok_or_else(|| {
159 serde::de::Error::invalid_type(Unexpected::Other(&hash_v.to_string()), &"a string")
160 })?;
161 let hash = decode_digest(hash.as_bytes(), hash_algo)
162 .map_err(|e| serde::de::Error::custom(e.to_string()))?;
163 if mode_is_nar {
164 Ok(Some(Self::Nar(hash)))
165 } else {
166 Ok(Some(Self::Flat(hash)))
167 }
168 }
169}
170
171#[cfg(feature = "serde")]
172impl Serialize for CAHash {
173 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
175 where
176 S: Serializer,
177 {
178 let mut map = serializer.serialize_map(Some(2))?;
179 match self {
180 CAHash::Flat(h) => {
181 map.serialize_entry("hash", &nixbase32::encode(h.digest_as_bytes()))?;
182 map.serialize_entry("hashAlgo", &h.algo())?;
183 }
184 CAHash::Nar(h) => {
185 map.serialize_entry("hash", &nixbase32::encode(h.digest_as_bytes()))?;
186 map.serialize_entry("hashAlgo", &format!("r:{}", &h.algo()))?;
187 }
188 CAHash::Text(h) => {
192 map.serialize_entry("hash", &nixbase32::encode(h.as_ref()))?;
193 map.serialize_entry("hashAlgo", "text")?;
194 }
195 };
196 map.end()
197 }
198}
199
200#[cfg(feature = "serde")]
201impl<'de> Deserialize<'de> for CAHash {
202 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
203 where
204 D: Deserializer<'de>,
205 {
206 let value = Self::from_map::<D>(&Map::deserialize(deserializer)?)?;
207
208 match value {
209 None => Err(serde::de::Error::custom("couldn't parse as map")),
210 Some(v) => Ok(v),
211 }
212 }
213}
214
215#[cfg(test)]
216mod tests {
217 #[cfg(feature = "serde")]
218 use hex_literal::hex;
219
220 #[cfg(feature = "serde")]
221 use crate::{
222 derivation::CAHash,
223 nixhash::{HashAlgo, NixHash},
224 };
225
226 #[cfg(feature = "serde")]
227 #[test]
228 fn serialize_flat() {
229 let json_bytes = r#"{
230 "hash": "1fnf2m46ya7r7afkcb8ba2j0sc4a85m749sh9jz64g4hx6z3r088",
231 "hashAlgo": "sha256"
232}"#;
233 let hash = CAHash::Flat(
234 NixHash::from_algo_and_digest(
235 HashAlgo::Sha256,
236 &hex!("08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba"),
237 )
238 .unwrap(),
239 );
240 let serialized = serde_json::to_string_pretty(&hash).unwrap();
241 assert_eq!(serialized, json_bytes);
242 }
243
244 #[cfg(feature = "serde")]
245 #[test]
246 fn serialize_nar() {
247 let json_bytes = r#"{
248 "hash": "1fnf2m46ya7r7afkcb8ba2j0sc4a85m749sh9jz64g4hx6z3r088",
249 "hashAlgo": "r:sha256"
250}"#;
251 let hash = CAHash::Nar(
252 NixHash::from_algo_and_digest(
253 HashAlgo::Sha256,
254 &hex!("08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba"),
255 )
256 .unwrap(),
257 );
258 let serialized = serde_json::to_string_pretty(&hash).unwrap();
259 assert_eq!(serialized, json_bytes);
260 }
261
262 #[cfg(feature = "serde")]
263 #[test]
264 fn deserialize_flat() {
265 let json_bytes = r#"
266 {
267 "hash": "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba",
268 "hashAlgo": "sha256"
269 }"#;
270 let hash: CAHash = serde_json::from_str(json_bytes).expect("must parse");
271
272 assert_eq!(
273 hash,
274 CAHash::Flat(
275 NixHash::from_algo_and_digest(
276 HashAlgo::Sha256,
277 &hex!("08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba")
278 )
279 .unwrap()
280 )
281 );
282 }
283
284 #[cfg(feature = "serde")]
285 #[test]
286 fn deserialize_hex() {
287 let json_bytes = r#"
288 {
289 "hash": "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba",
290 "hashAlgo": "r:sha256"
291 }"#;
292 let hash: CAHash = serde_json::from_str(json_bytes).expect("must parse");
293
294 assert_eq!(
295 hash,
296 CAHash::Nar(
297 NixHash::from_algo_and_digest(
298 HashAlgo::Sha256,
299 &hex!("08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba")
300 )
301 .unwrap()
302 )
303 );
304 }
305
306 #[cfg(feature = "serde")]
307 #[test]
308 fn deserialize_nixbase32() {
309 let json_bytes = r#"
310 {
311 "hash": "1fnf2m46ya7r7afkcb8ba2j0sc4a85m749sh9jz64g4hx6z3r088",
312 "hashAlgo": "r:sha256"
313 }"#;
314 let hash: CAHash = serde_json::from_str(json_bytes).expect("must parse");
315
316 assert_eq!(
317 hash,
318 CAHash::Nar(
319 NixHash::from_algo_and_digest(
320 HashAlgo::Sha256,
321 &hex!("08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba"),
322 )
323 .unwrap()
324 )
325 );
326 }
327
328 #[cfg(feature = "serde")]
329 #[test]
330 fn deserialize_base64() {
331 let json_bytes = r#"
332 {
333 "hash": "CIE8vumQPGK+TFAncmpBijANpFALLTadOvkob0gVzro=",
334 "hashAlgo": "r:sha256"
335 }"#;
336 let hash: CAHash = serde_json::from_str(json_bytes).expect("must parse");
337
338 assert_eq!(
339 hash,
340 CAHash::Nar(
341 NixHash::from_algo_and_digest(
342 HashAlgo::Sha256,
343 &hex!("08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba"),
344 )
345 .unwrap()
346 )
347 );
348 }
349
350 #[cfg(feature = "serde")]
351 #[test]
352 fn serialize_deserialize_nar() {
353 let json_bytes = r#"
354 {
355 "hash": "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba",
356 "hashAlgo": "r:sha256"
357 }"#;
358 let hash: CAHash = serde_json::from_str(json_bytes).expect("must parse");
359
360 let serialized = serde_json::to_string(&hash).expect("Serialize");
361 let hash2: CAHash = serde_json::from_str(&serialized).expect("must parse again");
362
363 assert_eq!(hash, hash2);
364 }
365
366 #[cfg(feature = "serde")]
367 #[test]
368 fn serialize_deserialize_flat() {
369 let json_bytes = r#"
370 {
371 "hash": "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba",
372 "hashAlgo": "sha256"
373 }"#;
374 let hash: CAHash = serde_json::from_str(json_bytes).expect("must parse");
375
376 let serialized = serde_json::to_string(&hash).expect("Serialize");
377 let hash2: CAHash = serde_json::from_str(&serialized).expect("must parse again");
378
379 assert_eq!(hash, hash2);
380 }
381}