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"); } }