1use anyhow::{Result, anyhow};
4
5pub fn parse_human_size(input: &str) -> Result<u32> {
24 let input = input.trim();
25 if input.is_empty() {
26 return Err(anyhow!("Empty size string"));
27 }
28
29 let (num_part, suffix) = {
31 let mut num_end = 0;
32 for (i, ch) in input.chars().enumerate() {
33 if ch.is_ascii_digit() || ch == '.' {
34 num_end = i + 1;
35 } else {
36 break;
37 }
38 }
39 (&input[..num_end], &input[num_end..])
40 };
41
42 if num_part.is_empty() {
43 return Err(anyhow!("No numeric value found in '{}'", input));
44 }
45
46 let num: f64 = num_part
47 .parse()
48 .map_err(|_| anyhow!("Invalid number '{}'", num_part))?;
49
50 if num < 0.0 {
51 return Err(anyhow!("Size cannot be negative"));
52 }
53
54 let multiplier: f64 = match suffix.trim().to_uppercase().as_str() {
56 "" | "M" | "MB" | "MIB" => 1.0,
57 "G" | "GB" | "GIB" => 1024.0,
58 "K" | "KB" | "KIB" => 1.0 / 1024.0,
59 other => return Err(anyhow!("Unknown size suffix '{}'", other)),
60 };
61
62 let result = num * multiplier;
63
64 if result > u32::MAX as f64 {
66 return Err(anyhow!("Size too large (max: {} MiB)", u32::MAX));
67 }
68
69 Ok(result.round() as u32)
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75
76 #[test]
77 fn test_parse_plain_number() {
78 assert_eq!(parse_human_size("512").unwrap(), 512);
79 assert_eq!(parse_human_size("1024").unwrap(), 1024);
80 assert_eq!(parse_human_size("1").unwrap(), 1);
81 }
82
83 #[test]
84 fn test_parse_megabytes() {
85 assert_eq!(parse_human_size("512M").unwrap(), 512);
86 assert_eq!(parse_human_size("512MB").unwrap(), 512);
87 assert_eq!(parse_human_size("512MiB").unwrap(), 512);
88 assert_eq!(parse_human_size("512m").unwrap(), 512);
89 assert_eq!(parse_human_size("512mb").unwrap(), 512);
90 }
91
92 #[test]
93 fn test_parse_gigabytes() {
94 assert_eq!(parse_human_size("1G").unwrap(), 1024);
95 assert_eq!(parse_human_size("1GB").unwrap(), 1024);
96 assert_eq!(parse_human_size("1GiB").unwrap(), 1024);
97 assert_eq!(parse_human_size("4G").unwrap(), 4096);
98 assert_eq!(parse_human_size("4g").unwrap(), 4096);
99 assert_eq!(parse_human_size("0.5G").unwrap(), 512);
100 }
101
102 #[test]
103 fn test_parse_kilobytes() {
104 assert_eq!(parse_human_size("1024K").unwrap(), 1);
105 assert_eq!(parse_human_size("1024KB").unwrap(), 1);
106 assert_eq!(parse_human_size("1024KiB").unwrap(), 1);
107 assert_eq!(parse_human_size("512K").unwrap(), 1); assert_eq!(parse_human_size("2048k").unwrap(), 2);
109 }
110
111 #[test]
112 fn test_parse_with_whitespace() {
113 assert_eq!(parse_human_size(" 512 ").unwrap(), 512);
114 assert_eq!(parse_human_size(" 4G ").unwrap(), 4096);
115 assert_eq!(parse_human_size("512 M").unwrap(), 512);
116 }
117
118 #[test]
119 fn test_parse_decimal() {
120 assert_eq!(parse_human_size("1.5G").unwrap(), 1536);
121 assert_eq!(parse_human_size("0.5G").unwrap(), 512);
122 assert_eq!(parse_human_size("2.5G").unwrap(), 2560);
123 }
124
125 #[test]
126 fn test_parse_errors() {
127 assert!(parse_human_size("").is_err());
128 assert!(parse_human_size("abc").is_err());
129 assert!(parse_human_size("512T").is_err()); assert!(parse_human_size("-512M").is_err()); assert!(parse_human_size("512XB").is_err()); }
133
134 #[test]
135 fn test_parse_overflow() {
136 assert!(parse_human_size("9999999G").is_err());
139 }
140
141 #[test]
142 fn test_parse_zero() {
143 assert_eq!(parse_human_size("0").unwrap(), 0);
144 assert_eq!(parse_human_size("0M").unwrap(), 0);
145 assert_eq!(parse_human_size("0G").unwrap(), 0);
146 }
147}