1use crate::u64_is_multiple_of;
2use core::fmt::Debug;
3use core::fmt::Display;
4use core::ops::Deref;
5use core::ops::DerefMut;
6use core::str::FromStr;
7
8#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
15#[cfg_attr(all(test, feature = "std"), derive(arbitrary::Arbitrary))]
16#[repr(transparent)]
17pub struct Size(pub u64);
18
19impl Size {
20 pub const MAX_STRING_LEN: usize = 20;
22}
23
24impl Display for Size {
25 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
26 let mut size = self.0;
27 let unit = if size == 0 {
28 ""
29 } else {
30 let mut unit = "";
31 for u in UNITS {
32 if !u64_is_multiple_of(size, 1024) {
33 break;
34 }
35 size /= 1024;
36 unit = u;
37 }
38 unit
39 };
40 write!(f, "{size}{unit}")
41 }
42}
43
44impl FromStr for Size {
45 type Err = SizeError;
46 fn from_str(other: &str) -> Result<Self, Self::Err> {
47 let other = other.trim();
48 match other.rfind(char::is_numeric) {
49 None => Err(SizeError),
50 Some(i) => {
51 let size: u64 = other[..=i].parse().map_err(|_| SizeError)?;
52 let unit = other[(i + 1)..].trim();
53 let factor = match unit.len() {
54 0 => 1_u64,
55 1 => unit_to_factor(unit.as_bytes()[0])?,
56 _ => return Err(SizeError),
57 };
58 let value = size.checked_mul(factor).ok_or(SizeError)?;
59 Ok(Self(value))
60 }
61 }
62 }
63}
64
65impl From<u64> for Size {
66 fn from(other: u64) -> Self {
67 Self(other)
68 }
69}
70
71impl From<Size> for u64 {
72 fn from(other: Size) -> Self {
73 other.0
74 }
75}
76
77impl Deref for Size {
78 type Target = u64;
79
80 fn deref(&self) -> &Self::Target {
81 &self.0
82 }
83}
84
85impl DerefMut for Size {
86 fn deref_mut(&mut self) -> &mut Self::Target {
87 &mut self.0
88 }
89}
90
91#[derive(Debug)]
93pub struct SizeError;
94
95impl Display for SizeError {
96 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
97 Debug::fmt(self, f)
98 }
99}
100
101#[cfg(feature = "std")]
102impl std::error::Error for SizeError {}
103
104const fn unit_to_factor(unit: u8) -> Result<u64, SizeError> {
105 match unit {
106 b'k' | b'K' => Ok(1024_u64),
107 b'm' | b'M' => Ok(1024_u64 * 1024_u64),
108 b'g' | b'G' => Ok(1024_u64 * 1024_u64 * 1024_u64),
109 b't' | b'T' => Ok(1024_u64 * 1024_u64 * 1024_u64 * 1024_u64),
110 _ => Err(SizeError),
111 }
112}
113
114const UNITS: [&str; 4] = ["k", "m", "g", "t"];
115
116#[cfg(all(test, feature = "std"))]
117mod tests {
118
119 use std::ops::AddAssign;
120
121 use arbtest::arbtest;
122
123 use super::*;
124
125 #[test]
126 fn test_display() {
127 assert_eq!("0", Size(0).to_string());
128 assert_eq!("1023", Size(1023).to_string());
129 assert_eq!("1k", Size(1024).to_string());
130 assert_eq!("1025", Size(1025).to_string());
131 }
132
133 #[test]
134 fn test_parse() {
135 assert_eq!("Err(SizeError)", format!("{:?}", "2km".parse::<Size>()));
136 assert_eq!("Err(SizeError)", format!("{:?}", "2s".parse::<Size>()));
137 assert_eq!("Err(SizeError)", format!("{:?}", "k".parse::<Size>()));
138 assert_eq!("Err(SizeError)", format!("{:?}", "".parse::<Size>()));
139 assert_eq!(
140 "Err(SizeError)",
141 format!("{:?}", format!("{}0", u64::MAX).parse::<Size>())
142 );
143 }
144
145 #[test]
146 fn test_deref() {
147 assert_eq!(1, *Size(1));
148 let mut tmp = Size(1);
149 tmp.add_assign(1);
150 assert_eq!(2, *tmp);
151 }
152
153 #[test]
154 fn test_from_into() {
155 let d1 = Size(1);
156 let d2: u64 = d1.into();
157 let d3: Size = d2.into();
158 assert_eq!(d1, d3);
159 assert_eq!(d1.0, d2);
160 }
161
162 #[test]
163 fn from_str_overflow_does_not_panic() {
164 let expected = u64::MAX;
165 let string = format!("{expected}t");
166 assert!(string.parse::<Size>().is_err(), "string = {string:?}");
167 }
168
169 #[test]
170 fn display_parse_symmetry() {
171 arbtest(|u| {
172 let expected: Size = u.arbitrary()?;
173 let string = expected.to_string();
174 let actual: Size = string.parse().unwrap();
175 assert_eq!(expected, actual, "string = `{string}`");
176 Ok(())
177 });
178 }
179
180 #[test]
181 fn parse_display_symmetry() {
182 arbtest(|u| {
183 let (unit, max) = *u
184 .choose(&[
185 ("", u64::MAX),
186 ("k", u64::MAX >> 10),
187 ("m", u64::MAX >> 20),
188 ("g", u64::MAX >> 30),
189 ("t", u64::MAX >> 40),
190 ])
191 .unwrap();
192 let mut unit = unit.to_string();
193 if u.arbitrary::<bool>()? {
194 unit.make_ascii_uppercase();
195 }
196 let number: u64 = u.int_in_range(0_u64..=max)?;
197 let prefix = *u.choose(&["", " ", " "]).unwrap();
198 let infix = *u.choose(&["", " ", " "]).unwrap();
199 let suffix = *u.choose(&["", " ", " "]).unwrap();
200 let expected = format!("{prefix}{number}{infix}{unit}{suffix}");
201 let expected_size: Size = expected.parse().unwrap();
202 let actual = expected_size.to_string();
203 let actual_size: Size = actual.parse().unwrap();
204 assert_eq!(
205 expected_size, actual_size,
206 "string 1 = `{expected}`, string 2 = `{actual}`"
207 );
208 assert!(expected == actual || u64_is_multiple_of(actual_size.0, number));
209 Ok(())
210 });
211 }
212}