llguidance/json/
numeric.rs

1use anyhow::{anyhow, Result};
2use regex_syntax::escape;
3
4use super::schema::NumberSchema;
5
6/// coef * 10^-exp
7#[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        // reduce to simplest form
20        let mut coef = coef;
21        let mut exp = exp;
22        while exp > 0 && coef % 10 == 0 {
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!("{}[{}-9]", lpref, lx));
166                }
167
168                if rx != "9" {
169                    right_rec -= 1;
170                    parts.push(format!("{}[0-{}]", rpref, 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!("{}[0-9]", inner));
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!("[{}-9][0-9]*", x))
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!("({}(\\.[0-9]+)?)", inner));
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!("{}(\\.0+)?", right_rec));
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!(
515                "minimum ({}) is greater than maximum ({})",
516                min, max
517            ));
518        }
519        if min == max && (exclusive_minimum || exclusive_maximum) {
520            let minimum_repr = if exclusive_minimum {
521                "exclusiveMinimum"
522            } else {
523                "minimum"
524            };
525            let maximum_repr = if exclusive_maximum {
526                "exclusiveMaximum"
527            } else {
528                "maximum"
529            };
530            return Err(format!(
531                "{} ({}) is equal to {} ({})",
532                minimum_repr, min, maximum_repr, max
533            ));
534        }
535    }
536    if let Some(d) = num.multiple_of.as_ref() {
537        if d.coef == 0 {
538            if let Some(min) = minimum {
539                if min > 0.0 || (exclusive_minimum && min >= 0.0) {
540                    return Err(format!(
541                        "minimum ({}) is greater than 0, but multipleOf is 0",
542                        min
543                    ));
544                }
545            };
546            if let Some(max) = maximum {
547                if max < 0.0 || (exclusive_maximum && max <= 0.0) {
548                    return Err(format!(
549                        "maximum ({}) is less than 0, but multipleOf is 0",
550                        max
551                    ));
552                }
553            };
554            return Ok(());
555        }
556        // If interval is not unbounded in at least one direction, check if the range contains a multiple of multipleOf
557        if let (Some(min), Some(max)) = (minimum, maximum) {
558            let step = d.to_f64();
559            // Adjust the range depending on whether it's exclusive or not
560            let min = {
561                let first_num_ge_min = (min / step).ceil() * step;
562                let adjusted_min = if exclusive_minimum && first_num_ge_min == min {
563                    first_num_ge_min + step
564                } else {
565                    first_num_ge_min
566                };
567                if num.integer {
568                    adjusted_min.ceil()
569                } else {
570                    adjusted_min
571                }
572            };
573            let max = {
574                let first_num_le_max = (max / step).floor() * step;
575                let adjusted_max = if exclusive_maximum && first_num_le_max == max {
576                    first_num_le_max - step
577                } else {
578                    first_num_le_max
579                };
580                if num.integer {
581                    adjusted_max.floor()
582                } else {
583                    adjusted_max
584                }
585            };
586            if min > max {
587                return Err(format!(
588                    "range {}{}, {}{} does not contain a multiple of {}",
589                    if exclusive_minimum { "(" } else { "[" },
590                    min,
591                    max,
592                    if exclusive_maximum { ")" } else { "]" },
593                    step
594                ));
595            }
596        }
597    }
598    Ok(())
599}
600
601#[cfg(test)]
602mod test_ranges {
603    use super::{rx_float_range, rx_int_range};
604    use regex::Regex;
605
606    fn do_test_int_range(rx: &str, left: Option<i64>, right: Option<i64>) {
607        let re = Regex::new(&format!("^{}$", rx)).unwrap();
608        for n in (left.unwrap_or(0) - 1000)..=(right.unwrap_or(0) + 1000) {
609            let matches = re.is_match(&n.to_string());
610            let expected =
611                (left.is_none() || left.unwrap() <= n) && (right.is_none() || n <= right.unwrap());
612            if expected != matches {
613                let range_str = match (left, right) {
614                    (Some(l), Some(r)) => format!("[{}, {}]", l, r),
615                    (Some(l), None) => format!("[{}, ∞)", l),
616                    (None, Some(r)) => format!("(-∞, {}]", r),
617                    (None, None) => "(-∞, ∞)".to_string(),
618                };
619                if matches {
620                    panic!("{} not in range {} but matches {:?}", n, range_str, rx);
621                } else {
622                    panic!("{} in range {} but does not match {:?}", n, range_str, rx);
623                }
624            }
625        }
626    }
627
628    #[test]
629    fn test_int_range() {
630        let cases = vec![
631            (Some(0), Some(9)),
632            (Some(1), Some(7)),
633            (Some(0), Some(99)),
634            (Some(13), Some(170)),
635            (Some(13), Some(17)),
636            (Some(13), Some(27)),
637            (Some(13), Some(57)),
638            (Some(72), Some(91)),
639            (Some(723), Some(915)),
640            (Some(23), Some(915)),
641            (Some(-1), Some(915)),
642            (Some(-9), Some(9)),
643            (Some(-3), Some(3)),
644            (Some(-3), Some(0)),
645            (Some(-72), Some(13)),
646            (None, Some(0)),
647            (None, Some(7)),
648            (None, Some(23)),
649            (None, Some(725)),
650            (None, Some(-1)),
651            (None, Some(-17)),
652            (None, Some(-283)),
653            (Some(0), None),
654            (Some(2), None),
655            (Some(33), None),
656            (Some(234), None),
657            (Some(-1), None),
658            (Some(-87), None),
659            (Some(-329), None),
660            (None, None),
661            (Some(-13), Some(-13)),
662            (Some(-1), Some(-1)),
663            (Some(0), Some(0)),
664            (Some(1), Some(1)),
665            (Some(13), Some(13)),
666        ];
667
668        for (left, right) in cases {
669            let rx = rx_int_range(left, right).unwrap();
670            do_test_int_range(&rx, left, right);
671        }
672    }
673
674    fn do_test_float_range(
675        rx: &str,
676        left: Option<f64>,
677        right: Option<f64>,
678        left_inclusive: bool,
679        right_inclusive: bool,
680    ) {
681        let re = Regex::new(&format!("^{}$", rx)).unwrap();
682        let left_int = left.map(|x| {
683            let left_int = x.ceil() as i64;
684            if !left_inclusive && x == left_int as f64 {
685                left_int + 1
686            } else {
687                left_int
688            }
689        });
690        let right_int = right.map(|x| {
691            let right_int = x.floor() as i64;
692            if !right_inclusive && x == right_int as f64 {
693                right_int - 1
694            } else {
695                right_int
696            }
697        });
698        do_test_int_range(rx, left_int, right_int);
699
700        let eps1 = 0.0000001;
701        let eps2 = 0.01;
702        let test_cases = vec![
703            left.unwrap_or(-1000.0),
704            right.unwrap_or(1000.0),
705            0.0,
706            left_int.unwrap_or(-1000) as f64,
707            right_int.unwrap_or(1000) as f64,
708        ];
709        for x in test_cases {
710            for offset in [0.0, -eps1, eps1, -eps2, eps2, 1.0, -1.0].iter() {
711                let n = x + offset;
712                let matches = re.is_match(&n.to_string());
713                let left_cond =
714                    left.is_none() || left.unwrap() < n || (left.unwrap() == n && left_inclusive);
715                let right_cond = right.is_none()
716                    || right.unwrap() > n
717                    || (right.unwrap() == n && right_inclusive);
718                let expected = left_cond && right_cond;
719                if expected != matches {
720                    let lket = if left_inclusive { "[" } else { "(" };
721                    let rket = if right_inclusive { "]" } else { ")" };
722                    let range_str = match (left, right) {
723                        (Some(l), Some(r)) => format!("{}{}, {}{}", lket, l, r, rket),
724                        (Some(l), None) => format!("{}{}, ∞)", lket, l),
725                        (None, Some(r)) => format!("(-∞, {}{}", r, rket),
726                        (None, None) => "(-∞, ∞)".to_string(),
727                    };
728                    if matches {
729                        panic!("{} not in range {} but matches {:?}", n, range_str, rx);
730                    } else {
731                        panic!("{} in range {} but does not match {:?}", n, range_str, rx);
732                    }
733                }
734            }
735        }
736    }
737
738    #[test]
739    fn test_float_range() {
740        let cases = vec![
741            (Some(0.0), Some(10.0)),
742            (Some(-10.0), Some(0.0)),
743            (Some(0.5), Some(0.72)),
744            (Some(0.5), Some(1.72)),
745            (Some(0.5), Some(1.32)),
746            (Some(0.45), Some(0.5)),
747            (Some(0.3245), Some(0.325)),
748            (Some(0.443245), Some(0.44325)),
749            (Some(1.0), Some(2.34)),
750            (Some(1.33), Some(2.0)),
751            (Some(1.0), Some(10.34)),
752            (Some(1.33), Some(10.0)),
753            (Some(-1.33), Some(10.0)),
754            (Some(-17.23), Some(-1.33)),
755            (Some(-1.23), Some(-1.221)),
756            (Some(-10.2), Some(45293.9)),
757            (None, Some(0.0)),
758            (None, Some(1.0)),
759            (None, Some(1.5)),
760            (None, Some(1.55)),
761            (None, Some(-17.23)),
762            (None, Some(-1.33)),
763            (None, Some(-1.23)),
764            (None, Some(103.74)),
765            (None, Some(100.0)),
766            (Some(0.0), None),
767            (Some(1.0), None),
768            (Some(1.5), None),
769            (Some(1.55), None),
770            (Some(-17.23), None),
771            (Some(-1.33), None),
772            (Some(-1.23), None),
773            (Some(103.74), None),
774            (Some(100.0), None),
775            (None, None),
776            (Some(-103.4), Some(-103.4)),
777            (Some(-27.0), Some(-27.0)),
778            (Some(-1.5), Some(-1.5)),
779            (Some(-1.0), Some(-1.0)),
780            (Some(0.0), Some(0.0)),
781            (Some(1.0), Some(1.0)),
782            (Some(1.5), Some(1.5)),
783            (Some(27.0), Some(27.0)),
784            (Some(103.4), Some(103.4)),
785        ];
786
787        for (left, right) in cases {
788            for left_inclusive in [true, false].iter() {
789                for right_inclusive in [true, false].iter() {
790                    match (left, right) {
791                        (Some(left), Some(right))
792                            if left == right && !(*left_inclusive && *right_inclusive) =>
793                        {
794                            assert!(rx_float_range(
795                                Some(left),
796                                Some(right),
797                                *left_inclusive,
798                                *right_inclusive
799                            )
800                            .is_err());
801                        }
802                        _ => {
803                            let rx = rx_float_range(left, right, *left_inclusive, *right_inclusive)
804                                .unwrap();
805                            do_test_float_range(
806                                &rx,
807                                left,
808                                right,
809                                *left_inclusive,
810                                *right_inclusive,
811                            );
812                        }
813                    }
814                }
815            }
816        }
817    }
818}
819
820#[cfg(test)]
821mod test_decimal {
822    use super::Decimal;
823
824    #[test]
825    fn test_from_f64() {
826        let cases = vec![
827            (0.0, Decimal { coef: 0, exp: 0 }),
828            (1.0, Decimal { coef: 1, exp: 0 }),
829            (10.0, Decimal { coef: 10, exp: 0 }),
830            (100.0, Decimal { coef: 100, exp: 0 }),
831            (0.1, Decimal { coef: 1, exp: 1 }),
832            (0.01, Decimal { coef: 1, exp: 2 }),
833            (1.1, Decimal { coef: 11, exp: 1 }),
834            (1.01, Decimal { coef: 101, exp: 2 }),
835            (10.1, Decimal { coef: 101, exp: 1 }),
836            (10.01, Decimal { coef: 1001, exp: 2 }),
837            (100.1, Decimal { coef: 1001, exp: 1 }),
838            (
839                100.01,
840                Decimal {
841                    coef: 10001,
842                    exp: 2,
843                },
844            ),
845        ];
846        for (f, d) in cases {
847            assert_eq!(Decimal::try_from(f).unwrap(), d);
848        }
849    }
850
851    #[test]
852    fn test_simplified() {
853        let cases = vec![
854            (Decimal::new(10, 1), Decimal { coef: 1, exp: 0 }),
855            (Decimal::new(100, 2), Decimal { coef: 1, exp: 0 }),
856            (Decimal::new(100, 1), Decimal { coef: 10, exp: 0 }),
857            (Decimal::new(1000, 3), Decimal { coef: 1, exp: 0 }),
858            (Decimal::new(1000, 2), Decimal { coef: 10, exp: 0 }),
859            (Decimal::new(1000, 1), Decimal { coef: 100, exp: 0 }),
860            (Decimal::new(10000, 4), Decimal { coef: 1, exp: 0 }),
861            (Decimal::new(10000, 3), Decimal { coef: 10, exp: 0 }),
862            (Decimal::new(10000, 2), Decimal { coef: 100, exp: 0 }),
863            (Decimal::new(10000, 1), Decimal { coef: 1000, exp: 0 }),
864        ];
865        for (d, s) in cases {
866            assert_eq!(d, s);
867        }
868    }
869
870    #[test]
871    fn test_lcm() {
872        let cases = vec![
873            (2.0, 3.0, 6.0),
874            (0.5, 1.5, 1.5),
875            (0.5, 0.5, 0.5),
876            (0.5, 0.25, 0.5),
877            (0.3, 0.2, 0.6),
878            (0.3, 0.4, 1.2),
879            (0.05, 0.36, 1.8),
880            (0.3, 14.0, 42.0),
881        ];
882        for (a, b, c) in cases {
883            let a = Decimal::try_from(a).unwrap();
884            let b = Decimal::try_from(b).unwrap();
885            let c = Decimal::try_from(c).unwrap();
886            assert_eq!(a.lcm(&b), c);
887        }
888    }
889}
890
891#[cfg(test)]
892mod test_number_bounds {
893    use crate::json::schema::NumberSchema;
894
895    use super::{check_number_bounds, Decimal};
896
897    #[derive(Debug)]
898    struct Case {
899        minimum: Option<f64>,
900        maximum: Option<f64>,
901        exclusive_minimum: bool,
902        exclusive_maximum: bool,
903        integer: bool,
904        multiple_of: Option<Decimal>,
905        ok: bool,
906    }
907
908    impl Case {
909        fn to_number_schema(&self) -> NumberSchema {
910            NumberSchema {
911                minimum: if self.exclusive_minimum {
912                    None
913                } else {
914                    self.minimum
915                },
916                maximum: if self.exclusive_maximum {
917                    None
918                } else {
919                    self.maximum
920                },
921                exclusive_minimum: if self.exclusive_minimum {
922                    self.minimum
923                } else {
924                    None
925                },
926                exclusive_maximum: if self.exclusive_maximum {
927                    self.maximum
928                } else {
929                    None
930                },
931                integer: self.integer,
932                multiple_of: self.multiple_of.clone(),
933            }
934        }
935    }
936
937    #[test]
938    fn test_check_number_bounds() {
939        let cases = vec![
940            Case {
941                minimum: Some(5.5),
942                maximum: Some(6.0),
943                exclusive_minimum: false,
944                exclusive_maximum: false,
945                integer: false,
946                multiple_of: Decimal::try_from(0.5).ok(),
947                ok: true,
948            },
949            Case {
950                minimum: Some(5.5),
951                maximum: Some(6.0),
952                exclusive_minimum: true,
953                exclusive_maximum: false,
954                integer: false,
955                multiple_of: Decimal::try_from(0.5).ok(),
956                ok: true,
957            },
958            Case {
959                minimum: Some(5.5),
960                maximum: Some(6.0),
961                exclusive_minimum: false,
962                exclusive_maximum: true,
963                integer: false,
964                multiple_of: Decimal::try_from(0.5).ok(),
965                ok: true,
966            },
967            Case {
968                minimum: Some(5.5),
969                maximum: Some(6.0),
970                exclusive_minimum: true,
971                exclusive_maximum: true,
972                integer: false,
973                multiple_of: Decimal::try_from(0.5).ok(),
974                ok: false,
975            },
976            Case {
977                minimum: Some(5.5),
978                maximum: Some(6.0),
979                exclusive_minimum: false,
980                exclusive_maximum: false,
981                integer: true,
982                multiple_of: Decimal::try_from(0.5).ok(),
983                ok: true,
984            },
985            Case {
986                minimum: Some(5.5),
987                maximum: Some(6.0),
988                exclusive_minimum: true,
989                exclusive_maximum: false,
990                integer: true,
991                multiple_of: Decimal::try_from(0.5).ok(),
992                ok: true,
993            },
994            Case {
995                minimum: Some(5.5),
996                maximum: Some(6.0),
997                exclusive_minimum: false,
998                exclusive_maximum: true,
999                integer: true,
1000                multiple_of: Decimal::try_from(0.5).ok(),
1001                ok: false,
1002            },
1003            Case {
1004                minimum: Some(5.5),
1005                maximum: Some(6.0),
1006                exclusive_minimum: true,
1007                exclusive_maximum: true,
1008                integer: true,
1009                multiple_of: Decimal::try_from(0.5).ok(),
1010                ok: false,
1011            },
1012            // Zero bounds
1013            Case {
1014                minimum: Some(0.0),
1015                maximum: Some(10.0),
1016                exclusive_minimum: false,
1017                exclusive_maximum: false,
1018                integer: true,
1019                multiple_of: Decimal::try_from(2.0).ok(),
1020                ok: true,
1021            },
1022            Case {
1023                minimum: Some(0.0),
1024                maximum: Some(10.0),
1025                exclusive_minimum: true,
1026                exclusive_maximum: false,
1027                integer: true,
1028                multiple_of: Decimal::try_from(2.0).ok(),
1029                ok: true,
1030            },
1031            Case {
1032                minimum: Some(0.0),
1033                maximum: Some(10.0),
1034                exclusive_minimum: true,
1035                exclusive_maximum: true,
1036                integer: true,
1037                multiple_of: Decimal::try_from(2.0).ok(),
1038                ok: true,
1039            },
1040            // Negative ranges
1041            Case {
1042                minimum: Some(-10.0),
1043                maximum: Some(-5.0),
1044                exclusive_minimum: false,
1045                exclusive_maximum: false,
1046                integer: true,
1047                multiple_of: Decimal::try_from(1.0).ok(),
1048                ok: true,
1049            },
1050            Case {
1051                minimum: Some(-10.0),
1052                maximum: Some(-5.0),
1053                exclusive_minimum: false,
1054                exclusive_maximum: false,
1055                integer: false,
1056                multiple_of: Decimal::try_from(0.1).ok(),
1057                ok: true,
1058            },
1059            // Tiny ranges
1060            Case {
1061                minimum: Some(1.0),
1062                maximum: Some(1.01),
1063                exclusive_minimum: false,
1064                exclusive_maximum: false,
1065                integer: false,
1066                multiple_of: Decimal::try_from(0.005).ok(),
1067                ok: true,
1068            },
1069            Case {
1070                minimum: Some(1.0),
1071                maximum: Some(1.01),
1072                exclusive_minimum: true,
1073                exclusive_maximum: true,
1074                integer: false,
1075                multiple_of: Decimal::try_from(0.005).ok(),
1076                ok: true,
1077            },
1078            Case {
1079                minimum: Some(1.0),
1080                maximum: Some(1.01),
1081                exclusive_minimum: false,
1082                exclusive_maximum: false,
1083                integer: false,
1084                multiple_of: Decimal::try_from(0.01).ok(),
1085                ok: true,
1086            },
1087            Case {
1088                minimum: Some(1.0),
1089                maximum: Some(1.01),
1090                exclusive_minimum: true,
1091                exclusive_maximum: true,
1092                integer: false,
1093                multiple_of: Decimal::try_from(0.01).ok(),
1094                ok: false,
1095            },
1096            // Large ranges
1097            Case {
1098                minimum: Some(1.0),
1099                maximum: Some(1e9),
1100                exclusive_minimum: false,
1101                exclusive_maximum: false,
1102                integer: true,
1103                multiple_of: Decimal::try_from(100000.0).ok(),
1104                ok: true,
1105            },
1106            // Non-finite values
1107            Case {
1108                minimum: Some(f64::NEG_INFINITY),
1109                maximum: Some(10.0),
1110                exclusive_minimum: false,
1111                exclusive_maximum: false,
1112                integer: true,
1113                multiple_of: Decimal::try_from(1.0).ok(),
1114                ok: true,
1115            },
1116            Case {
1117                minimum: Some(0.0),
1118                maximum: Some(f64::INFINITY),
1119                exclusive_minimum: false,
1120                exclusive_maximum: false,
1121                integer: true,
1122                multiple_of: Decimal::try_from(1.0).ok(),
1123                ok: true,
1124            },
1125            // `multiple_of` edge cases
1126            Case {
1127                minimum: Some(1.0),
1128                maximum: Some(10.0),
1129                exclusive_minimum: false,
1130                exclusive_maximum: false,
1131                integer: false,
1132                multiple_of: Decimal::try_from(1.0).ok(),
1133                ok: true,
1134            },
1135            Case {
1136                minimum: Some(1.0),
1137                maximum: Some(10.0),
1138                exclusive_minimum: false,
1139                exclusive_maximum: false,
1140                integer: false,
1141                multiple_of: Decimal::try_from(0.3).ok(),
1142                ok: true,
1143            },
1144            Case {
1145                minimum: Some(1.0),
1146                maximum: Some(10.0),
1147                exclusive_minimum: false,
1148                exclusive_maximum: false,
1149                integer: false,
1150                multiple_of: Decimal::try_from(0.0).ok(),
1151                ok: false,
1152            },
1153            Case {
1154                minimum: Some(0.0),
1155                maximum: Some(10.0),
1156                exclusive_minimum: true,
1157                exclusive_maximum: false,
1158                integer: false,
1159                multiple_of: Decimal::try_from(0.0).ok(),
1160                ok: false,
1161            },
1162            Case {
1163                minimum: Some(0.0),
1164                maximum: Some(10.0),
1165                exclusive_minimum: false,
1166                exclusive_maximum: false,
1167                integer: false,
1168                multiple_of: Decimal::try_from(0.0).ok(),
1169                ok: true,
1170            },
1171        ];
1172        for case in cases {
1173            let result = check_number_bounds(&case.to_number_schema());
1174            assert_eq!(
1175                result.is_ok(),
1176                case.ok,
1177                "Failed for case {:?} with result {:?}",
1178                case,
1179                result
1180            );
1181        }
1182    }
1183}