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}