icydb_model/base/normalizer/
num.rs1use crate::{
8 base::helper::try_cast_decimal, prelude::*, schema::NumericValue, visitor::Normalizer,
9};
10use std::any::type_name;
11
12#[normalizer]
17pub struct Clamp {
18 min: Decimal,
19 max: Decimal,
20}
21
22impl Clamp {
23 pub fn new<N: NumericValue>(min: N, max: N) -> Self {
24 let min = try_cast_decimal(&min).unwrap_or_default();
25 let max = try_cast_decimal(&max).unwrap_or_default();
26
27 Self { min, max }
28 }
29}
30
31impl<T: NumericValue> Normalizer<T> for Clamp {
32 fn normalize(&self, value: &mut T) -> Result<(), String> {
33 if self.min > self.max {
34 return Err(format!(
35 "Clamp requires min <= max (got {}..={})",
36 self.min, self.max
37 ));
38 }
39
40 let v = try_cast_decimal(value).ok_or_else(|| {
41 format!(
42 "value of type {} cannot be represented as Decimal",
43 type_name::<T>()
44 )
45 })?;
46
47 let clamped = if v < self.min {
48 self.min
49 } else if v > self.max {
50 self.max
51 } else {
52 v
53 };
54
55 *value = T::try_from_decimal(clamped).ok_or_else(|| {
56 format!(
57 "clamped value cannot be represented as {}",
58 type_name::<T>()
59 )
60 })?;
61
62 Ok(())
63 }
64}
65
66#[normalizer]
71pub struct RoundDecimalPlaces {
72 scale: u32,
73}
74
75impl RoundDecimalPlaces {
76 #[must_use]
77 pub fn new(scale: impl TryInto<u32>) -> Self {
78 Self {
79 scale: scale.try_into().unwrap_or_default(),
80 }
81 }
82}
83
84impl Normalizer<Decimal> for RoundDecimalPlaces {
85 fn normalize(&self, value: &mut Decimal) -> Result<(), String> {
86 *value = value.round_dp(self.scale);
87
88 Ok(())
89 }
90}
91
92#[cfg(test)]
97mod tests {
98 use super::*;
99 use core::str::FromStr;
100
101 fn dec(v: &str) -> Decimal {
102 Decimal::from_str(v).unwrap()
103 }
104
105 #[test]
106 fn clamps_integers() {
107 let clamp = Clamp::new(10, 20);
108
109 let mut v = 5;
110 clamp.normalize(&mut v).unwrap();
111 assert_eq!(v, 10);
112
113 let mut v = 25;
114 clamp.normalize(&mut v).unwrap();
115 assert_eq!(v, 20);
116
117 let mut v = 15;
118 clamp.normalize(&mut v).unwrap();
119 assert_eq!(v, 15);
120 }
121
122 #[test]
123 fn clamp_invalid_config() {
124 let clamp = Clamp::new(20, 10);
125
126 let mut v = 15;
127 assert!(clamp.normalize(&mut v).is_err());
128 }
129
130 #[test]
131 fn rounds_decimal_places_midpoint_away_from_zero() {
132 let round = RoundDecimalPlaces::new(2);
133
134 let mut v = dec("1.234");
135 round.normalize(&mut v).unwrap();
136 assert_eq!(v, dec("1.23"));
137
138 let mut v = dec("1.235");
139 round.normalize(&mut v).unwrap();
140 assert_eq!(v, dec("1.24"));
141
142 let mut v = dec("-1.235");
143 round.normalize(&mut v).unwrap();
144 assert_eq!(v, dec("-1.24"));
145 }
146}