use anyhow::{Result, bail};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct DevNetwork {
pub name: String,
pub bridge_name: String,
pub subnet: String,
pub gateway: String,
pub created_at: String,
}
impl DevNetwork {
pub fn default_network() -> Self {
Self {
name: "default".to_string(),
bridge_name: "br-mvm".to_string(),
subnet: "172.16.0.0/24".to_string(),
gateway: "172.16.0.1".to_string(),
created_at: chrono::Utc::now().to_rfc3339(),
}
}
pub fn new(name: &str, slot: u8) -> Result<Self> {
validate_network_name(name)?;
if slot == 0 {
bail!("slot 0 is reserved for the default network");
}
Ok(Self {
name: name.to_string(),
bridge_name: format!("br-mvm-{name}"),
subnet: format!("172.16.{slot}.0/24"),
gateway: format!("172.16.{slot}.1"),
created_at: chrono::Utc::now().to_rfc3339(),
})
}
pub fn gateway_cidr(&self) -> String {
let prefix = self.subnet.split('/').nth(1).unwrap_or("24");
format!("{}/{prefix}", self.gateway)
}
}
pub fn validate_network_name(name: &str) -> Result<()> {
crate::naming::validate_id(name, "network name")
}
pub fn networks_dir() -> String {
format!("{}/networks", crate::config::mvm_share_dir())
}
pub fn network_path(name: &str) -> String {
format!("{}/{name}.json", networks_dir())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_network() {
let net = DevNetwork::default_network();
assert_eq!(net.name, "default");
assert_eq!(net.bridge_name, "br-mvm");
assert_eq!(net.subnet, "172.16.0.0/24");
assert_eq!(net.gateway, "172.16.0.1");
assert!(!net.created_at.is_empty());
}
#[test]
fn test_new_network() {
let net = DevNetwork::new("isolated", 1).unwrap();
assert_eq!(net.name, "isolated");
assert_eq!(net.bridge_name, "br-mvm-isolated");
assert_eq!(net.subnet, "172.16.1.0/24");
assert_eq!(net.gateway, "172.16.1.1");
}
#[test]
fn test_new_network_slot_0_rejected() {
assert!(DevNetwork::new("bad", 0).is_err());
}
#[test]
fn test_gateway_cidr() {
let net = DevNetwork::default_network();
assert_eq!(net.gateway_cidr(), "172.16.0.1/24");
let net2 = DevNetwork::new("test", 5).unwrap();
assert_eq!(net2.gateway_cidr(), "172.16.5.1/24");
}
#[test]
fn test_serde_roundtrip() {
let net = DevNetwork::new("mynet", 3).unwrap();
let json = serde_json::to_string(&net).unwrap();
let parsed: DevNetwork = serde_json::from_str(&json).unwrap();
assert_eq!(net, parsed);
}
#[test]
fn test_validate_network_name() {
assert!(validate_network_name("default").is_ok());
assert!(validate_network_name("my-net-1").is_ok());
assert!(validate_network_name("").is_err());
assert!(validate_network_name("UPPER").is_err());
assert!(validate_network_name("-leading").is_err());
}
#[test]
fn test_network_path() {
let path = network_path("default");
assert!(path.ends_with("/networks/default.json"));
}
}