interface_rs/interface/
interface_struct.rs

1use super::{Family, InterfaceBuilder, InterfaceOption, Mapping, Method};
2use std::fmt;
3
4/// Represents a network interface configuration in an `interfaces(5)` file.
5///
6/// The `Interface` struct encapsulates all the configuration details for a
7/// network interface, including its name, whether it starts automatically,
8/// allowed hotplug options, address family, method of configuration, and
9/// additional options.
10///
11/// To construct an `Interface`, it is recommended to use the [`InterfaceBuilder`]
12/// via the [`Interface::builder`] method for a more ergonomic and fluent API.
13///
14/// # Examples
15///
16/// Creating a new `Interface` using the builder pattern:
17///
18/// ```rust
19/// use interface_rs::interface::{Interface, Family, Method};
20///
21/// let iface = Interface::builder("eth0")
22///     .with_auto(true)
23///     .with_allow("hotplug")
24///     .with_family(Family::Inet)
25///     .with_method(Method::Dhcp)
26///     .with_option("mtu", "1500")
27///     .build();
28/// ```
29#[derive(Debug, Clone, PartialEq)]
30pub struct Interface {
31    /// The name of the interface (e.g., `"eth0"`).
32    pub name: String,
33    /// Indicates if the interface is set to start automatically.
34    pub auto: bool,
35    /// A list of `allow-*` directives associated with the interface.
36    pub allow: Vec<String>,
37    /// The address family (e.g., `inet`).
38    pub family: Option<Family>,
39    /// The method of configuration (e.g., `static`, `dhcp`).
40    pub method: Option<Method>,
41    /// A list of options specified under the `iface` stanza.
42    pub options: Vec<InterfaceOption>,
43    /// Optional mapping configuration for the interface.
44    pub mapping: Option<Mapping>,
45}
46
47impl Interface {
48    /// Creates a new [`InterfaceBuilder`] for constructing an `Interface`.
49    ///
50    /// # Arguments
51    ///
52    /// * `name` - The name of the interface (e.g., `"eth0"`).
53    ///
54    /// # Examples
55    ///
56    /// ```rust
57    /// use interface_rs::interface::Interface;
58    ///
59    /// let builder = Interface::builder("eth0");
60    /// ```
61    pub fn builder(name: impl Into<String>) -> InterfaceBuilder {
62        InterfaceBuilder::new(name)
63    }
64
65    /// Creates a new [`InterfaceBuilder`] initialized with this `Interface`'s data.
66    ///
67    /// This method allows you to modify an existing `Interface` using the builder pattern.
68    ///
69    /// # Examples
70    ///
71    /// ```rust
72    /// use interface_rs::interface::{Interface, Family, Method};
73    ///
74    /// let iface = Interface::builder("eth0")
75    ///     .with_auto(true)
76    ///     .with_family(Family::Inet)
77    ///     .with_method(Method::Dhcp)
78    ///     .build();
79    ///
80    /// // Modify the existing interface
81    /// let modified_iface = iface.edit()
82    ///     .with_method(Method::Static)
83    ///     .with_option("address", "192.168.1.50")
84    ///     .build();
85    /// ```
86    pub fn edit(&self) -> InterfaceBuilder {
87        InterfaceBuilder {
88            name: self.name.clone(),
89            auto: self.auto,
90            allow: self.allow.clone(),
91            family: self.family.clone(),
92            method: self.method.clone(),
93            options: self.options.clone(),
94            mapping: self.mapping.clone(),
95        }
96    }
97
98    /// Returns the first value for the given option key.
99    ///
100    /// # Arguments
101    ///
102    /// * `key` - The option name to look up (e.g., `"address"`, `"netmask"`).
103    ///
104    /// # Returns
105    ///
106    /// `Some(String)` if the option exists, `None` otherwise.
107    ///
108    /// # Examples
109    ///
110    /// ```rust
111    /// use interface_rs::interface::{Interface, Method};
112    ///
113    /// let iface = Interface::builder("eth0")
114    ///     .with_method(Method::Static)
115    ///     .with_option("address", "192.168.1.100")
116    ///     .with_option("netmask", "255.255.255.0")
117    ///     .build();
118    ///
119    /// assert_eq!(iface.get_option("address"), Some("192.168.1.100".to_string()));
120    /// assert_eq!(iface.get_option("gateway"), None);
121    /// ```
122    pub fn get_option(&self, key: &str) -> Option<String> {
123        self.options
124            .iter()
125            .find(|opt| opt.name() == key)
126            .map(|opt| opt.value())
127    }
128
129    /// Returns all values for the given option key.
130    ///
131    /// Some options like `address` can appear multiple times. This method
132    /// returns all values for a given key.
133    ///
134    /// # Arguments
135    ///
136    /// * `key` - The option name to look up.
137    ///
138    /// # Returns
139    ///
140    /// A `Vec` of strings containing all values for the key.
141    ///
142    /// # Examples
143    ///
144    /// ```rust
145    /// use interface_rs::interface::{Interface, Method};
146    ///
147    /// let iface = Interface::builder("eth0")
148    ///     .with_method(Method::Static)
149    ///     .with_option("address", "192.168.1.100")
150    ///     .with_option("address", "192.168.1.101")
151    ///     .build();
152    ///
153    /// let addresses = iface.get_options("address");
154    /// assert_eq!(addresses, vec!["192.168.1.100", "192.168.1.101"]);
155    /// ```
156    pub fn get_options(&self, key: &str) -> Vec<String> {
157        self.options
158            .iter()
159            .filter(|opt| opt.name() == key)
160            .map(|opt| opt.value())
161            .collect()
162    }
163}
164
165impl fmt::Display for Interface {
166    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167        if self.auto {
168            writeln!(f, "auto {}", self.name)?;
169        }
170        for allow_type in &self.allow {
171            writeln!(f, "allow-{} {}", allow_type, self.name)?;
172        }
173        if let Some(mapping) = &self.mapping {
174            writeln!(f, "mapping {}", self.name)?;
175            writeln!(f, "    script {}", mapping.script.display())?;
176            for map in &mapping.maps {
177                writeln!(f, "    map {}", map)?;
178            }
179        }
180        write!(f, "iface {}", self.name)?;
181        if let Some(family) = &self.family {
182            write!(f, " {}", family)?;
183        }
184        if let Some(method) = &self.method {
185            write!(f, " {}", method)?;
186        }
187        writeln!(f)?;
188        // Sort options before printing
189        let mut sorted_options = self.options.clone();
190        sorted_options.sort_by(|a, b| a.name().cmp(b.name()));
191        for option in &sorted_options {
192            writeln!(f, "    {}", option)?;
193        }
194        Ok(())
195    }
196}