Skip to main content

nix_compat/derivation/
output.rs

1use std::str::FromStr;
2
3use crate::nixhash;
4use crate::nixhash::HashAlgo;
5use crate::nixhash::NixHash;
6
7/// Represents the information about the hash of a single-output FOD.
8/// We store it in a [OutputHashMode] and [NixHash].
9/// The serde model uses a different format, as we want to emit the same JSON:
10/// There we use `hashAlgo` and `hash`:
11///  - `hashAlgo`: optional `r:` prefix (for recursive),
12///    followed by hash algo identifier (`sha1`, `sha256`, `sha512`, `md5`)
13///  - `hash`: hexlower-encoded digest
14#[derive(Clone, Debug, Eq, PartialEq)]
15pub struct OutputHash {
16    /// Hashing mode for this output. Either `Flat` or `Recursive`.
17    pub mode: OutputHashMode,
18    /// The expected hash for this output.
19    pub hash: NixHash,
20}
21
22/// Whether the FOD describes the hash of the raw contents (only possible if it's a single file),
23/// or a digest over the NAR representation of the contents.
24#[derive(Clone, Debug, Default, Eq, PartialEq)]
25pub enum OutputHashMode {
26    ///The output uses flat hashing mode.
27    #[default]
28    Flat,
29    ///The output uses recursive hashing mode. This is also called NAR hashing mode.
30    Recursive,
31}
32
33impl OutputHashMode {
34    /// Return the prefix for this `OutputMode` as used in ATerm representation.
35    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    /// Construct from a string containing the algo (with an optional `r:` prefix), and a digest.
57    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    /// Returns the OutputHashMode prefix str and the algo, concatenated.
76    /// This is used in the ATerm representation.
77    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/// Errors that can occur during the validation of a specific
91// [crate::derivation::Output] of a [crate::derivation::Derivation].
92#[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}