1use anyhow::Result;
7
8pub fn parse_size(size_str: &str) -> Result<u64> {
49 if size_str == "0" {
50 return Ok(0);
51 }
52
53 let size_str = size_str.to_uppercase();
54 let (number_str, multiplier) = parse_size_unit(&size_str);
55
56 if number_str.contains('.') {
57 parse_decimal_size(number_str, multiplier)
58 } else {
59 parse_integer_size(number_str, multiplier)
60 }
61}
62
63fn parse_size_unit(size_str: &str) -> (&str, u64) {
65 const UNITS: &[(&str, u64)] = &[
66 ("GIB", 1_073_741_824),
67 ("MIB", 1_048_576),
68 ("KIB", 1_024),
69 ("GB", 1_000_000_000),
70 ("MB", 1_000_000),
71 ("KB", 1_000),
72 ];
73
74 for (suffix, multiplier) in UNITS {
75 if size_str.ends_with(suffix) {
76 return (size_str.trim_end_matches(suffix), *multiplier);
77 }
78 }
79
80 (size_str, 1)
81}
82
83fn parse_decimal_size(number_str: &str, multiplier: u64) -> Result<u64> {
85 let parts: Vec<&str> = number_str.split('.').collect();
86 if parts.len() != 2 {
87 return Err(anyhow::anyhow!("Invalid decimal format: {number_str}"));
88 }
89
90 let integer_part: u64 = parts[0].parse().unwrap_or(0);
91 let fractional_result = parse_fractional_part(parts[1])?;
92
93 let integer_bytes = multiply_with_overflow_check(integer_part, multiplier)?;
94 let fractional_bytes =
95 multiply_with_overflow_check(fractional_result, multiplier)? / 1_000_000_000;
96
97 add_with_overflow_check(integer_bytes, fractional_bytes)
98}
99
100fn parse_fractional_part(fractional_str: &str) -> Result<u64> {
102 let fractional_digits = fractional_str.len();
103 if fractional_digits > 9 {
104 return Err(anyhow::anyhow!("Too many decimal places: {fractional_str}"));
105 }
106
107 let fractional_part: u64 = fractional_str.parse()?;
108 let fractional_multiplier = 10u64.pow(9 - u32::try_from(fractional_digits)?);
109
110 Ok(fractional_part * fractional_multiplier)
111}
112
113fn parse_integer_size(number_str: &str, multiplier: u64) -> Result<u64> {
115 let number: u64 = number_str.parse()?;
116 multiply_with_overflow_check(number, multiplier)
117}
118
119fn multiply_with_overflow_check(a: u64, b: u64) -> Result<u64> {
121 a.checked_mul(b)
122 .ok_or_else(|| anyhow::anyhow!("Size value overflow: {a} * {b}"))
123}
124
125fn add_with_overflow_check(a: u64, b: u64) -> Result<u64> {
127 a.checked_add(b)
128 .ok_or_else(|| anyhow::anyhow!("Final overflow: {a} + {b}"))
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134
135 #[test]
136 fn test_parse_size_zero() {
137 assert_eq!(parse_size("0").unwrap(), 0);
138 }
139
140 #[test]
141 fn test_parse_size_plain_bytes() {
142 assert_eq!(parse_size("1000").unwrap(), 1000);
143 assert_eq!(parse_size("12345").unwrap(), 12345);
144 assert_eq!(parse_size("1").unwrap(), 1);
145 }
146
147 #[test]
148 fn test_parse_size_decimal_units() {
149 assert_eq!(parse_size("1KB").unwrap(), 1_000);
150 assert_eq!(parse_size("100KB").unwrap(), 100_000);
151 assert_eq!(parse_size("1MB").unwrap(), 1_000_000);
152 assert_eq!(parse_size("5MB").unwrap(), 5_000_000);
153 assert_eq!(parse_size("1GB").unwrap(), 1_000_000_000);
154 assert_eq!(parse_size("2GB").unwrap(), 2_000_000_000);
155 }
156
157 #[test]
158 fn test_parse_size_binary_units() {
159 assert_eq!(parse_size("1KiB").unwrap(), 1_024);
160 assert_eq!(parse_size("1MiB").unwrap(), 1_048_576);
161 assert_eq!(parse_size("1GiB").unwrap(), 1_073_741_824);
162 assert_eq!(parse_size("2KiB").unwrap(), 2_048);
163 assert_eq!(parse_size("10MiB").unwrap(), 10_485_760);
164 }
165
166 #[test]
167 fn test_parse_size_case_insensitive() {
168 assert_eq!(parse_size("1kb").unwrap(), 1_000);
169 assert_eq!(parse_size("1Kb").unwrap(), 1_000);
170 assert_eq!(parse_size("1kB").unwrap(), 1_000);
171 assert_eq!(parse_size("1mb").unwrap(), 1_000_000);
172 assert_eq!(parse_size("1mib").unwrap(), 1_048_576);
173 assert_eq!(parse_size("1gib").unwrap(), 1_073_741_824);
174 }
175
176 #[test]
177 fn test_parse_size_decimal_values() {
178 assert_eq!(parse_size("1.5KB").unwrap(), 1_500);
179 assert_eq!(parse_size("2.5MB").unwrap(), 2_500_000);
180 assert_eq!(parse_size("1.5MiB").unwrap(), 1_572_864); assert_eq!(parse_size("0.5GB").unwrap(), 500_000_000);
182 assert_eq!(parse_size("0.1KB").unwrap(), 100);
183 }
184
185 #[test]
186 fn test_parse_size_complex_decimals() {
187 assert_eq!(parse_size("1.25MB").unwrap(), 1_250_000);
188 assert_eq!(parse_size("3.14159KB").unwrap(), 3_141); assert_eq!(parse_size("2.75GiB").unwrap(), 2_952_790_016); }
191
192 #[test]
193 fn test_parse_size_invalid_formats() {
194 assert!(parse_size("").is_err());
195 assert!(parse_size("invalid").is_err());
196 assert!(parse_size("1.2.3MB").is_err());
197 assert!(parse_size("MB1").is_err());
198 assert!(parse_size("1XB").is_err());
199 assert!(parse_size("-1MB").is_err());
200 }
201
202 #[test]
203 fn test_parse_size_unit_order() {
204 assert_eq!(parse_size("1GiB").unwrap(), 1_073_741_824);
206 assert_eq!(parse_size("1GB").unwrap(), 1_000_000_000);
207 assert_eq!(parse_size("1MiB").unwrap(), 1_048_576);
208 assert_eq!(parse_size("1MB").unwrap(), 1_000_000);
209 }
210
211 #[test]
212 fn test_parse_size_overflow() {
213 let max_u64_str = format!("{}", u64::MAX);
215 let too_large = format!("{}GB", u64::MAX / 1000 + 1);
216
217 assert!(parse_size(&max_u64_str).is_ok());
218 assert!(parse_size(&too_large).is_err());
219 assert!(parse_size("999999999999999999999999GB").is_err());
220 }
221
222 #[test]
223 fn test_parse_fractional_part() {
224 assert_eq!(parse_fractional_part("5").unwrap(), 500_000_000);
225 assert_eq!(parse_fractional_part("25").unwrap(), 250_000_000);
226 assert_eq!(parse_fractional_part("125").unwrap(), 125_000_000);
227 assert_eq!(parse_fractional_part("999999999").unwrap(), 999_999_999);
228
229 assert!(parse_fractional_part("1234567890").is_err());
231 }
232
233 #[test]
234 fn test_multiply_with_overflow_check() {
235 assert_eq!(multiply_with_overflow_check(100, 200).unwrap(), 20_000);
236 assert_eq!(multiply_with_overflow_check(0, 999).unwrap(), 0);
237 assert_eq!(multiply_with_overflow_check(1, 1).unwrap(), 1);
238
239 assert!(multiply_with_overflow_check(u64::MAX, 2).is_err());
241 assert!(multiply_with_overflow_check(u64::MAX / 2 + 1, 2).is_err());
242 }
243
244 #[test]
245 fn test_add_with_overflow_check() {
246 assert_eq!(add_with_overflow_check(100, 200).unwrap(), 300);
247 assert_eq!(add_with_overflow_check(0, 999).unwrap(), 999);
248 assert_eq!(add_with_overflow_check(u64::MAX - 1, 1).unwrap(), u64::MAX);
249
250 assert!(add_with_overflow_check(u64::MAX, 1).is_err());
252 assert!(add_with_overflow_check(u64::MAX - 1, 2).is_err());
253 }
254
255 #[test]
256 fn test_parse_size_unit() {
257 assert_eq!(parse_size_unit("100GB"), ("100", 1_000_000_000));
258 assert_eq!(parse_size_unit("50MIB"), ("50", 1_048_576));
259 assert_eq!(parse_size_unit("1024"), ("1024", 1));
260 assert_eq!(parse_size_unit("2.5KB"), ("2.5", 1_000));
261 assert_eq!(parse_size_unit("1.5GIB"), ("1.5", 1_073_741_824));
262 }
263
264 #[test]
265 fn test_parse_decimal_size() {
266 assert_eq!(parse_decimal_size("1.5", 1_000_000).unwrap(), 1_500_000);
267 assert_eq!(parse_decimal_size("2.25", 1_000).unwrap(), 2_250);
268 assert_eq!(
269 parse_decimal_size("0.5", 2_000_000_000).unwrap(),
270 1_000_000_000
271 );
272
273 assert!(parse_decimal_size("1.2.3", 1000).is_err());
275 assert!(parse_decimal_size("invalid", 1000).is_err());
276 }
277
278 #[test]
279 fn test_parse_integer_size() {
280 assert_eq!(parse_integer_size("100", 1_000).unwrap(), 100_000);
281 assert_eq!(parse_integer_size("0", 999).unwrap(), 0);
282 assert_eq!(
283 parse_integer_size("1", 1_000_000_000).unwrap(),
284 1_000_000_000
285 );
286
287 assert!(parse_integer_size("not_a_number", 1000).is_err());
289 }
290
291 #[test]
292 fn test_edge_cases() {
293 assert_eq!(parse_size("0.001KB").unwrap(), 1);
295
296 let large_but_valid = (u64::MAX / 1_000_000_000).to_string() + "GB";
298 assert!(parse_size(&large_but_valid).is_ok());
299
300 assert_eq!(parse_size("0KB").unwrap(), 0);
302 assert_eq!(parse_size("0.0MB").unwrap(), 0);
303 }
304}