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