1use crate::prelude::*;
2
3#[derive(Clone, Debug, Serialize)]
11pub struct Type {
12 #[serde(skip_serializing_if = "<[_]>::is_empty")]
13 normalizers: &'static [TypeNormalizer],
14
15 #[serde(skip_serializing_if = "<[_]>::is_empty")]
16 validators: &'static [TypeValidator],
17
18 #[serde(skip_serializing_if = "<[_]>::is_empty")]
19 rules: &'static [SourceRule],
20}
21
22impl Type {
23 #[must_use]
24 pub const fn new(
25 normalizers: &'static [TypeNormalizer],
26 validators: &'static [TypeValidator],
27 rules: &'static [SourceRule],
28 ) -> Self {
29 Self {
30 normalizers,
31 validators,
32 rules,
33 }
34 }
35
36 #[must_use]
37 pub const fn normalizers(&self) -> &'static [TypeNormalizer] {
38 self.normalizers
39 }
40
41 #[must_use]
42 pub const fn validators(&self) -> &'static [TypeValidator] {
43 self.validators
44 }
45
46 #[must_use]
48 pub const fn rules(&self) -> &'static [SourceRule] {
49 self.rules
50 }
51}
52
53impl ValidateNode for Type {}
54
55impl VisitableNode for Type {
56 fn drive<V: Visitor>(&self, v: &mut V) {
57 for node in self.normalizers() {
58 node.accept(v);
59 }
60 for node in self.validators() {
61 node.accept(v);
62 }
63 for node in self.rules() {
64 node.accept(v);
65 }
66 }
67}
68
69#[derive(Clone, Debug, Serialize)]
78pub struct SourceRule {
79 name: &'static str,
80 operation: SourceRuleAuthoringOperation,
81}
82
83impl SourceRule {
84 #[must_use]
86 pub const fn new(name: &'static str, operation: SourceRuleAuthoringOperation) -> Self {
87 Self { name, operation }
88 }
89
90 #[must_use]
92 pub const fn name(&self) -> &'static str {
93 self.name
94 }
95
96 #[must_use]
98 pub const fn operation(&self) -> &SourceRuleAuthoringOperation {
99 &self.operation
100 }
101}
102
103impl ValidateNode for SourceRule {
104 fn validate(&self) -> Result<(), ErrorTree> {
105 let mut errs = ErrorTree::new();
106 validate_source_name(
107 &mut errs,
108 "rule",
109 self.name(),
110 icydb_schema::RuleSourceKey::try_new,
111 );
112 if let Err(message) = self.operation().validate_shape() {
113 err!(errs, "rule '{}': {message}", self.name());
114 }
115 errs.result()
116 }
117}
118
119impl VisitableNode for SourceRule {}
120
121#[derive(Clone, Debug, Serialize)]
129pub enum SourceRuleAuthoringOperation {
130 LengthRangeInclusive {
132 min: RuleNumber,
134 max: RuleNumber,
136 },
137 MultipleOf {
139 divisor: RuleNumber,
141 },
142 NumericMaximumInclusive {
144 value: RuleNumber,
146 },
147 NumericMinimumInclusive {
149 value: RuleNumber,
151 },
152 NumericRangeInclusive {
154 min: RuleNumber,
156 max: RuleNumber,
158 },
159}
160
161impl SourceRuleAuthoringOperation {
162 fn validate_shape(&self) -> Result<(), &'static str> {
163 match self {
164 Self::LengthRangeInclusive { min, max } => {
165 let min = rule_length_bound(min).ok_or(
166 "length_range_inclusive operands must be nonnegative integers within u64",
167 )?;
168 let max = rule_length_bound(max).ok_or(
169 "length_range_inclusive operands must be nonnegative integers within u64",
170 )?;
171 if min > max {
172 return Err("length_range_inclusive requires min <= max");
173 }
174 }
175 Self::MultipleOf { divisor } => {
176 if !rule_number_is_valid(divisor) {
177 return Err("multiple_of divisor must be a valid numeric literal");
178 }
179 if rule_number_is_zero(divisor) {
180 return Err("multiple_of divisor must be nonzero");
181 }
182 }
183 Self::NumericMaximumInclusive { value } | Self::NumericMinimumInclusive { value } => {
184 if !rule_number_is_valid(value) {
185 return Err("numeric rule value must be a valid numeric literal");
186 }
187 }
188 Self::NumericRangeInclusive { min, max } => {
189 if !rule_number_is_valid(min) || !rule_number_is_valid(max) {
190 return Err("numeric range operands must be valid numeric literals");
191 }
192 }
193 }
194 Ok(())
195 }
196}
197
198#[derive(Clone, Debug, Serialize)]
204pub enum RuleNumber {
205 Integer(&'static str),
207 Decimal(&'static str),
209 Float32(f32),
211 Float64(f64),
213}
214
215fn rule_length_bound(value: &RuleNumber) -> Option<u64> {
216 match value {
217 RuleNumber::Integer(value) => value.parse().ok(),
218 RuleNumber::Decimal(_) | RuleNumber::Float32(_) | RuleNumber::Float64(_) => None,
219 }
220}
221
222fn rule_number_is_zero(value: &RuleNumber) -> bool {
223 match value {
224 RuleNumber::Integer(value) => {
225 value.parse::<i128>().is_ok_and(|value| value == 0)
226 || value.parse::<u128>().is_ok_and(|value| value == 0)
227 }
228 RuleNumber::Decimal(value) => value
229 .parse::<icydb_schema::Decimal>()
230 .is_ok_and(|value| value.is_zero()),
231 RuleNumber::Float32(value) => *value == 0.0,
232 RuleNumber::Float64(value) => *value == 0.0,
233 }
234}
235
236fn rule_number_is_valid(value: &RuleNumber) -> bool {
237 match value {
238 RuleNumber::Integer(value) => {
239 value.parse::<i128>().is_ok() || value.parse::<u128>().is_ok()
240 }
241 RuleNumber::Decimal(value) => value.parse::<icydb_schema::Decimal>().is_ok(),
242 RuleNumber::Float32(value) => value.is_finite(),
243 RuleNumber::Float64(value) => value.is_finite(),
244 }
245}
246
247#[derive(Clone, Debug, Serialize)]
254pub struct TypeNormalizer {
255 path: &'static str,
256 args: Args,
257}
258
259impl TypeNormalizer {
260 #[must_use]
261 pub const fn new(path: &'static str, args: Args) -> Self {
262 Self { path, args }
263 }
264
265 #[must_use]
266 pub const fn path(&self) -> &'static str {
267 self.path
268 }
269
270 #[must_use]
271 pub const fn args(&self) -> &Args {
272 &self.args
273 }
274}
275
276impl ValidateNode for TypeNormalizer {
277 fn validate(&self) -> Result<(), ErrorTree> {
278 let mut errs = ErrorTree::new();
279
280 let res = schema_read().check_node_as::<Normalizer>(self.path());
282 if let Err(e) = res {
283 errs.add(e.to_string());
284 }
285
286 errs.result()
287 }
288}
289
290impl VisitableNode for TypeNormalizer {}
291
292#[derive(Clone, Debug, Serialize)]
299pub struct TypeValidator {
300 path: &'static str,
301 args: Args,
302}
303
304impl TypeValidator {
305 #[must_use]
306 pub const fn new(path: &'static str, args: Args) -> Self {
307 Self { path, args }
308 }
309
310 #[must_use]
311 pub const fn path(&self) -> &'static str {
312 self.path
313 }
314
315 #[must_use]
316 pub const fn args(&self) -> &Args {
317 &self.args
318 }
319}
320
321impl ValidateNode for TypeValidator {
322 fn validate(&self) -> Result<(), ErrorTree> {
323 let mut errs = ErrorTree::new();
324
325 let res = schema_read().check_node_as::<Validator>(self.path());
327 if let Err(e) = res {
328 errs.add(e.to_string());
329 }
330
331 errs.result()
332 }
333}
334
335impl VisitableNode for TypeValidator {}
336
337#[cfg(test)]
338mod tests {
339 use super::{RuleNumber, SourceRuleAuthoringOperation};
340
341 #[test]
342 fn directly_constructed_rule_numbers_validate_before_lowering() {
343 assert_eq!(
344 SourceRuleAuthoringOperation::MultipleOf {
345 divisor: RuleNumber::Decimal("not-a-decimal"),
346 }
347 .validate_shape(),
348 Err("multiple_of divisor must be a valid numeric literal"),
349 );
350 assert_eq!(
351 SourceRuleAuthoringOperation::NumericMaximumInclusive {
352 value: RuleNumber::Float64(f64::NAN),
353 }
354 .validate_shape(),
355 Err("numeric rule value must be a valid numeric literal"),
356 );
357 }
358}