human_units/
size.rs

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