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