use std::error::Error;
use std::fmt;
use std::str::FromStr;
pub const KB: i64 = 1 << 10;
pub const MB: i64 = 1 << 20;
pub const GB: i64 = 1 << 30;
pub const TB: i64 = 1 << 40;
pub const PB: i64 = 1 << 50;
pub const EB: i64 = 1 << 60;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SizeOp {
Gt,
Ge,
Lt,
Le,
Eq,
}
impl SizeOp {
pub const ALL: [SizeOp; 5] = [SizeOp::Gt, SizeOp::Ge, SizeOp::Lt, SizeOp::Le, SizeOp::Eq];
#[inline]
#[must_use]
pub fn applies(self, value: i64, threshold: i64) -> bool {
match self {
SizeOp::Gt => value > threshold,
SizeOp::Ge => value >= threshold,
SizeOp::Lt => value < threshold,
SizeOp::Le => value <= threshold,
SizeOp::Eq => value == threshold,
}
}
}
impl fmt::Display for SizeOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
SizeOp::Gt => ">",
SizeOp::Ge => ">=",
SizeOp::Lt => "<",
SizeOp::Le => "<=",
SizeOp::Eq => "=",
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SizeFilter {
pub op: SizeOp,
pub bytes: i64,
}
impl SizeFilter {
#[inline]
#[must_use]
pub const fn new(op: SizeOp, bytes: i64) -> Self {
SizeFilter { op, bytes }
}
#[inline]
#[must_use]
pub const fn gt(bytes: i64) -> Self { SizeFilter { op: SizeOp::Gt, bytes } }
#[inline]
#[must_use]
pub const fn ge(bytes: i64) -> Self { SizeFilter { op: SizeOp::Ge, bytes } }
#[inline]
#[must_use]
pub const fn lt(bytes: i64) -> Self { SizeFilter { op: SizeOp::Lt, bytes } }
#[inline]
#[must_use]
pub const fn le(bytes: i64) -> Self { SizeFilter { op: SizeOp::Le, bytes } }
#[inline]
#[must_use]
pub const fn eq(bytes: i64) -> Self { SizeFilter { op: SizeOp::Eq, bytes } }
#[inline]
#[must_use]
pub fn matches(self, value: i64) -> bool {
self.op.applies(value, self.bytes)
}
}
impl fmt::Display for SizeFilter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}{}", self.op, format_size(self.bytes))
}
}
impl FromStr for SizeFilter {
type Err = SizeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
parse_size_filter(s)
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for SizeFilter {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.collect_str(self)
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for SizeFilter {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s = String::deserialize(deserializer)?;
s.parse().map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct Size(i64);
impl Size {
pub const ZERO: Size = Size(0);
#[inline]
#[must_use]
pub const fn from_bytes(bytes: i64) -> Self {
Size(bytes)
}
#[inline]
#[must_use]
pub const fn from_kb(kb: i64) -> Self {
Size(kb * KB)
}
#[inline]
#[must_use]
pub const fn from_mb(mb: i64) -> Self {
Size(mb * MB)
}
#[inline]
#[must_use]
pub const fn from_gb(gb: i64) -> Self {
Size(gb * GB)
}
#[inline]
#[must_use]
pub const fn from_tb(tb: i64) -> Self {
Size(tb * TB)
}
#[inline]
#[must_use]
pub const fn bytes(self) -> i64 {
self.0
}
}
impl From<i64> for Size {
#[inline]
fn from(v: i64) -> Self {
Size(v)
}
}
impl From<Size> for i64 {
#[inline]
fn from(s: Size) -> Self {
s.0
}
}
impl fmt::Display for Size {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&format_size(self.0))
}
}
impl FromStr for Size {
type Err = SizeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
parse_size(s).map(Size)
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for Size {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.collect_str(self)
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for Size {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s = String::deserialize(deserializer)?;
s.parse().map_err(serde::de::Error::custom)
}
}
impl std::ops::Add for Size {
type Output = Size;
#[inline]
fn add(self, rhs: Size) -> Size {
Size(self.0 + rhs.0)
}
}
impl std::ops::Add<i64> for Size {
type Output = Size;
#[inline]
fn add(self, rhs: i64) -> Size {
Size(self.0 + rhs)
}
}
impl std::ops::Add<Size> for i64 {
type Output = Size;
#[inline]
fn add(self, rhs: Size) -> Size {
Size(self + rhs.0)
}
}
impl std::ops::AddAssign for Size {
#[inline]
fn add_assign(&mut self, rhs: Size) {
self.0 += rhs.0;
}
}
impl std::ops::AddAssign<i64> for Size {
#[inline]
fn add_assign(&mut self, rhs: i64) {
self.0 += rhs;
}
}
impl std::ops::Sub for Size {
type Output = Size;
#[inline]
fn sub(self, rhs: Size) -> Size {
Size(self.0 - rhs.0)
}
}
impl std::ops::Sub<i64> for Size {
type Output = Size;
#[inline]
fn sub(self, rhs: i64) -> Size {
Size(self.0 - rhs)
}
}
impl std::ops::Sub<Size> for i64 {
type Output = Size;
#[inline]
fn sub(self, rhs: Size) -> Size {
Size(self - rhs.0)
}
}
impl std::ops::SubAssign for Size {
#[inline]
fn sub_assign(&mut self, rhs: Size) {
self.0 -= rhs.0;
}
}
impl std::ops::SubAssign<i64> for Size {
#[inline]
fn sub_assign(&mut self, rhs: i64) {
self.0 -= rhs;
}
}
impl std::ops::Mul<i64> for Size {
type Output = Size;
#[inline]
fn mul(self, rhs: i64) -> Size {
Size(self.0 * rhs)
}
}
impl std::ops::Mul<Size> for i64 {
type Output = Size;
#[inline]
fn mul(self, rhs: Size) -> Size {
Size(self * rhs.0)
}
}
impl std::ops::MulAssign<i64> for Size {
#[inline]
fn mul_assign(&mut self, rhs: i64) {
self.0 *= rhs;
}
}
impl std::ops::Div<i64> for Size {
type Output = Size;
#[inline]
fn div(self, rhs: i64) -> Size {
Size(self.0 / rhs)
}
}
impl std::ops::DivAssign<i64> for Size {
#[inline]
fn div_assign(&mut self, rhs: i64) {
self.0 /= rhs;
}
}
impl std::ops::Rem<i64> for Size {
type Output = Size;
#[inline]
fn rem(self, rhs: i64) -> Size {
Size(self.0 % rhs)
}
}
impl std::ops::RemAssign<i64> for Size {
#[inline]
fn rem_assign(&mut self, rhs: i64) {
self.0 %= rhs;
}
}
impl std::ops::Neg for Size {
type Output = Size;
#[inline]
fn neg(self) -> Size {
Size(-self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum SizeError {
MissingOperator,
InvalidNumber,
UnknownUnit,
EmptyInput,
}
impl fmt::Display for SizeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
SizeError::MissingOperator => {
"size filter must start with an operator (>=, >, <=, <, =)"
}
SizeError::InvalidNumber => "failed to parse number",
SizeError::UnknownUnit => "unknown size unit",
SizeError::EmptyInput => "empty input",
})
}
}
impl Error for SizeError {}
pub type SizeResult<T> = Result<T, SizeError>;
pub fn parse_size_filter(s: &str) -> SizeResult<SizeFilter> {
let s = s.trim();
let (op, rest) = if let Some(r) = s.strip_prefix(">=") {
(SizeOp::Ge, r)
} else if let Some(r) = s.strip_prefix("<=") {
(SizeOp::Le, r)
} else if let Some(r) = s.strip_prefix('>') {
(SizeOp::Gt, r)
} else if let Some(r) = s.strip_prefix('<') {
(SizeOp::Lt, r)
} else if let Some(r) = s.strip_prefix('=') {
(SizeOp::Eq, r)
} else {
return Err(SizeError::MissingOperator);
};
let bytes = parse_size(rest)?;
Ok(SizeFilter { op, bytes })
}
pub fn parse_size(size_str: &str) -> SizeResult<i64> {
let size_str = size_str.trim();
if size_str.is_empty() {
return Err(SizeError::EmptyInput);
}
let search_start = usize::from(size_str.starts_with('-'));
let alpha_pos = size_str[search_start..].find(|c: char| c.is_ascii_alphabetic());
let (num_part, unit) = match alpha_pos {
Some(pos) => size_str.split_at(search_start + pos),
None => (size_str, ""),
};
let num: f64 = num_part
.trim()
.parse()
.map_err(|_| SizeError::InvalidNumber)?;
let multiplier = unit_multiplier(unit.trim()).ok_or(SizeError::UnknownUnit)?;
Ok((num * multiplier as f64) as i64)
}
fn unit_multiplier(unit: &str) -> Option<i64> {
if unit.is_empty() || unit.eq_ignore_ascii_case("B") {
Some(1)
} else if unit.eq_ignore_ascii_case("K") || unit.eq_ignore_ascii_case("KB") {
Some(KB)
} else if unit.eq_ignore_ascii_case("M") || unit.eq_ignore_ascii_case("MB") {
Some(MB)
} else if unit.eq_ignore_ascii_case("G") || unit.eq_ignore_ascii_case("GB") {
Some(GB)
} else if unit.eq_ignore_ascii_case("T") || unit.eq_ignore_ascii_case("TB") {
Some(TB)
} else {
None
}
}
#[must_use]
pub fn format_size(size: i64) -> String {
let abs = size.unsigned_abs();
let prefix = if size < 0 { "-" } else { "" };
if abs >= 1 << 60 {
format!("{}{:.1}EB", prefix, (abs as f64) / ((1u64 << 60) as f64))
} else if abs >= 1 << 50 {
format!("{}{:.1}PB", prefix, (abs as f64) / ((1u64 << 50) as f64))
} else if abs >= 1 << 40 {
format!("{}{:.1}TB", prefix, (abs as f64) / ((1u64 << 40) as f64))
} else if abs >= 1 << 30 {
format!("{}{:.1}GB", prefix, (abs as f64) / ((1u64 << 30) as f64))
} else if abs >= 1 << 20 {
format!("{}{:.1}MB", prefix, (abs as f64) / ((1u64 << 20) as f64))
} else if abs >= 1 << 10 {
format!("{}{:.1}KB", prefix, (abs as f64) / ((1u64 << 10) as f64))
} else {
format!("{}{}B", prefix, abs)
}
}
#[cfg(test)]
mod tests {
use crate::*;
#[test]
fn size_newtype_from_str() {
let s: Size = "1GB".parse().unwrap();
assert_eq!(s.bytes(), GB);
}
#[test]
fn size_newtype_display() {
assert_eq!(Size::from_bytes(MB).to_string(), "1.0MB");
}
#[test]
fn size_newtype_const_ctors() {
assert_eq!(Size::from_kb(1), Size::from_bytes(KB));
assert_eq!(Size::from_mb(1), Size::from_bytes(MB));
assert_eq!(Size::from_gb(1), Size::from_bytes(GB));
assert_eq!(Size::from_tb(1), Size::from_bytes(TB));
}
#[test]
fn size_newtype_from_i64() {
let s: Size = 2048.into();
assert_eq!(s.bytes(), 2048);
}
#[test]
fn size_newtype_into_i64() {
let s = Size::from_kb(2);
let v: i64 = s.into();
assert_eq!(v, 2048);
}
#[test]
fn size_newtype_default() {
assert_eq!(Size::default(), Size::ZERO);
}
#[test]
fn size_newtype_ord() {
assert!(Size::from_kb(1) < Size::from_mb(1));
}
#[test]
fn size_newtype_parse_error() {
assert_eq!("".parse::<Size>(), Err(SizeError::EmptyInput));
assert_eq!("abc".parse::<Size>(), Err(SizeError::InvalidNumber));
}
#[test]
fn size_op_all() {
assert_eq!(SizeOp::ALL.len(), 5);
}
#[test]
fn size_op_applies() {
assert!(SizeOp::Gt.applies(10, 5));
assert!(!SizeOp::Gt.applies(5, 5));
assert!(SizeOp::Ge.applies(5, 5));
assert!(SizeOp::Lt.applies(3, 5));
assert!(SizeOp::Le.applies(5, 5));
assert!(SizeOp::Eq.applies(5, 5));
assert!(!SizeOp::Eq.applies(4, 5));
}
#[test]
fn size_add() {
assert_eq!(Size::from_mb(1) + Size::from_kb(512), Size::from_bytes(MB + 512 * KB));
assert_eq!(Size::from_mb(1) + 512, Size::from_bytes(MB + 512));
assert_eq!(1024i64 + Size::from_kb(1), Size::from_bytes(2048));
}
#[test]
fn size_sub() {
assert_eq!(Size::from_mb(2) - Size::from_kb(512), Size::from_bytes(2 * MB - 512 * KB));
assert_eq!(Size::from_mb(1) - 1024, Size::from_bytes(MB - 1024));
}
#[test]
fn size_mul() {
assert_eq!(Size::from_mb(2) * 3, Size::from_bytes(6 * MB));
assert_eq!(3 * Size::from_mb(2), Size::from_bytes(6 * MB));
}
#[test]
fn size_div() {
assert_eq!(Size::from_mb(4) / 2, Size::from_bytes(2 * MB));
}
#[test]
fn size_neg() {
assert_eq!(-Size::from_kb(1), Size::from_bytes(-KB));
}
#[test]
fn size_add_assign() {
let mut s = Size::from_mb(1);
s += Size::from_kb(512);
assert_eq!(s, Size::from_bytes(MB + 512 * KB));
let mut s = Size::from_mb(1);
s += 1024i64;
assert_eq!(s, Size::from_bytes(MB + 1024));
}
#[test]
fn size_sub_assign() {
let mut s = Size::from_mb(2);
s -= Size::from_kb(512);
assert_eq!(s, Size::from_bytes(2 * MB - 512 * KB));
}
#[test]
fn size_mul_assign() {
let mut s = Size::from_mb(2);
s *= 3;
assert_eq!(s, Size::from_bytes(6 * MB));
}
#[test]
fn size_div_assign() {
let mut s = Size::from_mb(4);
s /= 2;
assert_eq!(s, Size::from_bytes(2 * MB));
}
#[test]
fn size_rem() {
assert_eq!(Size::from_bytes(7) % 4, Size::from_bytes(3));
assert_eq!(Size::from_kb(1) % 1000, Size::from_bytes(24));
}
#[test]
fn size_rem_assign() {
let mut s = Size::from_bytes(7);
s %= 4;
assert_eq!(s, Size::from_bytes(3));
}
#[test]
fn filter_from_str() {
let f: SizeFilter = ">=1GB".parse().unwrap();
assert_eq!(f.op, SizeOp::Ge);
assert_eq!(f.bytes, GB);
}
#[test]
fn filter_display() {
let f = SizeFilter::new(SizeOp::Gt, 1024);
assert_eq!(f.to_string(), ">1.0KB");
}
#[test]
fn filter_matches() {
let f = SizeFilter::new(SizeOp::Ge, GB);
assert!(f.matches(GB));
assert!(f.matches(GB + 1));
assert!(!f.matches(GB - 1));
}
#[test]
fn filter_convenience_ctors() {
assert_eq!(SizeFilter::gt(1), SizeFilter::new(SizeOp::Gt, 1));
assert_eq!(SizeFilter::ge(2), SizeFilter::new(SizeOp::Ge, 2));
assert_eq!(SizeFilter::lt(3), SizeFilter::new(SizeOp::Lt, 3));
assert_eq!(SizeFilter::le(4), SizeFilter::new(SizeOp::Le, 4));
assert_eq!(SizeFilter::eq(5), SizeFilter::new(SizeOp::Eq, 5));
}
#[test]
fn size_basic() {
assert_eq!(parse_size("1024").unwrap(), 1024);
assert_eq!(parse_size("1KB").unwrap(), KB);
assert_eq!(parse_size("1MB").unwrap(), MB);
assert_eq!(parse_size("1GB").unwrap(), GB);
assert_eq!(parse_size("1TB").unwrap(), TB);
}
#[test]
fn size_zero_and_small() {
assert_eq!(parse_size("0").unwrap(), 0);
assert_eq!(parse_size("0B").unwrap(), 0);
assert_eq!(parse_size("-0").unwrap(), 0);
assert_eq!(parse_size("1B").unwrap(), 1);
assert_eq!(parse_size("512").unwrap(), 512);
}
#[test]
fn size_short_unit() {
assert_eq!(parse_size("1K").unwrap(), KB);
assert_eq!(parse_size("1M").unwrap(), MB);
assert_eq!(parse_size("1G").unwrap(), GB);
assert_eq!(parse_size("1T").unwrap(), TB);
}
#[test]
fn size_case_insensitive() {
assert_eq!(parse_size("1KB").unwrap(), KB);
assert_eq!(parse_size("1Kb").unwrap(), KB);
assert_eq!(parse_size("1kb").unwrap(), KB);
assert_eq!(parse_size("1mb").unwrap(), MB);
assert_eq!(parse_size("1gb").unwrap(), GB);
assert_eq!(parse_size("1tb").unwrap(), TB);
}
#[test]
fn size_whitespace_padding() {
assert_eq!(parse_size(" 1KB ").unwrap(), KB);
assert_eq!(parse_size("\t1024\n").unwrap(), 1024);
}
#[test]
fn size_whitespace_between() {
assert_eq!(parse_size("1 KB").unwrap(), KB);
assert_eq!(parse_size("1 MB").unwrap(), MB);
assert_eq!(parse_size("1 kb").unwrap(), KB);
}
#[test]
fn size_decimal() {
assert_eq!(parse_size("1.5KB").unwrap(), 1536);
assert_eq!(parse_size("0.5KB").unwrap(), 512);
assert_eq!(parse_size("0.001MB").unwrap(), 1048);
assert_eq!(parse_size("0.1GB").unwrap(), 107374182);
}
#[test]
fn size_negative() {
assert_eq!(parse_size("-1024").unwrap(), -1024);
assert_eq!(parse_size("-1KB").unwrap(), -KB);
assert_eq!(parse_size("-2GB").unwrap(), -2 * GB);
assert_eq!(parse_size("-2.5MB").unwrap(), -2_621_440);
assert_eq!(parse_size("-0.5KB").unwrap(), -512);
}
#[test]
fn size_extreme() {
assert!(parse_size("9999GB").is_ok());
assert_eq!(parse_size("1024TB").unwrap(), 1024 * TB);
assert_eq!(parse_size("0.000000001KB").unwrap(), 0);
}
#[test]
fn size_invalid() {
assert_eq!(parse_size(""), Err(SizeError::EmptyInput));
assert_eq!(parse_size(" "), Err(SizeError::EmptyInput));
assert_eq!(parse_size("abc"), Err(SizeError::InvalidNumber));
assert_eq!(parse_size("1.5.3KB"), Err(SizeError::InvalidNumber));
assert_eq!(parse_size("1XB"), Err(SizeError::UnknownUnit));
assert!(parse_size("KB").is_err());
assert!(parse_size("MB").is_err());
}
#[test]
fn filter_ge() {
let f = parse_size_filter(">=1GB").unwrap();
assert_eq!(f.op, SizeOp::Ge);
assert_eq!(f.bytes, GB);
}
#[test]
fn filter_gt() {
let f = parse_size_filter(">500MB").unwrap();
assert_eq!(f.op, SizeOp::Gt);
assert_eq!(f.bytes, 500 * MB);
}
#[test]
fn filter_le() {
let f = parse_size_filter("<=100KB").unwrap();
assert_eq!(f.op, SizeOp::Le);
assert_eq!(f.bytes, 100 * KB);
}
#[test]
fn filter_lt() {
let f = parse_size_filter("<10MB").unwrap();
assert_eq!(f.op, SizeOp::Lt);
}
#[test]
fn filter_eq() {
let f = parse_size_filter("=0").unwrap();
assert_eq!(f.op, SizeOp::Eq);
assert_eq!(f.bytes, 0);
let f = parse_size_filter("=1KB").unwrap();
assert_eq!(f.op, SizeOp::Eq);
assert_eq!(f.bytes, KB);
}
#[test]
fn filter_no_operator_errors() {
assert_eq!(parse_size_filter("1GB"), Err(SizeError::MissingOperator));
assert_eq!(
parse_size_filter("500MB"),
Err(SizeError::MissingOperator)
);
assert_eq!(parse_size_filter("0"), Err(SizeError::MissingOperator));
assert_eq!(parse_size_filter("hello"), Err(SizeError::MissingOperator));
assert_eq!(parse_size_filter(""), Err(SizeError::MissingOperator));
}
#[test]
fn filter_whitespace() {
let f = parse_size_filter(" >= 1MB ").unwrap();
assert_eq!(f.op, SizeOp::Ge);
assert_eq!(f.bytes, MB);
let f = parse_size_filter(" < 500KB ").unwrap();
assert_eq!(f.op, SizeOp::Lt);
assert_eq!(f.bytes, 500 * KB);
}
#[test]
fn filter_negative() {
let f = parse_size_filter(">-1KB").unwrap();
assert_eq!(f.op, SizeOp::Gt);
assert_eq!(f.bytes, -KB);
let f = parse_size_filter("<=-1024").unwrap();
assert_eq!(f.op, SizeOp::Le);
assert_eq!(f.bytes, -1024);
let f = parse_size_filter("<=0").unwrap();
assert_eq!(f.op, SizeOp::Le);
assert_eq!(f.bytes, 0);
}
#[test]
fn filter_decimal() {
let f = parse_size_filter(">=1.5KB").unwrap();
assert_eq!(f.op, SizeOp::Ge);
assert_eq!(f.bytes, 1536);
let f = parse_size_filter("<0.5MB").unwrap();
assert_eq!(f.op, SizeOp::Lt);
assert_eq!(f.bytes, 524288);
}
#[test]
fn filter_invalid() {
assert_eq!(
parse_size_filter(">=abc"),
Err(SizeError::InvalidNumber)
);
assert_eq!(parse_size_filter("<= "), Err(SizeError::EmptyInput));
assert_eq!(
parse_size_filter("><1GB"),
Err(SizeError::InvalidNumber)
);
}
#[test]
fn format_basic() {
assert_eq!(format_size(0), "0B");
assert_eq!(format_size(1), "1B");
assert_eq!(format_size(100), "100B");
assert_eq!(format_size(500), "500B");
assert_eq!(format_size(KB), "1.0KB");
assert_eq!(format_size(1536), "1.5KB");
assert_eq!(format_size(2048), "2.0KB");
assert_eq!(format_size(MB), "1.0MB");
assert_eq!(format_size(GB), "1.0GB");
assert_eq!(format_size(TB), "1.0TB");
}
#[test]
fn format_negative() {
assert_eq!(format_size(-KB), "-1.0KB");
assert_eq!(format_size(-MB), "-1.0MB");
assert_eq!(format_size(-500), "-500B");
assert_eq!(format_size(i64::MIN), "-8.0EB");
}
#[test]
fn format_edge() {
assert_eq!(format_size(1023), "1023B");
assert_eq!(format_size(-0), "0B");
}
#[test]
fn size_roundtrip() {
let cases = [0i64, 1, 500, KB, 2048, MB, GB];
for &bytes in &cases {
let formatted = format_size(bytes);
let parsed = parse_size(&formatted).unwrap();
assert_eq!(parsed, bytes, "roundtrip failed for {}", bytes);
}
}
#[test]
fn const_values() {
assert_eq!(KB, 1024);
assert_eq!(MB, 1_048_576);
assert_eq!(GB, 1_073_741_824);
assert_eq!(TB, 1_099_511_627_776);
assert_eq!(PB, 1_125_899_906_842_624);
assert_eq!(EB, 1_152_921_504_606_846_976);
}
}