Skip to main content

bare_types/net/
ipaddr.rs

1//! IP address type for network programming.
2//!
3//! This module provides a type-safe abstraction for IP addresses (IPv4 and IPv6),
4//! wrapping the standard library's `IpAddr` type.
5//!
6//! # Examples
7//!
8//! ```rust
9//! use bare_types::net::IpAddr;
10//! use core::net::{Ipv4Addr, Ipv6Addr};
11//!
12//! // Create an IPv4 address
13//! let v4 = IpAddr::new(core::net::IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
14//! assert!(v4.is_ipv4());
15//! assert!(v4.is_loopback());
16//!
17//! // Create an IPv6 address
18//! let v6 = IpAddr::new(core::net::IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)));
19//! assert!(v6.is_ipv6());
20//! assert!(v6.is_loopback());
21//!
22//! // Parse from string
23//! let addr: IpAddr = "192.168.1.1".parse()?;
24//! # Ok::<(), bare_types::net::IpAddrError>(())
25//! ```
26
27use core::fmt;
28use core::str::FromStr;
29
30#[cfg(feature = "serde")]
31use serde::{Deserialize, Serialize};
32
33#[cfg(feature = "arbitrary")]
34use arbitrary::Arbitrary;
35
36/// Error type for IP address parsing.
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
39#[non_exhaustive]
40pub enum IpAddrError {
41    /// Invalid IP address format
42    ///
43    /// The input string is not a valid IPv4 or IPv6 address.
44    /// IPv4 addresses must be in dotted decimal notation (e.g., "192.168.1.1").
45    /// IPv6 addresses must be in colon-separated hexadecimal notation (e.g., "`::1`").
46    InvalidFormat,
47}
48
49impl fmt::Display for IpAddrError {
50    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51        match self {
52            Self::InvalidFormat => write!(f, "invalid IP address format"),
53        }
54    }
55}
56
57#[cfg(feature = "std")]
58impl std::error::Error for IpAddrError {}
59
60/// An IP address (IPv4 or IPv6).
61///
62/// This type wraps the standard library's `IpAddr` to provide type safety
63/// and consistent API with other types in this crate. It uses the newtype
64/// pattern with `#[repr(transparent)]` for zero-cost abstraction.
65///
66/// # Invariants
67///
68/// - The inner value is always a valid IP address
69/// - IPv4 addresses are 4 octets (0-255 each)
70/// - IPv6 addresses are 8 groups of 16 bits
71///
72/// # Examples
73///
74/// ```rust
75/// use bare_types::net::IpAddr;
76/// use core::net::{Ipv4Addr, Ipv6Addr};
77///
78/// // Create an IPv4 address
79/// let v4 = IpAddr::new(core::net::IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
80/// assert!(v4.is_ipv4());
81/// assert!(v4.is_loopback());
82///
83/// // Create an IPv6 address
84/// let v6 = IpAddr::new(core::net::IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)));
85/// assert!(v6.is_ipv6());
86/// assert!(v6.is_loopback());
87///
88/// // Access the inner value
89/// let inner: core::net::IpAddr = v4.into_inner();
90/// assert_eq!(inner, core::net::IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
91///
92/// // Parse from string
93/// let addr: IpAddr = "192.168.1.1".parse()?;
94/// assert!(addr.is_ipv4());
95/// # Ok::<(), bare_types::net::IpAddrError>(())
96/// ```
97#[repr(transparent)]
98#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
99#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
100#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
101pub struct IpAddr(core::net::IpAddr);
102
103impl IpAddr {
104    /// Creates a new IP address from a standard library `IpAddr`.
105    ///
106    /// # Examples
107    ///
108    /// ```rust
109    /// use bare_types::net::IpAddr;
110    /// use core::net::Ipv4Addr;
111    ///
112    /// let addr = IpAddr::new(core::net::IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
113    /// assert!(addr.is_ipv4());
114    /// ```
115    #[must_use]
116    #[inline]
117    pub const fn new(inner: core::net::IpAddr) -> Self {
118        Self(inner)
119    }
120
121    /// Returns a reference to the underlying `IpAddr`.
122    ///
123    /// # Examples
124    ///
125    /// ```rust
126    /// use bare_types::net::IpAddr;
127    /// use core::net::Ipv4Addr;
128    ///
129    /// let addr = IpAddr::new(core::net::IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
130    /// let inner = addr.as_inner();
131    /// assert_eq!(inner, &core::net::IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
132    /// ```
133    #[must_use]
134    #[inline]
135    pub const fn as_inner(&self) -> &core::net::IpAddr {
136        &self.0
137    }
138
139    /// Consumes this IP address and returns the underlying `IpAddr`.
140    ///
141    /// # Examples
142    ///
143    /// ```rust
144    /// use bare_types::net::IpAddr;
145    /// use core::net::Ipv4Addr;
146    ///
147    /// let addr = IpAddr::new(core::net::IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
148    /// let inner: core::net::IpAddr = addr.into_inner();
149    /// assert_eq!(inner, core::net::IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
150    /// ```
151    #[must_use]
152    #[inline]
153    pub const fn into_inner(self) -> core::net::IpAddr {
154        self.0
155    }
156
157    /// Returns `true` if this is an IPv4 address.
158    ///
159    /// # Examples
160    ///
161    /// ```rust
162    /// use bare_types::net::IpAddr;
163    /// use core::net::{Ipv4Addr, Ipv6Addr};
164    ///
165    /// let v4 = IpAddr::new(core::net::IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
166    /// let v6 = IpAddr::new(core::net::IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)));
167    ///
168    /// assert!(v4.is_ipv4());
169    /// assert!(!v6.is_ipv4());
170    /// ```
171    #[must_use]
172    #[inline]
173    pub const fn is_ipv4(&self) -> bool {
174        self.0.is_ipv4()
175    }
176
177    /// Returns `true` if this is an IPv6 address.
178    ///
179    /// # Examples
180    ///
181    /// ```rust
182    /// use bare_types::net::IpAddr;
183    /// use core::net::{Ipv4Addr, Ipv6Addr};
184    ///
185    /// let v4 = IpAddr::new(core::net::IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
186    /// let v6 = IpAddr::new(core::net::IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)));
187    ///
188    /// assert!(!v4.is_ipv6());
189    /// assert!(v6.is_ipv6());
190    /// ```
191    #[must_use]
192    #[inline]
193    pub const fn is_ipv6(&self) -> bool {
194        self.0.is_ipv6()
195    }
196
197    /// Returns `true` if this is a loopback address.
198    ///
199    /// For IPv4, this is 127.0.0.1.
200    /// For IPv6, this is `::1`.
201    ///
202    /// # Examples
203    ///
204    /// ```rust
205    /// use bare_types::net::IpAddr;
206    /// use core::net::{Ipv4Addr, Ipv6Addr};
207    ///
208    /// let v4_loopback = IpAddr::new(core::net::IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
209    /// let v6_loopback = IpAddr::new(core::net::IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)));
210    ///
211    /// assert!(v4_loopback.is_loopback());
212    /// assert!(v6_loopback.is_loopback());
213    /// ```
214    #[must_use]
215    #[inline]
216    pub const fn is_loopback(&self) -> bool {
217        self.0.is_loopback()
218    }
219
220    /// Returns `true` if this is an unspecified address.
221    ///
222    /// For IPv4, this is 0.0.0.0.
223    /// For IPv6, this is ::.
224    ///
225    /// # Examples
226    ///
227    /// ```rust
228    /// use bare_types::net::IpAddr;
229    /// use core::net::{Ipv4Addr, Ipv6Addr};
230    ///
231    /// let v4_unspecified = IpAddr::new(core::net::IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)));
232    /// let v6_unspecified = IpAddr::new(core::net::IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)));
233    ///
234    /// assert!(v4_unspecified.is_unspecified());
235    /// assert!(v6_unspecified.is_unspecified());
236    /// ```
237    #[must_use]
238    #[inline]
239    pub const fn is_unspecified(&self) -> bool {
240        self.0.is_unspecified()
241    }
242
243    /// Returns `true` if this is a multicast address.
244    ///
245    /// # Examples
246    ///
247    /// ```rust
248    /// use bare_types::net::IpAddr;
249    /// use core::net::{Ipv4Addr, Ipv6Addr};
250    ///
251    /// let v4_multicast = IpAddr::new(core::net::IpAddr::V4(Ipv4Addr::new(224, 0, 0, 1)));
252    /// let v6_multicast = IpAddr::new(core::net::IpAddr::V6(Ipv6Addr::new(0xff00, 0, 0, 0, 0, 0, 0, 0)));
253    ///
254    /// assert!(v4_multicast.is_multicast());
255    /// assert!(v6_multicast.is_multicast());
256    /// ```
257    #[must_use]
258    #[inline]
259    pub const fn is_multicast(&self) -> bool {
260        self.0.is_multicast()
261    }
262}
263
264impl From<core::net::IpAddr> for IpAddr {
265    fn from(inner: core::net::IpAddr) -> Self {
266        Self(inner)
267    }
268}
269
270impl From<IpAddr> for core::net::IpAddr {
271    fn from(addr: IpAddr) -> Self {
272        addr.0
273    }
274}
275
276impl FromStr for IpAddr {
277    type Err = IpAddrError;
278
279    fn from_str(s: &str) -> Result<Self, Self::Err> {
280        s.parse::<core::net::IpAddr>()
281            .map(Self)
282            .map_err(|_| IpAddrError::InvalidFormat)
283    }
284}
285
286impl fmt::Display for IpAddr {
287    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
288        write!(f, "{}", self.0)
289    }
290}
291
292#[cfg(test)]
293mod tests {
294    use super::*;
295    use core::net::{Ipv4Addr, Ipv6Addr};
296
297    #[test]
298    fn test_new_ipv4() {
299        let addr = IpAddr::new(core::net::IpAddr::V4(Ipv4Addr::LOCALHOST));
300        assert!(addr.is_ipv4());
301        assert!(!addr.is_ipv6());
302    }
303
304    #[test]
305    fn test_new_ipv6() {
306        let addr = IpAddr::new(core::net::IpAddr::V6(Ipv6Addr::LOCALHOST));
307        assert!(addr.is_ipv6());
308        assert!(!addr.is_ipv4());
309    }
310
311    #[test]
312    fn test_as_inner() {
313        let addr = IpAddr::new(core::net::IpAddr::V4(Ipv4Addr::LOCALHOST));
314        let inner = addr.as_inner();
315        assert_eq!(inner, &core::net::IpAddr::V4(Ipv4Addr::LOCALHOST));
316    }
317
318    #[test]
319    fn test_into_inner() {
320        let addr = IpAddr::new(core::net::IpAddr::V4(Ipv4Addr::LOCALHOST));
321        let inner: core::net::IpAddr = addr.into_inner();
322        assert_eq!(inner, core::net::IpAddr::V4(Ipv4Addr::LOCALHOST));
323    }
324
325    #[test]
326    fn test_is_ipv4() {
327        let v4 = IpAddr::new(core::net::IpAddr::V4(Ipv4Addr::LOCALHOST));
328        let v6 = IpAddr::new(core::net::IpAddr::V6(Ipv6Addr::LOCALHOST));
329
330        assert!(v4.is_ipv4());
331        assert!(!v6.is_ipv4());
332    }
333
334    #[test]
335    fn test_is_ipv6() {
336        let v4 = IpAddr::new(core::net::IpAddr::V4(Ipv4Addr::LOCALHOST));
337        let v6 = IpAddr::new(core::net::IpAddr::V6(Ipv6Addr::LOCALHOST));
338
339        assert!(!v4.is_ipv6());
340        assert!(v6.is_ipv6());
341    }
342
343    #[test]
344    fn test_is_loopback() {
345        let v4_loopback = IpAddr::new(core::net::IpAddr::V4(Ipv4Addr::LOCALHOST));
346        let v6_loopback = IpAddr::new(core::net::IpAddr::V6(Ipv6Addr::LOCALHOST));
347        let v4_not_loopback = IpAddr::new(core::net::IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)));
348
349        assert!(v4_loopback.is_loopback());
350        assert!(v6_loopback.is_loopback());
351        assert!(!v4_not_loopback.is_loopback());
352    }
353
354    #[test]
355    fn test_is_unspecified() {
356        let v4_unspecified = IpAddr::new(core::net::IpAddr::V4(Ipv4Addr::UNSPECIFIED));
357        let v6_unspecified = IpAddr::new(core::net::IpAddr::V6(Ipv6Addr::UNSPECIFIED));
358        let v4_specified = IpAddr::new(core::net::IpAddr::V4(Ipv4Addr::LOCALHOST));
359
360        assert!(v4_unspecified.is_unspecified());
361        assert!(v6_unspecified.is_unspecified());
362        assert!(!v4_specified.is_unspecified());
363    }
364
365    #[test]
366    fn test_is_multicast() {
367        let v4_multicast = IpAddr::new(core::net::IpAddr::V4(Ipv4Addr::new(224, 0, 0, 1)));
368        let v6_multicast = IpAddr::new(core::net::IpAddr::V6(Ipv6Addr::new(
369            0xff00, 0, 0, 0, 0, 0, 0, 0,
370        )));
371        let v4_not_multicast = IpAddr::new(core::net::IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)));
372
373        assert!(v4_multicast.is_multicast());
374        assert!(v6_multicast.is_multicast());
375        assert!(!v4_not_multicast.is_multicast());
376    }
377
378    #[test]
379    fn test_from_std_ipaddr() {
380        let std_addr = core::net::IpAddr::V4(Ipv4Addr::LOCALHOST);
381        let addr = IpAddr::from(std_addr);
382        assert!(addr.is_ipv4());
383    }
384
385    #[test]
386    fn test_into_std_ipaddr() {
387        let addr = IpAddr::new(core::net::IpAddr::V4(Ipv4Addr::LOCALHOST));
388        let std_addr: core::net::IpAddr = addr.into();
389        assert_eq!(std_addr, core::net::IpAddr::V4(Ipv4Addr::LOCALHOST));
390    }
391
392    #[test]
393    fn test_from_str_ipv4() {
394        let addr: IpAddr = "127.0.0.1".parse().unwrap();
395        assert!(addr.is_ipv4());
396        assert!(addr.is_loopback());
397    }
398
399    #[test]
400    fn test_from_str_ipv6() {
401        let addr: IpAddr = "::1".parse().unwrap();
402        assert!(addr.is_ipv6());
403        assert!(addr.is_loopback());
404    }
405
406    #[test]
407    fn test_from_str_invalid() {
408        let result: Result<IpAddr, _> = "invalid".parse();
409        assert!(result.is_err());
410    }
411
412    #[test]
413    fn test_display_ipv4() {
414        let addr = IpAddr::new(core::net::IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)));
415        assert_eq!(format!("{addr}"), "192.168.1.1");
416    }
417
418    #[test]
419    fn test_display_ipv6() {
420        let addr = IpAddr::new(core::net::IpAddr::V6(Ipv6Addr::LOCALHOST));
421        assert_eq!(format!("{addr}"), "::1");
422    }
423
424    #[test]
425    fn test_equality() {
426        let addr1 = IpAddr::new(core::net::IpAddr::V4(Ipv4Addr::LOCALHOST));
427        let addr2 = IpAddr::new(core::net::IpAddr::V4(Ipv4Addr::LOCALHOST));
428        let addr3 = IpAddr::new(core::net::IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)));
429
430        assert_eq!(addr1, addr2);
431        assert_ne!(addr1, addr3);
432    }
433
434    #[test]
435    fn test_ordering() {
436        let addr1 = IpAddr::new(core::net::IpAddr::V4(Ipv4Addr::LOCALHOST));
437        let addr2 = IpAddr::new(core::net::IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)));
438
439        assert!(addr1 < addr2);
440    }
441
442    #[test]
443    fn test_copy() {
444        let addr = IpAddr::new(core::net::IpAddr::V4(Ipv4Addr::LOCALHOST));
445        let addr2 = addr;
446        assert_eq!(addr, addr2);
447    }
448
449    #[test]
450    fn test_clone() {
451        let addr = IpAddr::new(core::net::IpAddr::V4(Ipv4Addr::LOCALHOST));
452        let addr2 = addr;
453        assert_eq!(addr, addr2);
454    }
455
456    #[test]
457    fn test_error_display() {
458        let err = IpAddrError::InvalidFormat;
459        assert_eq!(format!("{err}"), "invalid IP address format");
460    }
461}