Skip to main content

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}