kumo_address/
host.rs

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    /// Returns the unix domain socket representation of the address
36    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    /// Returns the ip representation of the address
44    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}