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