1use std::net::{IpAddr, ToSocketAddrs};
2
3use thiserror::Error;
4
5use crate::config::AddressFamily;
6
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct ResolveResult {
9 pub host: String,
10 pub addresses: Vec<IpAddr>,
11}
12
13#[derive(Debug, Error, Clone, PartialEq, Eq)]
14pub enum ResolveError {
15 #[error("host must not be empty")]
16 EmptyHost,
17 #[error("failed to resolve '{host}': {message}")]
18 LookupFailed { host: String, message: String },
19 #[error("resolved '{host}' but no address matches family {family:?}")]
20 NoAddressForFamily { host: String, family: AddressFamily },
21}
22
23pub fn resolve_once(host: &str, family: AddressFamily) -> Result<ResolveResult, ResolveError> {
24 let trimmed = host.trim();
25 if trimmed.is_empty() {
26 return Err(ResolveError::EmptyHost);
27 }
28
29 if let Ok(ip) = trimmed.parse::<IpAddr>() {
30 if matches_family(ip, family) {
31 return Ok(ResolveResult {
32 host: trimmed.to_string(),
33 addresses: vec![ip],
34 });
35 }
36 return Err(ResolveError::NoAddressForFamily {
37 host: trimmed.to_string(),
38 family,
39 });
40 }
41
42 let lookup = (trimmed, 0)
43 .to_socket_addrs()
44 .map_err(|err| ResolveError::LookupFailed {
45 host: trimmed.to_string(),
46 message: err.to_string(),
47 })?;
48
49 let mut addresses = Vec::new();
50 for socket_addr in lookup {
51 let ip = socket_addr.ip();
52 if matches_family(ip, family) && !addresses.contains(&ip) {
53 addresses.push(ip);
54 }
55 }
56
57 if addresses.is_empty() {
58 return Err(ResolveError::NoAddressForFamily {
59 host: trimmed.to_string(),
60 family,
61 });
62 }
63
64 Ok(ResolveResult {
65 host: trimmed.to_string(),
66 addresses,
67 })
68}
69
70fn matches_family(ip: IpAddr, family: AddressFamily) -> bool {
71 match family {
72 AddressFamily::Any => true,
73 AddressFamily::Ipv4 => ip.is_ipv4(),
74 AddressFamily::Ipv6 => ip.is_ipv6(),
75 }
76}
77
78#[cfg(test)]
79mod tests {
80 use super::{AddressFamily, ResolveError, resolve_once};
81
82 #[test]
83 fn literal_ipv6_is_rejected_when_ipv4_is_requested() {
84 let err = resolve_once("::1", AddressFamily::Ipv4).expect_err("must reject v6 for -4");
85 assert!(matches!(err, ResolveError::NoAddressForFamily { .. }));
86 }
87
88 #[test]
89 fn literal_ipv4_is_rejected_when_ipv6_is_requested() {
90 let err =
91 resolve_once("127.0.0.1", AddressFamily::Ipv6).expect_err("must reject v4 for -6");
92 assert!(matches!(err, ResolveError::NoAddressForFamily { .. }));
93 }
94}