Skip to main content

valence_core/
schema_api.rs

1//! Structured schema metadata produced by the Valence DSL.
2
3use crate::evaluator::DatabaseEvaluator;
4use crate::owner_ref::OwnershipConfig;
5use crate::privacy::PolicyEvaluator;
6use crate::ttl::SchemaTtlPolicy;
7use serde::{Deserialize, Serialize};
8
9/// One policy rule reference stored in schema metadata.
10#[derive(Debug, Clone, Serialize, Deserialize, Default)]
11pub struct SchemaPolicyRule {
12    pub name: String,
13    #[serde(skip_serializing_if = "Option::is_none")]
14    pub description: Option<String>,
15    #[serde(skip, default)]
16    pub evaluator: Option<&'static dyn PolicyEvaluator>,
17}
18
19/// Policy rule buckets for a single CRUD operation.
20#[derive(Debug, Clone, Serialize, Deserialize, Default)]
21pub struct SchemaPolicyRules {
22    #[serde(default, skip_serializing_if = "Vec::is_empty")]
23    pub always_allow: Vec<SchemaPolicyRule>,
24    #[serde(default, skip_serializing_if = "Vec::is_empty")]
25    pub allow: Vec<SchemaPolicyRule>,
26    #[serde(default, skip_serializing_if = "Vec::is_empty")]
27    pub block: Vec<SchemaPolicyRule>,
28    #[serde(default, skip_serializing_if = "Vec::is_empty")]
29    pub always_block: Vec<SchemaPolicyRule>,
30}
31
32/// Full entity-level policy declaration for the schema.
33#[derive(Debug, Clone, Serialize, Deserialize, Default)]
34pub struct SchemaPolicies {
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub read: Option<SchemaPolicyRules>,
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub create: Option<SchemaPolicyRules>,
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub update: Option<SchemaPolicyRules>,
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub delete: Option<SchemaPolicyRules>,
43}
44
45/// Top-level privacy summary for a schema.
46#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct SchemaPrivacy {
48    pub read: String,
49    pub write: String,
50}
51
52/// Normalized foreign-key/reference metadata for a field.
53#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct ForeignKeyRef {
55    pub ref_table: String,
56    pub field: String,
57}
58
59/// Expanded metadata for one field declared in `fields: [...]`.
60#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct SchemaField {
62    pub name: String,
63    pub field_type: String,
64    pub primary: bool,
65    pub nullable: bool,
66    pub indexed: bool,
67    #[serde(default, skip_serializing_if = "is_false")]
68    pub unique: bool,
69    #[serde(skip_serializing_if = "Option::is_none")]
70    pub default: Option<String>,
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub fk: Option<ForeignKeyRef>,
73    #[serde(default, skip_serializing_if = "Vec::is_empty")]
74    pub validations: Vec<String>,
75    #[serde(skip_serializing_if = "Option::is_none")]
76    pub policies: Option<SchemaPolicies>,
77    #[serde(default, skip_serializing_if = "is_false")]
78    pub encrypted: bool,
79    #[serde(default, skip_serializing_if = "Vec::is_empty")]
80    pub enum_variants: Vec<String>,
81    #[serde(default, skip_serializing_if = "Option::is_none")]
82    pub enum_type: Option<String>,
83    /// Explicit model path from `Record(…).target(…)` (connection / query hops).
84    #[serde(default, skip_serializing_if = "Option::is_none")]
85    pub model_path: Option<String>,
86}
87
88/// Edge/relationship definition retained for older schema shapes.
89#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct SchemaEdge {
91    pub from_field: String,
92    pub to_table: String,
93    pub label: String,
94}
95
96/// Connection/relationship definition from schema DSL.
97#[derive(Debug, Clone, Serialize, Deserialize)]
98pub struct SchemaConnection {
99    pub name: String,
100    pub from_table: String,
101    pub from_field: String,
102    pub to_table: String,
103    pub cardinality: String,
104    pub required: bool,
105    pub on_delete: String,
106    pub label: String,
107    #[serde(skip_serializing_if = "Option::is_none")]
108    pub model_path: Option<String>,
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub reverse_field: Option<String>,
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub edge_table: Option<String>,
113    #[serde(skip_serializing_if = "Option::is_none")]
114    pub target_trait: Option<String>,
115}
116
117impl From<SchemaEdge> for SchemaConnection {
118    fn from(edge: SchemaEdge) -> Self {
119        let label = edge.label.clone();
120        Self {
121            name: edge.from_field.clone(),
122            from_table: String::new(),
123            from_field: edge.from_field,
124            to_table: edge.to_table,
125            cardinality: "HasOne".to_string(),
126            required: true,
127            on_delete: "Cascade".to_string(),
128            label,
129            model_path: None,
130            reverse_field: None,
131            edge_table: None,
132            target_trait: None,
133        }
134    }
135}
136
137/// Schema metadata summary block.
138#[derive(Debug, Clone, Serialize, Deserialize)]
139pub struct SchemaMeta {
140    pub retention: String,
141    pub row_count: u64,
142    pub owner: String,
143    #[serde(skip_serializing_if = "Option::is_none")]
144    pub description: Option<String>,
145}
146
147/// Complete runtime schema produced from `valence_schema!`.
148#[derive(Debug, Clone, Serialize)]
149pub struct Schema {
150    pub name: String,
151    pub version: String,
152    pub databases: Vec<String>,
153    #[serde(skip)]
154    pub database_evaluator: &'static dyn DatabaseEvaluator,
155    pub privacy: SchemaPrivacy,
156    #[serde(skip_serializing_if = "Option::is_none")]
157    pub policies: Option<SchemaPolicies>,
158    pub fields: Vec<SchemaField>,
159    #[serde(default, skip_serializing_if = "Vec::is_empty")]
160    pub edges: Vec<SchemaEdge>,
161    #[serde(default, skip_serializing_if = "Vec::is_empty")]
162    pub connections: Vec<SchemaConnection>,
163    #[serde(default, skip_serializing_if = "Vec::is_empty")]
164    pub side_effects: Vec<String>,
165    #[serde(default, skip_serializing_if = "Vec::is_empty")]
166    pub iters: Vec<String>,
167    #[serde(default, skip_serializing_if = "Vec::is_empty")]
168    pub composite_key: Vec<String>,
169    #[serde(default, skip_serializing_if = "Vec::is_empty")]
170    pub traits: Vec<String>,
171    #[serde(skip_serializing_if = "Option::is_none")]
172    pub ttl: Option<SchemaTtlPolicy>,
173    #[serde(skip_serializing_if = "Option::is_none")]
174    pub ownership: Option<OwnershipConfig>,
175    pub meta: SchemaMeta,
176}
177
178#[allow(
179    clippy::trivially_copy_pass_by_ref,
180    reason = "serde's `skip_serializing_if` callback signature passes field values by reference"
181)]
182fn is_false(value: &bool) -> bool {
183    !*value
184}