use std::collections::BTreeSet;
use candid::CandidType;
use serde::{Deserialize, Serialize};
use crate::{
Account, Blob, Date, Decimal, Duration, FieldSourceKey, Float32, Float64, IntBig,
MAX_PROPOSAL_LITERAL_BYTES, MAX_SOURCE_CHECK_INSTRUCTIONS, NatBig, Principal, ScalarKind,
SchemaContractError, Subaccount, Timestamp, TypeSourceKey, Ulid, Unit,
};
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum ScalarLiteral {
Account(Account),
Blob(Blob),
Bool(bool),
Date(Date),
Decimal(Decimal),
Duration(Duration),
EnumUnit {
enum_type: TypeSourceKey,
variant: TypeSourceKey,
},
Float32(Float32),
Float64(Float64),
Int(i128),
IntBig(IntBig),
Nat(u128),
NatBig(NatBig),
Principal(Principal),
Subaccount(Subaccount),
Text(String),
Timestamp(Timestamp),
Ulid(Ulid),
Unit(Unit),
}
impl ScalarLiteral {
#[must_use]
pub const fn kind(&self) -> ScalarKind {
match self {
Self::Account(_) => ScalarKind::Account,
Self::Blob(_) => ScalarKind::Blob,
Self::Bool(_) => ScalarKind::Bool,
Self::Date(_) => ScalarKind::Date,
Self::Decimal(_) => ScalarKind::Decimal,
Self::Duration(_) => ScalarKind::Duration,
Self::EnumUnit { .. } => ScalarKind::Enum,
Self::Float32(_) => ScalarKind::Float32,
Self::Float64(_) => ScalarKind::Float64,
Self::Int(_) => ScalarKind::Int128,
Self::IntBig(_) => ScalarKind::IntBig,
Self::Nat(_) => ScalarKind::Nat128,
Self::NatBig(_) => ScalarKind::NatBig,
Self::Principal(_) => ScalarKind::Principal,
Self::Subaccount(_) => ScalarKind::Subaccount,
Self::Text(_) => ScalarKind::Text,
Self::Timestamp(_) => ScalarKind::Timestamp,
Self::Ulid(_) => ScalarKind::Ulid,
Self::Unit(_) => ScalarKind::Unit,
}
}
pub(crate) fn validate(&self) -> Result<(), SchemaContractError> {
match self {
Self::Blob(value) if value.len() > MAX_PROPOSAL_LITERAL_BYTES => {
Err(SchemaContractError::InvalidLiteral)
}
Self::Text(value) if value.len() > MAX_PROPOSAL_LITERAL_BYTES => {
Err(SchemaContractError::InvalidLiteral)
}
Self::IntBig(value) if value.to_leb128().len() > MAX_PROPOSAL_LITERAL_BYTES => {
Err(SchemaContractError::InvalidLiteral)
}
Self::NatBig(value) if value.to_leb128().len() > MAX_PROPOSAL_LITERAL_BYTES => {
Err(SchemaContractError::InvalidLiteral)
}
_ => Ok(()),
}
}
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum SourceCheckInstruction {
Field(FieldSourceKey),
Literal(ScalarLiteral),
Equal,
NotEqual,
LessThan,
LessThanOrEqual,
GreaterThan,
GreaterThanOrEqual,
And,
Or,
Not,
IsNull,
IsNotNull,
Length,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct SourceCheckExpr {
instructions: Vec<SourceCheckInstruction>,
}
impl SourceCheckExpr {
pub fn try_new(instructions: Vec<SourceCheckInstruction>) -> Result<Self, SchemaContractError> {
let expression = Self { instructions };
expression.validate()?;
Ok(expression)
}
#[must_use]
pub fn instructions(&self) -> &[SourceCheckInstruction] {
&self.instructions
}
#[must_use]
pub fn dependencies(&self) -> BTreeSet<FieldSourceKey> {
self.instructions
.iter()
.filter_map(|instruction| match instruction {
SourceCheckInstruction::Field(field) => Some(field.clone()),
_ => None,
})
.collect()
}
pub(crate) fn validate(&self) -> Result<(), SchemaContractError> {
if self.instructions.is_empty() || self.instructions.len() > MAX_SOURCE_CHECK_INSTRUCTIONS {
return Err(SchemaContractError::InvalidExpression);
}
let mut stack_depth = 0usize;
for instruction in &self.instructions {
match instruction {
SourceCheckInstruction::Field(_) => {
stack_depth = stack_depth
.checked_add(1)
.ok_or(SchemaContractError::InvalidExpression)?;
}
SourceCheckInstruction::Literal(literal) => {
literal.validate()?;
stack_depth = stack_depth
.checked_add(1)
.ok_or(SchemaContractError::InvalidExpression)?;
}
SourceCheckInstruction::Not
| SourceCheckInstruction::IsNull
| SourceCheckInstruction::IsNotNull
| SourceCheckInstruction::Length => {
if stack_depth < 1 {
return Err(SchemaContractError::InvalidExpression);
}
}
SourceCheckInstruction::Equal
| SourceCheckInstruction::NotEqual
| SourceCheckInstruction::LessThan
| SourceCheckInstruction::LessThanOrEqual
| SourceCheckInstruction::GreaterThan
| SourceCheckInstruction::GreaterThanOrEqual
| SourceCheckInstruction::And
| SourceCheckInstruction::Or => {
if stack_depth < 2 {
return Err(SchemaContractError::InvalidExpression);
}
stack_depth -= 1;
}
}
}
if stack_depth != 1 {
return Err(SchemaContractError::InvalidExpression);
}
Ok(())
}
}