use serde::Deserialize;
use super::{
balloon::RawBalloonConfig, boot_source::RawBootSourceConfig, cpu_config::RawCpuConfig,
drive::RawDriveConfig, entropy::RawEntropyConfig, logger::RawLoggerConfig,
machine_config::RawMachineConfig, metrics::RawMetricsConfig, mmds::RawMmdsConfig,
network::RawNetworkInterfaceConfig, pmem::RawPmemConfig, serial::RawSerialConfig,
vsock::RawVsockConfig,
};
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SquibNetworkMode {
#[default]
Shared,
Bridged,
Host,
Userspace,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SquibExtension {
#[serde(default)]
pub network: Option<SquibNetworkMode>,
#[serde(default)]
pub vsock_tsi: bool,
#[serde(default)]
pub gvproxy_path: Option<String>,
#[serde(default)]
pub macos_sandbox_profile: Option<String>,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct ConfigFile {
#[serde(default)]
pub boot_source: Option<RawBootSourceConfig>,
#[serde(default)]
pub drives: Vec<RawDriveConfig>,
#[serde(default)]
pub machine_config: Option<RawMachineConfig>,
#[serde(default)]
pub cpu_config: Option<RawCpuConfig>,
#[serde(default)]
pub network_interfaces: Vec<RawNetworkInterfaceConfig>,
#[serde(default)]
pub vsock: Option<RawVsockConfig>,
#[serde(default)]
pub mmds_config: Option<RawMmdsConfig>,
#[serde(default)]
pub mmds: Option<serde_json::Value>,
#[serde(default)]
pub balloon: Option<RawBalloonConfig>,
#[serde(default)]
pub entropy: Option<RawEntropyConfig>,
#[serde(default)]
pub serial: Option<RawSerialConfig>,
#[serde(default)]
pub pmem: Vec<RawPmemConfig>,
#[serde(default)]
pub hotplug_memory: Option<super::hotplug_memory::RawHotplugMemoryConfig>,
#[serde(default)]
pub logger: Option<RawLoggerConfig>,
#[serde(default)]
pub metrics: Option<RawMetricsConfig>,
#[serde(default)]
pub squib: SquibExtension,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_should_parse_minimal_config_file() {
let json = r#"{"boot-source":{"kernel_image_path":"/tmp/k"}}"#;
let cfg: ConfigFile = serde_json::from_str(json).unwrap();
assert!(cfg.boot_source.is_some());
assert!(cfg.drives.is_empty());
}
#[test]
fn test_should_tolerate_unknown_top_level_keys() {
let json = r#"{"boot-source":{"kernel_image_path":"/tmp/k"},"future-key":42}"#;
let cfg: ConfigFile = serde_json::from_str(json).unwrap();
assert!(cfg.boot_source.is_some());
}
#[test]
fn test_should_reject_unknown_keys_inside_squib_extension() {
let json = r#"{"squib":{"unknown":1}}"#;
let res: Result<ConfigFile, _> = serde_json::from_str(json);
assert!(res.is_err());
}
#[test]
fn test_should_parse_squib_network_mode() {
let json = r#"{"squib":{"network":"shared"}}"#;
let cfg: ConfigFile = serde_json::from_str(json).unwrap();
assert_eq!(cfg.squib.network, Some(SquibNetworkMode::Shared));
}
#[test]
fn test_should_parse_full_envelope() {
let json = r#"{
"boot-source": {"kernel_image_path":"/tmp/k","boot_args":"console=ttyAMA0"},
"machine-config": {"vcpu_count":2,"mem_size_mib":256},
"drives": [{"drive_id":"rootfs","path_on_host":"/tmp/r.img","is_root_device":true}],
"network-interfaces": [{"iface_id":"eth0","host_dev_name":"tap0"}],
"logger": {"log_path":"/tmp/squib.log"},
"squib": {"network":"userspace","vsock_tsi":false}
}"#;
let cfg: ConfigFile = serde_json::from_str(json).unwrap();
assert_eq!(cfg.machine_config.as_ref().unwrap().vcpu_count, 2);
assert_eq!(cfg.drives.len(), 1);
assert_eq!(cfg.network_interfaces.len(), 1);
assert_eq!(cfg.squib.network, Some(SquibNetworkMode::Userspace));
}
}