uni_addr/
lib.rs

1//! # uni-addr
2
3use std::{fmt, io};
4
5pub mod listener;
6#[cfg(unix)]
7pub mod unix;
8
9/// The prefix for Unix domain socket URIs.
10///
11/// - `unix:///path/to/socket` for a pathname socket address.
12/// - `unix://@abstract.unix.socket` for an abstract socket address.
13pub const UNIX_URI_PREFIX: &str = "unix://";
14
15#[derive(Clone, PartialEq, Eq, Hash)]
16/// A unified address type that can represent both
17/// [`std::net::SocketAddr`] and [`unix::SocketAddr`] (a wrapper over
18/// [`std::os::unix::net::SocketAddr`]).
19///
20/// ## Notes
21///
22/// For Unix domain sockets addresses, serialization/deserialization will be
23/// performed in URI format (see [`UNIX_URI_PREFIX`]), which is different from
24/// [`unix::SocketAddr`]'s serialization/deserialization behaviour.
25pub enum SocketAddr {
26    /// See [`std::net::SocketAddr`].
27    Inet(std::net::SocketAddr),
28
29    #[cfg(unix)]
30    /// See [`unix::SocketAddr`].
31    Unix(unix::SocketAddr),
32}
33
34impl fmt::Debug for SocketAddr {
35    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36        match self {
37            SocketAddr::Inet(addr) => addr.fmt(f),
38            #[cfg(unix)]
39            SocketAddr::Unix(addr) => addr.fmt(f),
40        }
41    }
42}
43
44impl From<std::net::SocketAddr> for SocketAddr {
45    fn from(addr: std::net::SocketAddr) -> Self {
46        SocketAddr::Inet(addr)
47    }
48}
49
50#[cfg(unix)]
51impl From<unix::SocketAddr> for SocketAddr {
52    fn from(addr: unix::SocketAddr) -> Self {
53        SocketAddr::Unix(addr)
54    }
55}
56
57#[cfg(all(unix, feature = "feat-tokio"))]
58impl From<tokio::net::unix::SocketAddr> for SocketAddr {
59    fn from(addr: tokio::net::unix::SocketAddr) -> Self {
60        SocketAddr::Unix(unix::SocketAddr::from(addr.into()))
61    }
62}
63
64impl SocketAddr {
65    #[inline]
66    /// Creates a new [`SocketAddr`] from its string representation.
67    ///
68    /// The string can be in one of the following formats:
69    ///
70    /// - Network socket address: `"127.0.0.1:8080"`, `"[::1]:8080"`
71    /// - Unix domain socket (pathname): `"unix:///run/listen.sock"`
72    /// - Unix domain socket (abstract): `"unix://@abstract.unix.socket"`
73    ///
74    /// # Examples
75    ///
76    /// ```rust
77    /// # use uni_addr::SocketAddr;
78    /// // Network addresses
79    /// let addr_v4 = SocketAddr::new("127.0.0.1:8080").unwrap();
80    /// let addr_v6 = SocketAddr::new("[::1]:8080").unwrap();
81    ///
82    /// // Unix domain sockets
83    /// let addr_unix_filename = SocketAddr::new("unix:///run/listen.sock").unwrap();
84    /// let addr_unix_abstract = SocketAddr::new("unix://@abstract.unix.socket").unwrap();
85    /// ```
86    ///
87    /// See [`unix::SocketAddr::new`] for more details on Unix socket address
88    /// formats.
89    pub fn new(addr: &str) -> io::Result<Self> {
90        if let Some(addr) = addr.strip_prefix(UNIX_URI_PREFIX) {
91            #[cfg(unix)]
92            return unix::SocketAddr::new(addr).map(SocketAddr::Unix);
93
94            #[cfg(not(unix))]
95            return Err(io::Error::new(
96                io::ErrorKind::Unsupported,
97                "Unix socket addresses are not supported on this platform",
98            ));
99        }
100
101        addr.parse()
102            .map(SocketAddr::Inet)
103            .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "unknown format"))
104    }
105
106    #[inline]
107    /// Binds a standard (TCP) listener to the address.
108    pub fn bind_std(&self) -> io::Result<listener::StdListener> {
109        match self {
110            SocketAddr::Inet(addr) => std::net::TcpListener::bind(addr).map(listener::StdListener::Tcp),
111            #[cfg(unix)]
112            SocketAddr::Unix(addr) => addr.bind_std().map(listener::StdListener::Unix),
113        }
114    }
115
116    #[cfg(feature = "feat-tokio")]
117    #[inline]
118    /// Binds a Tokio (TCP) listener to the address.
119    pub async fn bind(&self) -> io::Result<listener::Listener> {
120        match self {
121            SocketAddr::Inet(addr) => tokio::net::TcpListener::bind(addr).await.map(listener::Listener::Tcp),
122            #[cfg(unix)]
123            SocketAddr::Unix(addr) => addr.bind().map(listener::Listener::Unix),
124        }
125    }
126
127    /// Serializes the address to a `String`.
128    pub fn to_string_ext(&self) -> Option<String> {
129        match self {
130            Self::Inet(addr) => Some(addr.to_string()),
131            Self::Unix(addr) => addr._to_os_string(UNIX_URI_PREFIX, "@").into_string().ok(),
132        }
133    }
134}
135
136#[cfg(feature = "feat-serde")]
137impl serde::Serialize for SocketAddr {
138    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
139    where
140        S: serde::Serializer,
141    {
142        serializer.serialize_str(
143            &self
144                .to_string_ext()
145                .ok_or_else(|| serde::ser::Error::custom("invalid UTF-8"))?,
146        )
147    }
148}
149
150#[cfg(feature = "feat-serde")]
151impl<'de> serde::Deserialize<'de> for SocketAddr {
152    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
153    where
154        D: serde::Deserializer<'de>,
155    {
156        Self::new(<&str>::deserialize(deserializer)?).map_err(serde::de::Error::custom)
157    }
158}
159
160#[cfg(test)]
161mod tests {
162    #[cfg(unix)]
163    use std::os::linux::net::SocketAddrExt;
164
165    use super::*;
166
167    #[test]
168    fn test_socket_addr_new_ipv4() {
169        let addr = SocketAddr::new("127.0.0.1:8080").unwrap();
170
171        match addr {
172            SocketAddr::Inet(std_addr) => {
173                assert_eq!(std_addr.ip().to_string(), "127.0.0.1");
174                assert_eq!(std_addr.port(), 8080);
175            }
176            #[cfg(unix)]
177            SocketAddr::Unix(_) => unreachable!(),
178        }
179    }
180
181    #[test]
182    fn test_socket_addr_new_ipv6() {
183        let addr = SocketAddr::new("[::1]:8080").unwrap();
184
185        match addr {
186            SocketAddr::Inet(std_addr) => {
187                assert_eq!(std_addr.ip().to_string(), "::1");
188                assert_eq!(std_addr.port(), 8080);
189            }
190            #[cfg(unix)]
191            SocketAddr::Unix(_) => unreachable!(),
192        }
193    }
194
195    #[cfg(unix)]
196    #[test]
197    fn test_socket_addr_new_unix_pathname() {
198        let addr = SocketAddr::new("unix:///tmp/test.sock").unwrap();
199
200        match addr {
201            SocketAddr::Inet(_) => unreachable!(),
202            SocketAddr::Unix(unix_addr) => {
203                assert!(unix_addr.as_pathname().is_some());
204            }
205        }
206    }
207
208    #[cfg(unix)]
209    #[test]
210    fn test_socket_addr_new_unix_abstract() {
211        let addr = SocketAddr::new("unix://@test.abstract").unwrap();
212
213        match addr {
214            SocketAddr::Inet(_) => unreachable!(),
215            SocketAddr::Unix(unix_addr) => {
216                assert!(unix_addr.as_abstract_name().is_some());
217            }
218        }
219    }
220
221    #[test]
222    fn test_socket_addr_new_invalid() {
223        // Invalid format
224        assert!(SocketAddr::new("invalid").is_err());
225        assert!(SocketAddr::new("127.0.0.1").is_err()); // Missing port
226        assert!(SocketAddr::new("127.0.0.1:invalid").is_err()); // Invalid port
227    }
228
229    #[cfg(not(unix))]
230    #[test]
231    fn test_socket_addr_new_unix_unsupported() {
232        // Unix sockets should be unsupported on non-Unix platforms
233        let result = SocketAddr::new("unix:///tmp/test.sock");
234
235        assert_eq!(result.unwrap_err().kind(), io::ErrorKind::Unsupported);
236    }
237
238    #[test]
239    fn test_socket_addr_display() {
240        let addr = SocketAddr::new("127.0.0.1:8080").unwrap();
241        assert_eq!(&addr.to_string_ext().unwrap(), "127.0.0.1:8080");
242
243        let addr = SocketAddr::new("[::1]:8080").unwrap();
244        assert_eq!(&addr.to_string_ext().unwrap(), "[::1]:8080");
245
246        #[cfg(unix)]
247        {
248            let addr = SocketAddr::new("unix:///tmp/test.sock").unwrap();
249            assert_eq!(&addr.to_string_ext().unwrap(), "unix:///tmp/test.sock");
250
251            let addr = SocketAddr::new("unix://@test.abstract").unwrap();
252            assert_eq!(&addr.to_string_ext().unwrap(), "unix://@test.abstract");
253        }
254    }
255
256    #[test]
257    fn test_socket_addr_debug() {
258        let addr = SocketAddr::new("127.0.0.1:8080").unwrap();
259        let debug_str = format!("{:?}", addr);
260
261        assert!(debug_str.contains("127.0.0.1:8080"));
262    }
263
264    #[test]
265    fn test_bind_std() {
266        let addr = SocketAddr::new("127.0.0.1:0").unwrap();
267        let _listener = addr.bind_std().unwrap();
268    }
269
270    #[cfg(feature = "feat-tokio")]
271    #[tokio::test]
272    async fn test_bind_tokio() {
273        let addr = SocketAddr::new("127.0.0.1:0").unwrap();
274        let _listener = addr.bind().await.unwrap();
275    }
276
277    #[cfg(all(unix, feature = "feat-tokio"))]
278    #[tokio::test]
279    async fn test_bind_tokio_unix() {
280        let addr = SocketAddr::new("unix:///tmp/test_bind_tokio_unix.sock").unwrap();
281        let _listener = addr.bind().await.unwrap();
282    }
283
284    #[cfg(all(any(target_os = "android", target_os = "linux"), feature = "feat-tokio"))]
285    #[tokio::test]
286    async fn test_bind_tokio_unix_abstract() {
287        let addr = SocketAddr::new("unix://@abstract.test").unwrap();
288        let _listener = addr.bind().await.unwrap();
289    }
290
291    #[test]
292    fn test_edge_cases() {
293        assert!(SocketAddr::new("").is_err());
294        assert!(SocketAddr::new("not-an-address").is_err());
295        assert!(SocketAddr::new("127.0.0.1:99999").is_err()); // Port too high
296
297        #[cfg(unix)]
298        {
299            assert!(SocketAddr::new("unix://").is_ok()); // Empty path -> unnamed one
300            #[cfg(any(target_os = "android", target_os = "linux"))]
301            assert!(SocketAddr::new("unix://@").is_ok()); // Empty abstract one
302        }
303    }
304}