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