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_cfg<N: NumericValue>(value: &N) -> Decimal {
12 try_cast_decimal(value).unwrap_or_default()
13}
14
15fn cast_decimal_val<N: NumericValue>(value: &N, ctx: &mut dyn VisitorContext) -> Option<Decimal> {
17 try_cast_decimal(value).or_else(|| {
18 ctx.issue(format!(
19 "value of type {} cannot be represented as Decimal",
20 type_name::<N>()
21 ));
22 None
23 })
24}
25
26macro_rules! cmp_validator {
31 ($name:ident, $op:tt, $msg:expr) => {
32 #[validator]
33 pub struct $name {
34 target: Decimal,
35 }
36
37 impl $name {
38 pub fn new<N: NumericValue>(target: N) -> Self {
39 let target = cast_decimal_cfg(&target);
40
41 Self { target }
42 }
43 }
44
45 impl<N: NumericValue> Validator<N> for $name {
46 fn validate(&self, value: &N, ctx: &mut dyn VisitorContext) {
47 let Some(v) = cast_decimal_val(value, ctx) else { return };
48
49 if !(v $op self.target) {
50 ctx.issue(format!($msg, v, self.target));
51 }
52 }
53 }
54 };
55}
56
57cmp_validator!(Lt, <, "{} must be < {}");
58cmp_validator!(Gt, >, "{} must be > {}");
59cmp_validator!(Lte, <=, "{} must be <= {}");
60cmp_validator!(Gte, >=, "{} must be >= {}");
61cmp_validator!(Equal, ==, "{} must be == {}");
62cmp_validator!(NotEqual, !=, "{} must be != {}");
63
64#[validator]
69pub struct Range {
70 min: Decimal,
71 max: Decimal,
72}
73
74impl Range {
75 pub fn new<N: NumericValue>(min: N, max: N) -> Self {
76 let min = cast_decimal_cfg(&min);
77 let max = cast_decimal_cfg(&max);
78
79 Self { min, max }
80 }
81}
82
83impl<N: NumericValue> Validator<N> for Range {
84 fn validate(&self, value: &N, ctx: &mut dyn VisitorContext) {
85 let Some(v) = cast_decimal_val(value, ctx) else {
86 return;
87 };
88
89 if v < self.min || v > self.max {
90 ctx.issue(format!("{v} must be between {} and {}", self.min, self.max));
91 }
92 }
93}
94
95#[validator]
100pub struct MultipleOf {
101 target: Decimal,
102}
103
104impl MultipleOf {
105 pub fn new<N: NumericValue>(target: N) -> Self {
106 let target = cast_decimal_cfg(&target);
107
108 Self { target }
109 }
110}
111
112impl<N: NumericValue> Validator<N> for MultipleOf {
113 fn validate(&self, value: &N, ctx: &mut dyn VisitorContext) {
114 if self.target.is_zero() {
115 ctx.issue("multipleOf target must be non-zero".to_string());
116 return;
117 }
118
119 let Some(v) = cast_decimal_val(value, ctx) else {
120 return;
121 };
122
123 if !(v % self.target).is_zero() {
124 ctx.issue(format!("{v} is not a multiple of {}", self.target));
125 }
126 }
127}
128
129#[cfg(test)]
134mod tests {
135 use super::*;
136
137 struct TestCtx {
138 issues: crate::visitor::VisitorIssues,
139 }
140
141 impl TestCtx {
142 fn new() -> Self {
143 Self {
144 issues: crate::visitor::VisitorIssues::new(),
145 }
146 }
147 }
148
149 impl crate::visitor::VisitorContext for TestCtx {
150 fn add_issue(&mut self, issue: crate::visitor::Issue) {
151 self.issues.push(String::new(), issue);
152 }
153
154 fn add_issue_at(&mut self, _: crate::visitor::PathSegment, issue: crate::visitor::Issue) {
155 self.add_issue(issue);
156 }
157 }
158
159 #[test]
160 fn lt() {
161 let v = Lt::new(10);
162 let mut ctx = TestCtx::new();
163
164 v.validate(&5, &mut ctx);
165 assert!(ctx.issues.is_empty());
166
167 v.validate(&10, &mut ctx);
168 assert!(!ctx.issues.is_empty());
169 }
170
171 #[test]
172 fn gte() {
173 let v = Gte::new(5);
174 let mut ctx = TestCtx::new();
175
176 v.validate(&5, &mut ctx);
177 assert!(ctx.issues.is_empty());
178
179 v.validate(&4, &mut ctx);
180 assert!(!ctx.issues.is_empty());
181 }
182
183 #[test]
184 fn range() {
185 let r = Range::new(1, 3);
186 let mut ctx = TestCtx::new();
187
188 r.validate(&2, &mut ctx);
189 assert!(ctx.issues.is_empty());
190
191 r.validate(&0, &mut ctx);
192 assert!(!ctx.issues.is_empty());
193 }
194
195 #[test]
196 fn multiple_of() {
197 let m = MultipleOf::new(5);
198 let mut ctx = TestCtx::new();
199
200 m.validate(&10, &mut ctx);
201 assert!(ctx.issues.is_empty());
202
203 m.validate(&11, &mut ctx);
204 assert!(!ctx.issues.is_empty());
205 }
206}