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::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}