cliquenet/
addr.rs

1use std::{
2    fmt,
3    net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
4};
5
6use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
7
8/// A network address.
9///
10/// Either an IP address and port number or else a hostname and port number.
11#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
12pub enum Address {
13    Inet(IpAddr, u16),
14    Name(String, u16),
15}
16
17impl Address {
18    /// Get the port number of an address.
19    pub fn port(&self) -> u16 {
20        match self {
21            Self::Inet(_, p) => *p,
22            Self::Name(_, p) => *p,
23        }
24    }
25
26    /// Set the address port.
27    pub fn set_port(&mut self, p: u16) {
28        match self {
29            Self::Inet(_, o) => *o = p,
30            Self::Name(_, o) => *o = p,
31        }
32    }
33
34    pub fn with_port(mut self, p: u16) -> Self {
35        match self {
36            Self::Inet(ip, _) => self = Self::Inet(ip, p),
37            Self::Name(hn, _) => self = Self::Name(hn, p),
38        }
39        self
40    }
41
42    pub fn with_offset(mut self, o: u16) -> Self {
43        match self {
44            Self::Inet(ip, p) => self = Self::Inet(ip, p + o),
45            Self::Name(hn, p) => self = Self::Name(hn, p + o),
46        }
47        self
48    }
49
50    pub fn is_ip(&self) -> bool {
51        matches!(self, Self::Inet(..))
52    }
53}
54
55impl fmt::Display for Address {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        match self {
58            Self::Inet(a, p) => write!(f, "{a}:{p}"),
59            Self::Name(h, p) => write!(f, "{h}:{p}"),
60        }
61    }
62}
63
64impl From<(&str, u16)> for Address {
65    fn from((h, p): (&str, u16)) -> Self {
66        Self::Name(h.to_string(), p)
67    }
68}
69
70impl From<(String, u16)> for Address {
71    fn from((h, p): (String, u16)) -> Self {
72        Self::Name(h, p)
73    }
74}
75
76impl From<(IpAddr, u16)> for Address {
77    fn from((ip, p): (IpAddr, u16)) -> Self {
78        Self::Inet(ip, p)
79    }
80}
81
82impl From<(Ipv4Addr, u16)> for Address {
83    fn from((ip, p): (Ipv4Addr, u16)) -> Self {
84        Self::Inet(IpAddr::V4(ip), p)
85    }
86}
87
88impl From<(Ipv6Addr, u16)> for Address {
89    fn from((ip, p): (Ipv6Addr, u16)) -> Self {
90        Self::Inet(IpAddr::V6(ip), p)
91    }
92}
93
94impl From<SocketAddr> for Address {
95    fn from(a: SocketAddr) -> Self {
96        Self::Inet(a.ip(), a.port())
97    }
98}
99
100impl std::str::FromStr for Address {
101    type Err = InvalidAddress;
102
103    fn from_str(s: &str) -> Result<Self, Self::Err> {
104        let parse = |a: &str, p: Option<&str>| {
105            let p: u16 = if let Some(p) = p {
106                p.parse().map_err(|_| InvalidAddress(()))?
107            } else {
108                0
109            };
110            IpAddr::from_str(a)
111                .map(|a| Self::Inet(a, p))
112                .or_else(|_| Ok(Self::Name(a.to_string(), p)))
113        };
114        match s.rsplit_once(':') {
115            None => parse(s, None),
116            Some((a, p)) => parse(a, Some(p)),
117        }
118    }
119}
120
121impl TryFrom<&str> for Address {
122    type Error = InvalidAddress;
123
124    fn try_from(val: &str) -> Result<Self, Self::Error> {
125        val.parse()
126    }
127}
128
129#[derive(Debug, Clone, thiserror::Error)]
130#[error("invalid address")]
131pub struct InvalidAddress(());
132
133impl Serialize for Address {
134    fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
135        self.to_string().serialize(s)
136    }
137}
138
139impl<'de> Deserialize<'de> for Address {
140    fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
141        let s = String::deserialize(d)?;
142        let a = s.parse().map_err(de::Error::custom)?;
143        Ok(a)
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use std::net::IpAddr;
150
151    use super::Address;
152
153    #[test]
154    fn test_parse() {
155        let a: Address = "127.0.0.1:1234".parse().unwrap();
156        let Address::Inet(a, p) = a else {
157            unreachable!()
158        };
159        assert_eq!(IpAddr::from([127, 0, 0, 1]), a);
160        assert_eq!(1234, p);
161
162        let a: Address = "::1:1234".parse().unwrap();
163        let Address::Inet(a, p) = a else {
164            unreachable!()
165        };
166        assert_eq!("::1".parse::<IpAddr>().unwrap(), a);
167        assert_eq!(1234, p);
168
169        let a: Address = "localhost:1234".parse().unwrap();
170        let Address::Name(h, p) = a else {
171            unreachable!()
172        };
173        assert_eq!("localhost", &h);
174        assert_eq!(1234, p);
175
176        let a: Address = "sub.domain.com:1234".parse().unwrap();
177        let Address::Name(h, p) = a else {
178            unreachable!()
179        };
180        assert_eq!("sub.domain.com", &h);
181        assert_eq!(1234, p);
182    }
183}