icydb/base/validator/
num.rs1use crate::{
2 design::prelude::*,
3 traits::{NumCast, Validator},
4};
5use std::any::type_name;
6
7fn cast_decimal_cfg<N: NumCast + Clone>(value: &N) -> Decimal {
13 <Decimal as NumCast>::from(value.clone()).unwrap_or_default()
14}
15
16fn cast_decimal_val<N: NumCast + Clone>(
18 value: &N,
19 ctx: &mut dyn VisitorContext,
20) -> Option<Decimal> {
21 <Decimal as NumCast>::from(value.clone()).or_else(|| {
22 ctx.issue(format!(
23 "value of type {} cannot be represented as Decimal",
24 type_name::<N>()
25 ));
26 None
27 })
28}
29
30macro_rules! cmp_validator {
35 ($name:ident, $op:tt, $msg:expr) => {
36 #[validator]
37 pub struct $name {
38 target: Decimal,
39 }
40
41 impl $name {
42 pub fn new<N: NumCast + Clone>(target: N) -> Self {
43 let target = cast_decimal_cfg(&target);
44
45 Self { target }
46 }
47 }
48
49 impl<N: NumCast + Clone> Validator<N> for $name {
50 fn validate(&self, value: &N, ctx: &mut dyn VisitorContext) {
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}
77
78impl Range {
79 pub fn new<N: NumCast + Clone>(min: N, max: N) -> Self {
80 let min = cast_decimal_cfg(&min);
81 let max = cast_decimal_cfg(&max);
82
83 Self { min, max }
84 }
85}
86
87impl<N: NumCast + Clone> Validator<N> for Range {
88 fn validate(&self, value: &N, ctx: &mut dyn VisitorContext) {
89 let Some(v) = cast_decimal_val(value, ctx) else {
90 return;
91 };
92
93 if v < self.min || v > self.max {
94 ctx.issue(format!("{v} must be between {} and {}", self.min, self.max));
95 }
96 }
97}
98
99#[validator]
104pub struct MultipleOf {
105 target: Decimal,
106}
107
108impl MultipleOf {
109 pub fn new<N: NumCast + Clone>(target: N) -> Self {
110 let target = cast_decimal_cfg(&target);
111
112 Self { target }
113 }
114}
115
116impl<N: NumCast + Clone> Validator<N> for MultipleOf {
117 fn validate(&self, value: &N, ctx: &mut dyn VisitorContext) {
118 if self.target.is_zero() {
119 ctx.issue("multipleOf target must be non-zero".to_string());
120 return;
121 }
122
123 let Some(v) = cast_decimal_val(value, ctx) else {
124 return;
125 };
126
127 if !(v % self.target).is_zero() {
128 ctx.issue(format!("{v} is not a multiple of {}", self.target));
129 }
130 }
131}
132
133#[cfg(test)]
138mod tests {
139 use super::*;
140
141 struct TestCtx {
142 issues: crate::visitor::VisitorIssues,
143 }
144
145 impl TestCtx {
146 fn new() -> Self {
147 Self {
148 issues: crate::visitor::VisitorIssues::new(),
149 }
150 }
151 }
152
153 impl crate::visitor::VisitorContext for TestCtx {
154 fn add_issue(&mut self, issue: crate::visitor::Issue) {
155 self.issues
156 .entry(String::new())
157 .or_default()
158 .push(issue.message);
159 }
160
161 fn add_issue_at(&mut self, _: crate::visitor::PathSegment, issue: crate::visitor::Issue) {
162 self.add_issue(issue);
163 }
164 }
165
166 #[test]
167 fn lt() {
168 let v = Lt::new(10);
169 let mut ctx = TestCtx::new();
170
171 v.validate(&5, &mut ctx);
172 assert!(ctx.issues.is_empty());
173
174 v.validate(&10, &mut ctx);
175 assert!(!ctx.issues.is_empty());
176 }
177
178 #[test]
179 fn gte() {
180 let v = Gte::new(5);
181 let mut ctx = TestCtx::new();
182
183 v.validate(&5, &mut ctx);
184 assert!(ctx.issues.is_empty());
185
186 v.validate(&4, &mut ctx);
187 assert!(!ctx.issues.is_empty());
188 }
189
190 #[test]
191 fn range() {
192 let r = Range::new(1, 3);
193 let mut ctx = TestCtx::new();
194
195 r.validate(&2, &mut ctx);
196 assert!(ctx.issues.is_empty());
197
198 r.validate(&0, &mut ctx);
199 assert!(!ctx.issues.is_empty());
200 }
201
202 #[test]
203 fn multiple_of() {
204 let m = MultipleOf::new(5);
205 let mut ctx = TestCtx::new();
206
207 m.validate(&10, &mut ctx);
208 assert!(ctx.issues.is_empty());
209
210 m.validate(&11, &mut ctx);
211 assert!(!ctx.issues.is_empty());
212 }
213}