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 NumericMinimumInclusive {
23 value: ScalarLiteral,
25 },
26 NumericRangeInclusive {
28 min: ScalarLiteral,
30 max: ScalarLiteral,
32 },
33}
34
35impl SourceRuleOperation {
36 pub(crate) fn validate(&self) -> Result<(), SchemaContractError> {
38 match self {
39 Self::LengthRangeInclusive { min, max } if min <= max => Ok(()),
40 Self::NumericMinimumInclusive { value } if numeric_literal(value) => value.validate(),
41 Self::NumericRangeInclusive { min, max }
42 if numeric_literal(min)
43 && min.kind() == max.kind()
44 && scalar_literal_le(min, max) =>
45 {
46 min.validate()?;
47 max.validate()
48 }
49 Self::LengthRangeInclusive { .. }
50 | Self::NumericMinimumInclusive { .. }
51 | Self::NumericRangeInclusive { .. } => Err(SchemaContractError::InvalidRuleOperation),
52 }
53 }
54}
55
56const fn numeric_literal(literal: &ScalarLiteral) -> bool {
57 matches!(
58 literal.kind(),
59 ScalarKind::Decimal
60 | ScalarKind::Float32
61 | ScalarKind::Float64
62 | ScalarKind::Int128
63 | ScalarKind::IntBig
64 | ScalarKind::Nat128
65 | ScalarKind::NatBig
66 )
67}
68
69fn scalar_literal_le(left: &ScalarLiteral, right: &ScalarLiteral) -> bool {
70 match (left, right) {
71 (ScalarLiteral::Decimal(left), ScalarLiteral::Decimal(right)) => left <= right,
72 (ScalarLiteral::Float32(left), ScalarLiteral::Float32(right)) => left <= right,
73 (ScalarLiteral::Float64(left), ScalarLiteral::Float64(right)) => left <= right,
74 (ScalarLiteral::Int(left), ScalarLiteral::Int(right)) => left <= right,
75 (ScalarLiteral::IntBig(left), ScalarLiteral::IntBig(right)) => left <= right,
76 (ScalarLiteral::Nat(left), ScalarLiteral::Nat(right)) => left <= right,
77 (ScalarLiteral::NatBig(left), ScalarLiteral::NatBig(right)) => left <= right,
78 _ => false,
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use super::SourceRuleOperation;
85 use crate::{ScalarLiteral, SchemaContractError};
86
87 #[test]
88 fn source_rule_operation_rejects_reversed_and_mixed_ranges() {
89 assert_eq!(
90 SourceRuleOperation::LengthRangeInclusive { min: 2, max: 1 }.validate(),
91 Err(SchemaContractError::InvalidRuleOperation),
92 );
93 assert_eq!(
94 SourceRuleOperation::NumericRangeInclusive {
95 min: ScalarLiteral::Nat(2),
96 max: ScalarLiteral::Nat(1),
97 }
98 .validate(),
99 Err(SchemaContractError::InvalidRuleOperation),
100 );
101 assert_eq!(
102 SourceRuleOperation::NumericRangeInclusive {
103 min: ScalarLiteral::Int(0),
104 max: ScalarLiteral::Nat(1),
105 }
106 .validate(),
107 Err(SchemaContractError::InvalidRuleOperation),
108 );
109 }
110
111 #[test]
112 fn source_rule_operation_accepts_ordered_exact_ranges() {
113 assert!(
114 SourceRuleOperation::LengthRangeInclusive { min: 1, max: 2 }
115 .validate()
116 .is_ok()
117 );
118 assert!(
119 SourceRuleOperation::NumericRangeInclusive {
120 min: ScalarLiteral::Int(-1),
121 max: ScalarLiteral::Int(1),
122 }
123 .validate()
124 .is_ok()
125 );
126 }
127}