1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
use crate::derivation::{Derivation, DerivationError};
use crate::store_path;

impl Derivation {
    /// validate ensures a Derivation struct is properly populated,
    /// and returns a [DerivationError] if not.
    ///
    /// if `validate_output_paths` is set to false, the output paths are
    /// excluded from validation.
    ///
    /// This is helpful to validate struct population before invoking
    /// [Derivation::calculate_output_paths].
    pub fn validate(&self, validate_output_paths: bool) -> Result<(), DerivationError> {
        // Ensure the number of outputs is > 1
        if self.outputs.is_empty() {
            return Err(DerivationError::NoOutputs());
        }

        // Validate all outputs
        for (output_name, output) in &self.outputs {
            // empty output names are invalid.
            //
            // `drv` is an invalid output name too, as this would cause
            // a `builtins.derivation` call to return an attrset with a
            // `drvPath` key (which already exists) and has a different
            // meaning.
            //
            // Other output names that don't match the name restrictions from
            // [StorePathRef] will fail the [StorePathRef::validate_name] check.
            if output_name.is_empty()
                || output_name == "drv"
                || store_path::validate_name(output_name.as_bytes()).is_err()
            {
                return Err(DerivationError::InvalidOutputName(output_name.to_string()));
            }

            if output.is_fixed() {
                if self.outputs.len() != 1 {
                    return Err(DerivationError::MoreThanOneOutputButFixed());
                }
                if output_name != "out" {
                    return Err(DerivationError::InvalidOutputNameForFixed(
                        output_name.to_string(),
                    ));
                }
            }

            if let Err(e) = output.validate(validate_output_paths) {
                return Err(DerivationError::InvalidOutput(output_name.to_string(), e));
            }
        }

        // Validate all input_derivations
        for (input_derivation_path, output_names) in &self.input_derivations {
            // Validate input_derivation_path
            if !input_derivation_path.name().ends_with(".drv") {
                return Err(DerivationError::InvalidInputDerivationPrefix(
                    input_derivation_path.to_string(),
                ));
            }

            if output_names.is_empty() {
                return Err(DerivationError::EmptyInputDerivationOutputNames(
                    input_derivation_path.to_string(),
                ));
            }

            for output_name in output_names.iter() {
                // empty output names are invalid.
                //
                // `drv` is an invalid output name too, as this would cause
                // a `builtins.derivation` call to return an attrset with a
                // `drvPath` key (which already exists) and has a different
                // meaning.
                //
                // Other output names that don't match the name restrictions from
                // [StorePath] will fail the [StorePathRef::validate_name] check.
                if output_name.is_empty()
                    || output_name == "drv"
                    || store_path::validate_name(output_name.as_bytes()).is_err()
                {
                    return Err(DerivationError::InvalidInputDerivationOutputName(
                        input_derivation_path.to_string(),
                        output_name.to_string(),
                    ));
                }
            }
        }

        // validate platform
        if self.system.is_empty() {
            return Err(DerivationError::InvalidPlatform(self.system.to_string()));
        }

        // validate builder
        if self.builder.is_empty() {
            return Err(DerivationError::InvalidBuilder(self.builder.to_string()));
        }

        // validate env, none of the keys may be empty.
        // We skip the `name` validation seen in go-nix.
        for k in self.environment.keys() {
            if k.is_empty() {
                return Err(DerivationError::InvalidEnvironmentKey(k.to_string()));
            }
        }

        Ok(())
    }
}

#[cfg(test)]
mod test {
    use std::collections::BTreeMap;

    use crate::derivation::{CAHash, Derivation, Output};

    /// Regression test: produce a Derivation that's almost valid, except its
    /// fixed-output output has the wrong hash specified.
    #[test]
    fn output_validate() {
        let mut outputs = BTreeMap::new();
        outputs.insert(
            "out".to_string(),
            Output {
                path: None,
                ca_hash: Some(CAHash::Text([0; 32])), // This is disallowed
            },
        );

        let drv = Derivation {
            arguments: vec![],
            builder: "/bin/sh".to_string(),
            outputs,
            system: "x86_64-linux".to_string(),
            ..Default::default()
        };

        drv.validate(false).expect_err("must fail");
    }
}