icydb_model/base/validator/
num.rs1use crate::{base::helper::try_cast_decimal, prelude::*, schema::NumericValue, visitor::Validator};
8use std::any::type_name;
9
10fn cast_decimal_val<N: NumericValue>(value: &N, ctx: &mut dyn VisitorContext) -> Option<Decimal> {
12 try_cast_decimal(value).or_else(|| {
13 ctx.issue(format!(
14 "value of type {} cannot be represented as Decimal",
15 type_name::<N>()
16 ));
17 None
18 })
19}
20
21macro_rules! cmp_validator {
26 ($name:ident, $op:tt, $msg:expr) => {
27 #[validator]
28 pub struct $name {
29 target: Decimal,
30 invalid_configuration: bool,
31 }
32
33 impl $name {
34 pub fn new<N: NumericValue>(target: N) -> Self {
36 let target = try_cast_decimal(&target);
37
38 Self {
39 target: target.unwrap_or_default(),
40 invalid_configuration: target.is_none(),
41 }
42 }
43 }
44
45 impl<N: NumericValue> Validator<N> for $name {
46 fn validate(&self, value: &N, ctx: &mut dyn VisitorContext) {
47 if self.invalid_configuration {
48 ctx.issue("numeric validator bound cannot be represented as Decimal".to_string());
49 return;
50 }
51 let Some(v) = cast_decimal_val(value, ctx) else { return };
52
53 if !(v $op self.target) {
54 ctx.issue(format!($msg, v, self.target));
55 }
56 }
57 }
58 };
59}
60
61cmp_validator!(Lt, <, "{} must be < {}");
62cmp_validator!(Gt, >, "{} must be > {}");
63cmp_validator!(Lte, <=, "{} must be <= {}");
64cmp_validator!(Gte, >=, "{} must be >= {}");
65cmp_validator!(Equal, ==, "{} must be == {}");
66cmp_validator!(NotEqual, !=, "{} must be != {}");
67
68#[validator]
73pub struct Range {
74 min: Decimal,
75 max: Decimal,
76 invalid_configuration: bool,
77}
78
79impl Range {
80 pub fn new<N: NumericValue>(min: N, max: N) -> Self {
82 let min = try_cast_decimal(&min);
83 let max = try_cast_decimal(&max);
84
85 Self {
86 min: min.unwrap_or_default(),
87 max: max.unwrap_or_default(),
88 invalid_configuration: min.is_none() || max.is_none(),
89 }
90 }
91}
92
93impl<N: NumericValue> Validator<N> for Range {
94 fn validate(&self, value: &N, ctx: &mut dyn VisitorContext) {
95 if self.invalid_configuration {
96 ctx.issue("numeric validator bounds cannot be represented as Decimal".to_string());
97 return;
98 }
99 let Some(v) = cast_decimal_val(value, ctx) else {
100 return;
101 };
102
103 if v < self.min || v > self.max {
104 ctx.issue(format!("{v} must be between {} and {}", self.min, self.max));
105 }
106 }
107}
108
109#[validator]
114pub struct MultipleOf {
115 target: Decimal,
116 invalid_configuration: bool,
117}
118
119impl MultipleOf {
120 pub fn new<N: NumericValue>(target: N) -> Self {
122 let target = try_cast_decimal(&target);
123
124 Self {
125 target: target.unwrap_or_default(),
126 invalid_configuration: target.is_none(),
127 }
128 }
129}
130
131impl<N: NumericValue> Validator<N> for MultipleOf {
132 fn validate(&self, value: &N, ctx: &mut dyn VisitorContext) {
133 if self.invalid_configuration {
134 ctx.issue("multipleOf target cannot be represented as Decimal".to_string());
135 return;
136 }
137 if self.target.is_zero() {
138 ctx.issue("multipleOf target must be non-zero".to_string());
139 return;
140 }
141
142 let Some(v) = cast_decimal_val(value, ctx) else {
143 return;
144 };
145
146 if !(v % self.target).is_zero() {
147 ctx.issue(format!("{v} is not a multiple of {}", self.target));
148 }
149 }
150}
151
152#[cfg(test)]
157mod tests;