1use anyhow::{anyhow, Result};
2use regex_syntax::escape;
3
4use super::schema::NumberSchema;
5
6#[cfg_attr(test, derive(PartialEq))]
8#[derive(Debug, Clone)]
9pub struct Decimal {
10 pub coef: u32,
11 pub exp: u32,
12}
13
14impl Decimal {
15 fn new(coef: u32, exp: u32) -> Self {
16 if coef == 0 {
17 return Decimal { coef: 0, exp: 0 };
18 }
19 let mut coef = coef;
21 let mut exp = exp;
22 while exp > 0 && coef.is_multiple_of(10) {
23 coef /= 10;
24 exp -= 1;
25 }
26 Decimal { coef, exp }
27 }
28
29 pub fn lcm(&self, other: &Decimal) -> Decimal {
30 if self.coef == 0 || other.coef == 0 {
31 return Decimal::new(0, 0);
32 }
33 let a = self.coef * 10u32.pow(other.exp.saturating_sub(self.exp));
34 let b = other.coef * 10u32.pow(self.exp.saturating_sub(other.exp));
35 let coef = (a * b) / gcd(a, b);
36 Decimal::new(coef, self.exp.max(other.exp))
37 }
38
39 pub fn to_f64(&self) -> f64 {
40 self.coef as f64 / 10.0f64.powi(self.exp as i32)
41 }
42}
43
44impl TryFrom<f64> for Decimal {
45 type Error = anyhow::Error;
46
47 fn try_from(value: f64) -> Result<Self, Self::Error> {
48 if value < 0.0 {
49 return Err(anyhow!("Value for 'multipleOf' must be non-negative"));
50 }
51 let mut value = value;
52 let mut exp = 0;
53 while value.fract() != 0.0 {
54 value *= 10.0;
55 exp += 1;
56 }
57 if value > u32::MAX as f64 {
58 return Err(anyhow!(
59 "Value for 'multipleOf' has too many digits: {}",
60 value
61 ));
62 }
63 Ok(Decimal::new(value as u32, exp))
64 }
65}
66
67fn gcd(a: u32, b: u32) -> u32 {
68 if b == 0 {
69 a
70 } else {
71 gcd(b, a % b)
72 }
73}
74
75fn mk_or(parts: Vec<String>) -> String {
76 if parts.len() == 1 {
77 parts[0].clone()
78 } else {
79 format!("({})", parts.join("|"))
80 }
81}
82
83fn num_digits(n: i64) -> usize {
84 n.abs().to_string().len()
85}
86
87pub fn rx_int_range(left: Option<i64>, right: Option<i64>) -> Result<String> {
88 match (left, right) {
89 (None, None) => Ok("-?(0|[1-9][0-9]*)".to_string()),
90 (Some(left), None) => {
91 if left < 0 {
92 Ok(mk_or(vec![
93 rx_int_range(Some(left), Some(-1))?,
94 rx_int_range(Some(0), None)?,
95 ]))
96 } else {
97 let max_value = "9"
98 .repeat(num_digits(left))
99 .parse::<i64>()
100 .map_err(|e| anyhow!("Failed to parse max value for left {}: {}", left, e))?;
101 Ok(mk_or(vec![
102 rx_int_range(Some(left), Some(max_value))?,
103 format!("[1-9][0-9]{{{},}}", num_digits(left)),
104 ]))
105 }
106 }
107 (None, Some(right)) => {
108 if right >= 0 {
109 Ok(mk_or(vec![
110 rx_int_range(Some(0), Some(right))?,
111 rx_int_range(None, Some(-1))?,
112 ]))
113 } else {
114 Ok(format!("-{}", rx_int_range(Some(-right), None)?))
115 }
116 }
117 (Some(left), Some(right)) => {
118 if left > right {
119 return Err(anyhow!(
120 "Invalid range: left ({}) cannot be greater than right ({})",
121 left,
122 right
123 ));
124 }
125 if left < 0 {
126 if right < 0 {
127 Ok(format!("(-{})", rx_int_range(Some(-right), Some(-left))?))
128 } else {
129 Ok(format!(
130 "(-{}|{})",
131 rx_int_range(Some(0), Some(-left))?,
132 rx_int_range(Some(0), Some(right))?
133 ))
134 }
135 } else if num_digits(left) == num_digits(right) {
136 let l = left.to_string();
137 let r = right.to_string();
138 if left == right {
139 return Ok(format!("({l})"));
140 }
141
142 let lpref = &l[..l.len() - 1];
143 let lx = &l[l.len() - 1..];
144 let rpref = &r[..r.len() - 1];
145 let rx = &r[r.len() - 1..];
146
147 if lpref == rpref {
148 return Ok(format!("({lpref}[{lx}-{rx}])"));
149 }
150
151 let mut left_rec = lpref.parse::<i64>().unwrap_or(0);
152 let mut right_rec = rpref.parse::<i64>().unwrap_or(0);
153 if left_rec >= right_rec {
154 return Err(anyhow!(
155 "Invalid recursive range: left_rec ({}) must be less than right_rec ({})",
156 left_rec,
157 right_rec
158 ));
159 }
160
161 let mut parts = Vec::new();
162
163 if lx != "0" {
164 left_rec += 1;
165 parts.push(format!("{lpref}[{lx}-9]"));
166 }
167
168 if rx != "9" {
169 right_rec -= 1;
170 parts.push(format!("{rpref}[0-{rx}]"));
171 }
172
173 if left_rec <= right_rec {
174 let inner = rx_int_range(Some(left_rec), Some(right_rec))?;
175 parts.push(format!("{inner}[0-9]"));
176 }
177
178 Ok(mk_or(parts))
179 } else {
180 let break_point = 10_i64
181 .checked_pow(num_digits(left) as u32)
182 .ok_or_else(|| anyhow!("Overflow when calculating break point"))?
183 - 1;
184 Ok(mk_or(vec![
185 rx_int_range(Some(left), Some(break_point))?,
186 rx_int_range(Some(break_point + 1), Some(right))?,
187 ]))
188 }
189 }
190 }
191}
192
193fn lexi_x_to_9(x: &str, incl: bool) -> Result<String> {
194 if incl {
195 if x.is_empty() {
196 Ok("[0-9]*".to_string())
197 } else if x.len() == 1 {
198 Ok(format!("[{x}-9][0-9]*"))
199 } else {
200 let x0 = x
201 .chars()
202 .next()
203 .ok_or_else(|| anyhow!("String x is unexpectedly empty"))?
204 .to_digit(10)
205 .ok_or_else(|| anyhow!("Failed to parse character as digit"))?;
206 let x_rest = &x[1..];
207 let mut parts = vec![format!(
208 "{}{}",
209 x.chars()
210 .next()
211 .ok_or_else(|| anyhow!("String x is unexpectedly empty"))?,
212 lexi_x_to_9(x_rest, incl)?
213 )];
214 if x0 < 9 {
215 parts.push(format!("[{}-9][0-9]*", x0 + 1));
216 }
217 Ok(mk_or(parts))
218 }
219 } else if x.is_empty() {
220 Ok("[0-9]*[1-9]".to_string())
221 } else {
222 let x0 = x
223 .chars()
224 .next()
225 .ok_or_else(|| anyhow!("String x is unexpectedly empty"))?
226 .to_digit(10)
227 .ok_or_else(|| anyhow!("Failed to parse character as digit"))?;
228 let x_rest = &x[1..];
229 let mut parts = vec![format!(
230 "{}{}",
231 x.chars()
232 .next()
233 .ok_or_else(|| anyhow!("String x is unexpectedly empty"))?,
234 lexi_x_to_9(x_rest, incl)?
235 )];
236 if x0 < 9 {
237 parts.push(format!("[{}-9][0-9]*", x0 + 1));
238 }
239 Ok(mk_or(parts))
240 }
241}
242
243fn lexi_0_to_x(x: &str, incl: bool) -> Result<String> {
244 if x.is_empty() {
245 if incl {
246 Ok("".to_string())
247 } else {
248 Err(anyhow!("Inclusive flag must be true for an empty string"))
249 }
250 } else {
251 let x0 = x
252 .chars()
253 .next()
254 .ok_or_else(|| anyhow!("String x is unexpectedly empty"))?
255 .to_digit(10)
256 .ok_or_else(|| anyhow!("Failed to parse character as digit"))?;
257 let x_rest = &x[1..];
258
259 if !incl && x.len() == 1 {
260 if x0 == 0 {
261 return Err(anyhow!(
262 "x0 must be greater than 0 for non-inclusive single character"
263 ));
264 }
265 return Ok(format!("[0-{}][0-9]*", x0 - 1));
266 }
267
268 let mut parts = vec![format!(
269 "{}{}",
270 x.chars()
271 .next()
272 .ok_or_else(|| anyhow!("String x is unexpectedly empty"))?,
273 lexi_0_to_x(x_rest, incl)?
274 )];
275 if x0 > 0 {
276 parts.push(format!("[0-{}][0-9]*", x0 - 1));
277 }
278 Ok(mk_or(parts))
279 }
280}
281
282fn lexi_range(ld: &str, rd: &str, ld_incl: bool, rd_incl: bool) -> Result<String> {
283 if ld.len() != rd.len() {
284 return Err(anyhow!("ld and rd must have the same length"));
285 }
286 if ld == rd {
287 if ld_incl && rd_incl {
288 Ok(ld.to_string())
289 } else {
290 Err(anyhow!(
291 "Empty range when ld equals rd and not both inclusive"
292 ))
293 }
294 } else {
295 let l0 = ld
296 .chars()
297 .next()
298 .ok_or_else(|| anyhow!("ld is unexpectedly empty"))?
299 .to_digit(10)
300 .ok_or_else(|| anyhow!("Failed to parse character as digit"))?;
301 let r0 = rd
302 .chars()
303 .next()
304 .ok_or_else(|| anyhow!("rd is unexpectedly empty"))?
305 .to_digit(10)
306 .ok_or_else(|| anyhow!("Failed to parse character as digit"))?;
307 if l0 == r0 {
308 let ld_rest = &ld[1..];
309 let rd_rest = &rd[1..];
310 Ok(format!(
311 "{}{}",
312 ld.chars()
313 .next()
314 .ok_or_else(|| anyhow!("ld is unexpectedly empty"))?,
315 lexi_range(ld_rest, rd_rest, ld_incl, rd_incl)?
316 ))
317 } else {
318 if l0 >= r0 {
319 return Err(anyhow!("l0 must be less than r0"));
320 }
321 let ld_rest = ld[1..].trim_end_matches('0');
322 let mut parts = vec![format!(
323 "{}{}",
324 ld.chars()
325 .next()
326 .ok_or_else(|| anyhow!("ld is unexpectedly empty"))?,
327 lexi_x_to_9(ld_rest, ld_incl)?
328 )];
329 if l0 + 1 < r0 {
330 parts.push(format!("[{}-{}][0-9]*", l0 + 1, r0 - 1));
331 }
332 let rd_rest = rd[1..].trim_end_matches('0');
333 if !rd_rest.is_empty() || rd_incl {
334 parts.push(format!(
335 "{}{}",
336 rd.chars()
337 .next()
338 .ok_or_else(|| anyhow!("rd is unexpectedly empty"))?,
339 lexi_0_to_x(rd_rest, rd_incl)?
340 ));
341 }
342 Ok(mk_or(parts))
343 }
344 }
345}
346
347fn float_to_str(f: f64) -> String {
348 format!("{f}")
349}
350
351pub fn rx_float_range(
352 left: Option<f64>,
353 right: Option<f64>,
354 left_inclusive: bool,
355 right_inclusive: bool,
356) -> Result<String> {
357 match (left, right) {
358 (None, None) => Ok("-?(0|[1-9][0-9]*)(\\.[0-9]+)?([eE][+-]?[0-9]+)?".to_string()),
359 (Some(left), None) => {
360 if left < 0.0 {
361 Ok(mk_or(vec![
362 rx_float_range(Some(left), Some(0.0), left_inclusive, false)?,
363 rx_float_range(Some(0.0), None, true, false)?,
364 ]))
365 } else {
366 let left_int_part = left as i64;
367 Ok(mk_or(vec![
368 rx_float_range(
369 Some(left),
370 Some(10f64.powi(num_digits(left_int_part) as i32)),
371 left_inclusive,
372 false,
373 )?,
374 format!("[1-9][0-9]{{{},}}(\\.[0-9]+)?", num_digits(left_int_part)),
375 ]))
376 }
377 }
378 (None, Some(right)) => {
379 if right == 0.0 {
380 let r = format!("-{}", rx_float_range(Some(0.0), None, false, false)?);
381 if right_inclusive {
382 Ok(mk_or(vec![r, "0".to_string()]))
383 } else {
384 Ok(r)
385 }
386 } else if right > 0.0 {
387 Ok(mk_or(vec![
388 format!("-{}", rx_float_range(Some(0.0), None, false, false)?),
389 rx_float_range(Some(0.0), Some(right), true, right_inclusive)?,
390 ]))
391 } else {
392 Ok(format!(
393 "-{}",
394 rx_float_range(Some(-right), None, right_inclusive, false)?
395 ))
396 }
397 }
398 (Some(left), Some(right)) => {
399 if left > right {
400 return Err(anyhow!(
401 "Invalid range: left ({}) cannot be greater than right ({})",
402 left,
403 right
404 ));
405 }
406 if left == right {
407 if left_inclusive && right_inclusive {
408 Ok(format!("({})", escape(&float_to_str(left))))
409 } else {
410 Err(anyhow!(
411 "Empty range when left equals right and not both inclusive"
412 ))
413 }
414 } else if left < 0.0 {
415 if right < 0.0 {
416 Ok(format!(
417 "(-{})",
418 rx_float_range(Some(-right), Some(-left), right_inclusive, left_inclusive)?
419 ))
420 } else {
421 let mut parts = vec![];
422 let neg_part = rx_float_range(Some(0.0), Some(-left), false, left_inclusive)?;
423 parts.push(format!("(-{neg_part})"));
424
425 if right > 0.0 || right_inclusive {
426 let pos_part =
427 rx_float_range(Some(0.0), Some(right), true, right_inclusive)?;
428 parts.push(pos_part);
429 }
430 Ok(mk_or(parts))
431 }
432 } else {
433 let l = float_to_str(left);
434 let r = float_to_str(right);
435 if l == r {
436 return Err(anyhow!(
437 "Unexpected equality of left and right string representations"
438 ));
439 }
440 if !left.is_finite() || !right.is_finite() {
441 return Err(anyhow!("Infinite numbers not supported"));
442 }
443
444 let mut left_rec: i64 = l
445 .split('.')
446 .next()
447 .ok_or_else(|| anyhow!("Failed to split left integer part"))?
448 .parse()
449 .map_err(|e| anyhow!("Failed to parse left integer part: {}", e))?;
450 let right_rec: i64 = r
451 .split('.')
452 .next()
453 .ok_or_else(|| anyhow!("Failed to split right integer part"))?
454 .parse()
455 .map_err(|e| anyhow!("Failed to parse right integer part: {}", e))?;
456
457 let mut ld = l.split('.').nth(1).unwrap_or("").to_string();
458 let mut rd = r.split('.').nth(1).unwrap_or("").to_string();
459
460 if left_rec == right_rec {
461 while ld.len() < rd.len() {
462 ld.push('0');
463 }
464 while rd.len() < ld.len() {
465 rd.push('0');
466 }
467 let suff = format!(
468 "\\.{}",
469 lexi_range(&ld, &rd, left_inclusive, right_inclusive)?
470 );
471 if ld.parse::<i64>().unwrap_or(0) == 0 {
472 Ok(format!("({left_rec}({suff})?)"))
473 } else {
474 Ok(format!("({left_rec}{suff})"))
475 }
476 } else {
477 let mut parts = vec![];
478 if !ld.is_empty() || !left_inclusive {
479 parts.push(format!(
480 "({}\\.{})",
481 left_rec,
482 lexi_x_to_9(&ld, left_inclusive)?
483 ));
484 left_rec += 1;
485 }
486
487 if right_rec > left_rec {
488 let inner = rx_int_range(Some(left_rec), Some(right_rec - 1))?;
489 parts.push(format!("({inner}(\\.[0-9]+)?)"));
490 }
491
492 if !rd.is_empty() {
493 parts.push(format!(
494 "({}(\\.{})?)",
495 right_rec,
496 lexi_0_to_x(&rd, right_inclusive)?
497 ));
498 } else if right_inclusive {
499 parts.push(format!("{right_rec}(\\.0+)?"));
500 }
501
502 Ok(mk_or(parts))
503 }
504 }
505 }
506 }
507}
508
509pub fn check_number_bounds(num: &NumberSchema) -> Result<(), String> {
510 let (minimum, exclusive_minimum) = num.get_minimum();
511 let (maximum, exclusive_maximum) = num.get_maximum();
512 if let (Some(min), Some(max)) = (minimum, maximum) {
513 if min > max {
514 return Err(format!("minimum ({min}) is greater than maximum ({max})"));
515 }
516 if min == max && (exclusive_minimum || exclusive_maximum) {
517 let minimum_repr = if exclusive_minimum {
518 "exclusiveMinimum"
519 } else {
520 "minimum"
521 };
522 let maximum_repr = if exclusive_maximum {
523 "exclusiveMaximum"
524 } else {
525 "maximum"
526 };
527 return Err(format!(
528 "{minimum_repr} ({min}) is equal to {maximum_repr} ({max})"
529 ));
530 }
531 }
532 if let Some(d) = num.multiple_of.as_ref() {
533 if d.coef == 0 {
534 if let Some(min) = minimum {
535 if min > 0.0 || (exclusive_minimum && min >= 0.0) {
536 return Err(format!(
537 "minimum ({min}) is greater than 0, but multipleOf is 0"
538 ));
539 }
540 };
541 if let Some(max) = maximum {
542 if max < 0.0 || (exclusive_maximum && max <= 0.0) {
543 return Err(format!(
544 "maximum ({max}) is less than 0, but multipleOf is 0"
545 ));
546 }
547 };
548 return Ok(());
549 }
550 if let (Some(min), Some(max)) = (minimum, maximum) {
552 let step = d.to_f64();
553 let min = {
555 let first_num_ge_min = (min / step).ceil() * step;
556 let adjusted_min = if exclusive_minimum && first_num_ge_min == min {
557 first_num_ge_min + step
558 } else {
559 first_num_ge_min
560 };
561 if num.integer {
562 adjusted_min.ceil()
563 } else {
564 adjusted_min
565 }
566 };
567 let max = {
568 let first_num_le_max = (max / step).floor() * step;
569 let adjusted_max = if exclusive_maximum && first_num_le_max == max {
570 first_num_le_max - step
571 } else {
572 first_num_le_max
573 };
574 if num.integer {
575 adjusted_max.floor()
576 } else {
577 adjusted_max
578 }
579 };
580 if min > max {
581 return Err(format!(
582 "range {}{}, {}{} does not contain a multiple of {}",
583 if exclusive_minimum { "(" } else { "[" },
584 min,
585 max,
586 if exclusive_maximum { ")" } else { "]" },
587 step
588 ));
589 }
590 }
591 }
592 Ok(())
593}
594
595#[cfg(test)]
596mod test_ranges {
597 use super::{rx_float_range, rx_int_range};
598 use regex::Regex;
599
600 fn do_test_int_range(rx: &str, left: Option<i64>, right: Option<i64>) {
601 let re = Regex::new(&format!("^{rx}$")).unwrap();
602 for n in (left.unwrap_or(0) - 1000)..=(right.unwrap_or(0) + 1000) {
603 let matches = re.is_match(&n.to_string());
604 let expected =
605 (left.is_none() || left.unwrap() <= n) && (right.is_none() || n <= right.unwrap());
606 if expected != matches {
607 let range_str = match (left, right) {
608 (Some(l), Some(r)) => format!("[{l}, {r}]"),
609 (Some(l), None) => format!("[{l}, ∞)"),
610 (None, Some(r)) => format!("(-∞, {r}]"),
611 (None, None) => "(-∞, ∞)".to_string(),
612 };
613 if matches {
614 panic!("{n} not in range {range_str} but matches {rx:?}");
615 } else {
616 panic!("{n} in range {range_str} but does not match {rx:?}");
617 }
618 }
619 }
620 }
621
622 #[test]
623 fn test_int_range() {
624 let cases = vec![
625 (Some(0), Some(9)),
626 (Some(1), Some(7)),
627 (Some(0), Some(99)),
628 (Some(13), Some(170)),
629 (Some(13), Some(17)),
630 (Some(13), Some(27)),
631 (Some(13), Some(57)),
632 (Some(72), Some(91)),
633 (Some(723), Some(915)),
634 (Some(23), Some(915)),
635 (Some(-1), Some(915)),
636 (Some(-9), Some(9)),
637 (Some(-3), Some(3)),
638 (Some(-3), Some(0)),
639 (Some(-72), Some(13)),
640 (None, Some(0)),
641 (None, Some(7)),
642 (None, Some(23)),
643 (None, Some(725)),
644 (None, Some(-1)),
645 (None, Some(-17)),
646 (None, Some(-283)),
647 (Some(0), None),
648 (Some(2), None),
649 (Some(33), None),
650 (Some(234), None),
651 (Some(-1), None),
652 (Some(-87), None),
653 (Some(-329), None),
654 (None, None),
655 (Some(-13), Some(-13)),
656 (Some(-1), Some(-1)),
657 (Some(0), Some(0)),
658 (Some(1), Some(1)),
659 (Some(13), Some(13)),
660 ];
661
662 for (left, right) in cases {
663 let rx = rx_int_range(left, right).unwrap();
664 do_test_int_range(&rx, left, right);
665 }
666 }
667
668 fn do_test_float_range(
669 rx: &str,
670 left: Option<f64>,
671 right: Option<f64>,
672 left_inclusive: bool,
673 right_inclusive: bool,
674 ) {
675 let re = Regex::new(&format!("^{rx}$")).unwrap();
676 let left_int = left.map(|x| {
677 let left_int = x.ceil() as i64;
678 if !left_inclusive && x == left_int as f64 {
679 left_int + 1
680 } else {
681 left_int
682 }
683 });
684 let right_int = right.map(|x| {
685 let right_int = x.floor() as i64;
686 if !right_inclusive && x == right_int as f64 {
687 right_int - 1
688 } else {
689 right_int
690 }
691 });
692 do_test_int_range(rx, left_int, right_int);
693
694 let eps1 = 0.0000001;
695 let eps2 = 0.01;
696 let test_cases = vec![
697 left.unwrap_or(-1000.0),
698 right.unwrap_or(1000.0),
699 0.0,
700 left_int.unwrap_or(-1000) as f64,
701 right_int.unwrap_or(1000) as f64,
702 ];
703 for x in test_cases {
704 for offset in [0.0, -eps1, eps1, -eps2, eps2, 1.0, -1.0].iter() {
705 let n = x + offset;
706 let matches = re.is_match(&n.to_string());
707 let left_cond =
708 left.is_none() || left.unwrap() < n || (left.unwrap() == n && left_inclusive);
709 let right_cond = right.is_none()
710 || right.unwrap() > n
711 || (right.unwrap() == n && right_inclusive);
712 let expected = left_cond && right_cond;
713 if expected != matches {
714 let lket = if left_inclusive { "[" } else { "(" };
715 let rket = if right_inclusive { "]" } else { ")" };
716 let range_str = match (left, right) {
717 (Some(l), Some(r)) => format!("{lket}{l}, {r}{rket}"),
718 (Some(l), None) => format!("{lket}{l}, ∞)"),
719 (None, Some(r)) => format!("(-∞, {r}{rket}"),
720 (None, None) => "(-∞, ∞)".to_string(),
721 };
722 if matches {
723 panic!("{n} not in range {range_str} but matches {rx:?}");
724 } else {
725 panic!("{n} in range {range_str} but does not match {rx:?}");
726 }
727 }
728 }
729 }
730 }
731
732 #[test]
733 fn test_float_range() {
734 let cases = vec![
735 (Some(0.0), Some(10.0)),
736 (Some(-10.0), Some(0.0)),
737 (Some(0.5), Some(0.72)),
738 (Some(0.5), Some(1.72)),
739 (Some(0.5), Some(1.32)),
740 (Some(0.45), Some(0.5)),
741 (Some(0.3245), Some(0.325)),
742 (Some(0.443245), Some(0.44325)),
743 (Some(1.0), Some(2.34)),
744 (Some(1.33), Some(2.0)),
745 (Some(1.0), Some(10.34)),
746 (Some(1.33), Some(10.0)),
747 (Some(-1.33), Some(10.0)),
748 (Some(-17.23), Some(-1.33)),
749 (Some(-1.23), Some(-1.221)),
750 (Some(-10.2), Some(45293.9)),
751 (None, Some(0.0)),
752 (None, Some(1.0)),
753 (None, Some(1.5)),
754 (None, Some(1.55)),
755 (None, Some(-17.23)),
756 (None, Some(-1.33)),
757 (None, Some(-1.23)),
758 (None, Some(103.74)),
759 (None, Some(100.0)),
760 (Some(0.0), None),
761 (Some(1.0), None),
762 (Some(1.5), None),
763 (Some(1.55), None),
764 (Some(-17.23), None),
765 (Some(-1.33), None),
766 (Some(-1.23), None),
767 (Some(103.74), None),
768 (Some(100.0), None),
769 (None, None),
770 (Some(-103.4), Some(-103.4)),
771 (Some(-27.0), Some(-27.0)),
772 (Some(-1.5), Some(-1.5)),
773 (Some(-1.0), Some(-1.0)),
774 (Some(0.0), Some(0.0)),
775 (Some(1.0), Some(1.0)),
776 (Some(1.5), Some(1.5)),
777 (Some(27.0), Some(27.0)),
778 (Some(103.4), Some(103.4)),
779 ];
780
781 for (left, right) in cases {
782 for left_inclusive in [true, false].iter() {
783 for right_inclusive in [true, false].iter() {
784 match (left, right) {
785 (Some(left), Some(right))
786 if left == right && !(*left_inclusive && *right_inclusive) =>
787 {
788 assert!(rx_float_range(
789 Some(left),
790 Some(right),
791 *left_inclusive,
792 *right_inclusive
793 )
794 .is_err());
795 }
796 _ => {
797 let rx = rx_float_range(left, right, *left_inclusive, *right_inclusive)
798 .unwrap();
799 do_test_float_range(
800 &rx,
801 left,
802 right,
803 *left_inclusive,
804 *right_inclusive,
805 );
806 }
807 }
808 }
809 }
810 }
811 }
812}
813
814#[cfg(test)]
815mod test_decimal {
816 use super::Decimal;
817
818 #[test]
819 fn test_from_f64() {
820 let cases = vec![
821 (0.0, Decimal { coef: 0, exp: 0 }),
822 (1.0, Decimal { coef: 1, exp: 0 }),
823 (10.0, Decimal { coef: 10, exp: 0 }),
824 (100.0, Decimal { coef: 100, exp: 0 }),
825 (0.1, Decimal { coef: 1, exp: 1 }),
826 (0.01, Decimal { coef: 1, exp: 2 }),
827 (1.1, Decimal { coef: 11, exp: 1 }),
828 (1.01, Decimal { coef: 101, exp: 2 }),
829 (10.1, Decimal { coef: 101, exp: 1 }),
830 (10.01, Decimal { coef: 1001, exp: 2 }),
831 (100.1, Decimal { coef: 1001, exp: 1 }),
832 (
833 100.01,
834 Decimal {
835 coef: 10001,
836 exp: 2,
837 },
838 ),
839 ];
840 for (f, d) in cases {
841 assert_eq!(Decimal::try_from(f).unwrap(), d);
842 }
843 }
844
845 #[test]
846 fn test_simplified() {
847 let cases = vec![
848 (Decimal::new(10, 1), Decimal { coef: 1, exp: 0 }),
849 (Decimal::new(100, 2), Decimal { coef: 1, exp: 0 }),
850 (Decimal::new(100, 1), Decimal { coef: 10, exp: 0 }),
851 (Decimal::new(1000, 3), Decimal { coef: 1, exp: 0 }),
852 (Decimal::new(1000, 2), Decimal { coef: 10, exp: 0 }),
853 (Decimal::new(1000, 1), Decimal { coef: 100, exp: 0 }),
854 (Decimal::new(10000, 4), Decimal { coef: 1, exp: 0 }),
855 (Decimal::new(10000, 3), Decimal { coef: 10, exp: 0 }),
856 (Decimal::new(10000, 2), Decimal { coef: 100, exp: 0 }),
857 (Decimal::new(10000, 1), Decimal { coef: 1000, exp: 0 }),
858 ];
859 for (d, s) in cases {
860 assert_eq!(d, s);
861 }
862 }
863
864 #[test]
865 fn test_lcm() {
866 let cases = vec![
867 (2.0, 3.0, 6.0),
868 (0.5, 1.5, 1.5),
869 (0.5, 0.5, 0.5),
870 (0.5, 0.25, 0.5),
871 (0.3, 0.2, 0.6),
872 (0.3, 0.4, 1.2),
873 (0.05, 0.36, 1.8),
874 (0.3, 14.0, 42.0),
875 ];
876 for (a, b, c) in cases {
877 let a = Decimal::try_from(a).unwrap();
878 let b = Decimal::try_from(b).unwrap();
879 let c = Decimal::try_from(c).unwrap();
880 assert_eq!(a.lcm(&b), c);
881 }
882 }
883}
884
885#[cfg(test)]
886mod test_number_bounds {
887 use crate::json::schema::NumberSchema;
888
889 use super::{check_number_bounds, Decimal};
890
891 #[derive(Debug)]
892 struct Case {
893 minimum: Option<f64>,
894 maximum: Option<f64>,
895 exclusive_minimum: bool,
896 exclusive_maximum: bool,
897 integer: bool,
898 multiple_of: Option<Decimal>,
899 ok: bool,
900 }
901
902 impl Case {
903 fn to_number_schema(&self) -> NumberSchema {
904 NumberSchema {
905 minimum: if self.exclusive_minimum {
906 None
907 } else {
908 self.minimum
909 },
910 maximum: if self.exclusive_maximum {
911 None
912 } else {
913 self.maximum
914 },
915 exclusive_minimum: if self.exclusive_minimum {
916 self.minimum
917 } else {
918 None
919 },
920 exclusive_maximum: if self.exclusive_maximum {
921 self.maximum
922 } else {
923 None
924 },
925 integer: self.integer,
926 multiple_of: self.multiple_of.clone(),
927 }
928 }
929 }
930
931 #[test]
932 fn test_check_number_bounds() {
933 let cases = vec![
934 Case {
935 minimum: Some(5.5),
936 maximum: Some(6.0),
937 exclusive_minimum: false,
938 exclusive_maximum: false,
939 integer: false,
940 multiple_of: Decimal::try_from(0.5).ok(),
941 ok: true,
942 },
943 Case {
944 minimum: Some(5.5),
945 maximum: Some(6.0),
946 exclusive_minimum: true,
947 exclusive_maximum: false,
948 integer: false,
949 multiple_of: Decimal::try_from(0.5).ok(),
950 ok: true,
951 },
952 Case {
953 minimum: Some(5.5),
954 maximum: Some(6.0),
955 exclusive_minimum: false,
956 exclusive_maximum: true,
957 integer: false,
958 multiple_of: Decimal::try_from(0.5).ok(),
959 ok: true,
960 },
961 Case {
962 minimum: Some(5.5),
963 maximum: Some(6.0),
964 exclusive_minimum: true,
965 exclusive_maximum: true,
966 integer: false,
967 multiple_of: Decimal::try_from(0.5).ok(),
968 ok: false,
969 },
970 Case {
971 minimum: Some(5.5),
972 maximum: Some(6.0),
973 exclusive_minimum: false,
974 exclusive_maximum: false,
975 integer: true,
976 multiple_of: Decimal::try_from(0.5).ok(),
977 ok: true,
978 },
979 Case {
980 minimum: Some(5.5),
981 maximum: Some(6.0),
982 exclusive_minimum: true,
983 exclusive_maximum: false,
984 integer: true,
985 multiple_of: Decimal::try_from(0.5).ok(),
986 ok: true,
987 },
988 Case {
989 minimum: Some(5.5),
990 maximum: Some(6.0),
991 exclusive_minimum: false,
992 exclusive_maximum: true,
993 integer: true,
994 multiple_of: Decimal::try_from(0.5).ok(),
995 ok: false,
996 },
997 Case {
998 minimum: Some(5.5),
999 maximum: Some(6.0),
1000 exclusive_minimum: true,
1001 exclusive_maximum: true,
1002 integer: true,
1003 multiple_of: Decimal::try_from(0.5).ok(),
1004 ok: false,
1005 },
1006 Case {
1008 minimum: Some(0.0),
1009 maximum: Some(10.0),
1010 exclusive_minimum: false,
1011 exclusive_maximum: false,
1012 integer: true,
1013 multiple_of: Decimal::try_from(2.0).ok(),
1014 ok: true,
1015 },
1016 Case {
1017 minimum: Some(0.0),
1018 maximum: Some(10.0),
1019 exclusive_minimum: true,
1020 exclusive_maximum: false,
1021 integer: true,
1022 multiple_of: Decimal::try_from(2.0).ok(),
1023 ok: true,
1024 },
1025 Case {
1026 minimum: Some(0.0),
1027 maximum: Some(10.0),
1028 exclusive_minimum: true,
1029 exclusive_maximum: true,
1030 integer: true,
1031 multiple_of: Decimal::try_from(2.0).ok(),
1032 ok: true,
1033 },
1034 Case {
1036 minimum: Some(-10.0),
1037 maximum: Some(-5.0),
1038 exclusive_minimum: false,
1039 exclusive_maximum: false,
1040 integer: true,
1041 multiple_of: Decimal::try_from(1.0).ok(),
1042 ok: true,
1043 },
1044 Case {
1045 minimum: Some(-10.0),
1046 maximum: Some(-5.0),
1047 exclusive_minimum: false,
1048 exclusive_maximum: false,
1049 integer: false,
1050 multiple_of: Decimal::try_from(0.1).ok(),
1051 ok: true,
1052 },
1053 Case {
1055 minimum: Some(1.0),
1056 maximum: Some(1.01),
1057 exclusive_minimum: false,
1058 exclusive_maximum: false,
1059 integer: false,
1060 multiple_of: Decimal::try_from(0.005).ok(),
1061 ok: true,
1062 },
1063 Case {
1064 minimum: Some(1.0),
1065 maximum: Some(1.01),
1066 exclusive_minimum: true,
1067 exclusive_maximum: true,
1068 integer: false,
1069 multiple_of: Decimal::try_from(0.005).ok(),
1070 ok: true,
1071 },
1072 Case {
1073 minimum: Some(1.0),
1074 maximum: Some(1.01),
1075 exclusive_minimum: false,
1076 exclusive_maximum: false,
1077 integer: false,
1078 multiple_of: Decimal::try_from(0.01).ok(),
1079 ok: true,
1080 },
1081 Case {
1082 minimum: Some(1.0),
1083 maximum: Some(1.01),
1084 exclusive_minimum: true,
1085 exclusive_maximum: true,
1086 integer: false,
1087 multiple_of: Decimal::try_from(0.01).ok(),
1088 ok: false,
1089 },
1090 Case {
1092 minimum: Some(1.0),
1093 maximum: Some(1e9),
1094 exclusive_minimum: false,
1095 exclusive_maximum: false,
1096 integer: true,
1097 multiple_of: Decimal::try_from(100000.0).ok(),
1098 ok: true,
1099 },
1100 Case {
1102 minimum: Some(f64::NEG_INFINITY),
1103 maximum: Some(10.0),
1104 exclusive_minimum: false,
1105 exclusive_maximum: false,
1106 integer: true,
1107 multiple_of: Decimal::try_from(1.0).ok(),
1108 ok: true,
1109 },
1110 Case {
1111 minimum: Some(0.0),
1112 maximum: Some(f64::INFINITY),
1113 exclusive_minimum: false,
1114 exclusive_maximum: false,
1115 integer: true,
1116 multiple_of: Decimal::try_from(1.0).ok(),
1117 ok: true,
1118 },
1119 Case {
1121 minimum: Some(1.0),
1122 maximum: Some(10.0),
1123 exclusive_minimum: false,
1124 exclusive_maximum: false,
1125 integer: false,
1126 multiple_of: Decimal::try_from(1.0).ok(),
1127 ok: true,
1128 },
1129 Case {
1130 minimum: Some(1.0),
1131 maximum: Some(10.0),
1132 exclusive_minimum: false,
1133 exclusive_maximum: false,
1134 integer: false,
1135 multiple_of: Decimal::try_from(0.3).ok(),
1136 ok: true,
1137 },
1138 Case {
1139 minimum: Some(1.0),
1140 maximum: Some(10.0),
1141 exclusive_minimum: false,
1142 exclusive_maximum: false,
1143 integer: false,
1144 multiple_of: Decimal::try_from(0.0).ok(),
1145 ok: false,
1146 },
1147 Case {
1148 minimum: Some(0.0),
1149 maximum: Some(10.0),
1150 exclusive_minimum: true,
1151 exclusive_maximum: false,
1152 integer: false,
1153 multiple_of: Decimal::try_from(0.0).ok(),
1154 ok: false,
1155 },
1156 Case {
1157 minimum: Some(0.0),
1158 maximum: Some(10.0),
1159 exclusive_minimum: false,
1160 exclusive_maximum: false,
1161 integer: false,
1162 multiple_of: Decimal::try_from(0.0).ok(),
1163 ok: true,
1164 },
1165 ];
1166 for case in cases {
1167 let result = check_number_bounds(&case.to_number_schema());
1168 assert_eq!(
1169 result.is_ok(),
1170 case.ok,
1171 "Failed for case {case:?} with result {result:?}"
1172 );
1173 }
1174 }
1175}