Skip to main content

bare_types/net/
socketaddr.rs

1//! Socket address type for network programming.
2//!
3//! This module provides a type-safe abstraction for socket addresses,
4//! combining a host (IP address, domain name, or hostname) with a port number.
5//!
6//! # Interoperability with `std::net`
7//!
8//! When the `std` feature is enabled, `SocketAddr` provides `TryFrom` implementations
9//! for converting to/from `std::net::SocketAddr`. Note that:
10//!
11//! - **Only IP addresses can be converted**: `std::net::SocketAddr` only supports IP addresses
12//! - **Domain names and hostnames cannot be converted**: They require DNS resolution, which is
13//!   outside the scope of this library
14//! - **Use `TryFrom`**: Conversions may fail if the host is not an IP address
15//!
16//! # Examples
17//!
18//! ```rust
19//! use bare_types::net::{SocketAddr, Host, Port};
20//!
21//! // Create from IP address and port
22//! let host = Host::parse_str("192.168.1.1").unwrap();
23//! let port = Port::new(8080).unwrap();
24//! let addr = SocketAddr::new(host, port);
25//!
26//! // Parse from string
27//! let addr: SocketAddr = "192.168.1.1:8080".parse().unwrap();
28//!
29//! // Access components
30//! assert!(addr.as_host().is_ipaddr());
31//! assert_eq!(addr.as_port().as_u16(), 8080);
32//! ```
33
34use core::fmt;
35use core::str::FromStr;
36
37#[cfg(feature = "std")]
38use super::IpAddr;
39use super::{Host, Port};
40
41#[cfg(feature = "serde")]
42use serde::{Deserialize, Serialize};
43
44#[cfg(feature = "arbitrary")]
45use arbitrary::Arbitrary;
46
47/// Error type for socket address parsing.
48#[derive(Debug, Clone, PartialEq, Eq)]
49pub enum SocketAddrError {
50    /// Missing port separator (':')
51    MissingPortSeparator,
52    /// Invalid host part
53    InvalidHost,
54    /// Invalid port part
55    InvalidPort,
56    /// Empty input
57    EmptyInput,
58}
59
60impl fmt::Display for SocketAddrError {
61    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62        match self {
63            Self::MissingPortSeparator => write!(f, "missing port separator ':'"),
64            Self::InvalidHost => write!(f, "invalid host"),
65            Self::InvalidPort => write!(f, "invalid port"),
66            Self::EmptyInput => write!(f, "empty input"),
67        }
68    }
69}
70
71#[cfg(feature = "std")]
72impl std::error::Error for SocketAddrError {}
73
74/// Error type for `std::net::SocketAddr` conversion.
75#[derive(Debug, Clone, PartialEq, Eq)]
76pub enum StdConversionError {
77    /// Host is not an IP address (domain name or hostname)
78    NotIpAddress,
79    /// Port is zero (`std::net::SocketAddr` allows port 0, but our Port type does not)
80    PortZero,
81}
82
83impl fmt::Display for StdConversionError {
84    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85        match self {
86            Self::NotIpAddress => write!(
87                f,
88                "cannot convert to std::net::SocketAddr: host is not an IP address"
89            ),
90            Self::PortZero => write!(
91                f,
92                "cannot convert from std::net::SocketAddr: port 0 is not allowed"
93            ),
94        }
95    }
96}
97
98#[cfg(feature = "std")]
99impl std::error::Error for StdConversionError {}
100
101/// A socket address combining a host and a port.
102///
103/// This type provides a type-safe socket address that can represent:
104/// - IP addresses with ports (e.g., "192.168.1.1:8080")
105/// - Domain names with ports (e.g., "example.com:443")
106/// - Hostnames with ports (e.g., "localhost:3000")
107///
108/// # Invariants
109///
110/// - The host is always valid (IP address, domain name, or hostname)
111/// - The port is always in the valid range (1-65535)
112///
113/// # Examples
114///
115/// ```rust
116/// use bare_types::net::{SocketAddr, Host, Port};
117///
118/// // Create from components
119/// let host = Host::parse_str("192.168.1.1")?;
120/// let port = Port::new(8080)?;
121/// let addr = SocketAddr::new(host, port);
122///
123/// // Parse from string
124/// let addr: SocketAddr = "example.com:443".parse()?;
125///
126/// // Access components
127/// assert_eq!(addr.as_port().as_u16(), 443);
128///
129/// ```
130#[derive(Debug, Clone, PartialEq, Eq, Hash)]
131#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
132pub struct SocketAddr {
133    /// The host part of the socket address
134    host: Host,
135    /// The port part of the socket address
136    port: Port,
137}
138
139#[cfg(feature = "arbitrary")]
140impl<'a> Arbitrary<'a> for SocketAddr {
141    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
142        Ok(Self {
143            host: Host::arbitrary(u)?,
144            port: Port::arbitrary(u)?,
145        })
146    }
147}
148
149impl SocketAddr {
150    /// Creates a new socket address from a host and port.
151    ///
152    /// # Examples
153    ///
154    /// ```rust
155    /// use bare_types::net::{SocketAddr, Host, Port};
156    ///
157    /// let host = Host::parse_str("192.168.1.1")?;
158    /// let port = Port::new(8080)?;
159    /// let addr = SocketAddr::new(host, port);
160    ///
161    /// ```
162    #[must_use]
163    #[inline]
164    pub const fn new(host: Host, port: Port) -> Self {
165        Self { host, port }
166    }
167
168    /// Returns a reference to the host part of this socket address.
169    ///
170    /// # Examples
171    ///
172    /// ```rust
173    /// use bare_types::net::{SocketAddr, Host};
174    ///
175    /// let addr: SocketAddr = "192.168.1.1:8080".parse()?;
176    /// assert!(addr.as_host().is_ipaddr());
177    ///
178    /// ```
179    #[must_use]
180    #[inline]
181    pub const fn as_host(&self) -> &Host {
182        &self.host
183    }
184
185    /// Returns a reference to the port part of this socket address.
186    ///
187    /// # Examples
188    ///
189    /// ```rust
190    /// use bare_types::net::SocketAddr;
191    ///
192    /// let addr: SocketAddr = "192.168.1.1:8080".parse()?;
193    /// assert_eq!(addr.as_port().as_u16(), 8080);
194    ///
195    /// ```
196    #[must_use]
197    #[inline]
198    pub const fn as_port(&self) -> &Port {
199        &self.port
200    }
201
202    /// Sets the host part of this socket address.
203    ///
204    /// # Examples
205    ///
206    /// ```rust
207    /// use bare_types::net::{SocketAddr, Host};
208    ///
209    /// let mut addr: SocketAddr = "192.168.1.1:8080".parse()?;
210    /// let new_host = Host::parse_str("10.0.0.1")?;
211    /// addr.set_host(new_host);
212    ///
213    /// ```
214    #[inline]
215    pub fn set_host(&mut self, host: Host) {
216        self.host = host;
217    }
218
219    /// Sets the port part of this socket address.
220    ///
221    /// # Examples
222    ///
223    /// ```rust
224    /// use bare_types::net::{SocketAddr, Port};
225    ///
226    /// let mut addr: SocketAddr = "192.168.1.1:8080".parse()?;
227    /// let new_port = Port::new(9090)?;
228    /// addr.set_port(new_port);
229    ///
230    /// ```
231    #[inline]
232    pub const fn set_port(&mut self, port: Port) {
233        self.port = port;
234    }
235
236    /// Consumes this socket address and returns the host and port components.
237    ///
238    /// # Examples
239    ///
240    /// ```rust
241    /// use bare_types::net::{SocketAddr, Host, Port};
242    ///
243    /// let host = Host::parse_str("192.168.1.1")?;
244    /// let port = Port::new(8080)?;
245    /// let addr = SocketAddr::new(host, port);
246    ///
247    /// let (host, port) = addr.into_parts();
248    /// assert!(host.is_ipaddr());
249    /// assert_eq!(port.as_u16(), 8080);
250    ///
251    /// ```
252    #[must_use]
253    #[inline]
254    pub fn into_parts(self) -> (Host, Port) {
255        (self.host, self.port)
256    }
257
258    /// Parses a string into a socket address.
259    ///
260    /// The string must be in the format `<host>:<port>`, where:
261    /// - `<host>` is a valid IP address, domain name, or hostname
262    /// - `<port>` is a valid port number (1-65535)
263    ///
264    /// # Errors
265    ///
266    /// Returns `SocketAddrError` if:
267    /// - The input is empty
268    /// - The port separator ':' is missing
269    /// - The host part is invalid
270    /// - The port part is invalid
271    ///
272    /// # Examples
273    ///
274    /// ```rust
275    /// use bare_types::net::SocketAddr;
276    ///
277    /// // Parse IP address with port
278    /// let addr = SocketAddr::parse_str("192.168.1.1:8080")?;
279    /// assert!(addr.as_host().is_ipaddr());
280    ///
281    /// // Parse domain name with port
282    /// let addr = SocketAddr::parse_str("example.com:443")?;
283    /// assert!(addr.as_host().is_domainname());
284    ///
285    /// ```
286    pub fn parse_str(s: &str) -> Result<Self, SocketAddrError> {
287        if s.is_empty() {
288            return Err(SocketAddrError::EmptyInput);
289        }
290
291        // Find the last ':' character (IPv6 addresses contain ':')
292        let Some(colon_pos) = s.rfind(':') else {
293            return Err(SocketAddrError::MissingPortSeparator);
294        };
295
296        // Split into host and port parts
297        let host_str = &s[..colon_pos];
298        let port_str = &s[colon_pos + 1..];
299
300        // Validate that we have both parts
301        if host_str.is_empty() || port_str.is_empty() {
302            return Err(SocketAddrError::MissingPortSeparator);
303        }
304
305        // Handle IPv6 addresses with brackets (e.g., "[::1]:8080")
306        let host_str = if host_str.starts_with('[') && host_str.ends_with(']') {
307            &host_str[1..host_str.len() - 1]
308        } else {
309            host_str
310        };
311
312        // Parse host
313        let host = Host::parse_str(host_str).map_err(|_| SocketAddrError::InvalidHost)?;
314
315        // Parse port
316        let port = port_str
317            .parse::<Port>()
318            .map_err(|_| SocketAddrError::InvalidPort)?;
319
320        Ok(Self { host, port })
321    }
322
323    /// Returns `true` if the host is an IP address.
324    ///
325    /// # Examples
326    ///
327    /// ```rust
328    /// use bare_types::net::SocketAddr;
329    ///
330    /// let addr: SocketAddr = "192.168.1.1:8080".parse()?;
331    /// assert!(addr.is_ip());
332    ///
333    /// ```
334    #[must_use]
335    #[inline]
336    pub const fn is_ip(&self) -> bool {
337        self.host.is_ipaddr()
338    }
339
340    /// Returns `true` if the host is a domain name.
341    ///
342    /// # Examples
343    ///
344    /// ```rust
345    /// use bare_types::net::SocketAddr;
346    ///
347    /// let addr: SocketAddr = "example.com:443".parse()?;
348    /// assert!(addr.is_domain_name());
349    ///
350    /// ```
351    #[must_use]
352    #[inline]
353    pub const fn is_domain_name(&self) -> bool {
354        self.host.is_domainname()
355    }
356
357    /// Returns `true` if the host is a hostname.
358    ///
359    /// # Examples
360    ///
361    /// ```rust
362    /// use bare_types::net::{SocketAddr, Host, Hostname, Port};
363    ///
364    /// let hostname = Hostname::new("localhost")?;
365    /// let host = Host::from_hostname(hostname);
366    /// let port = Port::new(3000)?;
367    /// let addr = SocketAddr::new(host, port);
368    /// assert!(addr.is_hostname());
369    ///
370    /// ```
371    #[must_use]
372    #[inline]
373    pub const fn is_hostname(&self) -> bool {
374        self.host.is_hostname()
375    }
376}
377
378impl FromStr for SocketAddr {
379    type Err = SocketAddrError;
380
381    fn from_str(s: &str) -> Result<Self, Self::Err> {
382        Self::parse_str(s)
383    }
384}
385
386impl fmt::Display for SocketAddr {
387    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
388        // IPv6 addresses must be enclosed in brackets per RFC 3986/5952
389        match &self.host {
390            Host::IpAddr(ip) if ip.as_inner().is_ipv6() => {
391                write!(f, "[{}]:{}", self.host, self.port)
392            }
393            _ => write!(f, "{}:{}", self.host, self.port),
394        }
395    }
396}
397
398#[cfg(feature = "std")]
399impl TryFrom<std::net::SocketAddr> for SocketAddr {
400    type Error = StdConversionError;
401
402    fn try_from(addr: std::net::SocketAddr) -> Result<Self, Self::Error> {
403        let port = Port::new(addr.port()).map_err(|_| StdConversionError::PortZero)?;
404        Ok(Self {
405            host: Host::from_ipaddr(IpAddr::from(addr.ip())),
406            port,
407        })
408    }
409}
410
411#[cfg(feature = "std")]
412impl TryFrom<SocketAddr> for std::net::SocketAddr {
413    type Error = StdConversionError;
414
415    fn try_from(addr: SocketAddr) -> Result<Self, Self::Error> {
416        match addr.host {
417            Host::IpAddr(ip) => Ok(Self::new(ip.into(), addr.port.as_u16())),
418            Host::DomainName(_) | Host::Hostname(_) => Err(StdConversionError::NotIpAddress),
419        }
420    }
421}
422
423#[cfg(test)]
424mod tests {
425    use super::*;
426
427    #[test]
428    fn test_new() {
429        let host = Host::parse_str("192.168.1.1").unwrap();
430        let port = Port::new(8080).unwrap();
431        let addr = SocketAddr::new(host, port);
432        assert!(addr.as_host().is_ipaddr());
433        assert_eq!(addr.as_port().as_u16(), 8080);
434    }
435
436    #[test]
437    fn test_parse_ipv4() {
438        let addr: SocketAddr = "192.168.1.1:8080".parse().unwrap();
439        assert!(addr.as_host().is_ipaddr());
440        assert_eq!(addr.as_port().as_u16(), 8080);
441        assert_eq!(format!("{addr}"), "192.168.1.1:8080");
442    }
443
444    #[test]
445    fn test_parse_ipv6() {
446        let addr: SocketAddr = "[::1]:8080".parse().unwrap();
447        assert!(addr.as_host().is_ipaddr());
448        assert_eq!(addr.as_port().as_u16(), 8080);
449    }
450
451    #[test]
452    fn test_parse_domainname() {
453        let addr: SocketAddr = "example.com:443".parse().unwrap();
454        assert!(addr.as_host().is_domainname());
455        assert_eq!(addr.as_port().as_u16(), 443);
456        assert_eq!(format!("{addr}"), "example.com:443");
457    }
458
459    #[test]
460    fn test_parse_domainname_with_digits() {
461        let addr: SocketAddr = "123.example.com:443".parse().unwrap();
462        assert!(addr.as_host().is_domainname());
463        assert_eq!(addr.as_port().as_u16(), 443);
464    }
465
466    #[test]
467    fn test_parse_str_empty() {
468        assert!(SocketAddr::parse_str("").is_err());
469    }
470
471    #[test]
472    fn test_parse_str_missing_separator() {
473        assert!(SocketAddr::parse_str("192.168.1.1").is_err());
474    }
475
476    #[test]
477    fn test_parse_str_empty_host() {
478        assert!(SocketAddr::parse_str(":8080").is_err());
479    }
480
481    #[test]
482    fn test_parse_str_empty_port() {
483        assert!(SocketAddr::parse_str("192.168.1.1:").is_err());
484    }
485
486    #[test]
487    fn test_parse_str_invalid_host() {
488        assert!(SocketAddr::parse_str("-invalid:8080").is_err());
489    }
490
491    #[test]
492    fn test_parse_str_invalid_port() {
493        assert!(SocketAddr::parse_str("192.168.1.1:invalid").is_err());
494    }
495
496    #[test]
497    fn test_parse_str_port_zero() {
498        assert!(SocketAddr::parse_str("192.168.1.1:0").is_err());
499    }
500
501    #[test]
502    fn test_parse_str_port_out_of_range() {
503        assert!(SocketAddr::parse_str("192.168.1.1:99999").is_err());
504    }
505
506    #[test]
507    fn test_set_host() {
508        let mut addr: SocketAddr = "192.168.1.1:8080".parse().unwrap();
509        let new_host = Host::parse_str("10.0.0.1").unwrap();
510        addr.set_host(new_host);
511        assert_eq!(format!("{addr}"), "10.0.0.1:8080");
512    }
513
514    #[test]
515    fn test_set_port() {
516        let mut addr: SocketAddr = "192.168.1.1:8080".parse().unwrap();
517        let new_port = Port::new(9090).unwrap();
518        addr.set_port(new_port);
519        assert_eq!(format!("{addr}"), "192.168.1.1:9090");
520    }
521
522    #[test]
523    fn test_into_parts() {
524        let host = Host::parse_str("192.168.1.1").unwrap();
525        let port = Port::new(8080).unwrap();
526        let addr = SocketAddr::new(host, port);
527
528        let (h, p) = addr.into_parts();
529        assert!(h.is_ipaddr());
530        assert_eq!(p.as_u16(), 8080);
531    }
532
533    #[test]
534    fn test_as_host_and_as_port() {
535        let addr: SocketAddr = "192.168.1.1:8080".parse().unwrap();
536        assert!(addr.as_host().is_ipaddr());
537        assert_eq!(addr.as_port().as_u16(), 8080);
538    }
539
540    #[test]
541    fn test_is_ip() {
542        let addr: SocketAddr = "192.168.1.1:8080".parse().unwrap();
543        assert!(addr.is_ip());
544        assert!(!addr.is_domain_name());
545        assert!(!addr.is_hostname());
546    }
547
548    #[test]
549    fn test_is_domain_name() {
550        let addr: SocketAddr = "example.com:443".parse().unwrap();
551        assert!(!addr.is_ip());
552        assert!(addr.is_domain_name());
553        assert!(!addr.is_hostname());
554    }
555
556    #[test]
557    fn test_host_is_domainname() {
558        let addr: SocketAddr = "example.com:443".parse().unwrap();
559        assert!(addr.as_host().is_domainname());
560    }
561
562    #[test]
563    fn test_socket_addr_is_domain_name() {
564        let addr: SocketAddr = "example.com:443".parse().unwrap();
565        assert!(addr.is_domain_name());
566    }
567
568    #[test]
569    fn test_is_hostname() {
570        let hostname = crate::net::Hostname::new("localhost").unwrap();
571        let host = Host::from_hostname(hostname);
572        let port = Port::new(3000).unwrap();
573        let addr = SocketAddr::new(host, port);
574        assert!(!addr.is_ip());
575        assert!(!addr.is_domain_name());
576        assert!(addr.is_hostname());
577    }
578
579    #[test]
580    fn test_equality() {
581        let addr1: SocketAddr = "192.168.1.1:8080".parse().unwrap();
582        let addr2: SocketAddr = "192.168.1.1:8080".parse().unwrap();
583        let addr3: SocketAddr = "192.168.1.1:9090".parse().unwrap();
584        let addr4: SocketAddr = "10.0.0.1:8080".parse().unwrap();
585
586        assert_eq!(addr1, addr2);
587        assert_ne!(addr1, addr3);
588        assert_ne!(addr1, addr4);
589    }
590
591    #[test]
592    fn test_clone() {
593        let addr: SocketAddr = "192.168.1.1:8080".parse().unwrap();
594        let addr2 = addr.clone();
595        assert_eq!(addr, addr2);
596    }
597
598    #[test]
599    fn test_display() {
600        let addr: SocketAddr = "192.168.1.1:8080".parse().unwrap();
601        assert_eq!(format!("{addr}"), "192.168.1.1:8080");
602
603        let addr: SocketAddr = "example.com:443".parse().unwrap();
604        assert_eq!(format!("{addr}"), "example.com:443");
605
606        // IPv6 addresses must be enclosed in brackets
607        let addr: SocketAddr = "[::1]:8080".parse().unwrap();
608        assert_eq!(format!("{addr}"), "[::1]:8080");
609
610        let addr: SocketAddr = "[2001:db8::1]:443".parse().unwrap();
611        assert_eq!(format!("{addr}"), "[2001:db8::1]:443");
612    }
613
614    #[test]
615    fn test_debug() {
616        let addr: SocketAddr = "192.168.1.1:8080".parse().unwrap();
617        let debug = format!("{:?}", addr);
618        assert!(debug.contains("SocketAddr"));
619    }
620
621    #[test]
622    fn test_hash() {
623        use core::hash::Hash;
624        use core::hash::Hasher;
625
626        #[derive(Default)]
627        struct SimpleHasher(u64);
628
629        impl Hasher for SimpleHasher {
630            fn finish(&self) -> u64 {
631                self.0
632            }
633
634            fn write(&mut self, bytes: &[u8]) {
635                for byte in bytes {
636                    self.0 = self.0.wrapping_mul(31).wrapping_add(*byte as u64);
637                }
638            }
639        }
640
641        let addr1: SocketAddr = "192.168.1.1:8080".parse().unwrap();
642        let addr2: SocketAddr = "192.168.1.1:8080".parse().unwrap();
643        let addr3: SocketAddr = "10.0.0.1:8080".parse().unwrap();
644
645        let mut hasher1 = SimpleHasher::default();
646        let mut hasher2 = SimpleHasher::default();
647        let mut hasher3 = SimpleHasher::default();
648
649        addr1.hash(&mut hasher1);
650        addr2.hash(&mut hasher2);
651        addr3.hash(&mut hasher3);
652
653        assert_eq!(hasher1.finish(), hasher2.finish());
654        assert_ne!(hasher1.finish(), hasher3.finish());
655    }
656
657    #[test]
658    fn test_parse_ipv6_with_brackets() {
659        let addr: SocketAddr = "[2001:db8::1]:8080".parse().unwrap();
660        assert!(addr.as_host().is_ipaddr());
661        assert_eq!(addr.as_port().as_u16(), 8080);
662    }
663
664    #[test]
665    fn test_parse_localhost() {
666        let addr: SocketAddr = "localhost:3000".parse().unwrap();
667        assert!(addr.as_host().is_domainname());
668        assert_eq!(addr.as_port().as_u16(), 3000);
669    }
670
671    #[test]
672    fn test_parse_system_port() {
673        let addr: SocketAddr = "192.168.1.1:80".parse().unwrap();
674        assert_eq!(addr.as_port().as_u16(), 80);
675        assert!(addr.as_port().is_system_port());
676    }
677
678    #[test]
679    fn test_parse_registered_port() {
680        let addr: SocketAddr = "192.168.1.1:8080".parse().unwrap();
681        assert_eq!(addr.as_port().as_u16(), 8080);
682        assert!(addr.as_port().is_registered_port());
683    }
684
685    #[test]
686    fn test_parse_dynamic_port() {
687        let addr: SocketAddr = "192.168.1.1:50000".parse().unwrap();
688        assert_eq!(addr.as_port().as_u16(), 50000);
689        assert!(addr.as_port().is_dynamic_port());
690    }
691
692    #[test]
693    fn test_error_display() {
694        let err = SocketAddrError::MissingPortSeparator;
695        assert_eq!(format!("{err}"), "missing port separator ':'");
696
697        let err = SocketAddrError::InvalidHost;
698        assert_eq!(format!("{err}"), "invalid host");
699
700        let err = SocketAddrError::InvalidPort;
701        assert_eq!(format!("{err}"), "invalid port");
702
703        let err = SocketAddrError::EmptyInput;
704        assert_eq!(format!("{err}"), "empty input");
705    }
706
707    #[test]
708    fn test_parse_str_method() {
709        let addr = SocketAddr::parse_str("192.168.1.1:8080").unwrap();
710        assert!(addr.as_host().is_ipaddr());
711        assert_eq!(addr.as_port().as_u16(), 8080);
712    }
713
714    #[test]
715    fn test_case_insensitive() {
716        let addr1: SocketAddr = "EXAMPLE.COM:443".parse().unwrap();
717        let addr2: SocketAddr = "example.com:443".parse().unwrap();
718        assert_eq!(addr1, addr2);
719    }
720
721    #[test]
722    fn test_ipv4_loopback() {
723        let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap();
724        assert!(addr.as_host().is_ipaddr());
725    }
726
727    #[test]
728    fn test_ipv4_private() {
729        let addr: SocketAddr = "10.0.0.1:8080".parse().unwrap();
730        assert!(addr.as_host().is_ipaddr());
731    }
732
733    #[test]
734    fn test_ipv6_loopback() {
735        let addr: SocketAddr = "[::1]:8080".parse().unwrap();
736        assert!(addr.as_host().is_ipaddr());
737    }
738
739    #[test]
740    fn test_numeric_domainname() {
741        let addr: SocketAddr = "123:8080".parse().unwrap();
742        assert!(addr.as_host().is_domainname());
743    }
744
745    #[test]
746    fn test_multi_label_domainname() {
747        let addr: SocketAddr = "api.v1.example.com:443".parse().unwrap();
748        assert!(addr.as_host().is_domainname());
749    }
750
751    #[test]
752    fn test_max_port() {
753        let addr: SocketAddr = "192.168.1.1:65535".parse().unwrap();
754        assert_eq!(addr.as_port().as_u16(), 65535);
755    }
756
757    #[test]
758    fn test_min_port() {
759        let addr: SocketAddr = "192.168.1.1:1".parse().unwrap();
760        assert_eq!(addr.as_port().as_u16(), 1);
761    }
762
763    #[cfg(feature = "std")]
764    #[test]
765    fn test_try_from_std_socket_addr() {
766        let std_addr = std::net::SocketAddr::from(([127, 0, 0, 1], 8080));
767        let addr = SocketAddr::try_from(std_addr).unwrap();
768        assert!(addr.as_host().is_ipaddr());
769        assert_eq!(addr.as_port().as_u16(), 8080);
770    }
771
772    #[cfg(feature = "std")]
773    #[test]
774    fn test_try_from_std_socket_addr_ipv6() {
775        let std_addr = std::net::SocketAddr::from(([0, 0, 0, 0, 0, 0, 0, 1], 8080));
776        let addr = SocketAddr::try_from(std_addr).unwrap();
777        assert!(addr.as_host().is_ipaddr());
778        assert_eq!(addr.as_port().as_u16(), 8080);
779    }
780
781    #[cfg(feature = "std")]
782    #[test]
783    fn test_try_from_std_socket_addr_port_zero_fails() {
784        let std_addr = std::net::SocketAddr::from(([127, 0, 0, 1], 0));
785        let result = SocketAddr::try_from(std_addr);
786        assert!(result.is_err());
787        assert_eq!(result.unwrap_err(), StdConversionError::PortZero);
788    }
789
790    #[cfg(feature = "std")]
791    #[test]
792    fn test_try_to_std_socket_addr_ip() {
793        let addr: SocketAddr = "192.168.1.1:8080".parse().unwrap();
794        let std_addr = std::net::SocketAddr::try_from(addr).unwrap();
795        assert_eq!(std_addr.ip(), std::net::IpAddr::from([192, 168, 1, 1]));
796        assert_eq!(std_addr.port(), 8080);
797    }
798
799    #[cfg(feature = "std")]
800    #[test]
801    fn test_try_to_std_socket_addr_domainname_fails() {
802        let addr: SocketAddr = "example.com:443".parse().unwrap();
803        let result = std::net::SocketAddr::try_from(addr);
804        assert!(result.is_err());
805        assert_eq!(result.unwrap_err(), StdConversionError::NotIpAddress);
806    }
807
808    #[cfg(feature = "std")]
809    #[test]
810    fn test_try_to_std_socket_addr_hostname_fails() {
811        let hostname = crate::net::Hostname::new("localhost").unwrap();
812        let host = Host::from_hostname(hostname);
813        let port = Port::new(3000).unwrap();
814        let addr = SocketAddr::new(host, port);
815        let result = std::net::SocketAddr::try_from(addr);
816        assert!(result.is_err());
817        assert_eq!(result.unwrap_err(), StdConversionError::NotIpAddress);
818    }
819
820    #[cfg(feature = "std")]
821    #[test]
822    fn test_std_conversion_error_display() {
823        let err = StdConversionError::NotIpAddress;
824        assert_eq!(
825            format!("{err}"),
826            "cannot convert to std::net::SocketAddr: host is not an IP address"
827        );
828
829        let err = StdConversionError::PortZero;
830        assert_eq!(
831            format!("{err}"),
832            "cannot convert from std::net::SocketAddr: port 0 is not allowed"
833        );
834    }
835
836    #[cfg(feature = "std")]
837    #[test]
838    fn test_roundtrip_std_socket_addr() {
839        let std_addr1 = std::net::SocketAddr::from(([192, 168, 1, 1], 8080));
840        let addr = SocketAddr::try_from(std_addr1).unwrap();
841        let std_addr2 = std::net::SocketAddr::try_from(addr).unwrap();
842        assert_eq!(std_addr1, std_addr2);
843    }
844}