1use std::fmt::{self, Debug, Display, Write};
6use std::ops::{Div, Mul};
7use std::str::FromStr;
8
9use serde::de::{Unexpected, Visitor};
10use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
11
12const UNIT: u64 = 1;
13
14const BINARY_DATA_MAGNITUDE: u64 = 1024;
15pub const B: u64 = UNIT;
16pub const KIB: u64 = B * BINARY_DATA_MAGNITUDE;
17pub const MIB: u64 = KIB * BINARY_DATA_MAGNITUDE;
18pub const GIB: u64 = MIB * BINARY_DATA_MAGNITUDE;
19pub const TIB: u64 = GIB * BINARY_DATA_MAGNITUDE;
20pub const PIB: u64 = TIB * BINARY_DATA_MAGNITUDE;
21
22#[derive(Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Default)]
23pub struct ReadableSize(pub u64);
24
25impl ReadableSize {
26 pub const fn kb(count: u64) -> ReadableSize {
27 ReadableSize(count * KIB)
28 }
29
30 pub const fn mb(count: u64) -> ReadableSize {
31 ReadableSize(count * MIB)
32 }
33
34 pub const fn gb(count: u64) -> ReadableSize {
35 ReadableSize(count * GIB)
36 }
37
38 pub const fn as_mb(self) -> u64 {
39 self.0 / MIB
40 }
41
42 pub const fn as_bytes(self) -> u64 {
43 self.0
44 }
45}
46
47impl Div<u64> for ReadableSize {
48 type Output = ReadableSize;
49
50 fn div(self, rhs: u64) -> ReadableSize {
51 ReadableSize(self.0 / rhs)
52 }
53}
54
55impl Div<ReadableSize> for ReadableSize {
56 type Output = u64;
57
58 fn div(self, rhs: ReadableSize) -> u64 {
59 self.0 / rhs.0
60 }
61}
62
63impl Mul<u64> for ReadableSize {
64 type Output = ReadableSize;
65
66 fn mul(self, rhs: u64) -> ReadableSize {
67 ReadableSize(self.0 * rhs)
68 }
69}
70
71impl Serialize for ReadableSize {
72 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
73 where
74 S: Serializer,
75 {
76 let size = self.0;
77 let mut buffer = String::new();
78 if size == 0 {
79 write!(buffer, "{}KiB", size).unwrap();
80 } else if size % PIB == 0 {
81 write!(buffer, "{}PiB", size / PIB).unwrap();
82 } else if size % TIB == 0 {
83 write!(buffer, "{}TiB", size / TIB).unwrap();
84 } else if size % GIB as u64 == 0 {
85 write!(buffer, "{}GiB", size / GIB).unwrap();
86 } else if size % MIB as u64 == 0 {
87 write!(buffer, "{}MiB", size / MIB).unwrap();
88 } else if size % KIB as u64 == 0 {
89 write!(buffer, "{}KiB", size / KIB).unwrap();
90 } else {
91 return serializer.serialize_u64(size);
92 }
93 serializer.serialize_str(&buffer)
94 }
95}
96
97impl FromStr for ReadableSize {
98 type Err = String;
99
100 fn from_str(s: &str) -> Result<ReadableSize, String> {
102 let size_str = s.trim();
103 if size_str.is_empty() {
104 return Err(format!("{:?} is not a valid size.", s));
105 }
106
107 if !size_str.is_ascii() {
108 return Err(format!("ASCII string is expected, but got {:?}", s));
109 }
110
111 let size_len = size_str
113 .to_string()
114 .chars()
115 .take_while(|c| char::is_ascii_digit(c) || ['.', 'e', 'E', '-', '+'].contains(c))
116 .count();
117
118 let (size, unit) = size_str.split_at(size_len);
120
121 let unit = match unit.trim() {
122 "K" | "KB" | "KiB" => KIB,
123 "M" | "MB" | "MiB" => MIB,
124 "G" | "GB" | "GiB" => GIB,
125 "T" | "TB" | "TiB" => TIB,
126 "P" | "PB" | "PiB" => PIB,
127 "B" | "" => B,
128 _ => {
129 return Err(format!(
130 "only B, KB, KiB, MB, MiB, GB, GiB, TB, TiB, PB, and PiB are supported: {:?}",
131 s
132 ));
133 }
134 };
135
136 match size.parse::<f64>() {
137 Ok(n) => Ok(ReadableSize((n * unit as f64) as u64)),
138 Err(_) => Err(format!("invalid size string: {:?}", s)),
139 }
140 }
141}
142
143impl Debug for ReadableSize {
144 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145 write!(f, "{}", self)
146 }
147}
148
149impl Display for ReadableSize {
150 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151 if self.0 >= PIB {
152 write!(f, "{:.1}PiB", self.0 as f64 / PIB as f64)
153 } else if self.0 >= TIB {
154 write!(f, "{:.1}TiB", self.0 as f64 / TIB as f64)
155 } else if self.0 >= GIB {
156 write!(f, "{:.1}GiB", self.0 as f64 / GIB as f64)
157 } else if self.0 >= MIB {
158 write!(f, "{:.1}MiB", self.0 as f64 / MIB as f64)
159 } else if self.0 >= KIB {
160 write!(f, "{:.1}KiB", self.0 as f64 / KIB as f64)
161 } else {
162 write!(f, "{}B", self.0)
163 }
164 }
165}
166
167impl<'de> Deserialize<'de> for ReadableSize {
168 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
169 where
170 D: Deserializer<'de>,
171 {
172 struct SizeVisitor;
173
174 impl<'de> Visitor<'de> for SizeVisitor {
175 type Value = ReadableSize;
176
177 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
178 formatter.write_str("valid size")
179 }
180
181 fn visit_i64<E>(self, size: i64) -> Result<ReadableSize, E>
182 where
183 E: de::Error,
184 {
185 if size >= 0 {
186 self.visit_u64(size as u64)
187 } else {
188 Err(E::invalid_value(Unexpected::Signed(size), &self))
189 }
190 }
191
192 fn visit_u64<E>(self, size: u64) -> Result<ReadableSize, E>
193 where
194 E: de::Error,
195 {
196 Ok(ReadableSize(size))
197 }
198
199 fn visit_str<E>(self, size_str: &str) -> Result<ReadableSize, E>
200 where
201 E: de::Error,
202 {
203 size_str.parse().map_err(E::custom)
204 }
205 }
206
207 deserializer.deserialize_any(SizeVisitor)
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214
215 #[test]
216 fn test_readable_size() {
217 let s = ReadableSize::kb(2);
218 assert_eq!(s.0, 2048);
219 assert_eq!(s.as_mb(), 0);
220 let s = ReadableSize::mb(2);
221 assert_eq!(s.0, 2 * 1024 * 1024);
222 assert_eq!(s.as_mb(), 2);
223 let s = ReadableSize::gb(2);
224 assert_eq!(s.0, 2 * 1024 * 1024 * 1024);
225 assert_eq!(s.as_mb(), 2048);
226
227 assert_eq!((ReadableSize::mb(2) / 2).0, MIB);
228 assert_eq!((ReadableSize::mb(1) / 2).0, 512 * KIB);
229 assert_eq!(ReadableSize::mb(2) / ReadableSize::kb(1), 2048);
230 }
231
232 #[test]
233 fn test_parse_readable_size() {
234 #[derive(Serialize, Deserialize)]
235 struct SizeHolder {
236 s: ReadableSize,
237 }
238
239 let legal_cases = vec![
240 (0, "0KiB"),
241 (2 * KIB, "2KiB"),
242 (4 * MIB, "4MiB"),
243 (5 * GIB, "5GiB"),
244 (7 * TIB, "7TiB"),
245 (11 * PIB, "11PiB"),
246 ];
247 for (size, exp) in legal_cases {
248 let c = SizeHolder {
249 s: ReadableSize(size),
250 };
251 let res_str = toml::to_string(&c).unwrap();
252 let exp_str = format!("s = {:?}\n", exp);
253 assert_eq!(res_str, exp_str);
254 let res_size: SizeHolder = toml::from_str(&exp_str).unwrap();
255 assert_eq!(res_size.s.0, size);
256 }
257
258 let c = SizeHolder {
259 s: ReadableSize(512),
260 };
261 let res_str = toml::to_string(&c).unwrap();
262 assert_eq!(res_str, "s = 512\n");
263 let res_size: SizeHolder = toml::from_str(&res_str).unwrap();
264 assert_eq!(res_size.s.0, c.s.0);
265
266 let decode_cases = vec![
267 (" 0.5 PB", PIB / 2),
268 ("0.5 TB", TIB / 2),
269 ("0.5GB ", GIB / 2),
270 ("0.5MB", MIB / 2),
271 ("0.5KB", KIB / 2),
272 ("0.5P", PIB / 2),
273 ("0.5T", TIB / 2),
274 ("0.5G", GIB / 2),
275 ("0.5M", MIB / 2),
276 ("0.5K", KIB / 2),
277 ("23", 23),
278 ("1", 1),
279 ("1024B", KIB),
280 (" 0.5 PiB", PIB / 2),
282 ("1PiB", PIB),
283 ("0.5 TiB", TIB / 2),
284 ("2 TiB", TIB * 2),
285 ("0.5GiB ", GIB / 2),
286 ("787GiB ", GIB * 787),
287 ("0.5MiB", MIB / 2),
288 ("3MiB", MIB * 3),
289 ("0.5KiB", KIB / 2),
290 ("1 KiB", KIB),
291 ("0.5e6 B", B * 500000),
293 ("0.5E6 B", B * 500000),
294 ("1e6B", B * 1000000),
295 ("8E6B", B * 8000000),
296 ("8e7", B * 80000000),
297 ("1e-1MB", MIB / 10),
298 ("1e+1MB", MIB * 10),
299 ("0e+10MB", 0),
300 ];
301 for (src, exp) in decode_cases {
302 let src = format!("s = {:?}", src);
303 let res: SizeHolder = toml::from_str(&src).unwrap();
304 assert_eq!(res.s.0, exp);
305 }
306
307 let illegal_cases = vec![
308 "0.5kb", "0.5kB", "0.5Kb", "0.5k", "0.5g", "b", "gb", "1b", "B", "1K24B", " 5_KB",
309 "4B7", "5M_",
310 ];
311 for src in illegal_cases {
312 let src_str = format!("s = {:?}", src);
313 assert!(toml::from_str::<SizeHolder>(&src_str).is_err(), "{}", src);
314 }
315 }
316}