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::other("unix domain path must be absolute"),
116 })
117 } else {
118 match UnixSocketAddr::from_pathname(path) {
119 Ok(unix) => Ok(HostAddress::UnixDomain(unix.into())),
120 Err(unix_err) => Err(AddressParseError {
121 candidate: s.to_string(),
122 net_err,
123 unix_err,
124 }),
125 }
126 }
127 }
128 }
129 }
130}
131
132impl TryFrom<String> for HostAddress {
133 type Error = AddressParseError;
134 fn try_from(s: String) -> Result<HostAddress, Self::Error> {
135 HostAddress::from_str(&s)
136 }
137}
138
139impl From<UnixSocketAddr> for HostAddress {
140 fn from(unix: UnixSocketAddr) -> HostAddress {
141 HostAddress::UnixDomain(unix.into())
142 }
143}
144
145impl From<Ipv4Addr> for HostAddress {
146 fn from(ip: Ipv4Addr) -> HostAddress {
147 HostAddress::V4(ip)
148 }
149}
150
151impl From<Ipv6Addr> for HostAddress {
152 fn from(ip: Ipv6Addr) -> HostAddress {
153 HostAddress::V6(ip)
154 }
155}
156
157impl From<IpAddr> for HostAddress {
158 fn from(ip: IpAddr) -> HostAddress {
159 match ip {
160 IpAddr::V4(a) => HostAddress::V4(a),
161 IpAddr::V6(a) => HostAddress::V6(a),
162 }
163 }
164}
165
166impl From<SocketAddr> for HostAddress {
167 fn from(a: SocketAddr) -> HostAddress {
168 a.ip().into()
169 }
170}
171
172#[cfg(test)]
173mod test {
174 use super::*;
175
176 #[test]
177 fn parse() {
178 assert_eq!(
179 "10.0.0.1".parse::<HostAddress>(),
180 Ok(HostAddress::V4(Ipv4Addr::new(10, 0, 0, 1)))
181 );
182 assert_eq!(
183 "[10.0.0.1]".parse::<HostAddress>(),
184 Ok(HostAddress::V4(Ipv4Addr::new(10, 0, 0, 1)))
185 );
186 assert_eq!(
187 "::1".parse::<HostAddress>(),
188 Ok(HostAddress::V6(Ipv6Addr::LOCALHOST))
189 );
190 assert_eq!(
191 "[::1]".parse::<HostAddress>(),
192 Ok(HostAddress::V6(Ipv6Addr::LOCALHOST))
193 );
194 assert_eq!(
195 "/some/path".parse::<HostAddress>(),
196 Ok(HostAddress::UnixDomain(
197 UnixSocketAddr::from_pathname("/some/path").unwrap().into()
198 ))
199 );
200 assert_eq!(
201 format!("{:#}", "[/some/path]".parse::<HostAddress>().unwrap_err()),
202 "Failed to parse '[/some/path]' as an address. \
203 Got 'invalid IP address syntax' when considering it as \
204 an IP address and 'unix domain path must be absolute' \
205 when considering it as a unix domain socket path."
206 );
207 assert_eq!(
208 format!("{:#}", "hello there".parse::<HostAddress>().unwrap_err()),
209 "Failed to parse 'hello there' as an address. \
210 Got 'invalid IP address syntax' when considering it as \
211 an IP address and 'unix domain path must be absolute' \
212 when considering it as a unix domain socket path."
213 );
214 }
215}