1use crate::{ScalarKind, ScalarLiteral, SchemaContractError};
7
8#[derive(Clone, Debug, Eq, PartialEq)]
10pub enum SourceRuleOperation {
11 LengthRangeInclusive {
13 min: u64,
15 max: u64,
17 },
18 MultipleOf {
20 divisor: ScalarLiteral,
22 },
23 NumericMaximumInclusive {
25 value: ScalarLiteral,
27 },
28 NumericMinimumInclusive {
30 value: ScalarLiteral,
32 },
33 NumericRangeInclusive {
35 min: ScalarLiteral,
37 max: ScalarLiteral,
39 },
40}
41
42impl SourceRuleOperation {
43 pub(crate) fn validate(&self) -> Result<(), SchemaContractError> {
45 match self {
46 Self::LengthRangeInclusive { min, max } if min <= max => Ok(()),
47 Self::MultipleOf { divisor }
48 if exact_multiple_literal(divisor) && !scalar_literal_is_zero(divisor) =>
49 {
50 divisor.validate()
51 }
52 Self::NumericMaximumInclusive { value } | Self::NumericMinimumInclusive { value }
53 if numeric_literal(value) =>
54 {
55 value.validate()
56 }
57 Self::NumericRangeInclusive { min, max }
58 if numeric_literal(min)
59 && min.kind() == max.kind()
60 && scalar_literal_le(min, max) =>
61 {
62 min.validate()?;
63 max.validate()
64 }
65 Self::LengthRangeInclusive { .. }
66 | Self::MultipleOf { .. }
67 | Self::NumericMaximumInclusive { .. }
68 | Self::NumericMinimumInclusive { .. }
69 | Self::NumericRangeInclusive { .. } => Err(SchemaContractError::InvalidRuleOperation),
70 }
71 }
72}
73
74pub(crate) const fn exact_multiple_literal(literal: &ScalarLiteral) -> bool {
75 matches!(
76 literal.kind(),
77 ScalarKind::Decimal
78 | ScalarKind::Int128
79 | ScalarKind::IntBig
80 | ScalarKind::Nat128
81 | ScalarKind::NatBig
82 )
83}
84
85const fn numeric_literal(literal: &ScalarLiteral) -> bool {
86 matches!(
87 literal.kind(),
88 ScalarKind::Decimal
89 | ScalarKind::Float32
90 | ScalarKind::Float64
91 | ScalarKind::Int128
92 | ScalarKind::IntBig
93 | ScalarKind::Nat128
94 | ScalarKind::NatBig
95 | ScalarKind::U256
96 )
97}
98
99fn scalar_literal_le(left: &ScalarLiteral, right: &ScalarLiteral) -> bool {
100 match (left, right) {
101 (ScalarLiteral::Decimal(left), ScalarLiteral::Decimal(right)) => left <= right,
102 (ScalarLiteral::Float32(left), ScalarLiteral::Float32(right)) => left <= right,
103 (ScalarLiteral::Float64(left), ScalarLiteral::Float64(right)) => left <= right,
104 (ScalarLiteral::Int(left), ScalarLiteral::Int(right)) => left <= right,
105 (ScalarLiteral::IntBig(left), ScalarLiteral::IntBig(right)) => left <= right,
106 (ScalarLiteral::Nat(left), ScalarLiteral::Nat(right)) => left <= right,
107 (ScalarLiteral::NatBig(left), ScalarLiteral::NatBig(right)) => left <= right,
108 (ScalarLiteral::U256(left), ScalarLiteral::U256(right)) => left <= right,
109 _ => false,
110 }
111}
112
113fn scalar_literal_is_zero(literal: &ScalarLiteral) -> bool {
114 match literal {
115 ScalarLiteral::Decimal(value) => value.is_zero(),
116 ScalarLiteral::Int(value) => *value == 0,
117 ScalarLiteral::IntBig(value) => value == &crate::IntBig::default(),
118 ScalarLiteral::Nat(value) => *value == 0,
119 ScalarLiteral::NatBig(value) => value == &crate::NatBig::default(),
120 ScalarLiteral::U256(value) => *value == crate::U256::ZERO,
121 ScalarLiteral::Account(_)
122 | ScalarLiteral::Blob(_)
123 | ScalarLiteral::Bool(_)
124 | ScalarLiteral::Date(_)
125 | ScalarLiteral::Duration(_)
126 | ScalarLiteral::EnumUnit { .. }
127 | ScalarLiteral::Float32(_)
128 | ScalarLiteral::Float64(_)
129 | ScalarLiteral::Principal(_)
130 | ScalarLiteral::Subaccount(_)
131 | ScalarLiteral::Text(_)
132 | ScalarLiteral::Timestamp(_)
133 | ScalarLiteral::Ulid(_)
134 | ScalarLiteral::Unit(_) => false,
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::SourceRuleOperation;
141 use crate::{ScalarLiteral, SchemaContractError};
142
143 #[test]
144 fn source_rule_operation_rejects_reversed_and_mixed_ranges() {
145 assert_eq!(
146 SourceRuleOperation::LengthRangeInclusive { min: 2, max: 1 }.validate(),
147 Err(SchemaContractError::InvalidRuleOperation),
148 );
149 assert_eq!(
150 SourceRuleOperation::NumericRangeInclusive {
151 min: ScalarLiteral::Nat(2),
152 max: ScalarLiteral::Nat(1),
153 }
154 .validate(),
155 Err(SchemaContractError::InvalidRuleOperation),
156 );
157 assert_eq!(
158 SourceRuleOperation::NumericRangeInclusive {
159 min: ScalarLiteral::Int(0),
160 max: ScalarLiteral::Nat(1),
161 }
162 .validate(),
163 Err(SchemaContractError::InvalidRuleOperation),
164 );
165 }
166
167 #[test]
168 fn source_rule_operation_accepts_ordered_exact_ranges() {
169 assert!(
170 SourceRuleOperation::LengthRangeInclusive { min: 1, max: 2 }
171 .validate()
172 .is_ok()
173 );
174 assert!(
175 SourceRuleOperation::NumericRangeInclusive {
176 min: ScalarLiteral::Int(-1),
177 max: ScalarLiteral::Int(1),
178 }
179 .validate()
180 .is_ok()
181 );
182 }
183
184 #[test]
185 fn source_rule_operation_accepts_exact_nonzero_multiple_and_maximum() {
186 assert!(
187 SourceRuleOperation::NumericMaximumInclusive {
188 value: ScalarLiteral::Nat(10),
189 }
190 .validate()
191 .is_ok()
192 );
193 assert!(
194 SourceRuleOperation::MultipleOf {
195 divisor: ScalarLiteral::Int(-5),
196 }
197 .validate()
198 .is_ok()
199 );
200 }
201
202 #[test]
203 fn source_rule_operation_rejects_zero_and_float_multiple() {
204 for divisor in [
205 ScalarLiteral::Nat(0),
206 ScalarLiteral::Float64(crate::Float64::try_new(1.0).expect("finite float")),
207 ] {
208 assert_eq!(
209 SourceRuleOperation::MultipleOf { divisor }.validate(),
210 Err(SchemaContractError::InvalidRuleOperation),
211 );
212 }
213 }
214}