1use std::fmt::Debug;
13use std::ops::{Bound, RangeBounds, RangeFrom, RangeInclusive};
14use std::time::Duration;
15
16use mz_repr::adt::numeric::Numeric;
17use mz_repr::bytes::ByteSize;
18
19use super::{Value, Var, VarError};
20
21pub static NUMERIC_NON_NEGATIVE: NumericNonNegNonNan = NumericNonNegNonNan;
22
23pub static NON_ZERO_DURATION: NonZeroDuration = NonZeroDuration;
24
25pub static NUMERIC_BOUNDED_0_1_INCLUSIVE: NumericInRange<RangeInclusive<f64>> =
26 NumericInRange(0.0f64..=1.0);
27
28pub static BYTESIZE_AT_LEAST_1MB: ByteSizeInRange<RangeFrom<ByteSize>> =
29 ByteSizeInRange(ByteSize::mb(1)..);
30
31pub static U32_AT_LEAST_1: U32InRange<RangeFrom<u32>> = U32InRange(1..);
32
33#[derive(Debug)]
34pub enum ValueConstraint {
35 ReadOnly,
37 Fixed,
39 Domain(&'static dyn DynDomainConstraint),
41}
42
43impl ValueConstraint {
44 pub fn check_constraint(
45 &self,
46 var: &dyn Var,
47 cur_value: &dyn Value,
48 new_value: &dyn Value,
49 ) -> Result<(), VarError> {
50 match self {
51 ValueConstraint::ReadOnly => return Err(VarError::ReadOnlyParameter(var.name())),
52 ValueConstraint::Fixed => {
53 if cur_value != new_value {
54 return Err(VarError::FixedValueParameter {
55 name: var.name(),
56 value: cur_value.format(),
57 });
58 }
59 }
60 ValueConstraint::Domain(check) => check.check(var, new_value)?,
61 }
62
63 Ok(())
64 }
65}
66
67impl Clone for ValueConstraint {
68 fn clone(&self) -> Self {
69 match self {
70 ValueConstraint::Fixed => ValueConstraint::Fixed,
71 ValueConstraint::ReadOnly => ValueConstraint::ReadOnly,
72 ValueConstraint::Domain(c) => ValueConstraint::Domain(*c),
73 }
74 }
75}
76
77pub trait DynDomainConstraint: Debug + Send + Sync + 'static {
81 fn check(&self, var: &dyn Var, v: &dyn Value) -> Result<(), VarError>;
82 fn describe(&self) -> DomainDescription;
83}
84
85impl<D> DynDomainConstraint for D
86where
87 D: DomainConstraint + Send + Sync + 'static,
88 D::Value: Value,
89{
90 fn check(&self, var: &dyn Var, v: &dyn Value) -> Result<(), VarError> {
91 let val = v
92 .as_any()
93 .downcast_ref::<D::Value>()
94 .expect("type should match");
95 self.check(var, val)
96 }
97
98 fn describe(&self) -> DomainDescription {
99 DomainConstraint::describe(self)
100 }
101}
102
103pub trait DomainConstraint: Debug + Send + Sync + 'static {
104 type Value;
105
106 fn check(&self, var: &dyn Var, v: &Self::Value) -> Result<(), VarError>;
107
108 fn describe(&self) -> DomainDescription;
111}
112
113#[derive(Debug, Clone, PartialEq, Eq)]
115pub enum DomainDescription {
116 NonNegativeNonNan,
118 NonZero,
120 Range {
122 start: Bound<String>,
123 end: Bound<String>,
124 },
125}
126
127impl DomainDescription {
128 fn range<T: Value>(range: &impl RangeBounds<T>) -> Self {
129 DomainDescription::Range {
130 start: range.start_bound().map(Value::format),
131 end: range.end_bound().map(Value::format),
132 }
133 }
134}
135
136#[derive(Debug, Clone, Eq, PartialEq)]
137pub struct NumericNonNegNonNan;
138
139impl DomainConstraint for NumericNonNegNonNan {
140 type Value = Numeric;
141
142 fn describe(&self) -> DomainDescription {
143 DomainDescription::NonNegativeNonNan
144 }
145
146 fn check(&self, var: &dyn Var, n: &Numeric) -> Result<(), VarError> {
147 if n.is_nan() || n.is_negative() {
148 Err(VarError::InvalidParameterValue {
149 name: var.name(),
150 invalid_values: vec![n.to_string()],
151 reason: "only supports non-negative, non-NaN numeric values".to_string(),
152 })
153 } else {
154 Ok(())
155 }
156 }
157}
158
159#[derive(Debug, Clone, Eq, PartialEq)]
160pub struct NonZeroDuration;
161
162impl DomainConstraint for NonZeroDuration {
163 type Value = Duration;
164
165 fn describe(&self) -> DomainDescription {
166 DomainDescription::NonZero
167 }
168
169 fn check(&self, var: &dyn Var, d: &Duration) -> Result<(), VarError> {
170 if d.is_zero() {
171 Err(VarError::InvalidParameterValue {
172 name: var.name(),
173 invalid_values: vec![format!("{:?}", d)],
174 reason: "only supports non-zero durations".to_string(),
175 })
176 } else {
177 Ok(())
178 }
179 }
180}
181
182#[derive(Debug, Clone, Eq, PartialEq)]
183pub struct NumericInRange<R>(pub R);
184
185impl<R> DomainConstraint for NumericInRange<R>
186where
187 R: RangeBounds<f64> + std::fmt::Debug + Send + Sync + 'static,
188{
189 type Value = Numeric;
190
191 fn describe(&self) -> DomainDescription {
192 DomainDescription::range(&self.0)
193 }
194
195 fn check(&self, var: &dyn Var, n: &Numeric) -> Result<(), VarError> {
196 let n: f64 = (*n)
197 .try_into()
198 .map_err(|_e| VarError::InvalidParameterValue {
199 name: var.name(),
200 invalid_values: vec![n.to_string()],
201 reason: format!("only supports values in range {:?}", self.0),
213 })?;
214 if !self.0.contains(&n) {
215 Err(VarError::InvalidParameterValue {
216 name: var.name(),
217 invalid_values: vec![n.to_string()],
218 reason: format!("only supports values in range {:?}", self.0),
219 })
220 } else {
221 Ok(())
222 }
223 }
224}
225
226#[derive(Debug, Clone, Eq, PartialEq)]
227pub struct ByteSizeInRange<R>(pub R);
228
229impl<R> DomainConstraint for ByteSizeInRange<R>
230where
231 R: RangeBounds<ByteSize> + std::fmt::Debug + Send + Sync + 'static,
232{
233 type Value = ByteSize;
234
235 fn describe(&self) -> DomainDescription {
236 DomainDescription::range(&self.0)
237 }
238
239 fn check(&self, var: &dyn Var, size: &ByteSize) -> Result<(), VarError> {
240 if self.0.contains(size) {
241 Ok(())
242 } else {
243 Err(VarError::InvalidParameterValue {
244 name: var.name(),
245 invalid_values: vec![size.to_string()],
246 reason: format!("only supports values in range {:?}", self.0),
247 })
248 }
249 }
250}
251
252#[derive(Debug, Clone, Eq, PartialEq)]
253pub struct U32InRange<R>(pub R);
254
255impl<R> DomainConstraint for U32InRange<R>
256where
257 R: RangeBounds<u32> + std::fmt::Debug + Send + Sync + 'static,
258{
259 type Value = u32;
260
261 fn describe(&self) -> DomainDescription {
262 DomainDescription::range(&self.0)
263 }
264
265 fn check(&self, var: &dyn Var, n: &u32) -> Result<(), VarError> {
266 if self.0.contains(n) {
267 Ok(())
268 } else {
269 Err(VarError::InvalidParameterValue {
270 name: var.name(),
271 invalid_values: vec![n.to_string()],
272 reason: format!("only supports values in range {:?}", self.0),
273 })
274 }
275 }
276}