interface_rs/interface/interface_builder.rs
1use super::{Family, Interface, InterfaceOption, Mapping, Method};
2
3/// A builder for constructing [`Interface`] instances.
4///
5/// The `InterfaceBuilder` struct provides a fluent API for building
6/// `Interface` objects. It allows you to chain method calls to set various
7/// fields, culminating in a `build()` method that constructs the `Interface`.
8///
9/// # Examples
10///
11/// ```rust
12/// use interface_rs::interface::{Interface, Family, Method};
13///
14/// let iface = Interface::builder("eth0")
15/// .with_auto(true)
16/// .with_allow("hotplug")
17/// .with_family(Family::Inet)
18/// .with_method(Method::Dhcp)
19/// .with_option("mtu", "1500")
20/// .build();
21/// ```
22#[derive(Debug, Clone)]
23pub struct InterfaceBuilder {
24 pub(crate) name: String,
25 pub(crate) auto: bool,
26 pub(crate) allow: Vec<String>,
27 pub(crate) family: Option<Family>,
28 pub(crate) method: Option<Method>,
29 pub(crate) options: Vec<InterfaceOption>,
30 pub(crate) mapping: Option<Mapping>,
31}
32
33impl InterfaceBuilder {
34 /// Creates a new `InterfaceBuilder` with the specified interface name.
35 ///
36 /// # Arguments
37 ///
38 /// * `name` - The name of the interface (e.g., `"eth0"`).
39 ///
40 /// # Examples
41 ///
42 /// ```rust
43 /// use interface_rs::interface::Interface;
44 ///
45 /// let builder = Interface::builder("eth0");
46 /// ```
47 pub fn new(name: impl Into<String>) -> Self {
48 InterfaceBuilder {
49 name: name.into(),
50 auto: false,
51 allow: Vec::new(),
52 family: None,
53 method: None,
54 options: Vec::new(),
55 mapping: None,
56 }
57 }
58
59 /// Sets whether the interface should start automatically.
60 ///
61 /// # Arguments
62 ///
63 /// * `auto` - A boolean indicating if the interface should start automatically.
64 ///
65 /// # Examples
66 ///
67 /// ```rust
68 /// # use interface_rs::interface::Interface;
69 /// # let builder = Interface::builder("eth0");
70 /// let builder = builder.with_auto(true);
71 /// ```
72 pub fn with_auto(mut self, auto: bool) -> Self {
73 self.auto = auto;
74 self
75 }
76
77 /// Adds an `allow-*` directive to the interface.
78 ///
79 /// # Arguments
80 ///
81 /// * `allow` - A string representing the allow directive (e.g., `"hotplug"`).
82 ///
83 /// # Examples
84 ///
85 /// ```rust
86 /// # use interface_rs::interface::Interface;
87 /// # let builder = Interface::builder("eth0");
88 /// let builder = builder.with_allow("hotplug");
89 /// ```
90 pub fn with_allow(mut self, allow: impl Into<String>) -> Self {
91 self.allow.push(allow.into());
92 self
93 }
94
95 /// Sets the address family of the interface.
96 ///
97 /// # Arguments
98 ///
99 /// * `family` - The [`Family`] of the interface (e.g., `Family::Inet`).
100 ///
101 /// # Examples
102 ///
103 /// ```rust
104 /// use interface_rs::interface::{Interface, Family};
105 /// let builder = Interface::builder("eth0")
106 /// .with_family(Family::Inet);
107 /// ```
108 pub fn with_family(mut self, family: Family) -> Self {
109 self.family = Some(family);
110 self
111 }
112
113 /// Sets the method of configuration for the interface.
114 ///
115 /// # Arguments
116 ///
117 /// * `method` - The [`Method`] of configuration (e.g., `Method::Static`, `Method::Dhcp`).
118 ///
119 /// # Examples
120 ///
121 /// ```rust
122 /// use interface_rs::interface::{Interface, Method};
123 /// let builder = Interface::builder("eth0")
124 /// .with_method(Method::Dhcp);
125 /// ```
126 pub fn with_method(mut self, method: Method) -> Self {
127 self.method = Some(method);
128 self
129 }
130
131 /// Adds an option to the interface using string key-value pairs.
132 ///
133 /// This method parses the key and value into the appropriate [`InterfaceOption`]
134 /// variant automatically.
135 ///
136 /// # Arguments
137 ///
138 /// * `key` - The option name (e.g., `"address"`).
139 /// * `value` - The option value (e.g., `"192.168.1.100"`).
140 ///
141 /// # Examples
142 ///
143 /// ```rust
144 /// # use interface_rs::interface::Interface;
145 /// let builder = Interface::builder("eth0")
146 /// .with_option("address", "192.168.1.100");
147 /// ```
148 pub fn with_option(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
149 let key = key.into();
150 let value = value.into();
151 self.options.push(InterfaceOption::from_key_value(&key, &value));
152 self
153 }
154
155 /// Adds a typed option to the interface.
156 ///
157 /// # Arguments
158 ///
159 /// * `option` - The [`InterfaceOption`] to add.
160 ///
161 /// # Examples
162 ///
163 /// ```rust
164 /// use interface_rs::interface::{Interface, InterfaceOption};
165 ///
166 /// let builder = Interface::builder("eth0")
167 /// .with_typed_option(InterfaceOption::Mtu(1500))
168 /// .with_typed_option(InterfaceOption::Address("192.168.1.100".to_string()));
169 /// ```
170 pub fn with_typed_option(mut self, option: InterfaceOption) -> Self {
171 self.options.push(option);
172 self
173 }
174
175 /// Sets the mapping configuration for the interface.
176 ///
177 /// # Arguments
178 ///
179 /// * `mapping` - A [`Mapping`] struct representing the mapping configuration.
180 ///
181 /// # Examples
182 ///
183 /// ```rust
184 /// use interface_rs::interface::{Interface, Mapping};
185 /// use std::path::PathBuf;
186 /// let mapping = Mapping {
187 /// script: PathBuf::from("/usr/local/bin/map-script"),
188 /// maps: vec!["eth0".to_string()],
189 /// };
190 /// let builder = Interface::builder("eth0")
191 /// .with_mapping(mapping);
192 /// ```
193 pub fn with_mapping(mut self, mapping: Mapping) -> Self {
194 self.mapping = Some(mapping);
195 self
196 }
197
198 /// Removes all options with the specified key from the interface configuration.
199 ///
200 /// This method removes all options where the name matches the specified `key`.
201 ///
202 /// # Arguments
203 ///
204 /// * `key` - The name of the option to remove (e.g., `"address"`).
205 ///
206 /// # Returns
207 ///
208 /// Returns the builder instance with the specified options removed, allowing
209 /// further chained method calls.
210 ///
211 /// # Examples
212 ///
213 /// ```rust
214 /// # use interface_rs::interface::Interface;
215 /// let builder = Interface::builder("eth0")
216 /// .with_option("address", "192.168.1.100")
217 /// .with_option("address", "192.168.1.101")
218 /// .remove_option("address");
219 ///
220 /// // The builder no longer contains any "address" options.
221 /// ```
222 pub fn remove_option(mut self, key: &str) -> Self {
223 self.options.retain(|opt| opt.name() != key);
224 self
225 }
226
227 /// Removes a specific option by its key and value from the interface configuration.
228 ///
229 /// This method removes only the option where both the name matches the specified
230 /// `key` and the value matches the specified `value`.
231 ///
232 /// # Arguments
233 ///
234 /// * `key` - The name of the option to remove (e.g., `"address"`).
235 /// * `value` - The specific value of the option to remove (e.g., `"192.168.1.100"`).
236 ///
237 /// # Returns
238 ///
239 /// Returns the builder instance with the specified option removed,
240 /// allowing further chained method calls.
241 ///
242 /// # Examples
243 ///
244 /// ```rust
245 /// # use interface_rs::interface::Interface;
246 /// let builder = Interface::builder("eth0")
247 /// .with_option("address", "192.168.1.100")
248 /// .with_option("address", "192.168.1.101")
249 /// .remove_option_value("address", "192.168.1.100");
250 ///
251 /// // The builder retains the option with the value "192.168.1.101" for "address",
252 /// // but the pair ("address", "192.168.1.100") is removed.
253 /// ```
254 pub fn remove_option_value(mut self, key: &str, value: &str) -> Self {
255 self.options.retain(|opt| !(opt.name() == key && opt.value() == value));
256 self
257 }
258
259 /// Builds the [`Interface`] instance.
260 ///
261 /// # Returns
262 ///
263 /// An `Interface` with the specified configuration.
264 ///
265 /// # Examples
266 ///
267 /// ```rust
268 /// use interface_rs::interface::{Interface, Family, Method};
269 /// let iface = Interface::builder("eth0")
270 /// .with_auto(true)
271 /// .with_family(Family::Inet)
272 /// .with_method(Method::Dhcp)
273 /// .build();
274 /// ```
275 pub fn build(self) -> Interface {
276 Interface {
277 name: self.name,
278 auto: self.auto,
279 allow: self.allow,
280 family: self.family,
281 method: self.method,
282 options: self.options,
283 mapping: self.mapping,
284 }
285 }
286}
287
288#[cfg(test)]
289mod tests {
290 use super::*;
291
292 #[test]
293 fn test_remove_option() {
294 let iface = Interface::builder("eth0")
295 .with_option("address", "192.168.1.50")
296 .with_option("netmask", "255.255.255.0")
297 .with_option("address", "192.168.1.51") // Duplicate address key with different value
298 .remove_option("address") // Should remove all "address" options
299 .build();
300
301 assert_eq!(iface.options.len(), 1);
302 assert_eq!(
303 iface.options[0],
304 InterfaceOption::Netmask("255.255.255.0".to_string())
305 );
306 }
307
308 #[test]
309 fn test_remove_option_value() {
310 let iface = Interface::builder("eth0")
311 .with_option("address", "192.168.1.50")
312 .with_option("netmask", "255.255.255.0")
313 .with_option("address", "192.168.1.51") // Duplicate address key with different value
314 .with_option("address", "192.168.1.52") // Duplicate address key with different value
315 .remove_option_value("address", "192.168.1.50") // Should remove only this address pair
316 .build();
317
318 assert_eq!(iface.options.len(), 3);
319 assert!(iface
320 .options
321 .contains(&InterfaceOption::Netmask("255.255.255.0".to_string())));
322 assert!(iface
323 .options
324 .contains(&InterfaceOption::Address("192.168.1.51".to_string())));
325 assert!(iface
326 .options
327 .contains(&InterfaceOption::Address("192.168.1.52".to_string())));
328 }
329
330 #[test]
331 fn test_with_typed_option() {
332 let iface = Interface::builder("eth0")
333 .with_typed_option(InterfaceOption::Mtu(1500))
334 .with_typed_option(InterfaceOption::BridgeVlanAware(true))
335 .build();
336
337 assert_eq!(iface.options.len(), 2);
338 assert!(iface.options.contains(&InterfaceOption::Mtu(1500)));
339 assert!(iface.options.contains(&InterfaceOption::BridgeVlanAware(true)));
340 }
341}