1 use std::path::PathBuf; 2 3 use anyhow::Result; 4 use serde::Deserialize; 5 6 use crate::common::target_arch::TargetArch; 7 8 use std::fs; 9 use toml; 10 11 /// The main configuration file for DADK 12 #[derive(Debug, Clone, Deserialize)] 13 pub struct DadkManifest { 14 pub metadata: Metadata, 15 16 /// A flag variable used to indicate whether 17 /// the default value function was called during deserialization. 18 #[serde(skip)] 19 pub used_default: bool, 20 } 21 22 impl DadkManifest { 23 pub fn load(path: &PathBuf) -> Result<Self> { 24 // 读取文件内容 25 let content = fs::read_to_string(path)?; 26 Self::do_load(&content) 27 } 28 29 fn do_load(content: &str) -> Result<Self> { 30 // Parse TOML content 31 let mut manifest_toml: DadkManifest = toml::from_str(content)?; 32 33 manifest_toml.used_default = check_used_default(); 34 35 Ok(manifest_toml) 36 } 37 } 38 39 thread_local! { 40 /// Global variable to track if default values were used during deserialization. 41 static USED_DEFAULT: std::cell::Cell<bool> = const { std::cell::Cell::new(false) }; 42 } 43 44 /// Call this function to set a flag when 45 /// default values are used during DADK manifest parsing 46 fn set_used_default() { 47 USED_DEFAULT.with(|used_default| { 48 used_default.set(true); 49 }); 50 } 51 52 /// Check if default values were used during deserialization. 53 fn check_used_default() -> bool { 54 USED_DEFAULT.with(|used_default| used_default.get()) 55 } 56 57 #[derive(Debug, Clone, Deserialize)] 58 pub struct Metadata { 59 /// Target processor architecture 60 pub arch: TargetArch, 61 /// Rootfs configuration file path 62 #[serde(default = "default_rootfs_config_path")] 63 pub rootfs_config: PathBuf, 64 65 /// Hypervisor configuration file path 66 #[serde(default = "default_hypervisor_config_path")] 67 pub hypervisor_config: PathBuf, 68 69 /// Boot configuration file path 70 #[serde(default = "default_boot_config_path")] 71 pub boot_config: PathBuf, 72 } 73 74 /// Returns the default path for the rootfs configuration file. 75 fn default_rootfs_config_path() -> PathBuf { 76 set_used_default(); 77 "config/rootfs.toml".into() 78 } 79 80 /// Returns the default path for the hypervisor configuration file. 81 fn default_hypervisor_config_path() -> PathBuf { 82 set_used_default(); 83 "config/hypervisor.toml".into() 84 } 85 86 /// Returns the default path for the boot configuration file. 87 fn default_boot_config_path() -> PathBuf { 88 set_used_default(); 89 "config/boot.toml".into() 90 } 91 92 #[cfg(test)] 93 mod tests { 94 use super::*; 95 use std::io::Write; 96 use tempfile::NamedTempFile; 97 98 /// Test loading a complete configuration file 99 #[test] 100 fn test_full_load_success() -> Result<()> { 101 let toml_content = r#" 102 [metadata] 103 arch = "x86_64" 104 rootfs_config = "config/rootfs-x86_64.toml" 105 hypervisor_config = "config/hypervisor-x86_64.toml" 106 boot_config = "config/boot-x86_64.toml" 107 "#; 108 109 let mut temp_file = NamedTempFile::new()?; 110 temp_file.write_all(toml_content.as_bytes())?; 111 112 let path = temp_file.path().to_path_buf(); 113 let manifest = DadkManifest::load(&path)?; 114 115 assert_eq!(manifest.metadata.arch, TargetArch::X86_64); 116 assert_eq!( 117 manifest.metadata.rootfs_config, 118 PathBuf::from("config/rootfs-x86_64.toml") 119 ); 120 assert_eq!( 121 manifest.metadata.hypervisor_config, 122 PathBuf::from("config/hypervisor-x86_64.toml") 123 ); 124 assert_eq!( 125 manifest.metadata.boot_config, 126 PathBuf::from("config/boot-x86_64.toml") 127 ); 128 assert!(!manifest.used_default); 129 130 Ok(()) 131 } 132 133 /// Test whether an error is reported when the file does not exist. 134 #[test] 135 fn test_load_file_not_found() { 136 let path = PathBuf::from("non_existent_file.toml"); 137 let result = DadkManifest::load(&path); 138 139 assert!(result.is_err()); 140 } 141 142 /// Test whether an error is reported when the TOML content is invalid 143 #[test] 144 fn test_load_invalid_toml() -> Result<()> { 145 let invalid_toml_content = r#" 146 [metadata 147 arch = "x86_64" 148 "#; 149 150 let mut temp_file = NamedTempFile::new()?; 151 temp_file.write_all(invalid_toml_content.as_bytes())?; 152 153 let path = temp_file.path().to_path_buf(); 154 let result = DadkManifest::load(&path); 155 156 assert!(result.is_err()); 157 158 Ok(()) 159 } 160 161 /// Test whether an error is reported when the arch field is invalid 162 #[test] 163 fn test_load_invalid_arch_toml() -> Result<()> { 164 // Invalid arch value 165 let invalid_toml_content = r#" 166 [metadata] 167 arch = "abcde" 168 "#; 169 170 let mut temp_file = NamedTempFile::new()?; 171 temp_file.write_all(invalid_toml_content.as_bytes())?; 172 173 let path = temp_file.path().to_path_buf(); 174 let result = DadkManifest::load(&path); 175 176 assert!(result.is_err()); 177 178 Ok(()) 179 } 180 181 /// Test whether an error is reported when a required field is missing 182 #[test] 183 fn test_load_missing_required_fields() -> Result<()> { 184 let toml_content = r#" 185 [metadata] 186 # arch field is missing 187 "#; 188 189 let mut temp_file = NamedTempFile::new()?; 190 temp_file.write_all(toml_content.as_bytes())?; 191 192 let path = temp_file.path().to_path_buf(); 193 let result = DadkManifest::load(&path); 194 195 assert!(result.is_err()); 196 197 Ok(()) 198 } 199 200 /// Test whether default values are used 201 /// when the rootfs_config and other configuration file path fields are not set 202 #[test] 203 fn test_load_default_config_path_value() -> Result<()> { 204 let toml_content = r#" 205 [metadata] 206 arch = "x86_64" 207 "#; 208 let mut temp_file = NamedTempFile::new()?; 209 temp_file.write_all(toml_content.as_bytes())?; 210 let path = temp_file.path().to_path_buf(); 211 let manifest = DadkManifest::load(&path)?; 212 assert_eq!(manifest.used_default, true); 213 assert_eq!( 214 manifest.metadata.rootfs_config, 215 PathBuf::from("config/rootfs.toml") 216 ); 217 assert_eq!( 218 manifest.metadata.hypervisor_config, 219 PathBuf::from("config/hypervisor.toml") 220 ); 221 assert_eq!( 222 manifest.metadata.boot_config, 223 PathBuf::from("config/boot.toml") 224 ); 225 Ok(()) 226 } 227 } 228