use serde::{Deserialize, Serialize};
use squib_core::HostDevName;
use super::common::{IfaceId, MacAddr};
#[derive(Debug, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RawNetworkInterfaceConfig {
pub iface_id: String,
pub host_dev_name: String,
#[serde(default)]
pub guest_mac: Option<String>,
#[serde(default)]
pub rx_rate_limiter: Option<serde_json::Value>,
#[serde(default)]
pub tx_rate_limiter: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize)]
#[non_exhaustive]
pub struct NetworkInterfaceConfig {
pub iface_id: IfaceId,
pub host_dev_name: HostDevName,
pub guest_mac: Option<MacAddr>,
pub rx_rate_limiter: Option<serde_json::Value>,
pub tx_rate_limiter: Option<serde_json::Value>,
}
impl TryFrom<RawNetworkInterfaceConfig> for NetworkInterfaceConfig {
type Error = String;
fn try_from(raw: RawNetworkInterfaceConfig) -> Result<Self, Self::Error> {
let iface_id = IfaceId::new(raw.iface_id)?;
let host_dev_name = HostDevName::new(raw.host_dev_name).map_err(|e| e.to_string())?;
let guest_mac = match raw.guest_mac {
Some(s) => Some(MacAddr::parse(&s)?),
None => None,
};
Ok(Self {
iface_id,
host_dev_name,
guest_mac,
rx_rate_limiter: raw.rx_rate_limiter,
tx_rate_limiter: raw.tx_rate_limiter,
})
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RawNetworkPatch {
pub iface_id: String,
#[serde(default)]
pub rx_rate_limiter: Option<serde_json::Value>,
#[serde(default)]
pub tx_rate_limiter: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize)]
#[non_exhaustive]
pub struct NetworkPatch {
pub iface_id: IfaceId,
pub rx_rate_limiter: Option<serde_json::Value>,
pub tx_rate_limiter: Option<serde_json::Value>,
}
impl TryFrom<RawNetworkPatch> for NetworkPatch {
type Error = String;
fn try_from(raw: RawNetworkPatch) -> Result<Self, Self::Error> {
Ok(Self {
iface_id: IfaceId::new(raw.iface_id)?,
rx_rate_limiter: raw.rx_rate_limiter,
tx_rate_limiter: raw.tx_rate_limiter,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_should_accept_minimal_network_interface() {
let raw = RawNetworkInterfaceConfig {
iface_id: "eth0".into(),
host_dev_name: "tap0".into(),
guest_mac: None,
rx_rate_limiter: None,
tx_rate_limiter: None,
};
let cfg = NetworkInterfaceConfig::try_from(raw).unwrap();
assert_eq!(cfg.iface_id.as_str(), "eth0");
assert!(cfg.guest_mac.is_none());
}
#[test]
fn test_should_validate_guest_mac() {
let raw = RawNetworkInterfaceConfig {
iface_id: "eth0".into(),
host_dev_name: "tap0".into(),
guest_mac: Some("aa:bb:cc:dd:ee:ff".into()),
rx_rate_limiter: None,
tx_rate_limiter: None,
};
let cfg = NetworkInterfaceConfig::try_from(raw).unwrap();
assert!(cfg.guest_mac.is_some());
}
#[test]
fn test_should_reject_empty_host_dev_name() {
let raw = RawNetworkInterfaceConfig {
iface_id: "eth0".into(),
host_dev_name: String::new(),
guest_mac: None,
rx_rate_limiter: None,
tx_rate_limiter: None,
};
assert!(NetworkInterfaceConfig::try_from(raw).is_err());
}
#[test]
fn test_should_reject_unknown_fields() {
let json = r#"{"iface_id":"eth0","host_dev_name":"tap0","unexpected":1}"#;
assert!(serde_json::from_str::<RawNetworkInterfaceConfig>(json).is_err());
}
}