net_lattice_model/dns.rs
1use crate::IpAddress;
2
3/// The resolver configuration observed through the active platform backend.
4///
5/// This is an observed resolver view, not a promise of complete persistent or
6/// per-interface DNS state. For example, Unix backends currently read the
7/// active resolver file, while Windows collects adapter-provided resolver
8/// data. Server and search-domain order is preserved as reported by the
9/// backend.
10///
11/// `#[non_exhaustive]`: platforms surface DNS configuration differently
12/// (a flat `/etc/resolv.conf` on Linux/BSD/macOS vs. per-adapter DNS server
13/// lists plus a global suffix on Windows) — this carries the subset that
14/// maps cleanly across all of them. Marking it non-exhaustive now means
15/// adding platform-specific fields later (e.g. per-interface resolvers) is
16/// not a breaking change for consumers who construct a `DnsConfig` via
17/// [`DnsConfig::new`] rather than a struct literal — see ARCHITECTURE.md's
18/// note on model extensibility.
19#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
20#[non_exhaustive]
21pub struct DnsConfig {
22 /// Nameserver addresses, in resolution order.
23 pub nameservers: Vec<IpAddress>,
24 /// Search-list domains appended to unqualified lookups, in order.
25 pub search_domains: Vec<String>,
26}
27
28impl DnsConfig {
29 pub fn new() -> Self {
30 Self::default()
31 }
32}
33
34/// Desired system resolver configuration.
35///
36/// This input model intentionally does not include backend-observed metadata:
37/// callers state the resolver servers and search domains they want, and a
38/// successful mutation returns the resulting observed [`DnsConfig`].
39#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
40#[non_exhaustive]
41pub struct NewDnsConfig {
42 /// Nameserver addresses, in resolution order.
43 pub nameservers: Vec<IpAddress>,
44 /// Search-list domains appended to unqualified lookups, in order.
45 pub search_domains: Vec<String>,
46}
47
48impl NewDnsConfig {
49 /// Creates an empty resolver configuration.
50 ///
51 /// An empty configuration requests removal of explicitly configured
52 /// nameservers and search domains where the platform supports it.
53 pub fn new() -> Self {
54 Self::default()
55 }
56
57 /// Creates a desired resolver configuration from ordered servers and
58 /// search domains.
59 pub fn with(nameservers: Vec<IpAddress>, search_domains: Vec<String>) -> Self {
60 Self {
61 nameservers,
62 search_domains,
63 }
64 }
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70 use net_lattice_ip::Ipv4Address;
71
72 #[test]
73 fn new_config_has_no_entries() {
74 let config = DnsConfig::new();
75 assert!(config.nameservers.is_empty());
76 assert!(config.search_domains.is_empty());
77 }
78
79 #[test]
80 fn config_carries_nameservers_and_search_domains() {
81 let config = DnsConfig {
82 nameservers: vec![IpAddress::V4(Ipv4Address::new(1, 1, 1, 1))],
83 search_domains: vec!["example.com".to_string()],
84 };
85 assert_eq!(config.nameservers.len(), 1);
86 assert_eq!(config.search_domains, vec!["example.com".to_string()]);
87 }
88
89 #[test]
90 fn new_desired_config_has_no_entries() {
91 let config = NewDnsConfig::new();
92 assert!(config.nameservers.is_empty());
93 assert!(config.search_domains.is_empty());
94 }
95
96 #[test]
97 fn desired_config_constructor_preserves_entries() {
98 let config = NewDnsConfig::with(
99 vec![IpAddress::V4(Ipv4Address::new(9, 9, 9, 9))],
100 vec!["example.test".to_string()],
101 );
102 assert_eq!(config.nameservers.len(), 1);
103 assert_eq!(config.search_domains, ["example.test"]);
104 }
105}