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 + utils::number_len(self.number)
47    }
48
49    /// Check if the number if empty, e.g. if and only if it is `""`.
50    /// ```rust
51    /// # use padded_number::*;
52    /// assert!(bound_padded_number!(0, 1, "").is_empty());
53    /// assert!(!padded_number!("01").is_empty());
54    /// ```
55    pub const fn is_empty(&self) -> bool {
56        self.leading_zeros == 0 && self.number == 0
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63    use crate::tests::mock_from_str;
64
65    #[test]
66    fn new_with_leading_zeros() {
67        let number = mock_from_str::<1, 3>("001");
68        assert_eq!(2, number.leading_zeros);
69    }
70
71    #[test]
72    fn new_with_leading_zeros_only() {
73        let number = mock_from_str::<1, 3>("000");
74        let expected = PaddedNumber { leading_zeros: 3, number: 0 };
75        assert_eq!(expected, number)
76    }
77
78    #[test]
79    fn new_with_empty_str() {
80        let number = mock_from_str::<0, 0>("");
81        assert!(number.is_empty());
82    }
83
84    #[test]
85    fn too_long_error() {
86        let invalid_number = "123";
87
88        let actual_err = invalid_number.parse::<PaddedNumber<1, 2>>().unwrap_err();
89
90        assert_eq!(ParsePaddedNumberError::TooLong(2, 3), actual_err);
91    }
92
93    #[test]
94    fn too_short_error() {
95        let invalid_number = "";
96
97        let actual_err = invalid_number.parse::<PaddedNumber<1, 2>>().unwrap_err();
98
99        assert_eq!(ParsePaddedNumberError::TooShort(1, 0), actual_err);
100    }
101
102    #[test]
103    fn non_ascii_digits_error() {
104        let invalid_number = "123abc";
105
106        let actual_err = invalid_number.parse::<PaddedNumber<0, 10>>().unwrap_err();
107
108        assert!(matches!(actual_err, ParsePaddedNumberError::InvalidNumber(_)));
109    }
110
111    #[test]
112    fn is_empty() {
113        let number = PaddedNumber::<0, 0> { leading_zeros: 0, number: 0 };
114        assert!(number.is_empty())
115    }
116
117    #[test]
118    fn length() {
119        assert_len(0, "");
120        assert_len(1, "0");
121        assert_len(3, "000");
122        assert_len(3, "467");
123        assert_len(5, "00467");
124
125        fn assert_len(expected_length: u8, number_str: &str) {
126            let number = mock_from_str::<0, 10>(number_str);
127            assert_eq!(expected_length, number.len());
128        }
129    }
130}