pub fn parse_size(s: &str) -> Result<u64, String> {
let t = s.trim();
if t.is_empty() {
return Err("empty size; expected e.g. 512M, 2G, or a byte count".into());
}
let (digits, mult) = match t.as_bytes()[t.len() - 1] {
b'K' | b'k' => (&t[..t.len() - 1], 1024u64),
b'M' | b'm' => (&t[..t.len() - 1], 1024 * 1024),
b'G' | b'g' => (&t[..t.len() - 1], 1024 * 1024 * 1024),
_ => (t, 1),
};
let n: u64 = digits.trim().parse::<u64>().map_err(|e| match e.kind() {
std::num::IntErrorKind::PosOverflow => {
format!("size `{s}` overflows a 64-bit byte count")
}
_ => format!("invalid size `{s}`; expected e.g. 512M, 2G, or a byte count"),
})?;
n.checked_mul(mult)
.ok_or_else(|| format!("size `{s}` overflows a 64-bit byte count"))
}
pub use stuffr::size::format_size;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_exact_rendering_parses_back_to_itself() {
for n in [
512u64,
1024,
4096,
256 * 1024 * 1024,
2 * 1024 * 1024 * 1024,
] {
let rendered = format_size(n);
let compact: String = rendered
.replace(" bytes", "")
.replace(" KiB", "K")
.replace(" MiB", "M")
.replace(" GiB", "G");
assert_eq!(
parse_size(&compact).unwrap(),
n,
"{n} rendered as {rendered:?} did not parse back"
);
}
}
#[test]
fn plain_bytes_and_every_suffix() {
assert_eq!(parse_size("512").unwrap(), 512);
assert_eq!(parse_size("512K").unwrap(), 512 * 1024);
assert_eq!(parse_size("512M").unwrap(), 512 * 1024 * 1024);
assert_eq!(parse_size("2G").unwrap(), 2 * 1024 * 1024 * 1024);
assert_eq!(parse_size("2g").unwrap(), 2 * 1024 * 1024 * 1024);
}
#[test]
fn suffixes_are_binary_not_decimal() {
assert_eq!(parse_size("1M").unwrap(), 1_048_576);
assert_ne!(parse_size("1M").unwrap(), 1_000_000);
}
#[test]
fn malformed_input_is_an_error_naming_the_accepted_forms() {
for bad in [
"", " ", "512MB", "M", "-1", "1.5G", "abc", "512 MB", "K512",
] {
let err = parse_size(bad).unwrap_err();
assert!(
err.contains("512M") && err.contains("2G"),
"error for {bad:?} must show the accepted forms concretely, got: {err}"
);
}
}
#[test]
fn forms_that_parse_but_are_worth_pinning() {
assert_eq!(parse_size("512 M").unwrap(), 512 * 1024 * 1024);
assert_eq!(parse_size("+512").unwrap(), 512);
}
#[test]
fn a_digit_string_too_large_for_u64_reports_overflow_not_a_syntax_error() {
let err = parse_size("99999999999999999999999").unwrap_err();
assert!(
err.contains("overflow"),
"expected an overflow message, got: {err}"
);
}
#[test]
fn overflow_is_reported_rather_than_wrapping() {
assert!(
parse_size("18446744073709551615G")
.unwrap_err()
.contains("overflow")
);
}
}