1#[must_use]
24#[allow(clippy::cast_possible_truncation)]
25pub fn precision_from_str(s: &str) -> u8 {
26 let s = s.trim().to_ascii_lowercase();
27
28 if s.contains("e-") {
30 return s.split("e-").last().unwrap().parse::<u8>().unwrap();
31 }
32
33 if let Some((_, decimal_part)) = s.split_once('.') {
35 decimal_part.len().min(u8::MAX as usize) as u8
37 } else {
38 0
39 }
40}
41
42#[must_use]
47#[allow(clippy::cast_possible_truncation)]
48pub fn min_increment_precision_from_str(s: &str) -> u8 {
49 let s = s.trim().to_ascii_lowercase();
50
51 if let Some(pos) = s.find('e') {
53 if s[pos + 1..].starts_with('-') {
54 return s[pos + 2..].parse::<u8>().unwrap_or(0);
55 }
56 }
57
58 if let Some(dot_pos) = s.find('.') {
60 let decimal_part = &s[dot_pos + 1..];
61 if decimal_part.chars().any(|c| c != '0') {
62 let trimmed_len = decimal_part.trim_end_matches('0').len();
63 return trimmed_len.min(u8::MAX as usize) as u8;
64 }
65 return decimal_part.len().min(u8::MAX as usize) as u8;
66 }
67
68 0
69}
70
71pub fn bytes_to_usize(bytes: &[u8]) -> anyhow::Result<usize> {
77 if bytes.len() >= std::mem::size_of::<usize>() {
79 let mut buffer = [0u8; std::mem::size_of::<usize>()];
80 buffer.copy_from_slice(&bytes[..std::mem::size_of::<usize>()]);
81
82 Ok(usize::from_le_bytes(buffer))
83 } else {
84 anyhow::bail!("Not enough bytes to represent a `usize`");
85 }
86}
87
88#[cfg(test)]
92mod tests {
93 use rstest::rstest;
94
95 use super::*;
96
97 #[rstest]
98 #[case("", 0)]
99 #[case("0", 0)]
100 #[case("1.0", 1)]
101 #[case("1.00", 2)]
102 #[case("1.23456789", 8)]
103 #[case("123456.789101112", 9)]
104 #[case("0.000000001", 9)]
105 #[case("1e-1", 1)]
106 #[case("1e-2", 2)]
107 #[case("1e-3", 3)]
108 #[case("1e8", 0)]
109 #[case("-1.23", 2)]
110 #[case("-1e-2", 2)]
111 #[case("1E-2", 2)]
112 #[case(" 1.23", 2)]
113 #[case("1.23 ", 2)]
114 fn test_precision_from_str(#[case] s: &str, #[case] expected: u8) {
115 let result = precision_from_str(s);
116 assert_eq!(result, expected);
117 }
118
119 #[rstest]
120 #[case("", 0)]
121 #[case("0", 0)]
122 #[case("1.0", 1)]
123 #[case("1.00", 2)]
124 #[case("1.23456789", 8)]
125 #[case("123456.789101112", 9)]
126 #[case("0.000000001", 9)]
127 #[case("1e-1", 1)]
128 #[case("1e-2", 2)]
129 #[case("1e-3", 3)]
130 #[case("1e8", 0)]
131 #[case("-1.23", 2)]
132 #[case("-1e-2", 2)]
133 #[case("1E-2", 2)]
134 #[case(" 1.23", 2)]
135 #[case("1.23 ", 2)]
136 #[case("1.010", 2)]
137 #[case("1.00100", 3)]
138 #[case("0.0001000", 4)]
139 #[case("1.000000000", 9)]
140 fn test_min_increment_precision_from_str(#[case] s: &str, #[case] expected: u8) {
141 let result = min_increment_precision_from_str(s);
142 assert_eq!(result, expected);
143 }
144
145 #[rstest]
146 fn test_bytes_to_usize_empty() {
147 let payload: Vec<u8> = vec![];
148 let result = bytes_to_usize(&payload);
149 assert!(result.is_err());
150 assert_eq!(
151 result.err().unwrap().to_string(),
152 "Not enough bytes to represent a `usize`"
153 );
154 }
155
156 #[rstest]
157 fn test_bytes_to_usize_invalid() {
158 let payload: Vec<u8> = vec![0x01, 0x02, 0x03];
159 let result = bytes_to_usize(&payload);
160 assert!(result.is_err());
161 assert_eq!(
162 result.err().unwrap().to_string(),
163 "Not enough bytes to represent a `usize`"
164 );
165 }
166
167 #[rstest]
168 fn test_bytes_to_usize_valid() {
169 let payload: Vec<u8> = vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08];
170 let result = bytes_to_usize(&payload).unwrap();
171 assert_eq!(result, 0x0807_0605_0403_0201);
172 assert_eq!(result, 578_437_695_752_307_201);
173 }
174}