nix_compat/derivation/
output.rs1use std::str::FromStr;
2
3use crate::nixhash;
4use crate::nixhash::HashAlgo;
5use crate::nixhash::NixHash;
6
7#[derive(Clone, Debug, Eq, PartialEq)]
15pub struct OutputHash {
16 pub mode: OutputHashMode,
18 pub hash: NixHash,
20}
21
22#[derive(Clone, Debug, Default, Eq, PartialEq)]
25pub enum OutputHashMode {
26 #[default]
28 Flat,
29 Recursive,
31}
32
33impl OutputHashMode {
34 pub const fn as_mode_prefix(&self) -> &'static str {
36 match self {
37 OutputHashMode::Flat => "",
38 OutputHashMode::Recursive => "r:",
39 }
40 }
41}
42
43impl FromStr for OutputHashMode {
44 type Err = ParseOutputHashModeError;
45
46 fn from_str(s: &str) -> Result<Self, Self::Err> {
47 match s {
48 "" | "flat" => Ok(Self::Flat),
49 "recursive" => Ok(Self::Recursive),
50 _ => Err(ParseOutputHashModeError::InvalidHashMode(s.to_owned())),
51 }
52 }
53}
54
55impl OutputHash {
56 pub fn from_mode_algo_and_digest(
58 mode_and_algo: &str,
59 digest: impl AsRef<[u8]>,
60 ) -> Result<Self, nixhash::Error> {
61 let (hash_mode, algo_str) = if let Some(algo_str) = mode_and_algo.strip_prefix("r:") {
62 (OutputHashMode::Recursive, algo_str)
63 } else {
64 (OutputHashMode::Flat, mode_and_algo)
65 };
66
67 let algo = algo_str.parse()?;
68
69 Ok(OutputHash {
70 mode: hash_mode,
71 hash: NixHash::from_algo_and_digest(algo, digest.as_ref())?,
72 })
73 }
74
75 pub const fn as_mode_and_algo_str(&self) -> &'static str {
78 match self.mode {
79 OutputHashMode::Flat => self.hash.algo().as_str(),
80 OutputHashMode::Recursive => match self.hash.algo() {
81 HashAlgo::Md5 => "r:md5",
82 HashAlgo::Sha1 => "r:sha1",
83 HashAlgo::Sha256 => "r:sha256",
84 HashAlgo::Sha512 => "r:sha512",
85 },
86 }
87 }
88}
89
90#[derive(Debug, thiserror::Error, PartialEq)]
93pub enum ParseOutputHashModeError {
94 #[error("Invalid hash mode: {0}")]
95 InvalidHashMode(String),
96}
97
98#[cfg(test)]
99mod tests {
100 use crate::nixhash::NixHash;
101
102 use super::{OutputHash, OutputHashMode};
103 use hex_literal::hex;
104 use rstest::rstest;
105
106 const DIGEST_SHA256: [u8; 32] =
107 hex!("a5ce9c155ed09397614646c9717fc7cd94b1023d7b76b618d409e4fefd6e9d39");
108 const NIXHASH_SHA256: NixHash = NixHash::Sha256(DIGEST_SHA256);
109
110 #[rstest]
111 #[case::sha256_flat("sha256", &DIGEST_SHA256, OutputHash { mode: OutputHashMode::Flat, hash: NIXHASH_SHA256.clone()})]
112 #[case::sha256_recursive("r:sha256", &DIGEST_SHA256, OutputHash { mode: OutputHashMode::Recursive, hash: NIXHASH_SHA256.clone()})]
113 fn test_from_algo_and_mode_and_digest(
114 #[case] algo_and_mode: &str,
115 #[case] digest: &[u8],
116 #[case] expected: OutputHash,
117 ) {
118 assert_eq!(
119 expected,
120 OutputHash::from_mode_algo_and_digest(algo_and_mode, digest).expect("to parse")
121 );
122 }
123
124 #[test]
125 fn from_algo_and_mode_and_digest_failure() {
126 assert!(OutputHash::from_mode_algo_and_digest("r:sha256", []).is_err());
127 assert!(OutputHash::from_mode_algo_and_digest("ha256", DIGEST_SHA256).is_err());
128 }
129}