Skip to main content

substrate_data/schema/reader/
numbers.rs

1//! [`Numbers`]: the numbers a value holds, checked against a field's range.
2
3use super::Reader;
4use crate::diagnostic::Problem;
5use crate::model::number::whole_to_i64;
6use crate::model::value::Seg;
7
8/// The numbers a value holds — what `#[data(range = …)]` checks.
9pub trait Numbers {
10    /// Puts its numbers on `out`.
11    fn numbers(&self, out: &mut Vec<f64>);
12}
13
14macro_rules! numbers {
15    ($($t:ty),*) => {$(
16        impl Numbers for $t {
17            fn numbers(&self, out: &mut Vec<f64>) {
18                out.push(f64::from(*self));
19            }
20        }
21    )*};
22}
23numbers!(i8, i16, i32, u8, u16, u32, f32, f64);
24
25macro_rules! wide_numbers {
26    ($($t:ty),*) => {$(
27        impl Numbers for $t {
28            #[expect(
29                clippy::cast_precision_loss,
30                reason = "a range is checked on the nearest float"
31            )]
32            fn numbers(&self, out: &mut Vec<f64>) {
33                out.push(*self as f64);
34            }
35        }
36    )*};
37}
38wide_numbers!(i64, u64, usize);
39
40impl<T: Numbers> Numbers for Option<T> {
41    fn numbers(&self, out: &mut Vec<f64>) {
42        if let Some(x) = self {
43            x.numbers(out);
44        }
45    }
46}
47
48impl<T: Numbers> Numbers for Vec<T> {
49    fn numbers(&self, out: &mut Vec<f64>) {
50        for x in self {
51            x.numbers(out);
52        }
53    }
54}
55
56impl<T: Numbers, const N: usize> Numbers for [T; N] {
57    fn numbers(&self, out: &mut Vec<f64>) {
58        for x in self {
59            x.numbers(out);
60        }
61    }
62}
63
64impl Reader<'_> {
65    /// `value` (field `key`) checked against `lo..=hi`; `None` (a problem) if it's out.
66    pub fn in_range<T: Numbers>(
67        &mut self,
68        key: &str,
69        value: Option<T>,
70        lo: f64,
71        hi: f64,
72    ) -> Option<T> {
73        let v = value?;
74        let mut ns = Vec::new();
75        v.numbers(&mut ns);
76        match ns.into_iter().find(|n| !(lo..=hi).contains(n)) {
77            None => Some(v),
78            Some(n) => {
79                let show = |x: f64| {
80                    whole_to_i64(x).map_or_else(|| x.to_string(), |whole| whole.to_string())
81                };
82                let bad = show(n);
83                self.at(Seg::Key(key.to_string()), |cx| {
84                    cx.problem_of(
85                        Problem::OutOfRange,
86                        format!("{bad} is out of range ({}..={})", show(lo), show(hi)),
87                    );
88                });
89                None
90            }
91        }
92    }
93}