interface_rs/interface/
interface_builder.rs

1use super::{Family, Interface, Mapping};
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};
13///
14/// let iface = Interface::builder("eth0")
15///     .with_auto(true)
16///     .with_allow("hotplug")
17///     .with_family(Family::Inet)
18///     .with_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<String>,
29    pub(crate) options: Vec<(String, String)>,
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` - A string representing the method (e.g., `"static"`, `"dhcp"`).
118    ///
119    /// # Examples
120    ///
121    /// ```rust
122    /// # use interface_rs::interface::Interface;
123    /// let builder = Interface::builder("eth0")
124    ///     .with_method("dhcp");
125    /// ```
126    pub fn with_method(mut self, method: impl Into<String>) -> Self {
127        self.method = Some(method.into());
128        self
129    }
130
131    /// Adds an option to the interface.
132    ///
133    /// # Arguments
134    ///
135    /// * `key` - The option name (e.g., `"address"`).
136    /// * `value` - The option value (e.g., `"192.168.1.100"`).
137    ///
138    /// # Examples
139    ///
140    /// ```rust
141    /// # use interface_rs::interface::Interface;
142    /// let builder = Interface::builder("eth0")
143    ///     .with_option("address", "192.168.1.100");
144    /// ```
145    pub fn with_option(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
146        self.options.push((key.into(), value.into()));
147        self
148    }
149
150    /// Sets the mapping configuration for the interface.
151    ///
152    /// # Arguments
153    ///
154    /// * `mapping` - A [`Mapping`] struct representing the mapping configuration.
155    ///
156    /// # Examples
157    ///
158    /// ```rust
159    /// use interface_rs::interface::{Interface, Mapping};
160    /// let mapping = Mapping {
161    ///     script: "/usr/local/bin/map-script".to_string(),
162    ///     maps: vec!["eth0".to_string()],
163    /// };
164    /// let builder = Interface::builder("eth0")
165    ///     .with_mapping(mapping);
166    /// ```
167    pub fn with_mapping(mut self, mapping: Mapping) -> Self {
168        self.mapping = Some(mapping);
169        self
170    }
171
172    /// Removes all options with the specified key from the interface configuration.
173    ///
174    /// This method removes all key-value pairs in the options where the key matches
175    /// the specified `key`.
176    ///
177    /// # Arguments
178    ///
179    /// * `key` - The name of the option to remove (e.g., `"address"`).
180    ///
181    /// # Returns
182    ///
183    /// Returns the builder instance with the specified options removed, allowing
184    /// further chained method calls.
185    ///
186    /// # Examples
187    ///
188    /// ```rust
189    /// # use interface_rs::interface::Interface;
190    /// let builder = Interface::builder("eth0")
191    ///     .with_option("address", "192.168.1.100")
192    ///     .with_option("address", "192.168.1.101")
193    ///     .remove_option("address");
194    ///
195    /// // The builder no longer contains any "address" options.
196    /// ```
197    pub fn remove_option(mut self, key: &str) -> Self {
198        self.options.retain(|(k, _)| k != key);
199        self
200    }
201
202    /// Removes a specific option by its key and value from the interface configuration.
203    ///
204    /// This method removes only the key-value pair in the options where both the key
205    /// matches the specified `key` and the value matches the specified `value`.
206    ///
207    /// # Arguments
208    ///
209    /// * `key` - The name of the option to remove (e.g., `"address"`).
210    /// * `value` - The specific value of the option to remove (e.g., `"192.168.1.100"`).
211    ///
212    /// # Returns
213    ///
214    /// Returns the builder instance with the specified key-value pair removed,
215    /// allowing further chained method calls.
216    ///
217    /// # Examples
218    ///
219    /// ```rust
220    /// # use interface_rs::interface::Interface;
221    /// let builder = Interface::builder("eth0")
222    ///     .with_option("address", "192.168.1.100")
223    ///     .with_option("address", "192.168.1.101")
224    ///     .remove_option_value("address", "192.168.1.100");
225    ///
226    /// // The builder retains the option with the value "192.168.1.101" for "address",
227    /// // but the pair ("address", "192.168.1.100") is removed.
228    /// ```
229    pub fn remove_option_value(mut self, key: &str, value: &str) -> Self {
230        self.options.retain(|(k, v)| !(k == key && v == value));
231        self
232    }
233
234    /// Builds the [`Interface`] instance.
235    ///
236    /// # Returns
237    ///
238    /// An `Interface` with the specified configuration.
239    ///
240    /// # Examples
241    ///
242    /// ```rust
243    /// use interface_rs::interface::{Interface, Family};
244    /// let iface = Interface::builder("eth0")
245    ///     .with_auto(true)
246    ///     .with_family(Family::Inet)
247    ///     .with_method("dhcp")
248    ///     .build();
249    /// ```
250    pub fn build(self) -> Interface {
251        Interface {
252            name: self.name,
253            auto: self.auto,
254            allow: self.allow,
255            family: self.family,
256            method: self.method,
257            options: self.options,
258            mapping: self.mapping,
259        }
260    }
261}
262
263#[cfg(test)]
264mod tests {
265    use super::*;
266
267    #[test]
268    fn test_remove_option() {
269        let iface = Interface::builder("eth0")
270            .with_option("address", "192.168.1.50")
271            .with_option("netmask", "255.255.255.0")
272            .with_option("address", "192.168.1.51") // Duplicate address key with different value
273            .remove_option("address") // Should remove all "address" options
274            .build();
275
276        assert_eq!(iface.options.len(), 1);
277        assert_eq!(
278            iface.options[0],
279            ("netmask".to_string(), "255.255.255.0".to_string())
280        );
281    }
282
283    #[test]
284    fn test_remove_option_value() {
285        let iface = Interface::builder("eth0")
286            .with_option("address", "192.168.1.50")
287            .with_option("netmask", "255.255.255.0")
288            .with_option("address", "192.168.1.51") // Duplicate address key with different value
289            .with_option("address", "192.168.1.52") // Duplicate address key with different value
290            .remove_option_value("address", "192.168.1.50") // Should remove only this address pair
291            .build();
292
293        assert_eq!(iface.options.len(), 3);
294        assert!(iface
295            .options
296            .contains(&("netmask".to_string(), "255.255.255.0".to_string())));
297        assert!(iface
298            .options
299            .contains(&("address".to_string(), "192.168.1.51".to_string())));
300        assert!(iface
301            .options
302            .contains(&("address".to_string(), "192.168.1.52".to_string())));
303    }
304}