nix_compat/derivation/
validate.rs

1use crate::derivation::{Derivation, DerivationError};
2use crate::store_path;
3
4impl Derivation {
5    /// validate ensures a Derivation struct is properly populated,
6    /// and returns a [DerivationError] if not.
7    ///
8    /// if `validate_output_paths` is set to false, the output paths are
9    /// excluded from validation.
10    ///
11    /// This is helpful to validate struct population before invoking
12    /// [Derivation::calculate_output_paths].
13    pub fn validate(&self, validate_output_paths: bool) -> Result<(), DerivationError> {
14        // Ensure the number of outputs is > 1
15        if self.outputs.is_empty() {
16            return Err(DerivationError::NoOutputs());
17        }
18
19        // Validate all outputs
20        for (output_name, output) in &self.outputs {
21            // empty output names are invalid.
22            //
23            // `drv` is an invalid output name too, as this would cause
24            // a `builtins.derivation` call to return an attrset with a
25            // `drvPath` key (which already exists) and has a different
26            // meaning.
27            //
28            // Other output names that don't match the name restrictions from
29            // [StorePathRef] will fail the [StorePathRef::validate_name] check.
30            if output_name.is_empty()
31                || output_name == "drv"
32                || store_path::validate_name(output_name.as_bytes()).is_err()
33            {
34                return Err(DerivationError::InvalidOutputName(output_name.to_string()));
35            }
36
37            if output.is_fixed() {
38                if self.outputs.len() != 1 {
39                    return Err(DerivationError::MoreThanOneOutputButFixed());
40                }
41                if output_name != "out" {
42                    return Err(DerivationError::InvalidOutputNameForFixed(
43                        output_name.to_string(),
44                    ));
45                }
46            }
47
48            if let Err(e) = output.validate(validate_output_paths) {
49                return Err(DerivationError::InvalidOutput(output_name.to_string(), e));
50            }
51        }
52
53        // Validate all input_derivations
54        for (input_derivation_path, output_names) in &self.input_derivations {
55            // Validate input_derivation_path
56            if !input_derivation_path.name().ends_with(".drv") {
57                return Err(DerivationError::InvalidInputDerivationPrefix(
58                    input_derivation_path.to_string(),
59                ));
60            }
61
62            if output_names.is_empty() {
63                return Err(DerivationError::EmptyInputDerivationOutputNames(
64                    input_derivation_path.to_string(),
65                ));
66            }
67
68            for output_name in output_names.iter() {
69                // empty output names are invalid.
70                //
71                // `drv` is an invalid output name too, as this would cause
72                // a `builtins.derivation` call to return an attrset with a
73                // `drvPath` key (which already exists) and has a different
74                // meaning.
75                //
76                // Other output names that don't match the name restrictions from
77                // [StorePath] will fail the [StorePathRef::validate_name] check.
78                if output_name.is_empty()
79                    || output_name == "drv"
80                    || store_path::validate_name(output_name.as_bytes()).is_err()
81                {
82                    return Err(DerivationError::InvalidInputDerivationOutputName(
83                        input_derivation_path.to_string(),
84                        output_name.to_string(),
85                    ));
86                }
87            }
88        }
89
90        // validate platform
91        if self.system.is_empty() {
92            return Err(DerivationError::InvalidPlatform(self.system.to_string()));
93        }
94
95        // validate builder
96        if self.builder.is_empty() {
97            return Err(DerivationError::InvalidBuilder(self.builder.to_string()));
98        }
99
100        // validate env, none of the keys may be empty.
101        // We skip the `name` validation seen in go-nix.
102        for k in self.environment.keys() {
103            if k.is_empty() {
104                return Err(DerivationError::InvalidEnvironmentKey(k.to_string()));
105            }
106        }
107
108        Ok(())
109    }
110}
111
112#[cfg(test)]
113mod test {
114    use std::collections::BTreeMap;
115
116    use crate::derivation::{CAHash, Derivation, Output};
117
118    /// Regression test: produce a Derivation that's almost valid, except its
119    /// fixed-output output has the wrong hash specified.
120    #[test]
121    fn output_validate() {
122        let mut outputs = BTreeMap::new();
123        outputs.insert(
124            "out".to_string(),
125            Output {
126                path: None,
127                ca_hash: Some(CAHash::Text([0; 32])), // This is disallowed
128            },
129        );
130
131        let drv = Derivation {
132            arguments: vec![],
133            builder: "/bin/sh".to_string(),
134            outputs,
135            system: "x86_64-linux".to_string(),
136            ..Default::default()
137        };
138
139        drv.validate(false).expect_err("must fail");
140    }
141}