1use std::fmt;
4use std::net::{Ipv4Addr, Ipv6Addr};
5
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
15pub enum IpVersion {
16 V4,
18 V6,
20 Both,
22}
23
24impl IpVersion {
25 #[must_use]
27 pub const fn includes_v4(self) -> bool {
28 matches!(self, Self::V4 | Self::Both)
29 }
30
31 #[must_use]
33 pub const fn includes_v6(self) -> bool {
34 matches!(self, Self::V6 | Self::Both)
35 }
36}
37
38impl fmt::Display for IpVersion {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 match self {
41 Self::V4 => write!(f, "IPv4"),
42 Self::V6 => write!(f, "IPv6"),
43 Self::Both => write!(f, "Both"),
44 }
45 }
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
53pub enum AdapterKind {
54 Ethernet,
56 Wireless,
58 Loopback,
60 Virtual,
62 Other(u32),
64}
65
66impl AdapterKind {
67 #[must_use]
69 pub const fn is_virtual(&self) -> bool {
70 matches!(self, Self::Virtual)
71 }
72
73 #[must_use]
75 pub const fn is_loopback(&self) -> bool {
76 matches!(self, Self::Loopback)
77 }
78}
79
80#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
87pub struct AdapterSnapshot {
88 pub name: String,
90 pub kind: AdapterKind,
92 pub ipv4_addresses: Vec<Ipv4Addr>,
94 pub ipv6_addresses: Vec<Ipv6Addr>,
96}
97
98impl AdapterSnapshot {
99 #[must_use]
101 pub fn new(
102 name: impl Into<String>,
103 kind: AdapterKind,
104 ipv4_addresses: Vec<Ipv4Addr>,
105 ipv6_addresses: Vec<Ipv6Addr>,
106 ) -> Self {
107 Self {
108 name: name.into(),
109 kind,
110 ipv4_addresses,
111 ipv6_addresses,
112 }
113 }
114
115 #[must_use]
117 pub fn has_addresses(&self) -> bool {
118 !self.ipv4_addresses.is_empty() || !self.ipv6_addresses.is_empty()
119 }
120
121 #[must_use]
123 pub fn address_count(&self) -> usize {
124 self.ipv4_addresses.len() + self.ipv6_addresses.len()
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 mod ip_version {
133 use super::*;
134
135 #[test]
136 fn v4_includes_only_v4() {
137 assert!(IpVersion::V4.includes_v4());
138 assert!(!IpVersion::V4.includes_v6());
139 }
140
141 #[test]
142 fn v6_includes_only_v6() {
143 assert!(!IpVersion::V6.includes_v4());
144 assert!(IpVersion::V6.includes_v6());
145 }
146
147 #[test]
148 fn both_includes_both() {
149 assert!(IpVersion::Both.includes_v4());
150 assert!(IpVersion::Both.includes_v6());
151 }
152
153 #[test]
154 fn display_formats_correctly() {
155 assert_eq!(format!("{}", IpVersion::V4), "IPv4");
156 assert_eq!(format!("{}", IpVersion::V6), "IPv6");
157 assert_eq!(format!("{}", IpVersion::Both), "Both");
158 }
159 }
160
161 mod adapter_kind {
162 use super::*;
163
164 #[test]
165 fn virtual_is_virtual() {
166 assert!(AdapterKind::Virtual.is_virtual());
167 assert!(!AdapterKind::Ethernet.is_virtual());
168 assert!(!AdapterKind::Wireless.is_virtual());
169 assert!(!AdapterKind::Loopback.is_virtual());
170 assert!(!AdapterKind::Other(999).is_virtual());
171 }
172
173 #[test]
174 fn loopback_is_loopback() {
175 assert!(AdapterKind::Loopback.is_loopback());
176 assert!(!AdapterKind::Ethernet.is_loopback());
177 assert!(!AdapterKind::Virtual.is_loopback());
178 }
179
180 #[test]
181 fn other_preserves_type_code() {
182 let kind = AdapterKind::Other(42);
183 assert_eq!(kind, AdapterKind::Other(42));
184 assert_ne!(kind, AdapterKind::Other(99));
185 }
186 }
187
188 mod adapter_snapshot {
189 use super::*;
190
191 fn make_snapshot() -> AdapterSnapshot {
192 AdapterSnapshot::new(
193 "eth0",
194 AdapterKind::Ethernet,
195 vec!["192.168.1.1".parse().unwrap()],
196 vec!["fe80::1".parse().unwrap()],
197 )
198 }
199
200 #[test]
201 fn new_creates_snapshot_with_correct_fields() {
202 let snapshot = make_snapshot();
203
204 assert_eq!(snapshot.name, "eth0");
205 assert_eq!(snapshot.kind, AdapterKind::Ethernet);
206 assert_eq!(snapshot.ipv4_addresses.len(), 1);
207 assert_eq!(snapshot.ipv6_addresses.len(), 1);
208 }
209
210 #[test]
211 fn has_addresses_true_with_ipv4() {
212 let snapshot = AdapterSnapshot::new(
213 "eth0",
214 AdapterKind::Ethernet,
215 vec!["192.168.1.1".parse().unwrap()],
216 vec![],
217 );
218 assert!(snapshot.has_addresses());
219 }
220
221 #[test]
222 fn has_addresses_true_with_ipv6() {
223 let snapshot = AdapterSnapshot::new(
224 "eth0",
225 AdapterKind::Ethernet,
226 vec![],
227 vec!["fe80::1".parse().unwrap()],
228 );
229 assert!(snapshot.has_addresses());
230 }
231
232 #[test]
233 fn has_addresses_false_when_empty() {
234 let snapshot = AdapterSnapshot::new("eth0", AdapterKind::Ethernet, vec![], vec![]);
235 assert!(!snapshot.has_addresses());
236 }
237
238 #[test]
239 fn address_count_sums_both_types() {
240 let snapshot = AdapterSnapshot::new(
241 "eth0",
242 AdapterKind::Ethernet,
243 vec![
244 "192.168.1.1".parse().unwrap(),
245 "192.168.1.2".parse().unwrap(),
246 ],
247 vec!["fe80::1".parse().unwrap()],
248 );
249 assert_eq!(snapshot.address_count(), 3);
250 }
251
252 #[test]
253 fn address_count_zero_when_empty() {
254 let snapshot = AdapterSnapshot::new("eth0", AdapterKind::Ethernet, vec![], vec![]);
255 assert_eq!(snapshot.address_count(), 0);
256 }
257
258 #[test]
259 fn equality_requires_same_name() {
260 let snapshot1 = make_snapshot();
261 let mut snapshot2 = make_snapshot();
262 snapshot2.name = "eth1".to_string();
263
264 assert_ne!(snapshot1, snapshot2);
265 }
266
267 #[test]
268 fn equality_requires_same_kind() {
269 let snapshot1 = make_snapshot();
270 let mut snapshot2 = make_snapshot();
271 snapshot2.kind = AdapterKind::Wireless;
272
273 assert_ne!(snapshot1, snapshot2);
274 }
275
276 #[test]
277 fn equality_requires_same_addresses() {
278 let snapshot1 = make_snapshot();
279 let mut snapshot2 = make_snapshot();
280 snapshot2.ipv4_addresses.push("10.0.0.1".parse().unwrap());
281
282 assert_ne!(snapshot1, snapshot2);
283 }
284 }
285}