human_units/
size.rs

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/**
10Exact size in bytes.
11
12The intended use is the configuration files where exact numbers are required,
13i.e. cache size, maximum HTTP body size etc.
14*/
15#[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    /// Max. length in string form.
22    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/// Size parsing error.
93#[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}