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