use std::error::Error;
use std::str::FromStr;
use crate::consts::*;
use crate::Size;
#[derive(Debug, PartialEq, Clone, Eq)]
pub struct ParseSizeError;
impl Error for ParseSizeError {}
impl core::fmt::Display for ParseSizeError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("Error parsing Size")
}
}
impl Size {
#[allow(clippy::should_implement_trait)]
pub fn from_str(s: &str) -> Result<Size, crate::ParseSizeError> {
FromStr::from_str(s)
}
}
#[test]
fn from_str() {
let input = "12.34 kIloByte";
let parsed = Size::from_str(input);
let expected = Size::from_bytes(12.34 * crate::consts::KB as f64);
assert_eq!(parsed, Ok(expected));
}
#[test]
fn parse() {
let size = "12.34 kIloByte".parse();
assert_eq!(size, Ok(Size::from_bytes(12 * KB + 340)));
}
impl FromStr for Size {
type Err = ParseSizeError;
fn from_str(s: &str) -> Result<Size, Self::Err> {
if !s.is_ascii() {
return Err(ParseSizeError);
}
let s = s.trim();
let (num_str, unit) = match s.rfind(|c: char| !c.is_ascii_alphabetic()).map(|i| i + 1) {
None => (s, ""), Some(idx) => s.split_at(idx),
};
let number: f64 = num_str.trim_end().parse().map_err(|_| ParseSizeError)?;
let unit = unit.to_lowercase();
let unit = unit.strip_suffix('s').unwrap_or(&unit);
let multiplier = match unit {
"" | "b" | "byte" => B,
"kb" | "kilobyte" => KB,
"mb" | "megabyte" => MB,
"gb" | "gigabyte" => GB,
"tb" | "terabyte" => TB,
"pb" | "petabyte" => PB,
"eb" | "exabyte" => EB,
"kib" | "kibibyte" => KiB,
"mib" | "mebibyte" => MiB,
"gib" | "gibibyte" => GiB,
"tib" | "tebibyte" => TiB,
"pib" | "pebibyte" => PiB,
"eib" | "exbibyte" => EiB,
_ => return Err(ParseSizeError),
};
let bytes = number * multiplier as f64;
if !bytes.is_finite() || bytes < i64::MIN as f64 || bytes >= i64::MAX as f64 {
return Err(ParseSizeError);
}
if multiplier == B && bytes.fract() != 0.0 {
return Err(ParseSizeError);
}
return Ok(Size::from_bytes(bytes));
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_bare_bytes() {
assert_eq!(Size::from_str("1234"), Ok(Size { bytes: 1234 }));
assert_eq!(Size::from_str(" 1234 "), Ok(Size { bytes: 1234 })); }
#[test]
fn parse_abbr_unit() {
let tests = vec![
("1234B", 1234),
("1234 KB", 1234 * KB),
("1234KiB", 1234 * KiB),
("12.34 MB", (12.34 * MB as f64) as i64),
("12.34MiB", (12.34 * MiB as f64) as i64),
(" 1234 GB ", 1234 * GB),
];
for (input, expected) in tests {
assert_eq!(Size::from_str(input), Ok(Size { bytes: expected }));
}
}
#[test]
fn parse_full_unit() {
let tests = vec![
("1234 bytes", 1234),
("1234 kilobytes", 1234 * KB),
("1234 kibibytes", 1234 * KiB),
("12.34 gigabytes", (12.34 * GB as f64) as i64),
("12.34 gibibytes", (12.34 * GiB as f64) as i64),
];
for (input, expected) in tests {
assert_eq!(Size::from_str(input), Ok(Size { bytes: expected }));
}
}
#[test]
fn parse_invalid_inputs() {
let tests = vec![
"Not a number",
"1234 XB", "12..34 MB", ];
for input in tests {
assert_eq!(dbg!(Size::from_str(input)), Err(ParseSizeError));
}
}
#[test]
fn parse_boundary() {
assert_eq!(Size::from_str("42.0"), Ok(Size::from_bytes(42)));
assert_eq!(Size::from_str("42."), Ok(Size::from_bytes(42)));
assert_eq!(Size::from_str("42.0kib "), Ok(Size::from_bytes(42 * KiB)));
}
#[test]
fn parse_scientific() {
assert_eq!(Size::from_str("0.423E3"), Ok(Size::from_bytes(423)));
assert_eq!(Size::from_str("423E-3 mb"), Ok(Size::from_bytes(423_000)));
assert_eq!(Size::from_str("0.423e3kb"), Ok(Size::from_bytes(423_000)));
}
#[test]
fn parse_impossible_sizes() {
assert_eq!(Size::from_str("mb"), Err(ParseSizeError), "Can't have partial byte literals");
assert_eq!(Size::from_str("0.4b"), Err(ParseSizeError), "Can't have partial byte literals");
assert_eq!(Size::from_str("NaN b"), Err(ParseSizeError), "Can't have non-finite values");
assert_eq!(Size::from_str("2e63 b"), Err(ParseSizeError), "Can't overflow i64::MAX");
assert_eq!(Size::from_str("-2.1e63 b"), Err(ParseSizeError), "Can't underflow i64::MIN");
assert_eq!(
Size::from_str("1e-1 bytes"),
Err(ParseSizeError),
"Can't have partial (scientific) byte literals"
);
}
#[test]
fn parse_unicode_inputs() {
let tests = vec![
"1.5 GB 😱", "1234\u{200d}MB", ];
for input in tests {
assert_eq!(dbg!(Size::from_str(input)), Err(ParseSizeError));
}
}
}