2018-08-29 20:53:52 -04:00
|
|
|
/// An error that occurs when parsing a human readable size description.
|
|
|
|
///
|
2020-06-04 21:06:09 +08:00
|
|
|
/// This error provides an end user friendly message describing why the
|
2022-06-27 06:49:54 +08:00
|
|
|
/// description couldn't be parsed and what the expected format is.
|
2018-08-29 20:53:52 -04:00
|
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
|
|
pub struct ParseSizeError {
|
|
|
|
original: String,
|
|
|
|
kind: ParseSizeErrorKind,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
|
|
enum ParseSizeErrorKind {
|
|
|
|
InvalidFormat,
|
2023-09-20 14:42:03 -04:00
|
|
|
InvalidInt(std::num::ParseIntError),
|
2018-08-29 20:53:52 -04:00
|
|
|
Overflow,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ParseSizeError {
|
|
|
|
fn format(original: &str) -> ParseSizeError {
|
|
|
|
ParseSizeError {
|
|
|
|
original: original.to_string(),
|
|
|
|
kind: ParseSizeErrorKind::InvalidFormat,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-20 14:42:03 -04:00
|
|
|
fn int(original: &str, err: std::num::ParseIntError) -> ParseSizeError {
|
2018-08-29 20:53:52 -04:00
|
|
|
ParseSizeError {
|
|
|
|
original: original.to_string(),
|
|
|
|
kind: ParseSizeErrorKind::InvalidInt(err),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn overflow(original: &str) -> ParseSizeError {
|
|
|
|
ParseSizeError {
|
|
|
|
original: original.to_string(),
|
|
|
|
kind: ParseSizeErrorKind::Overflow,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-20 14:42:03 -04:00
|
|
|
impl std::error::Error for ParseSizeError {}
|
2018-08-29 20:53:52 -04:00
|
|
|
|
2023-09-20 14:42:03 -04:00
|
|
|
impl std::fmt::Display for ParseSizeError {
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
2018-08-29 20:53:52 -04:00
|
|
|
use self::ParseSizeErrorKind::*;
|
|
|
|
|
|
|
|
match self.kind {
|
2020-02-17 18:08:47 -05:00
|
|
|
InvalidFormat => write!(
|
|
|
|
f,
|
2023-09-20 14:42:03 -04:00
|
|
|
"invalid format for size '{}', which should be a non-empty \
|
|
|
|
sequence of digits followed by an optional 'K', 'M' or 'G' \
|
|
|
|
suffix",
|
2020-02-17 18:08:47 -05:00
|
|
|
self.original
|
|
|
|
),
|
|
|
|
InvalidInt(ref err) => write!(
|
|
|
|
f,
|
|
|
|
"invalid integer found in size '{}': {}",
|
|
|
|
self.original, err
|
|
|
|
),
|
|
|
|
Overflow => write!(f, "size too big in '{}'", self.original),
|
2018-08-29 20:53:52 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-20 14:42:03 -04:00
|
|
|
impl From<ParseSizeError> for std::io::Error {
|
|
|
|
fn from(size_err: ParseSizeError) -> std::io::Error {
|
|
|
|
std::io::Error::new(std::io::ErrorKind::Other, size_err)
|
2018-08-29 20:53:52 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Parse a human readable size like `2M` into a corresponding number of bytes.
|
|
|
|
///
|
|
|
|
/// Supported size suffixes are `K` (for kilobyte), `M` (for megabyte) and `G`
|
|
|
|
/// (for gigabyte). If a size suffix is missing, then the size is interpreted
|
|
|
|
/// as bytes. If the size is too big to fit into a `u64`, then this returns an
|
|
|
|
/// error.
|
|
|
|
///
|
|
|
|
/// Additional suffixes may be added over time.
|
|
|
|
pub fn parse_human_readable_size(size: &str) -> Result<u64, ParseSizeError> {
|
2023-09-20 14:42:03 -04:00
|
|
|
let digits_end =
|
|
|
|
size.as_bytes().iter().take_while(|&b| b.is_ascii_digit()).count();
|
|
|
|
let digits = &size[..digits_end];
|
|
|
|
if digits.is_empty() {
|
|
|
|
return Err(ParseSizeError::format(size));
|
2018-08-29 20:53:52 -04:00
|
|
|
}
|
2023-09-20 14:42:03 -04:00
|
|
|
let value =
|
|
|
|
digits.parse::<u64>().map_err(|e| ParseSizeError::int(size, e))?;
|
2018-08-29 20:53:52 -04:00
|
|
|
|
2023-09-20 14:42:03 -04:00
|
|
|
let suffix = &size[digits_end..];
|
|
|
|
if suffix.is_empty() {
|
|
|
|
return Ok(value);
|
|
|
|
}
|
2018-08-29 20:53:52 -04:00
|
|
|
let bytes = match suffix {
|
2020-02-17 18:08:47 -05:00
|
|
|
"K" => value.checked_mul(1 << 10),
|
|
|
|
"M" => value.checked_mul(1 << 20),
|
|
|
|
"G" => value.checked_mul(1 << 30),
|
2023-09-20 14:42:03 -04:00
|
|
|
_ => return Err(ParseSizeError::format(size)),
|
2018-08-29 20:53:52 -04:00
|
|
|
};
|
|
|
|
bytes.ok_or_else(|| ParseSizeError::overflow(size))
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn suffix_none() {
|
|
|
|
let x = parse_human_readable_size("123").unwrap();
|
|
|
|
assert_eq!(123, x);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn suffix_k() {
|
|
|
|
let x = parse_human_readable_size("123K").unwrap();
|
2020-02-17 18:08:47 -05:00
|
|
|
assert_eq!(123 * (1 << 10), x);
|
2018-08-29 20:53:52 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn suffix_m() {
|
|
|
|
let x = parse_human_readable_size("123M").unwrap();
|
2020-02-17 18:08:47 -05:00
|
|
|
assert_eq!(123 * (1 << 20), x);
|
2018-08-29 20:53:52 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn suffix_g() {
|
|
|
|
let x = parse_human_readable_size("123G").unwrap();
|
2020-02-17 18:08:47 -05:00
|
|
|
assert_eq!(123 * (1 << 30), x);
|
2018-08-29 20:53:52 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn invalid_empty() {
|
|
|
|
assert!(parse_human_readable_size("").is_err());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn invalid_non_digit() {
|
|
|
|
assert!(parse_human_readable_size("a").is_err());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn invalid_overflow() {
|
|
|
|
assert!(parse_human_readable_size("9999999999999999G").is_err());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn invalid_suffix() {
|
|
|
|
assert!(parse_human_readable_size("123T").is_err());
|
|
|
|
}
|
|
|
|
}
|