padded_number/
core.rs

1use crate::*;
2
3/// Newtype encapsulating the padded number invariants
4///
5/// Consists only of an `u8` and an `u64` which keep track of the
6/// leading zeros count and the remaining number value respectively.
7///
8/// Check out the crate-level documentation for an introduction.
9///
10/// `PaddedNumber` uses const generic parameters for setting lower (inclusive)
11/// and upper (inclusive) length bounds. These parameters are by default set to
12/// 1 and 255 (u8::MAX) respectively.
13///
14/// - `MIN < MAX` allows for variable digit length.
15/// - `MIN == MAX` requires the digit to be exactly of length MIN/MAX.
16/// - `MIN == 0` results in empty values ("") being allowed as valid numbers.
17/// - `MIN > MAX, where MIN, MAX > 0` is technically declarable, but any
18///   attempts at constructing such a padded number will fail.
19#[derive(PartialEq, Eq, Clone, Copy, Hash)]
20pub struct PaddedNumber<const A: u8 = 1, const B: u8 = { u8::MAX }> {
21    pub(crate) leading_zeros: u8,
22    pub(crate) number: u64,
23}
24
25impl<const A: u8, const B: u8> PaddedNumber<A, B> {
26    #[doc(hidden)]
27    pub const unsafe fn new_unchecked(leading_zeros: u8, number: u64) -> Self {
28        Self { leading_zeros, number }
29    }
30
31    /// Create a new [`PaddedNumber`]
32    pub const fn try_new(str: &str) -> Result<Self, ParsePaddedNumberError> {
33        let (leading_zeros, remaining_number) = konst::try_!(padded_number_internal::parse(A, B, str));
34
35        Ok(Self { leading_zeros, number: remaining_number })
36    }
37
38    /// Calculate the length of the padded number, including any leading zeros
39    ///
40    /// ```rust
41    /// # use padded_number::*;
42    /// assert_eq!(2, padded_number!("01").len());
43    /// assert_eq!(3, padded_number!("123").len());
44    /// ```
45    pub const fn len(&self) -> u8 {
46        self.leading_zeros + self.number_len()
47    }
48
49    /// Length of number, excluding leading zeros
50    pub(crate) const fn number_len(&self) -> u8 {
51        let number = self.number;
52
53        if number == 0 {
54            return 0;
55        }
56
57        let mut number_length = 1;
58        let mut remaining_number = number;
59
60        while remaining_number >= 10 {
61            number_length += 1;
62            remaining_number /= 10;
63        }
64
65        number_length
66    }
67
68    /// Check if the number if empty, e.g. if and only if it is `""`.
69    /// ```rust
70    /// # use padded_number::*;
71    /// assert!(bound_padded_number!(0, 1, "").is_empty());
72    /// assert!(!padded_number!("01").is_empty());
73    /// ```
74    pub const fn is_empty(&self) -> bool {
75        self.leading_zeros == 0 && self.number == 0
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82    use crate::tests::mock_from_str;
83
84    #[test]
85    fn new_with_leading_zeros() {
86        let number = mock_from_str::<1, 3>("001");
87        assert_eq!(2, number.leading_zeros);
88    }
89
90    #[test]
91    fn new_with_leading_zeros_only() {
92        let number = mock_from_str::<1, 3>("000");
93        let expected = PaddedNumber { leading_zeros: 3, number: 0 };
94        assert_eq!(expected, number)
95    }
96
97    #[test]
98    fn new_with_empty_str() {
99        let number = mock_from_str::<0, 0>("");
100        assert!(number.is_empty());
101    }
102
103    #[test]
104    fn too_long_error() {
105        let invalid_number = "123";
106
107        let actual_err = invalid_number.parse::<PaddedNumber<1, 2>>().unwrap_err();
108
109        assert_eq!(ParsePaddedNumberError::TooLong(2, 3), actual_err);
110    }
111
112    #[test]
113    fn too_short_error() {
114        let invalid_number = "";
115
116        let actual_err = invalid_number.parse::<PaddedNumber<1, 2>>().unwrap_err();
117
118        assert_eq!(ParsePaddedNumberError::TooShort(1, 0), actual_err);
119    }
120
121    #[test]
122    fn non_ascii_digits_error() {
123        let invalid_number = "123abc";
124
125        let actual_err = invalid_number.parse::<PaddedNumber<0, 10>>().unwrap_err();
126
127        assert!(matches!(actual_err, ParsePaddedNumberError::InvalidNumber(_)));
128    }
129
130    #[test]
131    fn is_empty() {
132        let number = PaddedNumber::<0, 0> { leading_zeros: 0, number: 0 };
133        assert!(number.is_empty())
134    }
135
136    #[test]
137    fn length() {
138        assert_len(0, "");
139        assert_len(1, "0");
140        assert_len(3, "000");
141        assert_len(3, "467");
142        assert_len(5, "00467");
143
144        fn assert_len(expected_length: u8, number_str: &str) {
145            let number = mock_from_str::<0, 10>(number_str);
146            assert_eq!(expected_length, number.len());
147        }
148    }
149}