diff --git a/src/size.rs b/src/size.rs index c03f788..be0c8ec 100644 --- a/src/size.rs +++ b/src/size.rs @@ -1,20 +1,41 @@ -use std::{cmp::Ordering, fmt::Display}; +use std::{ + cmp::Ordering, + fmt::Display, + hash::{Hash, Hasher}, +}; use crate::error::NetworkSizeError; +use NetworkSize::*; -/// Represents a generic network size. For IPv4, the max size is a u32 and for IPv6, it is a u128 -#[derive(Debug, Clone, Copy, Hash)] +/// Represents a generic network size. +/// +/// IPv4 network sizes are represented as `u32` values, while IPv6 network sizes are represented as `u128` values. +/// +/// # Comparisons +/// +/// Network sizes are compared by _value_, not by type. +/// +/// ``` +/// use ipnetwork::NetworkSize; +/// +/// let ns1 = NetworkSize::V4(100); +/// let ns2 = NetworkSize::V6(100); +/// +/// assert_eq!(ns1, ns2); +/// ``` +#[derive(Debug, Clone, Copy)] pub enum NetworkSize { V4(u32), V6(u128), } -use NetworkSize::*; -// Conversions - -impl From for NetworkSize { - fn from(value: u128) -> Self { - V6(value) +impl NetworkSize { + /// Returns the size of the network as a `u128` + fn as_u128(&self) -> u128 { + match *self { + V4(a) => a as u128, + V6(a) => a, + } } } @@ -24,40 +45,50 @@ impl From for NetworkSize { } } -impl TryInto for NetworkSize { +impl From for NetworkSize { + fn from(value: u128) -> Self { + V6(value) + } +} + +impl TryFrom for u32 { type Error = NetworkSizeError; - fn try_into(self) -> Result { - match self { + fn try_from(value: NetworkSize) -> Result { + match value { V4(a) => Ok(a), V6(_) => Err(NetworkSizeError::NetworkIsTooLarge), } } } -impl Into for NetworkSize { - fn into(self) -> u128 { - match self { - V4(a) => a as u128, - V6(a) => a, - } +impl From for u128 { + fn from(val: NetworkSize) -> Self { + val.as_u128() } } -// Equality/comparisons - impl PartialEq for NetworkSize { fn eq(&self, other: &Self) -> bool { - let a: u128 = (*self).into(); - let b: u128 = (*other).into(); + let a = self.as_u128(); + let b = other.as_u128(); a == b } } +impl Eq for NetworkSize {} + +impl Hash for NetworkSize { + fn hash(&self, state: &mut H) { + let a = self.as_u128(); + a.hash(state); + } +} + impl Ord for NetworkSize { fn cmp(&self, other: &Self) -> Ordering { - let a: u128 = (*self).into(); - let b: u128 = (*other).into(); - return a.cmp(&b); + let a = self.as_u128(); + let b = other.as_u128(); + a.cmp(&b) } } @@ -67,18 +98,12 @@ impl PartialOrd for NetworkSize { } } -impl Eq for NetworkSize {} - -// Display - impl Display for NetworkSize { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", Into::::into(*self)) + write!(f, "{}", self.as_u128()) } } -// Tests - #[cfg(test)] mod tests { use super::*; @@ -158,4 +183,25 @@ mod tests { let ns2 = V6(ns1.into()); assert_eq!(ns1.to_string(), ns2.to_string()); } + + // Verify that [`std::hash::Hash`] and [`std::cmp::PartialEq`] are consistent + #[test] + fn test_hash() { + let a = NetworkSize::V4(100); + let b = NetworkSize::V6(100); + + // Calculate the hash of the two values + let mut hasher = std::hash::DefaultHasher::default(); + a.hash(&mut hasher); + let hash_a = hasher.finish(); + + let mut hasher = std::hash::DefaultHasher::default(); + b.hash(&mut hasher); + let hash_b = hasher.finish(); + + // a == b + assert_eq!(a, b); + // implies hash(a) == hash(b) + assert_eq!(hash_a, hash_b); + } }