1use std::collections::BTreeSet;
4
5use candid::CandidType;
6use serde::{Deserialize, Serialize};
7
8use crate::{
9 Account, Blob, Date, Decimal, Duration, FieldSourceKey, Float32, Float64, IntBig,
10 MAX_PROPOSAL_LITERAL_BYTES, MAX_SOURCE_CHECK_INSTRUCTIONS, NatBig, Principal, ScalarKind,
11 SchemaContractError, Subaccount, Timestamp, TypeSourceKey, Ulid, Unit,
12};
13
14#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
16pub enum ScalarLiteral {
17 Account(Account),
19 Blob(Blob),
21 Bool(bool),
23 Date(Date),
25 Decimal(Decimal),
27 Duration(Duration),
29 EnumUnit {
31 enum_type: TypeSourceKey,
33 variant: TypeSourceKey,
35 },
36 Float32(Float32),
38 Float64(Float64),
40 Int(i128),
42 IntBig(IntBig),
44 Nat(u128),
46 NatBig(NatBig),
48 Principal(Principal),
50 Subaccount(Subaccount),
52 Text(String),
54 Timestamp(Timestamp),
56 Ulid(Ulid),
58 Unit(Unit),
60}
61
62impl ScalarLiteral {
63 #[must_use]
65 pub const fn kind(&self) -> ScalarKind {
66 match self {
67 Self::Account(_) => ScalarKind::Account,
68 Self::Blob(_) => ScalarKind::Blob,
69 Self::Bool(_) => ScalarKind::Bool,
70 Self::Date(_) => ScalarKind::Date,
71 Self::Decimal(_) => ScalarKind::Decimal,
72 Self::Duration(_) => ScalarKind::Duration,
73 Self::EnumUnit { .. } => ScalarKind::Enum,
74 Self::Float32(_) => ScalarKind::Float32,
75 Self::Float64(_) => ScalarKind::Float64,
76 Self::Int(_) => ScalarKind::Int128,
77 Self::IntBig(_) => ScalarKind::IntBig,
78 Self::Nat(_) => ScalarKind::Nat128,
79 Self::NatBig(_) => ScalarKind::NatBig,
80 Self::Principal(_) => ScalarKind::Principal,
81 Self::Subaccount(_) => ScalarKind::Subaccount,
82 Self::Text(_) => ScalarKind::Text,
83 Self::Timestamp(_) => ScalarKind::Timestamp,
84 Self::Ulid(_) => ScalarKind::Ulid,
85 Self::Unit(_) => ScalarKind::Unit,
86 }
87 }
88
89 pub(crate) fn validate(&self) -> Result<(), SchemaContractError> {
90 match self {
91 Self::Blob(value) if value.len() > MAX_PROPOSAL_LITERAL_BYTES => {
92 Err(SchemaContractError::InvalidLiteral)
93 }
94 Self::Text(value) if value.len() > MAX_PROPOSAL_LITERAL_BYTES => {
95 Err(SchemaContractError::InvalidLiteral)
96 }
97 Self::IntBig(value) if value.to_leb128().len() > MAX_PROPOSAL_LITERAL_BYTES => {
98 Err(SchemaContractError::InvalidLiteral)
99 }
100 Self::NatBig(value) if value.to_leb128().len() > MAX_PROPOSAL_LITERAL_BYTES => {
101 Err(SchemaContractError::InvalidLiteral)
102 }
103 _ => Ok(()),
104 }
105 }
106}
107
108#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
113pub enum SourceCheckInstruction {
114 Field(FieldSourceKey),
116 Literal(ScalarLiteral),
118 Equal,
120 NotEqual,
122 LessThan,
124 LessThanOrEqual,
126 GreaterThan,
128 GreaterThanOrEqual,
130 And,
132 Or,
134 Not,
136 IsNull,
138 IsNotNull,
140 Length,
142}
143
144#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
146pub struct SourceCheckExpr {
147 instructions: Vec<SourceCheckInstruction>,
148}
149
150impl SourceCheckExpr {
151 pub fn try_new(instructions: Vec<SourceCheckInstruction>) -> Result<Self, SchemaContractError> {
158 let expression = Self { instructions };
159 expression.validate()?;
160 Ok(expression)
161 }
162
163 #[must_use]
165 pub fn instructions(&self) -> &[SourceCheckInstruction] {
166 &self.instructions
167 }
168
169 #[must_use]
171 pub fn dependencies(&self) -> BTreeSet<FieldSourceKey> {
172 self.instructions
173 .iter()
174 .filter_map(|instruction| match instruction {
175 SourceCheckInstruction::Field(field) => Some(field.clone()),
176 _ => None,
177 })
178 .collect()
179 }
180
181 pub(crate) fn validate(&self) -> Result<(), SchemaContractError> {
182 if self.instructions.is_empty() || self.instructions.len() > MAX_SOURCE_CHECK_INSTRUCTIONS {
183 return Err(SchemaContractError::InvalidExpression);
184 }
185 let mut stack_depth = 0usize;
186 for instruction in &self.instructions {
187 match instruction {
188 SourceCheckInstruction::Field(_) => {
189 stack_depth = stack_depth
190 .checked_add(1)
191 .ok_or(SchemaContractError::InvalidExpression)?;
192 }
193 SourceCheckInstruction::Literal(literal) => {
194 literal.validate()?;
195 stack_depth = stack_depth
196 .checked_add(1)
197 .ok_or(SchemaContractError::InvalidExpression)?;
198 }
199 SourceCheckInstruction::Not
200 | SourceCheckInstruction::IsNull
201 | SourceCheckInstruction::IsNotNull
202 | SourceCheckInstruction::Length => {
203 if stack_depth < 1 {
204 return Err(SchemaContractError::InvalidExpression);
205 }
206 }
207 SourceCheckInstruction::Equal
208 | SourceCheckInstruction::NotEqual
209 | SourceCheckInstruction::LessThan
210 | SourceCheckInstruction::LessThanOrEqual
211 | SourceCheckInstruction::GreaterThan
212 | SourceCheckInstruction::GreaterThanOrEqual
213 | SourceCheckInstruction::And
214 | SourceCheckInstruction::Or => {
215 if stack_depth < 2 {
216 return Err(SchemaContractError::InvalidExpression);
217 }
218 stack_depth -= 1;
219 }
220 }
221 }
222 if stack_depth != 1 {
223 return Err(SchemaContractError::InvalidExpression);
224 }
225 Ok(())
226 }
227}