Skip to main content

nodedb_types/
typeguard.rs

1//! Type guard definitions for schemaless collection write-time validation.
2//!
3//! Type guards are per-field type + value constraints on schemaless collections.
4//! They are stored in the collection catalog and evaluated on the Data Plane
5//! at write time, before WAL append.
6
7use serde::{Deserialize, Serialize};
8
9/// A single field guard: type check + optional REQUIRED + optional CHECK expression.
10///
11/// Stored as part of the collection metadata in the catalog.
12/// The `check_expr` is stored as a string (the original SQL expression text)
13/// and parsed into an evaluable form at enforcement time.
14#[derive(
15    Serialize,
16    Deserialize,
17    zerompk::ToMessagePack,
18    zerompk::FromMessagePack,
19    Debug,
20    Clone,
21    PartialEq,
22)]
23pub struct TypeGuardFieldDef {
24    /// Field name. Supports dot-path for nested fields (e.g., "metadata.source").
25    pub field: String,
26    /// Type expression string (e.g., "STRING", "INT|NULL", "ARRAY<STRING>").
27    pub type_expr: String,
28    /// Whether the field must be present and non-null on every write.
29    pub required: bool,
30    /// Optional CHECK expression as SQL text (e.g., "amount > 0").
31    /// Stored as text, parsed at enforcement time.
32    #[serde(default, skip_serializing_if = "Option::is_none")]
33    pub check_expr: Option<String>,
34    /// DEFAULT expression: injected if the field is absent on write.
35    /// Does NOT overwrite a user-provided value.
36    /// Example: `DEFAULT 'draft'`, `DEFAULT gen_uuid_v7()`, `DEFAULT now()`.
37    #[serde(default, skip_serializing_if = "Option::is_none")]
38    pub default_expr: Option<String>,
39    /// VALUE expression: always injected, even if the field is provided.
40    /// Overwrites user input — use for computed/derived fields.
41    /// Example: `VALUE now()`, `VALUE LOWER(REPLACE(title, ' ', '-'))`.
42    #[serde(default, skip_serializing_if = "Option::is_none")]
43    pub value_expr: Option<String>,
44}