Skip to main content

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.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 interval is not unbounded in at least one direction, check if the range contains a multiple of multipleOf
551        if let (Some(min), Some(max)) = (minimum, maximum) {
552            let step = d.to_f64();
553            // Adjust the range depending on whether it's exclusive or not
554            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            // Zero bounds
1007            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            // Negative ranges
1035            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            // Tiny ranges
1054            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            // Large ranges
1091            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            // Non-finite values
1101            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            // `multiple_of` edge cases
1120            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}