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