icydb-schema 0.300.0

Bounded public schema proposal contract for IcyDB
Documentation
//! Bounded source-side durable-rule operations.
//!
//! These values are proposal facts. Accepted schema resolves their nominal
//! target and owns every runtime evaluator.

use serde::{Deserialize, Serialize};

use crate::{ScalarKind, ScalarLiteral, SchemaContractError};

/// One closed durable operation applied to every selected nominal value.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum SourceRuleOperation {
    /// Inclusive Unicode-scalar, octet, or collection-cardinality range.
    LengthRangeInclusive {
        /// Inclusive minimum length.
        min: u64,
        /// Inclusive maximum length.
        max: u64,
    },
    /// Exact nonzero integer or fixed-scale decimal divisor.
    MultipleOf {
        /// Exact divisor admitted against the target kind.
        divisor: ScalarLiteral,
    },
    /// Inclusive upper bound for one exact numeric kind.
    NumericMaximumInclusive {
        /// Exact upper-bound literal.
        value: ScalarLiteral,
    },
    /// Inclusive lower bound for one exact numeric kind.
    NumericMinimumInclusive {
        /// Exact lower-bound literal.
        value: ScalarLiteral,
    },
    /// Inclusive lower and upper bounds for one exact numeric kind.
    NumericRangeInclusive {
        /// Exact lower-bound literal.
        min: ScalarLiteral,
        /// Exact upper-bound literal.
        max: ScalarLiteral,
    },
}

impl SourceRuleOperation {
    /// Validate the bounded operation independently of its eventual target.
    pub(crate) fn validate(&self) -> Result<(), SchemaContractError> {
        match self {
            Self::LengthRangeInclusive { min, max } if min <= max => Ok(()),
            Self::MultipleOf { divisor }
                if exact_multiple_literal(divisor) && !scalar_literal_is_zero(divisor) =>
            {
                divisor.validate()
            }
            Self::NumericMaximumInclusive { value } | Self::NumericMinimumInclusive { value }
                if numeric_literal(value) =>
            {
                value.validate()
            }
            Self::NumericRangeInclusive { min, max }
                if numeric_literal(min)
                    && min.kind() == max.kind()
                    && scalar_literal_le(min, max) =>
            {
                min.validate()?;
                max.validate()
            }
            Self::LengthRangeInclusive { .. }
            | Self::MultipleOf { .. }
            | Self::NumericMaximumInclusive { .. }
            | Self::NumericMinimumInclusive { .. }
            | Self::NumericRangeInclusive { .. } => Err(SchemaContractError::InvalidRuleOperation),
        }
    }
}

pub(crate) const fn exact_multiple_literal(literal: &ScalarLiteral) -> bool {
    matches!(
        literal.kind(),
        ScalarKind::Decimal | ScalarKind::Int128 | ScalarKind::Nat128
    )
}

const fn numeric_literal(literal: &ScalarLiteral) -> bool {
    matches!(
        literal.kind(),
        ScalarKind::Decimal
            | ScalarKind::Float32
            | ScalarKind::Float64
            | ScalarKind::Int128
            | ScalarKind::Nat128
            | ScalarKind::U256
    )
}

fn scalar_literal_le(left: &ScalarLiteral, right: &ScalarLiteral) -> bool {
    match (left, right) {
        (ScalarLiteral::Decimal(left), ScalarLiteral::Decimal(right)) => left <= right,
        (ScalarLiteral::Float32(left), ScalarLiteral::Float32(right)) => left <= right,
        (ScalarLiteral::Float64(left), ScalarLiteral::Float64(right)) => left <= right,
        (ScalarLiteral::Int(left), ScalarLiteral::Int(right)) => left <= right,
        (ScalarLiteral::Nat(left), ScalarLiteral::Nat(right)) => left <= right,
        (ScalarLiteral::U256(left), ScalarLiteral::U256(right)) => left <= right,
        _ => false,
    }
}

fn scalar_literal_is_zero(literal: &ScalarLiteral) -> bool {
    match literal {
        ScalarLiteral::Decimal(value) => value.is_zero(),
        ScalarLiteral::Int(value) => *value == 0,
        ScalarLiteral::Nat(value) => *value == 0,
        ScalarLiteral::U256(value) => *value == crate::U256::ZERO,
        ScalarLiteral::Account(_)
        | ScalarLiteral::Blob(_)
        | ScalarLiteral::Bool(_)
        | ScalarLiteral::Date(_)
        | ScalarLiteral::Duration(_)
        | ScalarLiteral::EnumUnit { .. }
        | ScalarLiteral::Float32(_)
        | ScalarLiteral::Float64(_)
        | ScalarLiteral::Principal(_)
        | ScalarLiteral::Subaccount(_)
        | ScalarLiteral::Text(_)
        | ScalarLiteral::Timestamp(_)
        | ScalarLiteral::Ulid(_)
        | ScalarLiteral::Unit(_) => false,
    }
}

#[cfg(test)]
mod tests;