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 | ScalarKind::Int128 | ScalarKind::Nat128
78 )
79}
80
81const fn numeric_literal(literal: &ScalarLiteral) -> bool {
82 matches!(
83 literal.kind(),
84 ScalarKind::Decimal
85 | ScalarKind::Float32
86 | ScalarKind::Float64
87 | ScalarKind::Int128
88 | ScalarKind::Nat128
89 | ScalarKind::U256
90 )
91}
92
93fn scalar_literal_le(left: &ScalarLiteral, right: &ScalarLiteral) -> bool {
94 match (left, right) {
95 (ScalarLiteral::Decimal(left), ScalarLiteral::Decimal(right)) => left <= right,
96 (ScalarLiteral::Float32(left), ScalarLiteral::Float32(right)) => left <= right,
97 (ScalarLiteral::Float64(left), ScalarLiteral::Float64(right)) => left <= right,
98 (ScalarLiteral::Int(left), ScalarLiteral::Int(right)) => left <= right,
99 (ScalarLiteral::Nat(left), ScalarLiteral::Nat(right)) => left <= right,
100 (ScalarLiteral::U256(left), ScalarLiteral::U256(right)) => left <= right,
101 _ => false,
102 }
103}
104
105fn scalar_literal_is_zero(literal: &ScalarLiteral) -> bool {
106 match literal {
107 ScalarLiteral::Decimal(value) => value.is_zero(),
108 ScalarLiteral::Int(value) => *value == 0,
109 ScalarLiteral::Nat(value) => *value == 0,
110 ScalarLiteral::U256(value) => *value == crate::U256::ZERO,
111 ScalarLiteral::Account(_)
112 | ScalarLiteral::Blob(_)
113 | ScalarLiteral::Bool(_)
114 | ScalarLiteral::Date(_)
115 | ScalarLiteral::Duration(_)
116 | ScalarLiteral::EnumUnit { .. }
117 | ScalarLiteral::Float32(_)
118 | ScalarLiteral::Float64(_)
119 | ScalarLiteral::Principal(_)
120 | ScalarLiteral::Subaccount(_)
121 | ScalarLiteral::Text(_)
122 | ScalarLiteral::Timestamp(_)
123 | ScalarLiteral::Ulid(_)
124 | ScalarLiteral::Unit(_) => false,
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::SourceRuleOperation;
131 use crate::{ScalarLiteral, SchemaContractError};
132
133 #[test]
134 fn source_rule_operation_rejects_reversed_and_mixed_ranges() {
135 assert_eq!(
136 SourceRuleOperation::LengthRangeInclusive { min: 2, max: 1 }.validate(),
137 Err(SchemaContractError::InvalidRuleOperation),
138 );
139 assert_eq!(
140 SourceRuleOperation::NumericRangeInclusive {
141 min: ScalarLiteral::Nat(2),
142 max: ScalarLiteral::Nat(1),
143 }
144 .validate(),
145 Err(SchemaContractError::InvalidRuleOperation),
146 );
147 assert_eq!(
148 SourceRuleOperation::NumericRangeInclusive {
149 min: ScalarLiteral::Int(0),
150 max: ScalarLiteral::Nat(1),
151 }
152 .validate(),
153 Err(SchemaContractError::InvalidRuleOperation),
154 );
155 }
156
157 #[test]
158 fn source_rule_operation_accepts_ordered_exact_ranges() {
159 assert!(
160 SourceRuleOperation::LengthRangeInclusive { min: 1, max: 2 }
161 .validate()
162 .is_ok()
163 );
164 assert!(
165 SourceRuleOperation::NumericRangeInclusive {
166 min: ScalarLiteral::Int(-1),
167 max: ScalarLiteral::Int(1),
168 }
169 .validate()
170 .is_ok()
171 );
172 }
173
174 #[test]
175 fn source_rule_operation_accepts_exact_nonzero_multiple_and_maximum() {
176 assert!(
177 SourceRuleOperation::NumericMaximumInclusive {
178 value: ScalarLiteral::Nat(10),
179 }
180 .validate()
181 .is_ok()
182 );
183 assert!(
184 SourceRuleOperation::MultipleOf {
185 divisor: ScalarLiteral::Int(-5),
186 }
187 .validate()
188 .is_ok()
189 );
190 }
191
192 #[test]
193 fn source_rule_operation_rejects_zero_and_float_multiple() {
194 for divisor in [
195 ScalarLiteral::Nat(0),
196 ScalarLiteral::Float64(crate::Float64::try_new(1.0).expect("finite float")),
197 ] {
198 assert_eq!(
199 SourceRuleOperation::MultipleOf { divisor }.validate(),
200 Err(SchemaContractError::InvalidRuleOperation),
201 );
202 }
203 }
204}