use std::path::PathBuf; use anyhow::Result; use serde::Deserialize; use crate::common::target_arch::TargetArch; use std::fs; use toml; /// The main configuration file for DADK #[derive(Debug, Clone, Deserialize)] pub struct DadkManifestFile { pub metadata: Metadata, /// A flag variable used to indicate whether /// the default value function was called during deserialization. #[serde(skip)] pub used_default: bool, } impl DadkManifestFile { pub fn load(path: &PathBuf) -> Result { // 读取文件内容 let content = fs::read_to_string(path)?; Self::load_from_str(&content) } pub fn load_from_str(content: &str) -> Result { // Parse TOML content let mut manifest_toml: DadkManifestFile = toml::from_str(content)?; manifest_toml.used_default = check_used_default(); Ok(manifest_toml) } } thread_local! { /// Global variable to track if default values were used during deserialization. static USED_DEFAULT: std::cell::Cell = const { std::cell::Cell::new(false) }; } /// Call this function to set a flag when /// default values are used during DADK manifest parsing fn set_used_default() { USED_DEFAULT.with(|used_default| { used_default.set(true); }); } /// Check if default values were used during deserialization. fn check_used_default() -> bool { USED_DEFAULT.with(|used_default| used_default.get()) } #[derive(Debug, Clone, Deserialize)] pub struct Metadata { /// Target processor architecture pub arch: TargetArch, /// Rootfs configuration file path #[serde(default = "default_rootfs_config_path")] pub rootfs_config: PathBuf, /// Hypervisor configuration file path #[serde(default = "default_hypervisor_config_path")] pub hypervisor_config: PathBuf, /// Boot configuration file path #[serde(default = "default_boot_config_path")] pub boot_config: PathBuf, } /// Returns the default path for the rootfs configuration file. fn default_rootfs_config_path() -> PathBuf { set_used_default(); "config/rootfs.toml".into() } /// Returns the default path for the hypervisor configuration file. fn default_hypervisor_config_path() -> PathBuf { set_used_default(); "config/hypervisor.toml".into() } /// Returns the default path for the boot configuration file. fn default_boot_config_path() -> PathBuf { set_used_default(); "config/boot.toml".into() } #[cfg(test)] mod tests { use super::*; use std::io::Write; use tempfile::NamedTempFile; /// Test loading a complete configuration file #[test] fn test_full_load_success() -> Result<()> { let toml_content = r#" [metadata] arch = "x86_64" rootfs_config = "config/rootfs-x86_64.toml" hypervisor_config = "config/hypervisor-x86_64.toml" boot_config = "config/boot-x86_64.toml" "#; let mut temp_file = NamedTempFile::new()?; temp_file.write_all(toml_content.as_bytes())?; let path = temp_file.path().to_path_buf(); let manifest = DadkManifestFile::load(&path)?; assert_eq!(manifest.metadata.arch, TargetArch::X86_64); assert_eq!( manifest.metadata.rootfs_config, PathBuf::from("config/rootfs-x86_64.toml") ); assert_eq!( manifest.metadata.hypervisor_config, PathBuf::from("config/hypervisor-x86_64.toml") ); assert_eq!( manifest.metadata.boot_config, PathBuf::from("config/boot-x86_64.toml") ); assert!(!manifest.used_default); Ok(()) } /// Test whether an error is reported when the file does not exist. #[test] fn test_load_file_not_found() { let path = PathBuf::from("non_existent_file.toml"); let result = DadkManifestFile::load(&path); assert!(result.is_err()); } /// Test whether an error is reported when the TOML content is invalid #[test] fn test_load_invalid_toml() -> Result<()> { let invalid_toml_content = r#" [metadata arch = "x86_64" "#; let mut temp_file = NamedTempFile::new()?; temp_file.write_all(invalid_toml_content.as_bytes())?; let path = temp_file.path().to_path_buf(); let result = DadkManifestFile::load(&path); assert!(result.is_err()); Ok(()) } /// Test whether an error is reported when the arch field is invalid #[test] fn test_load_invalid_arch_toml() -> Result<()> { // Invalid arch value let invalid_toml_content = r#" [metadata] arch = "abcde" "#; let mut temp_file = NamedTempFile::new()?; temp_file.write_all(invalid_toml_content.as_bytes())?; let path = temp_file.path().to_path_buf(); let result = DadkManifestFile::load(&path); assert!(result.is_err()); Ok(()) } /// Test whether an error is reported when a required field is missing #[test] fn test_load_missing_required_fields() -> Result<()> { let toml_content = r#" [metadata] # arch field is missing "#; let mut temp_file = NamedTempFile::new()?; temp_file.write_all(toml_content.as_bytes())?; let path = temp_file.path().to_path_buf(); let result = DadkManifestFile::load(&path); assert!(result.is_err()); Ok(()) } /// Test whether default values are used /// when the rootfs_config and other configuration file path fields are not set #[test] fn test_load_default_config_path_value() -> Result<()> { let toml_content = r#" [metadata] arch = "x86_64" "#; let mut temp_file = NamedTempFile::new()?; temp_file.write_all(toml_content.as_bytes())?; let path = temp_file.path().to_path_buf(); let manifest = DadkManifestFile::load(&path)?; assert_eq!(manifest.used_default, true); assert_eq!( manifest.metadata.rootfs_config, PathBuf::from("config/rootfs.toml") ); assert_eq!( manifest.metadata.hypervisor_config, PathBuf::from("config/hypervisor.toml") ); assert_eq!( manifest.metadata.boot_config, PathBuf::from("config/boot.toml") ); Ok(()) } }