1use serde_json::Value;
2
3use crate::error::{value_type_name, IssueCode, VldError};
4use crate::schema::VldSchema;
5
6#[derive(Clone)]
7enum NumberCheck {
8 Min(f64, String),
9 Max(f64, String),
10 Gt(f64, String),
11 Lt(f64, String),
12 Positive(String),
13 Negative(String),
14 NonNegative(String),
15 NonPositive(String),
16 Finite(String),
17 MultipleOf(f64, String),
18 Safe(String),
19}
20
21impl NumberCheck {
22 fn key(&self) -> &str {
24 match self {
25 NumberCheck::Min(..) => "too_small",
26 NumberCheck::Max(..) => "too_big",
27 NumberCheck::Gt(..) => "too_small",
28 NumberCheck::Lt(..) => "too_big",
29 NumberCheck::Positive(..) => "not_positive",
30 NumberCheck::Negative(..) => "not_negative",
31 NumberCheck::NonNegative(..) => "not_non_negative",
32 NumberCheck::NonPositive(..) => "not_non_positive",
33 NumberCheck::Finite(..) => "not_finite",
34 NumberCheck::MultipleOf(..) => "not_multiple_of",
35 NumberCheck::Safe(..) => "not_safe",
36 }
37 }
38
39 fn set_message(&mut self, msg: String) {
41 match self {
42 NumberCheck::Min(_, ref mut m)
43 | NumberCheck::Max(_, ref mut m)
44 | NumberCheck::Gt(_, ref mut m)
45 | NumberCheck::Lt(_, ref mut m)
46 | NumberCheck::Positive(ref mut m)
47 | NumberCheck::Negative(ref mut m)
48 | NumberCheck::NonNegative(ref mut m)
49 | NumberCheck::NonPositive(ref mut m)
50 | NumberCheck::Finite(ref mut m)
51 | NumberCheck::MultipleOf(_, ref mut m)
52 | NumberCheck::Safe(ref mut m) => *m = msg,
53 }
54 }
55}
56
57#[derive(Clone)]
69pub struct ZNumber {
70 checks: Vec<NumberCheck>,
71 coerce: bool,
72 custom_type_error: Option<String>,
73}
74
75impl ZNumber {
76 pub fn new() -> Self {
77 Self {
78 checks: vec![],
79 coerce: false,
80 custom_type_error: None,
81 }
82 }
83
84 pub fn type_error(mut self, msg: impl Into<String>) -> Self {
94 self.custom_type_error = Some(msg.into());
95 self
96 }
97
98 pub fn with_messages<F>(mut self, f: F) -> Self
115 where
116 F: Fn(&str) -> Option<String>,
117 {
118 for check in &mut self.checks {
119 if let Some(msg) = f(check.key()) {
120 check.set_message(msg);
121 }
122 }
123 self
124 }
125
126 pub fn min(mut self, val: f64) -> Self {
128 self.checks.push(NumberCheck::Min(
129 val,
130 format!("Number must be at least {}", val),
131 ));
132 self
133 }
134
135 pub fn max(mut self, val: f64) -> Self {
137 self.checks.push(NumberCheck::Max(
138 val,
139 format!("Number must be at most {}", val),
140 ));
141 self
142 }
143
144 pub fn gt(mut self, val: f64) -> Self {
146 self.checks.push(NumberCheck::Gt(
147 val,
148 format!("Number must be greater than {}", val),
149 ));
150 self
151 }
152
153 pub fn gte(self, val: f64) -> Self {
155 self.min(val)
156 }
157
158 pub fn lt(mut self, val: f64) -> Self {
160 self.checks.push(NumberCheck::Lt(
161 val,
162 format!("Number must be less than {}", val),
163 ));
164 self
165 }
166
167 pub fn lte(self, val: f64) -> Self {
169 self.max(val)
170 }
171
172 pub fn positive(mut self) -> Self {
174 self.checks
175 .push(NumberCheck::Positive("Number must be positive".to_string()));
176 self
177 }
178
179 pub fn negative(mut self) -> Self {
181 self.checks
182 .push(NumberCheck::Negative("Number must be negative".to_string()));
183 self
184 }
185
186 pub fn non_negative(mut self) -> Self {
188 self.checks.push(NumberCheck::NonNegative(
189 "Number must be non-negative".to_string(),
190 ));
191 self
192 }
193
194 pub fn non_positive(mut self) -> Self {
196 self.checks.push(NumberCheck::NonPositive(
197 "Number must be non-positive".to_string(),
198 ));
199 self
200 }
201
202 pub fn finite(mut self) -> Self {
204 self.checks
205 .push(NumberCheck::Finite("Number must be finite".to_string()));
206 self
207 }
208
209 pub fn multiple_of(mut self, val: f64) -> Self {
211 self.checks.push(NumberCheck::MultipleOf(
212 val,
213 format!("Number must be a multiple of {}", val),
214 ));
215 self
216 }
217
218 pub fn safe(mut self) -> Self {
220 self.checks.push(NumberCheck::Safe(
221 "Number must be a safe integer (-(2^53-1) to 2^53-1)".to_string(),
222 ));
223 self
224 }
225
226 pub fn int(self) -> ZInt {
228 ZInt {
229 inner: self,
230 custom_int_error: None,
231 }
232 }
233
234 pub fn coerce(mut self) -> Self {
236 self.coerce = true;
237 self
238 }
239
240 fn extract_number(&self, value: &Value) -> Result<f64, VldError> {
241 let type_err = |value: &Value| -> VldError {
242 let msg = self
243 .custom_type_error
244 .clone()
245 .unwrap_or_else(|| format!("Expected number, received {}", value_type_name(value)));
246 VldError::single_with_value(
247 IssueCode::InvalidType {
248 expected: "number".to_string(),
249 received: value_type_name(value),
250 },
251 msg,
252 value,
253 )
254 };
255
256 if let Some(n) = value.as_f64() {
257 Ok(n)
258 } else if self.coerce {
259 match value {
260 Value::String(s) => s.parse::<f64>().map_err(|_| {
261 let msg = self
262 .custom_type_error
263 .clone()
264 .unwrap_or_else(|| format!("Cannot coerce \"{}\" to number", s));
265 VldError::single_with_value(
266 IssueCode::InvalidType {
267 expected: "number".to_string(),
268 received: "string".to_string(),
269 },
270 msg,
271 value,
272 )
273 }),
274 Value::Bool(b) => Ok(if *b { 1.0 } else { 0.0 }),
275 _ => Err(type_err(value)),
276 }
277 } else {
278 Err(type_err(value))
279 }
280 }
281
282 fn validate_number(&self, n: f64, value: &Value) -> Result<f64, VldError> {
283 let mut errors = VldError::new();
284
285 for check in &self.checks {
286 match check {
287 NumberCheck::Min(min, msg) => {
288 if n < *min {
289 errors.push_with_value(
290 IssueCode::TooSmall {
291 minimum: *min,
292 inclusive: true,
293 },
294 msg.clone(),
295 value,
296 );
297 }
298 }
299 NumberCheck::Max(max, msg) => {
300 if n > *max {
301 errors.push_with_value(
302 IssueCode::TooBig {
303 maximum: *max,
304 inclusive: true,
305 },
306 msg.clone(),
307 value,
308 );
309 }
310 }
311 NumberCheck::Gt(val, msg) => {
312 if n <= *val {
313 errors.push_with_value(
314 IssueCode::TooSmall {
315 minimum: *val,
316 inclusive: false,
317 },
318 msg.clone(),
319 value,
320 );
321 }
322 }
323 NumberCheck::Lt(val, msg) => {
324 if n >= *val {
325 errors.push_with_value(
326 IssueCode::TooBig {
327 maximum: *val,
328 inclusive: false,
329 },
330 msg.clone(),
331 value,
332 );
333 }
334 }
335 NumberCheck::Positive(msg) => {
336 if n <= 0.0 {
337 errors.push_with_value(
338 IssueCode::TooSmall {
339 minimum: 0.0,
340 inclusive: false,
341 },
342 msg.clone(),
343 value,
344 );
345 }
346 }
347 NumberCheck::Negative(msg) => {
348 if n >= 0.0 {
349 errors.push_with_value(
350 IssueCode::TooBig {
351 maximum: 0.0,
352 inclusive: false,
353 },
354 msg.clone(),
355 value,
356 );
357 }
358 }
359 NumberCheck::NonNegative(msg) => {
360 if n < 0.0 {
361 errors.push_with_value(
362 IssueCode::TooSmall {
363 minimum: 0.0,
364 inclusive: true,
365 },
366 msg.clone(),
367 value,
368 );
369 }
370 }
371 NumberCheck::NonPositive(msg) => {
372 if n > 0.0 {
373 errors.push_with_value(
374 IssueCode::TooBig {
375 maximum: 0.0,
376 inclusive: true,
377 },
378 msg.clone(),
379 value,
380 );
381 }
382 }
383 NumberCheck::Finite(msg) => {
384 if !n.is_finite() {
385 errors.push_with_value(IssueCode::NotFinite, msg.clone(), value);
386 }
387 }
388 NumberCheck::MultipleOf(val, msg) => {
389 if (n % val).abs() > f64::EPSILON {
390 errors.push_with_value(
391 IssueCode::Custom {
392 code: "not_multiple_of".to_string(),
393 },
394 msg.clone(),
395 value,
396 );
397 }
398 }
399 NumberCheck::Safe(msg) => {
400 const MAX_SAFE: f64 = 9007199254740991.0;
401 if !(-MAX_SAFE..=MAX_SAFE).contains(&n) {
402 errors.push_with_value(
403 IssueCode::Custom {
404 code: "not_safe".to_string(),
405 },
406 msg.clone(),
407 value,
408 );
409 }
410 }
411 }
412 }
413
414 if errors.is_empty() {
415 Ok(n)
416 } else {
417 Err(errors)
418 }
419 }
420}
421
422impl Default for ZNumber {
423 fn default() -> Self {
424 Self::new()
425 }
426}
427
428impl ZNumber {
429 #[cfg(feature = "openapi")]
433 pub fn to_json_schema(&self) -> serde_json::Value {
434 let mut schema = serde_json::json!({"type": "number"});
435 for check in &self.checks {
436 match check {
437 NumberCheck::Min(n, _) => {
438 schema["minimum"] = serde_json::json!(*n);
439 }
440 NumberCheck::Max(n, _) => {
441 schema["maximum"] = serde_json::json!(*n);
442 }
443 NumberCheck::Gt(n, _) => {
444 schema["exclusiveMinimum"] = serde_json::json!(*n);
445 }
446 NumberCheck::Lt(n, _) => {
447 schema["exclusiveMaximum"] = serde_json::json!(*n);
448 }
449 NumberCheck::MultipleOf(n, _) => {
450 schema["multipleOf"] = serde_json::json!(*n);
451 }
452 _ => {}
453 }
454 }
455 schema
456 }
457}
458
459impl VldSchema for ZNumber {
460 type Output = f64;
461
462 fn parse_value(&self, value: &Value) -> Result<f64, VldError> {
463 let n = self.extract_number(value)?;
464 self.validate_number(n, value)
465 }
466}
467
468#[derive(Clone)]
474pub struct ZInt {
475 inner: ZNumber,
476 custom_int_error: Option<String>,
477}
478
479impl ZInt {
480 pub fn type_error(mut self, msg: impl Into<String>) -> Self {
482 self.inner = self.inner.type_error(msg);
483 self
484 }
485
486 pub fn int_error(mut self, msg: impl Into<String>) -> Self {
496 self.custom_int_error = Some(msg.into());
497 self
498 }
499
500 pub fn with_messages<F>(mut self, f: F) -> Self
505 where
506 F: Fn(&str) -> Option<String>,
507 {
508 if let Some(msg) = f("not_int") {
509 self.custom_int_error = Some(msg);
510 }
511 self.inner = self.inner.with_messages(f);
512 self
513 }
514
515 pub fn min(mut self, val: i64) -> Self {
517 self.inner.checks.push(NumberCheck::Min(
518 val as f64,
519 format!("Number must be at least {}", val),
520 ));
521 self
522 }
523
524 pub fn max(mut self, val: i64) -> Self {
526 self.inner.checks.push(NumberCheck::Max(
527 val as f64,
528 format!("Number must be at most {}", val),
529 ));
530 self
531 }
532
533 pub fn gt(mut self, val: i64) -> Self {
535 self.inner.checks.push(NumberCheck::Gt(
536 val as f64,
537 format!("Number must be greater than {}", val),
538 ));
539 self
540 }
541
542 pub fn gte(self, val: i64) -> Self {
544 self.min(val)
545 }
546
547 pub fn lt(mut self, val: i64) -> Self {
549 self.inner.checks.push(NumberCheck::Lt(
550 val as f64,
551 format!("Number must be less than {}", val),
552 ));
553 self
554 }
555
556 pub fn lte(self, val: i64) -> Self {
558 self.max(val)
559 }
560
561 pub fn positive(mut self) -> Self {
563 self.inner = self.inner.positive();
564 self
565 }
566
567 pub fn negative(mut self) -> Self {
569 self.inner = self.inner.negative();
570 self
571 }
572
573 pub fn non_negative(mut self) -> Self {
575 self.inner = self.inner.non_negative();
576 self
577 }
578
579 pub fn non_positive(mut self) -> Self {
581 self.inner = self.inner.non_positive();
582 self
583 }
584
585 pub fn safe(mut self) -> Self {
587 self.inner = self.inner.safe();
588 self
589 }
590
591 pub fn multiple_of(mut self, val: i64) -> Self {
593 self.inner.checks.push(NumberCheck::MultipleOf(
594 val as f64,
595 format!("Number must be a multiple of {}", val),
596 ));
597 self
598 }
599}
600
601impl ZInt {
602 #[cfg(feature = "openapi")]
606 pub fn to_json_schema(&self) -> serde_json::Value {
607 let mut schema = self.inner.to_json_schema();
608 schema["type"] = serde_json::json!("integer");
609 schema
610 }
611}
612
613impl VldSchema for ZInt {
614 type Output = i64;
615
616 fn parse_value(&self, value: &Value) -> Result<i64, VldError> {
617 let n = self.inner.extract_number(value)?;
618
619 if n.fract() != 0.0 {
621 let msg = self
622 .custom_int_error
623 .clone()
624 .unwrap_or_else(|| "Expected integer, received float".to_string());
625 return Err(VldError::single_with_value(IssueCode::NotInt, msg, value));
626 }
627
628 self.inner.validate_number(n, value)?;
630
631 Ok(n as i64)
632 }
633}