Repository: GuillaumeGomez/sysinfo (language: rust)

Issue:
Implement `FromStr` for types that implement `Display`
For example, type `MacAddr`, `IpNetwork`, etc.


Discussion / hints:

Fix patch:
diff --git a/src/common/network.rs b/src/common/network.rs
index cf3930bc2..9d62196e6 100644
--- a/src/common/network.rs
+++ b/src/common/network.rs
@@ -2,7 +2,9 @@
 
 use std::collections::HashMap;
 use std::fmt;
-use std::net::IpAddr;
+use std::net::{AddrParseError, IpAddr};
+use std::num::ParseIntError;
+use std::str::FromStr;
 
 use crate::{NetworkDataInner, NetworksInner};
 
@@ -433,14 +435,58 @@ impl fmt::Display for MacAddr {
     }
 }
 
-/// Ip networks address for network interface.
+/// Error type returned from `MacAddr::from_str` implementation.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum MacAddrFromStrError {
+    /// A number is not in hexadecimal format.
+    IntError(ParseIntError),
+    /// Input is not of format `{02X}:{02X}:{02X}:{02X}:{02X}:{02X}`.
+    InvalidAddrFormat,
+}
+
+impl FromStr for MacAddr {
+    type Err = MacAddrFromStrError;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        let mut parts = s
+            .split(':')
+            .map(|s| u8::from_str_radix(s, 16).map_err(MacAddrFromStrError::IntError));
+
+        let Some(data0) = parts.next() else {
+            return Err(MacAddrFromStrError::InvalidAddrFormat);
+        };
+        let Some(data1) = parts.next() else {
+            return Err(MacAddrFromStrError::InvalidAddrFormat);
+        };
+        let Some(data2) = parts.next() else {
+            return Err(MacAddrFromStrError::InvalidAddrFormat);
+        };
+        let Some(data3) = parts.next() else {
+            return Err(MacAddrFromStrError::InvalidAddrFormat);
+        };
+        let Some(data4) = parts.next() else {
+            return Err(MacAddrFromStrError::InvalidAddrFormat);
+        };
+        let Some(data5) = parts.next() else {
+            return Err(MacAddrFromStrError::InvalidAddrFormat);
+        };
+
+        if parts.next().is_some() {
+            return Err(MacAddrFromStrError::InvalidAddrFormat);
+        }
+
+        Ok(MacAddr([data0?, data1?, data2?, data3?, data4?, data5?]))
+    }
+}
+
+/// IP networks address for network interface.
 ///
 /// It is returned by [`NetworkData::ip_networks`][crate::NetworkData::ip_networks].
 #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
 pub struct IpNetwork {
-    /// The ip of the network interface
+    /// The IP of the network interface.
     pub addr: IpAddr,
-    /// The netmask, prefix of the ipaddress
+    /// The netmask, prefix of the IP address.
     pub prefix: u8,
 }
 
@@ -450,10 +496,46 @@ impl fmt::Display for IpNetwork {
     }
 }
 
+/// Error type returned from `MacAddr::from_str` implementation.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum IpNetworkFromStrError {
+    /// Prefix is not an integer.
+    PrefixError(ParseIntError),
+    /// Failed to parse IP address.
+    AddrParseError(AddrParseError),
+    /// Input is not of format `[IP address]/[number]`.
+    InvalidAddrFormat,
+}
+
+impl FromStr for IpNetwork {
+    type Err = IpNetworkFromStrError;
+
+    #[allow(clippy::from_str_radix_10)]
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        let mut parts = s.split('/');
+
+        let Some(addr) = parts.next() else {
+            return Err(IpNetworkFromStrError::InvalidAddrFormat);
+        };
+        let Some(prefix) = parts.next() else {
+            return Err(IpNetworkFromStrError::InvalidAddrFormat);
+        };
+        if parts.next().is_some() {
+            return Err(IpNetworkFromStrError::InvalidAddrFormat);
+        }
+
+        Ok(IpNetwork {
+            addr: IpAddr::from_str(addr).map_err(IpNetworkFromStrError::AddrParseError)?,
+            prefix: u8::from_str_radix(prefix, 10).map_err(IpNetworkFromStrError::PrefixError)?,
+        })
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use crate::*;
     use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
+    use std::str::FromStr;
 
     // Ensure that the `Display` and `Debug` traits are implemented on the `MacAddr` struct
     #[test]
@@ -476,6 +558,24 @@ mod tests {
         assert!(!MacAddr([1, 2, 3, 4, 5, 6]).is_unspecified());
     }
 
+    #[test]
+    fn check_mac_address_conversions() {
+        let mac = MacAddr([0xa, 0xb, 0xc, 0xd, 0xe, 0xf]);
+
+        let mac_s = mac.to_string();
+        assert_eq!("0a:0b:0c:0d:0e:0f", mac_s);
+        assert_eq!(Ok(mac), MacAddr::from_str(&mac_s));
+
+        assert_eq!(
+            MacAddr::from_str("0a:0b:0c:0d:0e:0f:01"),
+            Err(MacAddrFromStrError::InvalidAddrFormat)
+        );
+        assert_eq!(
+            MacAddr::from_str("0a:0b:0c:0d:0e"),
+            Err(MacAddrFromStrError::InvalidAddrFormat)
+        );
+    }
+
     // Ensure that the `Display` and `Debug` traits are implemented on the `IpNetwork` struct
     #[test]
     fn check_display_impl_ip_network_ipv4() {
@@ -518,4 +618,38 @@ mod tests {
         }
         panic!("Networks should have at least one IP network ");
     }
+
+    #[test]
+    fn check_ip_network_conversions() {
+        let addr = IpNetwork {
+            addr: IpAddr::from(Ipv6Addr::new(0xff, 0xa, 0x8, 0x12, 0x7, 0xc, 0xa, 0xb)),
+            prefix: 12,
+        };
+
+        let addr_s = addr.to_string();
+        assert_eq!("ff:a:8:12:7:c:a:b/12", addr_s);
+        assert_eq!(Ok(addr), IpNetwork::from_str(&addr_s));
+
+        let addr = IpNetwork {
+            addr: IpAddr::from(Ipv4Addr::new(255, 255, 255, 0)),
+            prefix: 21,
+        };
+
+        let addr_s = addr.to_string();
+        assert_eq!("255.255.255.0/21", addr_s);
+        assert_eq!(Ok(addr), IpNetwork::from_str(&addr_s));
+
+        assert_eq!(
+            IpNetwork::from_str("ff:a:8:12:7:c:a:b"),
+            Err(IpNetworkFromStrError::InvalidAddrFormat)
+        );
+        assert_eq!(
+            IpNetwork::from_str("ff:a:8:12:7:c:a:b/12/12"),
+            Err(IpNetworkFromStrError::InvalidAddrFormat)
+        );
+        match IpNetwork::from_str("0a:0b:0c:0d:0e/12") {
+            Err(IpNetworkFromStrError::AddrParseError(_)) => {}
+            x => panic!("expected `IpNetworkFromStrError::AddrParseError`, found {x:?}"),
+        }
+    }
 }
diff --git a/src/lib.rs b/src/lib.rs
index 1423fc7ff..b2a455d2c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -73,7 +73,9 @@ pub use crate::common::component::{Component, Components};
 #[cfg(feature = "disk")]
 pub use crate::common::disk::{Disk, DiskKind, DiskRefreshKind, Disks};
 #[cfg(feature = "network")]
-pub use crate::common::network::{IpNetwork, MacAddr, NetworkData, Networks};
+pub use crate::common::network::{
+    IpNetwork, IpNetworkFromStrError, MacAddr, MacAddrFromStrError, NetworkData, Networks,
+};
 #[cfg(feature = "system")]
 pub use crate::common::system::{
     get_current_pid, CGroupLimits, Cpu, CpuRefreshKind, LoadAvg, MemoryRefreshKind, Pid, Process,


Test patch: