squib_api/schemas/
network.rs1use serde::{Deserialize, Serialize};
13use squib_core::HostDevName;
14
15use super::common::{IfaceId, MacAddr};
16
17#[derive(Debug, Clone, Deserialize)]
19#[serde(deny_unknown_fields)]
20pub struct RawNetworkInterfaceConfig {
21 pub iface_id: String,
23 pub host_dev_name: String,
25 #[serde(default)]
27 pub guest_mac: Option<String>,
28 #[serde(default)]
30 pub rx_rate_limiter: Option<serde_json::Value>,
31 #[serde(default)]
33 pub tx_rate_limiter: Option<serde_json::Value>,
34}
35
36#[derive(Debug, Clone, Serialize)]
38#[non_exhaustive]
39pub struct NetworkInterfaceConfig {
40 pub iface_id: IfaceId,
42 pub host_dev_name: HostDevName,
46 pub guest_mac: Option<MacAddr>,
48 pub rx_rate_limiter: Option<serde_json::Value>,
50 pub tx_rate_limiter: Option<serde_json::Value>,
52}
53
54impl TryFrom<RawNetworkInterfaceConfig> for NetworkInterfaceConfig {
55 type Error = String;
56
57 fn try_from(raw: RawNetworkInterfaceConfig) -> Result<Self, Self::Error> {
58 let iface_id = IfaceId::new(raw.iface_id)?;
59 let host_dev_name = HostDevName::new(raw.host_dev_name).map_err(|e| e.to_string())?;
60 let guest_mac = match raw.guest_mac {
61 Some(s) => Some(MacAddr::parse(&s)?),
62 None => None,
63 };
64 Ok(Self {
65 iface_id,
66 host_dev_name,
67 guest_mac,
68 rx_rate_limiter: raw.rx_rate_limiter,
69 tx_rate_limiter: raw.tx_rate_limiter,
70 })
71 }
72}
73
74#[derive(Debug, Clone, Deserialize)]
76#[serde(deny_unknown_fields)]
77pub struct RawNetworkPatch {
78 pub iface_id: String,
80 #[serde(default)]
82 pub rx_rate_limiter: Option<serde_json::Value>,
83 #[serde(default)]
85 pub tx_rate_limiter: Option<serde_json::Value>,
86}
87
88#[derive(Debug, Clone, Serialize)]
90#[non_exhaustive]
91pub struct NetworkPatch {
92 pub iface_id: IfaceId,
94 pub rx_rate_limiter: Option<serde_json::Value>,
96 pub tx_rate_limiter: Option<serde_json::Value>,
98}
99
100impl TryFrom<RawNetworkPatch> for NetworkPatch {
101 type Error = String;
102
103 fn try_from(raw: RawNetworkPatch) -> Result<Self, Self::Error> {
104 Ok(Self {
105 iface_id: IfaceId::new(raw.iface_id)?,
106 rx_rate_limiter: raw.rx_rate_limiter,
107 tx_rate_limiter: raw.tx_rate_limiter,
108 })
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
117 fn test_should_accept_minimal_network_interface() {
118 let raw = RawNetworkInterfaceConfig {
119 iface_id: "eth0".into(),
120 host_dev_name: "tap0".into(),
121 guest_mac: None,
122 rx_rate_limiter: None,
123 tx_rate_limiter: None,
124 };
125 let cfg = NetworkInterfaceConfig::try_from(raw).unwrap();
126 assert_eq!(cfg.iface_id.as_str(), "eth0");
127 assert!(cfg.guest_mac.is_none());
128 }
129
130 #[test]
131 fn test_should_validate_guest_mac() {
132 let raw = RawNetworkInterfaceConfig {
133 iface_id: "eth0".into(),
134 host_dev_name: "tap0".into(),
135 guest_mac: Some("aa:bb:cc:dd:ee:ff".into()),
136 rx_rate_limiter: None,
137 tx_rate_limiter: None,
138 };
139 let cfg = NetworkInterfaceConfig::try_from(raw).unwrap();
140 assert!(cfg.guest_mac.is_some());
141 }
142
143 #[test]
144 fn test_should_reject_empty_host_dev_name() {
145 let raw = RawNetworkInterfaceConfig {
146 iface_id: "eth0".into(),
147 host_dev_name: String::new(),
148 guest_mac: None,
149 rx_rate_limiter: None,
150 tx_rate_limiter: None,
151 };
152 assert!(NetworkInterfaceConfig::try_from(raw).is_err());
153 }
154
155 #[test]
156 fn test_should_reject_unknown_fields() {
157 let json = r#"{"iface_id":"eth0","host_dev_name":"tap0","unexpected":1}"#;
158 assert!(serde_json::from_str::<RawNetworkInterfaceConfig>(json).is_err());
159 }
160}