1use std::fmt;
7
8#[derive(Debug, Clone, PartialEq)]
15pub enum InterfaceOption {
16 Address(String),
18 Netmask(String),
20 Gateway(String),
22 Broadcast(String),
24 Network(String),
26 Mtu(u16),
28 BridgeAccess(u16),
30 BridgePorts(Vec<String>),
32 BridgePvid(u16),
34 BridgeVids(String),
36 BridgeVlanAware(bool),
38 MstpctlBpduguard(bool),
40 MstpctlPortadminedge(bool),
42 PostUp(String),
44 PreDown(String),
46 PostDown(String),
48 PreUp(String),
50 Vrf(String),
52 VrfTable(String),
54 VlanId(u16),
56 VlanRawDevice(String),
58 HwAddress(String),
60 DnsNameservers(String),
62 DnsSearch(String),
64 Metric(u32),
66 Pointopoint(String),
68 Media(String),
70 Other(String, String),
72}
73
74impl InterfaceOption {
75 pub fn name(&self) -> &str {
77 match self {
78 InterfaceOption::Address(_) => "address",
79 InterfaceOption::Netmask(_) => "netmask",
80 InterfaceOption::Gateway(_) => "gateway",
81 InterfaceOption::Broadcast(_) => "broadcast",
82 InterfaceOption::Network(_) => "network",
83 InterfaceOption::Mtu(_) => "mtu",
84 InterfaceOption::BridgeAccess(_) => "bridge-access",
85 InterfaceOption::BridgePorts(_) => "bridge-ports",
86 InterfaceOption::BridgePvid(_) => "bridge-pvid",
87 InterfaceOption::BridgeVids(_) => "bridge-vids",
88 InterfaceOption::BridgeVlanAware(_) => "bridge-vlan-aware",
89 InterfaceOption::MstpctlBpduguard(_) => "mstpctl-bpduguard",
90 InterfaceOption::MstpctlPortadminedge(_) => "mstpctl-portadminedge",
91 InterfaceOption::PostUp(_) => "post-up",
92 InterfaceOption::PreDown(_) => "pre-down",
93 InterfaceOption::PostDown(_) => "post-down",
94 InterfaceOption::PreUp(_) => "pre-up",
95 InterfaceOption::Vrf(_) => "vrf",
96 InterfaceOption::VrfTable(_) => "vrf-table",
97 InterfaceOption::VlanId(_) => "vlan-id",
98 InterfaceOption::VlanRawDevice(_) => "vlan-raw-device",
99 InterfaceOption::HwAddress(_) => "hwaddress",
100 InterfaceOption::DnsNameservers(_) => "dns-nameservers",
101 InterfaceOption::DnsSearch(_) => "dns-search",
102 InterfaceOption::Metric(_) => "metric",
103 InterfaceOption::Pointopoint(_) => "pointopoint",
104 InterfaceOption::Media(_) => "media",
105 InterfaceOption::Other(name, _) => name,
106 }
107 }
108
109 pub fn value(&self) -> String {
111 match self {
112 InterfaceOption::Address(v) => v.clone(),
113 InterfaceOption::Netmask(v) => v.clone(),
114 InterfaceOption::Gateway(v) => v.clone(),
115 InterfaceOption::Broadcast(v) => v.clone(),
116 InterfaceOption::Network(v) => v.clone(),
117 InterfaceOption::Mtu(v) => v.to_string(),
118 InterfaceOption::BridgeAccess(v) => v.to_string(),
119 InterfaceOption::BridgePorts(v) => v.join(" "),
120 InterfaceOption::BridgePvid(v) => v.to_string(),
121 InterfaceOption::BridgeVids(v) => v.clone(),
122 InterfaceOption::BridgeVlanAware(v) => if *v { "yes" } else { "no" }.to_string(),
123 InterfaceOption::MstpctlBpduguard(v) => if *v { "yes" } else { "no" }.to_string(),
124 InterfaceOption::MstpctlPortadminedge(v) => if *v { "yes" } else { "no" }.to_string(),
125 InterfaceOption::PostUp(v) => v.clone(),
126 InterfaceOption::PreDown(v) => v.clone(),
127 InterfaceOption::PostDown(v) => v.clone(),
128 InterfaceOption::PreUp(v) => v.clone(),
129 InterfaceOption::Vrf(v) => v.clone(),
130 InterfaceOption::VrfTable(v) => v.clone(),
131 InterfaceOption::VlanId(v) => v.to_string(),
132 InterfaceOption::VlanRawDevice(v) => v.clone(),
133 InterfaceOption::HwAddress(v) => v.clone(),
134 InterfaceOption::DnsNameservers(v) => v.clone(),
135 InterfaceOption::DnsSearch(v) => v.clone(),
136 InterfaceOption::Metric(v) => v.to_string(),
137 InterfaceOption::Pointopoint(v) => v.clone(),
138 InterfaceOption::Media(v) => v.clone(),
139 InterfaceOption::Other(_, v) => v.clone(),
140 }
141 }
142
143 pub fn from_key_value(key: &str, value: &str) -> Self {
147 match key {
148 "address" => InterfaceOption::Address(value.to_string()),
149 "netmask" => InterfaceOption::Netmask(value.to_string()),
150 "gateway" => InterfaceOption::Gateway(value.to_string()),
151 "broadcast" => InterfaceOption::Broadcast(value.to_string()),
152 "network" => InterfaceOption::Network(value.to_string()),
153 "mtu" => value
154 .parse()
155 .map(InterfaceOption::Mtu)
156 .unwrap_or_else(|_| InterfaceOption::Other(key.to_string(), value.to_string())),
157 "bridge-access" => value
158 .parse()
159 .map(InterfaceOption::BridgeAccess)
160 .unwrap_or_else(|_| InterfaceOption::Other(key.to_string(), value.to_string())),
161 "bridge-ports" => {
162 InterfaceOption::BridgePorts(value.split_whitespace().map(String::from).collect())
163 }
164 "bridge-pvid" => value
165 .parse()
166 .map(InterfaceOption::BridgePvid)
167 .unwrap_or_else(|_| InterfaceOption::Other(key.to_string(), value.to_string())),
168 "bridge-vids" => InterfaceOption::BridgeVids(value.to_string()),
169 "bridge-vlan-aware" => InterfaceOption::BridgeVlanAware(parse_bool(value)),
170 "mstpctl-bpduguard" => InterfaceOption::MstpctlBpduguard(parse_bool(value)),
171 "mstpctl-portadminedge" => InterfaceOption::MstpctlPortadminedge(parse_bool(value)),
172 "post-up" => InterfaceOption::PostUp(value.to_string()),
173 "pre-down" => InterfaceOption::PreDown(value.to_string()),
174 "post-down" => InterfaceOption::PostDown(value.to_string()),
175 "pre-up" => InterfaceOption::PreUp(value.to_string()),
176 "vrf" => InterfaceOption::Vrf(value.to_string()),
177 "vrf-table" => InterfaceOption::VrfTable(value.to_string()),
178 "vlan-id" => value
179 .parse()
180 .map(InterfaceOption::VlanId)
181 .unwrap_or_else(|_| InterfaceOption::Other(key.to_string(), value.to_string())),
182 "vlan-raw-device" => InterfaceOption::VlanRawDevice(value.to_string()),
183 "hwaddress" => InterfaceOption::HwAddress(value.to_string()),
184 "dns-nameservers" => InterfaceOption::DnsNameservers(value.to_string()),
185 "dns-search" => InterfaceOption::DnsSearch(value.to_string()),
186 "metric" => value
187 .parse()
188 .map(InterfaceOption::Metric)
189 .unwrap_or_else(|_| InterfaceOption::Other(key.to_string(), value.to_string())),
190 "pointopoint" => InterfaceOption::Pointopoint(value.to_string()),
191 "media" => InterfaceOption::Media(value.to_string()),
192 _ => InterfaceOption::Other(key.to_string(), value.to_string()),
193 }
194 }
195
196 pub fn to_key_value(&self) -> (String, String) {
198 (self.name().to_string(), self.value())
199 }
200}
201
202impl fmt::Display for InterfaceOption {
203 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
204 write!(f, "{} {}", self.name(), self.value())
205 }
206}
207
208fn parse_bool(value: &str) -> bool {
210 matches!(value.to_lowercase().as_str(), "yes" | "on" | "true" | "1")
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216
217 #[test]
218 fn test_from_key_value_address() {
219 let opt = InterfaceOption::from_key_value("address", "192.168.1.100");
220 assert_eq!(opt, InterfaceOption::Address("192.168.1.100".to_string()));
221 assert_eq!(opt.name(), "address");
222 assert_eq!(opt.value(), "192.168.1.100");
223 }
224
225 #[test]
226 fn test_from_key_value_mtu() {
227 let opt = InterfaceOption::from_key_value("mtu", "9216");
228 assert_eq!(opt, InterfaceOption::Mtu(9216));
229 assert_eq!(opt.name(), "mtu");
230 assert_eq!(opt.value(), "9216");
231 }
232
233 #[test]
234 fn test_from_key_value_bridge_ports() {
235 let opt = InterfaceOption::from_key_value("bridge-ports", "swp1 swp2 swp3");
236 assert_eq!(
237 opt,
238 InterfaceOption::BridgePorts(vec![
239 "swp1".to_string(),
240 "swp2".to_string(),
241 "swp3".to_string()
242 ])
243 );
244 assert_eq!(opt.value(), "swp1 swp2 swp3");
245 }
246
247 #[test]
248 fn test_from_key_value_bridge_vlan_aware() {
249 let opt_yes = InterfaceOption::from_key_value("bridge-vlan-aware", "yes");
250 assert_eq!(opt_yes, InterfaceOption::BridgeVlanAware(true));
251 assert_eq!(opt_yes.value(), "yes");
252
253 let opt_no = InterfaceOption::from_key_value("bridge-vlan-aware", "no");
254 assert_eq!(opt_no, InterfaceOption::BridgeVlanAware(false));
255 assert_eq!(opt_no.value(), "no");
256 }
257
258 #[test]
259 fn test_from_key_value_unknown() {
260 let opt = InterfaceOption::from_key_value("custom-option", "custom-value");
261 assert_eq!(
262 opt,
263 InterfaceOption::Other("custom-option".to_string(), "custom-value".to_string())
264 );
265 assert_eq!(opt.name(), "custom-option");
266 assert_eq!(opt.value(), "custom-value");
267 }
268
269 #[test]
270 fn test_display() {
271 let opt = InterfaceOption::Mtu(1500);
272 assert_eq!(format!("{}", opt), "mtu 1500");
273
274 let opt = InterfaceOption::BridgeVlanAware(true);
275 assert_eq!(format!("{}", opt), "bridge-vlan-aware yes");
276 }
277
278 #[test]
279 fn test_to_key_value() {
280 let opt = InterfaceOption::Gateway("192.168.1.1".to_string());
281 let (key, value) = opt.to_key_value();
282 assert_eq!(key, "gateway");
283 assert_eq!(value, "192.168.1.1");
284 }
285}