use std::net::IpAddr;
use serde::{Deserialize, Deserializer};
pub fn parse_ipnet(s: &str) -> Result<ipnet::IpNet, String> {
if let Ok(ipnet) = s.parse::<ipnet::IpNet>() {
return Ok(ipnet);
}
if let Ok(ip) = s.parse::<IpAddr>() {
let ipnet = match ip {
IpAddr::V4(ipv4) => ipnet::Ipv4Net::new(ipv4, 32)
.map(ipnet::IpNet::V4)
.map_err(|e| format!("Failed to create IPv4 network: {}", e))?,
IpAddr::V6(ipv6) => ipnet::Ipv6Net::new(ipv6, 128)
.map(ipnet::IpNet::V6)
.map_err(|e| format!("Failed to create IPv6 network: {}", e))?,
};
return Ok(ipnet);
}
Err(format!("Invalid IP address or CIDR notation: {}", s))
}
pub fn deserialize_ip_nets<'de, D>(deserializer: D) -> Result<Option<Vec<ipnet::IpNet>>, D::Error>
where
D: Deserializer<'de>,
{
use serde::de::Error;
let strings_opt = Option::<Vec<String>>::deserialize(deserializer)?;
match strings_opt {
Some(strings) => {
let mut ip_nets = Vec::new();
for s in strings {
let ip_net = parse_ipnet(&s).map_err(Error::custom)?;
ip_nets.push(ip_net);
}
Ok(Some(ip_nets))
}
None => Ok(None),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_ipv4_single_address() {
let result = parse_ipnet("192.168.1.100").unwrap();
assert_eq!(result.to_string(), "192.168.1.100/32");
}
#[test]
fn test_parse_ipv4_cidr() {
let result = parse_ipnet("10.0.0.0/8").unwrap();
assert_eq!(result.to_string(), "10.0.0.0/8");
}
#[test]
fn test_parse_ipv6_single_address() {
let result = parse_ipnet("2001:db8::1").unwrap();
assert_eq!(result.to_string(), "2001:db8::1/128");
}
#[test]
fn test_parse_ipv6_cidr() {
let result = parse_ipnet("2001:db8::/32").unwrap();
assert_eq!(result.to_string(), "2001:db8::/32");
}
#[test]
fn test_parse_localhost_ipv4() {
let result = parse_ipnet("127.0.0.1").unwrap();
assert_eq!(result.to_string(), "127.0.0.1/32");
}
#[test]
fn test_parse_localhost_ipv6() {
let result = parse_ipnet("::1").unwrap();
assert_eq!(result.to_string(), "::1/128");
}
#[test]
fn test_parse_private_networks() {
let cases = vec![
("192.168.1.0/24", "192.168.1.0/24"),
("10.0.0.0/8", "10.0.0.0/8"),
("172.16.0.0/12", "172.16.0.0/12"),
];
for (input, expected) in cases {
let result = parse_ipnet(input).unwrap();
assert_eq!(
result.to_string(),
expected,
"Private network {} should parse correctly",
input
);
}
}
#[test]
fn test_parse_invalid_ip() {
let result = parse_ipnet("not-an-ip");
assert!(
result.is_err(),
"Invalid IP should be rejected, got: {:?}",
result
);
let error_msg = result.unwrap_err();
assert!(
error_msg.contains("Invalid IP address or CIDR notation"),
"Error message should mention invalid IP or CIDR, got: {}",
error_msg
);
}
#[test]
fn test_parse_invalid_cidr() {
let result = parse_ipnet("192.168.1.0/33");
assert!(
result.is_err(),
"Expected error for invalid CIDR, got: {:?}",
result
);
}
#[test]
fn test_parse_empty_string() {
let result = parse_ipnet("");
assert!(
result.is_err(),
"Expected error for empty string, got: {:?}",
result
);
}
#[test]
fn test_parse_malformed_addresses() {
let invalid_cases = vec![
"192.168.1", "192.168.1.256", "2001:db8::gggg", "192.168.1.0/", "192.168.1.0/-1", ];
for invalid in invalid_cases {
let result = parse_ipnet(invalid);
assert!(
result.is_err(),
"Malformed input '{}' should be rejected, got: {:?}",
invalid,
result
);
}
}
#[test]
fn test_parse_edge_case_cidrs() {
let cases = vec![
("0.0.0.0/0", "0.0.0.0/0"), ("::/0", "::/0"), ("192.168.1.0/32", "192.168.1.0/32"), ];
for (input, expected) in cases {
let result = parse_ipnet(input).unwrap();
assert_eq!(
result.to_string(),
expected,
"Edge case CIDR {} should parse correctly",
input
);
}
}
}