microsandbox_network/model/dns/nameserver/
parse.rs1use std::fmt;
8use std::net::{IpAddr, SocketAddr};
9use std::str::FromStr;
10
11use serde::{Deserialize, Serialize};
12
13const DEFAULT_DNS_PORT: u16 = 53;
15
16#[derive(Debug, Clone, PartialEq, Eq)]
21pub enum Nameserver {
22 Addr(SocketAddr),
24 Host {
26 host: String,
28 port: u16,
30 },
31}
32
33impl fmt::Display for Nameserver {
34 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35 match self {
36 Self::Addr(sa) => write!(f, "{sa}"),
37 Self::Host { host, port } => write!(f, "{host}:{port}"),
38 }
39 }
40}
41
42#[derive(Debug, thiserror::Error)]
44#[error("invalid nameserver {0:?}; expected IP, IP:PORT, HOST, or HOST:PORT")]
45pub struct ParseNameserverError(pub String);
46
47impl FromStr for Nameserver {
57 type Err = ParseNameserverError;
58
59 fn from_str(input: &str) -> Result<Self, Self::Err> {
60 let s = input.trim();
61 if s.is_empty() {
62 return Err(ParseNameserverError(input.to_owned()));
63 }
64
65 if let Ok(sa) = s.parse::<SocketAddr>() {
67 return Ok(Self::Addr(sa));
68 }
69
70 if let Ok(ip) = s.parse::<IpAddr>() {
72 return Ok(Self::Addr(SocketAddr::new(ip, DEFAULT_DNS_PORT)));
73 }
74
75 if let Some((host, port)) = s.rsplit_once(':')
80 && !host.is_empty()
81 && !host.contains(':')
82 && host.parse::<IpAddr>().is_err()
83 && let Ok(port) = port.parse::<u16>()
84 {
85 return Ok(Self::Host {
86 host: host.to_owned(),
87 port,
88 });
89 }
90
91 if !s.contains(char::is_whitespace) && !s.contains(':') {
94 return Ok(Self::Host {
95 host: s.to_owned(),
96 port: DEFAULT_DNS_PORT,
97 });
98 }
99
100 Err(ParseNameserverError(input.to_owned()))
101 }
102}
103
104impl Serialize for Nameserver {
107 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
108 s.collect_str(self)
109 }
110}
111
112impl<'de> Deserialize<'de> for Nameserver {
113 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
114 let s = String::deserialize(d)?;
115 s.parse().map_err(serde::de::Error::custom)
116 }
117}
118
119impl From<SocketAddr> for Nameserver {
121 fn from(sa: SocketAddr) -> Self {
122 Self::Addr(sa)
123 }
124}
125
126impl From<IpAddr> for Nameserver {
127 fn from(ip: IpAddr) -> Self {
128 Self::Addr(SocketAddr::new(ip, DEFAULT_DNS_PORT))
129 }
130}
131
132#[cfg(test)]
137mod tests {
138 use super::*;
139
140 fn addr(s: &str) -> Nameserver {
141 Nameserver::Addr(s.parse().unwrap())
142 }
143
144 fn host(host: &str, port: u16) -> Nameserver {
145 Nameserver::Host {
146 host: host.to_owned(),
147 port,
148 }
149 }
150
151 #[test]
152 fn parses_ipv4_bare() {
153 assert_eq!("1.1.1.1".parse::<Nameserver>().unwrap(), addr("1.1.1.1:53"));
154 }
155
156 #[test]
157 fn parses_ipv4_with_port() {
158 assert_eq!(
159 "8.8.8.8:5353".parse::<Nameserver>().unwrap(),
160 addr("8.8.8.8:5353")
161 );
162 }
163
164 #[test]
165 fn parses_ipv6_bare() {
166 assert_eq!(
167 "2606:4700:4700::1111".parse::<Nameserver>().unwrap(),
168 addr("[2606:4700:4700::1111]:53")
169 );
170 }
171
172 #[test]
173 fn parses_ipv6_bracketed_with_port() {
174 assert_eq!(
175 "[2606:4700:4700::1111]:53".parse::<Nameserver>().unwrap(),
176 addr("[2606:4700:4700::1111]:53")
177 );
178 }
179
180 #[test]
181 fn parses_hostname_bare() {
182 assert_eq!(
183 "dns.google".parse::<Nameserver>().unwrap(),
184 host("dns.google", 53)
185 );
186 }
187
188 #[test]
189 fn parses_hostname_with_port() {
190 assert_eq!(
191 "dns.google:53".parse::<Nameserver>().unwrap(),
192 host("dns.google", 53)
193 );
194 assert_eq!(
195 "my-dns.corp.internal:5353".parse::<Nameserver>().unwrap(),
196 host("my-dns.corp.internal", 5353)
197 );
198 }
199
200 #[test]
201 fn trims_whitespace() {
202 assert_eq!(
203 " 1.1.1.1 ".parse::<Nameserver>().unwrap(),
204 addr("1.1.1.1:53")
205 );
206 }
207
208 #[test]
209 fn rejects_empty() {
210 assert!("".parse::<Nameserver>().is_err());
211 assert!(" ".parse::<Nameserver>().is_err());
212 }
213
214 #[test]
215 fn rejects_embedded_whitespace() {
216 assert!("dns google".parse::<Nameserver>().is_err());
217 }
218
219 #[test]
220 fn rejects_bad_port() {
221 assert!("dns.google:notaport".parse::<Nameserver>().is_err());
222 assert!("1.1.1.1:99999".parse::<Nameserver>().is_err());
223 }
224
225 #[test]
226 fn display_roundtrip() {
227 for s in ["1.1.1.1:53", "[2606:4700:4700::1111]:53", "dns.google:53"] {
228 let ns: Nameserver = s.parse().unwrap();
229 assert_eq!(ns.to_string(), s);
230 }
231 }
232
233 #[test]
234 fn display_feeds_back_into_parse() {
235 for s in ["1.1.1.1", "dns.google", "dns.google:53"] {
236 let ns: Nameserver = s.parse().unwrap();
237 let reparsed: Nameserver = ns.to_string().parse().unwrap();
239 assert_eq!(ns, reparsed);
240 }
241 }
242}