1#![warn(missing_docs)]
22
23pub mod backoff;
24pub mod bytes;
25pub mod collections;
26pub mod env;
27pub mod glob;
28pub mod hash;
29pub mod secret;
30pub mod sensitive;
31pub mod strings;
32pub mod template;
33pub mod time;
34
35pub use secret::SecretString;
36pub use sensitive::SecretKeyMatcher;
37pub use template::{Placeholder, Template, TemplatePart};
38
39pub(crate) fn parse_decimal_scaled(value: &str, multiplier: u128) -> Option<u128> {
40 if value.is_empty() || value.starts_with('-') || value.starts_with('+') {
41 return None;
42 }
43
44 let (whole, fraction) = value.split_once('.').unwrap_or((value, ""));
45 if whole.is_empty() && fraction.is_empty() {
46 return None;
47 }
48 if !whole.bytes().all(|byte| byte.is_ascii_digit())
49 || !fraction.bytes().all(|byte| byte.is_ascii_digit())
50 {
51 return None;
52 }
53
54 let whole = if whole.is_empty() {
55 0
56 } else {
57 whole.parse::<u128>().ok()?
58 };
59 let whole_scaled = whole.checked_mul(multiplier)?;
60
61 if fraction.is_empty() {
62 return Some(whole_scaled);
63 }
64
65 let scale = 10_u128.checked_pow(u32::try_from(fraction.len()).ok()?)?;
66 let fraction = fraction.parse::<u128>().ok()?;
67 let fraction_scaled = fraction.checked_mul(multiplier)?.checked_div(scale)?;
68 whole_scaled.checked_add(fraction_scaled)
69}
70
71#[cfg(test)]
72mod tests {
73 use super::parse_decimal_scaled;
74
75 #[test]
76 fn parse_decimal_scaled_accepts_whole_and_fractional_values() {
77 assert_eq!(parse_decimal_scaled("0", 1000), Some(0));
78 assert_eq!(parse_decimal_scaled("12", 1000), Some(12_000));
79 assert_eq!(parse_decimal_scaled("12.345", 1000), Some(12_345));
80 assert_eq!(parse_decimal_scaled(".5", 1000), Some(500));
81 assert_eq!(parse_decimal_scaled("1.", 1000), Some(1000));
82 assert_eq!(parse_decimal_scaled("1.2345", 1000), Some(1234));
83 }
84
85 #[test]
86 fn parse_decimal_scaled_rejects_invalid_or_overflowing_values() {
87 assert_eq!(parse_decimal_scaled("", 1000), None);
88 assert_eq!(parse_decimal_scaled("+1", 1000), None);
89 assert_eq!(parse_decimal_scaled("-1", 1000), None);
90 assert_eq!(parse_decimal_scaled(".", 1000), None);
91 assert_eq!(parse_decimal_scaled("1.2.3", 1000), None);
92 assert_eq!(parse_decimal_scaled("1a", 1000), None);
93 assert_eq!(parse_decimal_scaled("2", u128::MAX), None);
94 assert_eq!(parse_decimal_scaled("1.1", u128::MAX), None);
95 }
96}