nix_compat/derivation/
validate.rs

1use crate::derivation::{Derivation, DerivationError};
2use crate::store_path;
3
4/// Validates an output name using derivation output name rules.
5///
6/// Output names must:
7/// - Not be empty
8/// - Not be "drv" (reserved name that would conflict with the existing drvPath key in builtins.derivation)
9/// - Pass the `store_path::validate_name` check
10///
11/// This function is used by both derivation validation and builtins.placeholder.
12pub fn validate_output_name(output_name: &str) -> Result<(), DerivationError> {
13    if output_name.is_empty()
14        || output_name == "drv"
15        || store_path::validate_name(output_name.as_bytes()).is_err()
16    {
17        return Err(DerivationError::InvalidOutputName(output_name.to_string()));
18    }
19    Ok(())
20}
21
22impl Derivation {
23    /// validate ensures a Derivation struct is properly populated,
24    /// and returns a [DerivationError] if not.
25    ///
26    /// if `validate_output_paths` is set to false, the output paths are
27    /// excluded from validation.
28    ///
29    /// This is helpful to validate struct population before invoking
30    /// [Derivation::calculate_output_paths].
31    pub fn validate(&self, validate_output_paths: bool) -> Result<(), DerivationError> {
32        // Ensure the number of outputs is > 1
33        if self.outputs.is_empty() {
34            return Err(DerivationError::NoOutputs());
35        }
36
37        // Validate all outputs
38        for (output_name, output) in &self.outputs {
39            validate_output_name(output_name)?;
40
41            if output.is_fixed() {
42                if self.outputs.len() != 1 {
43                    return Err(DerivationError::MoreThanOneOutputButFixed());
44                }
45                if output_name != "out" {
46                    return Err(DerivationError::InvalidOutputNameForFixed(
47                        output_name.to_string(),
48                    ));
49                }
50            }
51
52            if let Err(e) = output.validate(validate_output_paths) {
53                return Err(DerivationError::InvalidOutput(output_name.to_string(), e));
54            }
55        }
56
57        // Validate all input_derivations
58        for (input_derivation_path, output_names) in &self.input_derivations {
59            // Validate input_derivation_path
60            if !input_derivation_path.name().ends_with(".drv") {
61                return Err(DerivationError::InvalidInputDerivationPrefix(
62                    input_derivation_path.to_string(),
63                ));
64            }
65
66            if output_names.is_empty() {
67                return Err(DerivationError::EmptyInputDerivationOutputNames(
68                    input_derivation_path.to_string(),
69                ));
70            }
71
72            for output_name in output_names.iter() {
73                // For input derivation output names, we use the same validation
74                // but map the error to the appropriate InputDerivationOutputName variant
75                if let Err(DerivationError::InvalidOutputName(_)) =
76                    validate_output_name(output_name)
77                {
78                    return Err(DerivationError::InvalidInputDerivationOutputName(
79                        input_derivation_path.to_string(),
80                        output_name.to_string(),
81                    ));
82                }
83            }
84        }
85
86        // validate platform
87        if self.system.is_empty() {
88            return Err(DerivationError::InvalidPlatform(self.system.to_string()));
89        }
90
91        // validate builder
92        if self.builder.is_empty() {
93            return Err(DerivationError::InvalidBuilder(self.builder.to_string()));
94        }
95
96        // validate env, none of the keys may be empty.
97        // We skip the `name` validation seen in go-nix.
98        for k in self.environment.keys() {
99            if k.is_empty() {
100                return Err(DerivationError::InvalidEnvironmentKey(k.to_string()));
101            }
102        }
103
104        Ok(())
105    }
106}
107
108#[cfg(test)]
109mod test {
110    use std::collections::BTreeMap;
111
112    use super::validate_output_name;
113    use crate::derivation::{CAHash, Derivation, DerivationError, Output};
114
115    /// Test the validate_output_name function with valid names
116    #[test]
117    fn test_validate_output_name_valid() {
118        // Valid output names should pass
119        assert!(validate_output_name("out").is_ok());
120        assert!(validate_output_name("dev").is_ok());
121        assert!(validate_output_name("lib").is_ok());
122        assert!(validate_output_name("bin").is_ok());
123        assert!(validate_output_name("debug").is_ok());
124    }
125
126    /// Test the validate_output_name function with invalid names
127    #[test]
128    fn test_validate_output_name_invalid() {
129        // Empty name should fail
130        assert!(matches!(
131            validate_output_name(""),
132            Err(DerivationError::InvalidOutputName(_))
133        ));
134
135        // "drv" is reserved and should fail
136        assert!(matches!(
137            validate_output_name("drv"),
138            Err(DerivationError::InvalidOutputName(_))
139        ));
140
141        // Invalid characters should fail
142        assert!(matches!(
143            validate_output_name("invalid/name"),
144            Err(DerivationError::InvalidOutputName(_))
145        ));
146
147        assert!(matches!(
148            validate_output_name("invalid name"),
149            Err(DerivationError::InvalidOutputName(_))
150        ));
151    }
152
153    /// Regression test: produce a Derivation that's almost valid, except its
154    /// fixed-output output has the wrong hash specified.
155    #[test]
156    fn output_validate() {
157        let mut outputs = BTreeMap::new();
158        outputs.insert(
159            "out".to_string(),
160            Output {
161                path: None,
162                ca_hash: Some(CAHash::Text([0; 32])), // This is disallowed
163            },
164        );
165
166        let drv = Derivation {
167            arguments: vec![],
168            builder: "/bin/sh".to_string(),
169            outputs,
170            system: "x86_64-linux".to_string(),
171            ..Default::default()
172        };
173
174        drv.validate(false).expect_err("must fail");
175    }
176}