1use candid::CandidType;
7use serde::{Deserialize, Serialize};
8
9use crate::{ScalarKind, ScalarLiteral, SchemaContractError};
10
11#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
13pub enum SourceRuleOperation {
14 LengthRangeInclusive {
16 min: u64,
18 max: u64,
20 },
21 MultipleOf {
23 divisor: ScalarLiteral,
25 },
26 NumericMaximumInclusive {
28 value: ScalarLiteral,
30 },
31 NumericMinimumInclusive {
33 value: ScalarLiteral,
35 },
36 NumericRangeInclusive {
38 min: ScalarLiteral,
40 max: ScalarLiteral,
42 },
43}
44
45impl SourceRuleOperation {
46 pub(crate) fn validate(&self) -> Result<(), SchemaContractError> {
48 match self {
49 Self::LengthRangeInclusive { min, max } if min <= max => Ok(()),
50 Self::MultipleOf { divisor }
51 if exact_multiple_literal(divisor) && !scalar_literal_is_zero(divisor) =>
52 {
53 divisor.validate()
54 }
55 Self::NumericMaximumInclusive { value } | Self::NumericMinimumInclusive { value }
56 if numeric_literal(value) =>
57 {
58 value.validate()
59 }
60 Self::NumericRangeInclusive { min, max }
61 if numeric_literal(min)
62 && min.kind() == max.kind()
63 && scalar_literal_le(min, max) =>
64 {
65 min.validate()?;
66 max.validate()
67 }
68 Self::LengthRangeInclusive { .. }
69 | Self::MultipleOf { .. }
70 | Self::NumericMaximumInclusive { .. }
71 | Self::NumericMinimumInclusive { .. }
72 | Self::NumericRangeInclusive { .. } => Err(SchemaContractError::InvalidRuleOperation),
73 }
74 }
75}
76
77pub(crate) const fn exact_multiple_literal(literal: &ScalarLiteral) -> bool {
78 matches!(
79 literal.kind(),
80 ScalarKind::Decimal
81 | ScalarKind::Int128
82 | ScalarKind::IntBig
83 | ScalarKind::Nat128
84 | ScalarKind::NatBig
85 )
86}
87
88const fn numeric_literal(literal: &ScalarLiteral) -> bool {
89 matches!(
90 literal.kind(),
91 ScalarKind::Decimal
92 | ScalarKind::Float32
93 | ScalarKind::Float64
94 | ScalarKind::Int128
95 | ScalarKind::IntBig
96 | ScalarKind::Nat128
97 | ScalarKind::NatBig
98 )
99}
100
101fn scalar_literal_le(left: &ScalarLiteral, right: &ScalarLiteral) -> bool {
102 match (left, right) {
103 (ScalarLiteral::Decimal(left), ScalarLiteral::Decimal(right)) => left <= right,
104 (ScalarLiteral::Float32(left), ScalarLiteral::Float32(right)) => left <= right,
105 (ScalarLiteral::Float64(left), ScalarLiteral::Float64(right)) => left <= right,
106 (ScalarLiteral::Int(left), ScalarLiteral::Int(right)) => left <= right,
107 (ScalarLiteral::IntBig(left), ScalarLiteral::IntBig(right)) => left <= right,
108 (ScalarLiteral::Nat(left), ScalarLiteral::Nat(right)) => left <= right,
109 (ScalarLiteral::NatBig(left), ScalarLiteral::NatBig(right)) => left <= right,
110 _ => false,
111 }
112}
113
114fn scalar_literal_is_zero(literal: &ScalarLiteral) -> bool {
115 match literal {
116 ScalarLiteral::Decimal(value) => value.is_zero(),
117 ScalarLiteral::Int(value) => *value == 0,
118 ScalarLiteral::IntBig(value) => value == &crate::IntBig::default(),
119 ScalarLiteral::Nat(value) => *value == 0,
120 ScalarLiteral::NatBig(value) => value == &crate::NatBig::default(),
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}