1use serde::{Deserialize, Serialize};
2use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
3use std::os::unix::net::SocketAddr as UnixSocketAddr;
4use std::path::Path;
5use std::str::FromStr;
6use thiserror::Error;
7
8#[derive(Error, Debug)]
9#[error(
10 "Failed to parse '{candidate}' as an address. \
11 Got '{net_err}' when considering it as an IP address and \
12 '{unix_err}' when considering it as a unix domain socket path."
13)]
14pub struct AddressParseError {
15 pub(crate) candidate: String,
16 pub(crate) net_err: std::net::AddrParseError,
17 pub(crate) unix_err: std::io::Error,
18}
19
20impl PartialEq for AddressParseError {
21 fn eq(&self, other: &Self) -> bool {
22 self.to_string().eq(&other.to_string())
23 }
24}
25
26#[derive(Clone, Deserialize, Serialize)]
27#[serde(try_from = "String", into = "String")]
28pub enum HostAddress {
29 UnixDomain(Box<UnixSocketAddr>),
30 V4(std::net::Ipv4Addr),
31 V6(std::net::Ipv6Addr),
32}
33
34impl HostAddress {
35 pub fn unix(&self) -> Option<UnixSocketAddr> {
37 match self {
38 Self::V4(_) | Self::V6(_) => None,
39 Self::UnixDomain(unix) => Some((**unix).clone()),
40 }
41 }
42
43 pub fn ip(&self) -> Option<IpAddr> {
45 match self {
46 Self::V4(a) => Some((*a).into()),
47 Self::V6(a) => Some((*a).into()),
48 Self::UnixDomain(_) => None,
49 }
50 }
51}
52
53impl PartialEq for HostAddress {
54 fn eq(&self, other: &Self) -> bool {
55 match (self, other) {
56 (Self::UnixDomain(a), Self::UnixDomain(b)) => {
57 match (a.as_pathname(), b.as_pathname()) {
58 (Some(a), Some(b)) => a.eq(b),
59 (None, None) => true,
60 _ => false,
61 }
62 }
63 (Self::V4(a), Self::V4(b)) => a.eq(b),
64 (Self::V6(a), Self::V6(b)) => a.eq(b),
65 _ => false,
66 }
67 }
68}
69
70impl Eq for HostAddress {}
71
72impl From<HostAddress> for String {
73 fn from(a: HostAddress) -> String {
74 format!("{a}")
75 }
76}
77
78impl std::fmt::Debug for HostAddress {
79 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
80 <Self as std::fmt::Display>::fmt(self, fmt)
81 }
82}
83
84impl std::fmt::Display for HostAddress {
85 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
86 match self {
87 Self::UnixDomain(unix) => match unix.as_pathname() {
88 Some(path) => path.display().fmt(fmt),
89 None => write!(fmt, "<unbound unix domain>"),
90 },
91 Self::V4(a) => a.fmt(fmt),
92 Self::V6(a) => a.fmt(fmt),
93 }
94 }
95}
96
97impl FromStr for HostAddress {
98 type Err = AddressParseError;
99 fn from_str(s: &str) -> Result<HostAddress, Self::Err> {
100 match IpAddr::from_str(s) {
101 Ok(a) => Ok(a.into()),
102 Err(net_err) => {
103 if s.starts_with('[') && s.ends_with(']') {
104 let alternative = &s[1..s.len() - 1];
105 if let Ok(a) = IpAddr::from_str(alternative) {
106 return Ok(a.into());
107 }
108 }
109
110 let path: &Path = s.as_ref();
111 if path.is_relative() {
112 Err(AddressParseError {
113 candidate: s.to_string(),
114 net_err,
115 unix_err: std::io::Error::new(
116 std::io::ErrorKind::Other,
117 "unix domain path must be absolute",
118 ),
119 })
120 } else {
121 match UnixSocketAddr::from_pathname(path) {
122 Ok(unix) => Ok(HostAddress::UnixDomain(unix.into())),
123 Err(unix_err) => Err(AddressParseError {
124 candidate: s.to_string(),
125 net_err,
126 unix_err,
127 }),
128 }
129 }
130 }
131 }
132 }
133}
134
135impl TryFrom<String> for HostAddress {
136 type Error = AddressParseError;
137 fn try_from(s: String) -> Result<HostAddress, Self::Error> {
138 HostAddress::from_str(&s)
139 }
140}
141
142impl From<UnixSocketAddr> for HostAddress {
143 fn from(unix: UnixSocketAddr) -> HostAddress {
144 HostAddress::UnixDomain(unix.into())
145 }
146}
147
148impl From<Ipv4Addr> for HostAddress {
149 fn from(ip: Ipv4Addr) -> HostAddress {
150 HostAddress::V4(ip)
151 }
152}
153
154impl From<Ipv6Addr> for HostAddress {
155 fn from(ip: Ipv6Addr) -> HostAddress {
156 HostAddress::V6(ip)
157 }
158}
159
160impl From<IpAddr> for HostAddress {
161 fn from(ip: IpAddr) -> HostAddress {
162 match ip {
163 IpAddr::V4(a) => HostAddress::V4(a),
164 IpAddr::V6(a) => HostAddress::V6(a),
165 }
166 }
167}
168
169impl From<SocketAddr> for HostAddress {
170 fn from(a: SocketAddr) -> HostAddress {
171 a.ip().into()
172 }
173}
174
175#[cfg(test)]
176mod test {
177 use super::*;
178
179 #[test]
180 fn parse() {
181 assert_eq!(
182 "10.0.0.1".parse::<HostAddress>(),
183 Ok(HostAddress::V4(Ipv4Addr::new(10, 0, 0, 1)))
184 );
185 assert_eq!(
186 "[10.0.0.1]".parse::<HostAddress>(),
187 Ok(HostAddress::V4(Ipv4Addr::new(10, 0, 0, 1)))
188 );
189 assert_eq!(
190 "::1".parse::<HostAddress>(),
191 Ok(HostAddress::V6(Ipv6Addr::LOCALHOST))
192 );
193 assert_eq!(
194 "[::1]".parse::<HostAddress>(),
195 Ok(HostAddress::V6(Ipv6Addr::LOCALHOST))
196 );
197 assert_eq!(
198 "/some/path".parse::<HostAddress>(),
199 Ok(HostAddress::UnixDomain(
200 UnixSocketAddr::from_pathname("/some/path").unwrap().into()
201 ))
202 );
203 assert_eq!(
204 format!("{:#}", "[/some/path]".parse::<HostAddress>().unwrap_err()),
205 "Failed to parse '[/some/path]' as an address. \
206 Got 'invalid IP address syntax' when considering it as \
207 an IP address and 'unix domain path must be absolute' \
208 when considering it as a unix domain socket path."
209 );
210 assert_eq!(
211 format!("{:#}", "hello there".parse::<HostAddress>().unwrap_err()),
212 "Failed to parse 'hello there' as an address. \
213 Got 'invalid IP address syntax' when considering it as \
214 an IP address and 'unix domain path must be absolute' \
215 when considering it as a unix domain socket path."
216 );
217 }
218}