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