use anyhow::{Result, anyhow};
pub fn parse_human_size(input: &str) -> Result<u32> {
let input = input.trim();
if input.is_empty() {
return Err(anyhow!("Empty size string"));
}
let (num_part, suffix) = {
let mut num_end = 0;
for (i, ch) in input.chars().enumerate() {
if ch.is_ascii_digit() || ch == '.' {
num_end = i + 1;
} else {
break;
}
}
(&input[..num_end], &input[num_end..])
};
if num_part.is_empty() {
return Err(anyhow!("No numeric value found in '{}'", input));
}
let num: f64 = num_part
.parse()
.map_err(|_| anyhow!("Invalid number '{}'", num_part))?;
if num < 0.0 {
return Err(anyhow!("Size cannot be negative"));
}
let multiplier: f64 = match suffix.trim().to_uppercase().as_str() {
"" | "M" | "MB" | "MIB" => 1.0,
"G" | "GB" | "GIB" => 1024.0,
"K" | "KB" | "KIB" => 1.0 / 1024.0,
other => return Err(anyhow!("Unknown size suffix '{}'", other)),
};
let result = num * multiplier;
if result > u32::MAX as f64 {
return Err(anyhow!("Size too large (max: {} MiB)", u32::MAX));
}
Ok(result.round() as u32)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_plain_number() {
assert_eq!(parse_human_size("512").unwrap(), 512);
assert_eq!(parse_human_size("1024").unwrap(), 1024);
assert_eq!(parse_human_size("1").unwrap(), 1);
}
#[test]
fn test_parse_megabytes() {
assert_eq!(parse_human_size("512M").unwrap(), 512);
assert_eq!(parse_human_size("512MB").unwrap(), 512);
assert_eq!(parse_human_size("512MiB").unwrap(), 512);
assert_eq!(parse_human_size("512m").unwrap(), 512);
assert_eq!(parse_human_size("512mb").unwrap(), 512);
}
#[test]
fn test_parse_gigabytes() {
assert_eq!(parse_human_size("1G").unwrap(), 1024);
assert_eq!(parse_human_size("1GB").unwrap(), 1024);
assert_eq!(parse_human_size("1GiB").unwrap(), 1024);
assert_eq!(parse_human_size("4G").unwrap(), 4096);
assert_eq!(parse_human_size("4g").unwrap(), 4096);
assert_eq!(parse_human_size("0.5G").unwrap(), 512);
}
#[test]
fn test_parse_kilobytes() {
assert_eq!(parse_human_size("1024K").unwrap(), 1);
assert_eq!(parse_human_size("1024KB").unwrap(), 1);
assert_eq!(parse_human_size("1024KiB").unwrap(), 1);
assert_eq!(parse_human_size("512K").unwrap(), 1); assert_eq!(parse_human_size("2048k").unwrap(), 2);
}
#[test]
fn test_parse_with_whitespace() {
assert_eq!(parse_human_size(" 512 ").unwrap(), 512);
assert_eq!(parse_human_size(" 4G ").unwrap(), 4096);
assert_eq!(parse_human_size("512 M").unwrap(), 512);
}
#[test]
fn test_parse_decimal() {
assert_eq!(parse_human_size("1.5G").unwrap(), 1536);
assert_eq!(parse_human_size("0.5G").unwrap(), 512);
assert_eq!(parse_human_size("2.5G").unwrap(), 2560);
}
#[test]
fn test_parse_errors() {
assert!(parse_human_size("").is_err());
assert!(parse_human_size("abc").is_err());
assert!(parse_human_size("512T").is_err()); assert!(parse_human_size("-512M").is_err()); assert!(parse_human_size("512XB").is_err()); }
#[test]
fn test_parse_overflow() {
assert!(parse_human_size("9999999G").is_err());
}
#[test]
fn test_parse_zero() {
assert_eq!(parse_human_size("0").unwrap(), 0);
assert_eq!(parse_human_size("0M").unwrap(), 0);
assert_eq!(parse_human_size("0G").unwrap(), 0);
}
}