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}