vector_config/
num.rs

1use std::num::{
2    NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8,
3    NonZeroUsize,
4};
5
6use num_traits::{Bounded, One, ToPrimitive, Zero};
7use serde::Serialize;
8use serde_json::Number;
9use vector_config_common::num::{NUMERIC_ENFORCED_LOWER_BOUND, NUMERIC_ENFORCED_UPPER_BOUND};
10
11use crate::schema::InstanceType;
12
13/// The class of a numeric type.
14#[derive(Clone, Copy, Serialize)]
15pub enum NumberClass {
16    /// A signed integer.
17    #[serde(rename = "int")]
18    Signed,
19
20    /// An unsigned integer.
21    #[serde(rename = "uint")]
22    Unsigned,
23
24    /// A floating-point number.
25    #[serde(rename = "float")]
26    FloatingPoint,
27}
28
29impl NumberClass {
30    /// Gets the equivalent instance type of this number class.
31    ///
32    /// The "instance type" is the JSON Schema term for value type i.e. string, number, integer,
33    /// array, and so on.
34    pub fn as_instance_type(self) -> InstanceType {
35        match self {
36            Self::Signed | Self::Unsigned => InstanceType::Integer,
37            Self::FloatingPoint => InstanceType::Number,
38        }
39    }
40}
41
42/// A numeric type that can be represented correctly in a JSON Schema document.
43pub trait ConfigurableNumber {
44    /// The integral numeric type.
45    ///
46    /// We parameterize the "integral" numeric type in this way to allow generating the schema for wrapper types such as
47    /// `NonZeroU64`, where the overall type must be represented as `NonZeroU64` but the integral numeric type that
48    /// we're constraining against is `u64`.
49    type Numeric: Bounded + ToPrimitive + Zero + One;
50
51    /// Gets the class of this numeric type.
52    fn class() -> NumberClass;
53
54    /// Whether or not this numeric type disallows nonzero values.
55    fn is_nonzero() -> bool {
56        false
57    }
58
59    /// Whether or not a generated schema for this numeric type must explicitly disallow zero values.
60    ///
61    /// In some cases, such as `NonZero*` types from `std::num`, a numeric type may not support zero values for reasons
62    /// of correctness and/or optimization. In some cases, we can simply adjust the normal minimum/maximum bounds in the
63    /// schema to encode this. In other cases, such as signed versions like `NonZeroI64`, zero is a discrete value
64    /// within the minimum and maximum bounds and must be excluded explicitly.
65    fn requires_nonzero_exclusion() -> bool {
66        false
67    }
68
69    /// Gets the JSON encoded version of the zero value for the integral numeric type.
70    fn get_encoded_zero_value() -> Number {
71        let zero_num_unsigned = Self::Numeric::zero().to_u64().map(Into::into);
72        let zero_num_floating = Self::Numeric::zero().to_f64().and_then(Number::from_f64);
73        zero_num_unsigned
74            .or(zero_num_floating)
75            .expect("No usable integer type should be unrepresentable by both `u64` and `f64`.")
76    }
77
78    /// Gets the minimum bound for this numeric type, limited by the representable range in JSON Schema.
79    fn get_enforced_min_bound() -> f64 {
80        let mechanical_minimum = match (Self::is_nonzero(), Self::requires_nonzero_exclusion()) {
81            // If the number is not a nonzero type, or it is a nonzero type, but needs an exclusion, we simply return
82            // its true mechanical minimum bound. For nonzero types, this is because we can only enforce the nonzero
83            // constraint through a negative schema bound, not through its normal minimum/maximum bounds validation.
84            (false, _) | (true, true) => Self::Numeric::min_value(),
85            // If the number is a nonzero type, but does not need an exclusion, its minimum bound is always 1.
86            (true, false) => Self::Numeric::one(),
87        };
88
89        let enforced_minimum = NUMERIC_ENFORCED_LOWER_BOUND;
90        let mechanical_minimum = mechanical_minimum
91            .to_f64()
92            .expect("`Configurable` does not support numbers larger than an `f64` representation");
93
94        if mechanical_minimum < enforced_minimum {
95            enforced_minimum
96        } else {
97            mechanical_minimum
98        }
99    }
100
101    /// Gets the maximum bound for this numeric type, limited by the representable range in JSON Schema.
102    fn get_enforced_max_bound() -> f64 {
103        let enforced_maximum = NUMERIC_ENFORCED_UPPER_BOUND;
104        let mechanical_maximum = Self::Numeric::max_value()
105            .to_f64()
106            .expect("`Configurable` does not support numbers larger than an `f64` representation");
107
108        if mechanical_maximum > enforced_maximum {
109            enforced_maximum
110        } else {
111            mechanical_maximum
112        }
113    }
114}
115
116macro_rules! impl_configurable_number {
117	([$class:expr] $($ty:ty),+) => {
118		$(
119			impl ConfigurableNumber for $ty {
120				type Numeric = $ty;
121
122                fn class() -> NumberClass {
123                    $class
124                }
125			}
126		)+
127	};
128}
129
130macro_rules! impl_configurable_number_nonzero {
131	([$class:expr] $($aty:ty => $ity:ty),+) => {
132		$(
133			impl ConfigurableNumber for $aty {
134				type Numeric = $ity;
135
136				fn is_nonzero() -> bool {
137					true
138				}
139
140                fn class() -> NumberClass {
141                    $class
142                }
143			}
144		)+
145	};
146
147	(with_exclusion, [$class:expr] $($aty:ty => $ity:ty),+) => {
148		$(
149			impl ConfigurableNumber for $aty {
150				type Numeric = $ity;
151
152				fn is_nonzero() -> bool {
153					true
154				}
155
156				fn requires_nonzero_exclusion() -> bool {
157					true
158				}
159
160                fn class() -> NumberClass {
161                    $class
162                }
163			}
164		)+
165	};
166}
167
168impl_configurable_number!([NumberClass::Unsigned] u8, u16, u32, u64, usize);
169impl_configurable_number!([NumberClass::Signed] i8, i16, i32, i64, isize);
170impl_configurable_number!([NumberClass::FloatingPoint] f32, f64);
171impl_configurable_number_nonzero!([NumberClass::Unsigned] NonZeroU8 => u8, NonZeroU16 => u16, NonZeroU32 => u32, NonZeroU64 => u64, NonZeroUsize => usize);
172impl_configurable_number_nonzero!(with_exclusion, [NumberClass::Signed] NonZeroI8 => i8, NonZeroI16 => i16, NonZeroI32 => i32, NonZeroI64 => i64);