use std::fmt;
#[derive(Debug, Clone, PartialEq)]
pub enum InterfaceOption {
Address(String),
Netmask(String),
Gateway(String),
Broadcast(String),
Network(String),
Mtu(u16),
BridgeAccess(u16),
BridgePorts(Vec<String>),
BridgePvid(u16),
BridgeVids(String),
BridgeVlanAware(bool),
MstpctlBpduguard(bool),
MstpctlPortadminedge(bool),
PostUp(String),
PreDown(String),
PostDown(String),
PreUp(String),
Vrf(String),
VrfTable(String),
VlanId(u16),
VlanRawDevice(String),
HwAddress(String),
DnsNameservers(String),
DnsSearch(String),
Metric(u32),
Pointopoint(String),
Media(String),
Other(String, String),
}
impl InterfaceOption {
pub fn name(&self) -> &str {
match self {
InterfaceOption::Address(_) => "address",
InterfaceOption::Netmask(_) => "netmask",
InterfaceOption::Gateway(_) => "gateway",
InterfaceOption::Broadcast(_) => "broadcast",
InterfaceOption::Network(_) => "network",
InterfaceOption::Mtu(_) => "mtu",
InterfaceOption::BridgeAccess(_) => "bridge-access",
InterfaceOption::BridgePorts(_) => "bridge-ports",
InterfaceOption::BridgePvid(_) => "bridge-pvid",
InterfaceOption::BridgeVids(_) => "bridge-vids",
InterfaceOption::BridgeVlanAware(_) => "bridge-vlan-aware",
InterfaceOption::MstpctlBpduguard(_) => "mstpctl-bpduguard",
InterfaceOption::MstpctlPortadminedge(_) => "mstpctl-portadminedge",
InterfaceOption::PostUp(_) => "post-up",
InterfaceOption::PreDown(_) => "pre-down",
InterfaceOption::PostDown(_) => "post-down",
InterfaceOption::PreUp(_) => "pre-up",
InterfaceOption::Vrf(_) => "vrf",
InterfaceOption::VrfTable(_) => "vrf-table",
InterfaceOption::VlanId(_) => "vlan-id",
InterfaceOption::VlanRawDevice(_) => "vlan-raw-device",
InterfaceOption::HwAddress(_) => "hwaddress",
InterfaceOption::DnsNameservers(_) => "dns-nameservers",
InterfaceOption::DnsSearch(_) => "dns-search",
InterfaceOption::Metric(_) => "metric",
InterfaceOption::Pointopoint(_) => "pointopoint",
InterfaceOption::Media(_) => "media",
InterfaceOption::Other(name, _) => name,
}
}
pub fn value(&self) -> String {
match self {
InterfaceOption::Address(v) => v.clone(),
InterfaceOption::Netmask(v) => v.clone(),
InterfaceOption::Gateway(v) => v.clone(),
InterfaceOption::Broadcast(v) => v.clone(),
InterfaceOption::Network(v) => v.clone(),
InterfaceOption::Mtu(v) => v.to_string(),
InterfaceOption::BridgeAccess(v) => v.to_string(),
InterfaceOption::BridgePorts(v) => v.join(" "),
InterfaceOption::BridgePvid(v) => v.to_string(),
InterfaceOption::BridgeVids(v) => v.clone(),
InterfaceOption::BridgeVlanAware(v) => if *v { "yes" } else { "no" }.to_string(),
InterfaceOption::MstpctlBpduguard(v) => if *v { "yes" } else { "no" }.to_string(),
InterfaceOption::MstpctlPortadminedge(v) => if *v { "yes" } else { "no" }.to_string(),
InterfaceOption::PostUp(v) => v.clone(),
InterfaceOption::PreDown(v) => v.clone(),
InterfaceOption::PostDown(v) => v.clone(),
InterfaceOption::PreUp(v) => v.clone(),
InterfaceOption::Vrf(v) => v.clone(),
InterfaceOption::VrfTable(v) => v.clone(),
InterfaceOption::VlanId(v) => v.to_string(),
InterfaceOption::VlanRawDevice(v) => v.clone(),
InterfaceOption::HwAddress(v) => v.clone(),
InterfaceOption::DnsNameservers(v) => v.clone(),
InterfaceOption::DnsSearch(v) => v.clone(),
InterfaceOption::Metric(v) => v.to_string(),
InterfaceOption::Pointopoint(v) => v.clone(),
InterfaceOption::Media(v) => v.clone(),
InterfaceOption::Other(_, v) => v.clone(),
}
}
pub fn from_key_value(key: &str, value: &str) -> Self {
match key {
"address" => InterfaceOption::Address(value.to_string()),
"netmask" => InterfaceOption::Netmask(value.to_string()),
"gateway" => InterfaceOption::Gateway(value.to_string()),
"broadcast" => InterfaceOption::Broadcast(value.to_string()),
"network" => InterfaceOption::Network(value.to_string()),
"mtu" => value
.parse()
.map(InterfaceOption::Mtu)
.unwrap_or_else(|_| InterfaceOption::Other(key.to_string(), value.to_string())),
"bridge-access" => value
.parse()
.map(InterfaceOption::BridgeAccess)
.unwrap_or_else(|_| InterfaceOption::Other(key.to_string(), value.to_string())),
"bridge-ports" => {
InterfaceOption::BridgePorts(value.split_whitespace().map(String::from).collect())
}
"bridge-pvid" => value
.parse()
.map(InterfaceOption::BridgePvid)
.unwrap_or_else(|_| InterfaceOption::Other(key.to_string(), value.to_string())),
"bridge-vids" => InterfaceOption::BridgeVids(value.to_string()),
"bridge-vlan-aware" => InterfaceOption::BridgeVlanAware(parse_bool(value)),
"mstpctl-bpduguard" => InterfaceOption::MstpctlBpduguard(parse_bool(value)),
"mstpctl-portadminedge" => InterfaceOption::MstpctlPortadminedge(parse_bool(value)),
"post-up" => InterfaceOption::PostUp(value.to_string()),
"pre-down" => InterfaceOption::PreDown(value.to_string()),
"post-down" => InterfaceOption::PostDown(value.to_string()),
"pre-up" => InterfaceOption::PreUp(value.to_string()),
"vrf" => InterfaceOption::Vrf(value.to_string()),
"vrf-table" => InterfaceOption::VrfTable(value.to_string()),
"vlan-id" => value
.parse()
.map(InterfaceOption::VlanId)
.unwrap_or_else(|_| InterfaceOption::Other(key.to_string(), value.to_string())),
"vlan-raw-device" => InterfaceOption::VlanRawDevice(value.to_string()),
"hwaddress" => InterfaceOption::HwAddress(value.to_string()),
"dns-nameservers" => InterfaceOption::DnsNameservers(value.to_string()),
"dns-search" => InterfaceOption::DnsSearch(value.to_string()),
"metric" => value
.parse()
.map(InterfaceOption::Metric)
.unwrap_or_else(|_| InterfaceOption::Other(key.to_string(), value.to_string())),
"pointopoint" => InterfaceOption::Pointopoint(value.to_string()),
"media" => InterfaceOption::Media(value.to_string()),
_ => InterfaceOption::Other(key.to_string(), value.to_string()),
}
}
pub fn to_key_value(&self) -> (String, String) {
(self.name().to_string(), self.value())
}
}
impl fmt::Display for InterfaceOption {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} {}", self.name(), self.value())
}
}
fn parse_bool(value: &str) -> bool {
matches!(value.to_lowercase().as_str(), "yes" | "on" | "true" | "1")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_from_key_value_address() {
let opt = InterfaceOption::from_key_value("address", "192.168.1.100");
assert_eq!(opt, InterfaceOption::Address("192.168.1.100".to_string()));
assert_eq!(opt.name(), "address");
assert_eq!(opt.value(), "192.168.1.100");
}
#[test]
fn test_from_key_value_mtu() {
let opt = InterfaceOption::from_key_value("mtu", "9216");
assert_eq!(opt, InterfaceOption::Mtu(9216));
assert_eq!(opt.name(), "mtu");
assert_eq!(opt.value(), "9216");
}
#[test]
fn test_from_key_value_bridge_ports() {
let opt = InterfaceOption::from_key_value("bridge-ports", "swp1 swp2 swp3");
assert_eq!(
opt,
InterfaceOption::BridgePorts(vec![
"swp1".to_string(),
"swp2".to_string(),
"swp3".to_string()
])
);
assert_eq!(opt.value(), "swp1 swp2 swp3");
}
#[test]
fn test_from_key_value_bridge_vlan_aware() {
let opt_yes = InterfaceOption::from_key_value("bridge-vlan-aware", "yes");
assert_eq!(opt_yes, InterfaceOption::BridgeVlanAware(true));
assert_eq!(opt_yes.value(), "yes");
let opt_no = InterfaceOption::from_key_value("bridge-vlan-aware", "no");
assert_eq!(opt_no, InterfaceOption::BridgeVlanAware(false));
assert_eq!(opt_no.value(), "no");
}
#[test]
fn test_from_key_value_unknown() {
let opt = InterfaceOption::from_key_value("custom-option", "custom-value");
assert_eq!(
opt,
InterfaceOption::Other("custom-option".to_string(), "custom-value".to_string())
);
assert_eq!(opt.name(), "custom-option");
assert_eq!(opt.value(), "custom-value");
}
#[test]
fn test_display() {
let opt = InterfaceOption::Mtu(1500);
assert_eq!(format!("{}", opt), "mtu 1500");
let opt = InterfaceOption::BridgeVlanAware(true);
assert_eq!(format!("{}", opt), "bridge-vlan-aware yes");
}
#[test]
fn test_to_key_value() {
let opt = InterfaceOption::Gateway("192.168.1.1".to_string());
let (key, value) = opt.to_key_value();
assert_eq!(key, "gateway");
assert_eq!(value, "192.168.1.1");
}
}