qail_core/
schema.rs

1//! Schema definitions for QAIL validation.
2//!
3//! Provides types for representing database schemas and loading them from JSON/TOML.
4//!
5//! # Example
6//! ```
7//! use qail_core::schema::Schema;
8//! 
9//! let json = r#"{
10//!     "tables": [{
11//!         "name": "users",
12//!         "columns": [
13//!             { "name": "id", "typ": "uuid", "nullable": false },
14//!             { "name": "email", "typ": "varchar", "nullable": false }
15//!         ]
16//!     }]
17//! }"#;
18//! 
19//! let schema: Schema = serde_json::from_str(json).unwrap();
20//! let validator = schema.to_validator();
21//! ```
22
23use serde::{Deserialize, Serialize};
24use crate::validator::Validator;
25
26/// Database schema definition.
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct Schema {
29    pub tables: Vec<TableDef>,
30}
31
32/// Table definition with columns.
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct TableDef {
35    pub name: String,
36    pub columns: Vec<ColumnDef>,
37}
38
39/// Column definition with type information.
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct ColumnDef {
42    pub name: String,
43    #[serde(rename = "type", alias = "typ")]
44    pub typ: String,
45    #[serde(default)]
46    pub nullable: bool,
47    #[serde(default)]
48    pub primary_key: bool,
49}
50
51impl Schema {
52    /// Create an empty schema.
53    pub fn new() -> Self {
54        Self { tables: Vec::new() }
55    }
56
57    /// Add a table to the schema.
58    pub fn add_table(&mut self, table: TableDef) {
59        self.tables.push(table);
60    }
61
62    /// Convert schema to a Validator for query validation.
63    pub fn to_validator(&self) -> Validator {
64        let mut v = Validator::new();
65        for table in &self.tables {
66            let cols: Vec<&str> = table.columns.iter().map(|c| c.name.as_str()).collect();
67            v.add_table(&table.name, &cols);
68        }
69        v
70    }
71
72    /// Load schema from JSON string.
73    pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
74        serde_json::from_str(json)
75    }
76}
77
78impl Default for Schema {
79    fn default() -> Self {
80        Self::new()
81    }
82}
83
84impl TableDef {
85    /// Create a new table definition.
86    pub fn new(name: &str) -> Self {
87        Self {
88            name: name.to_string(),
89            columns: Vec::new(),
90        }
91    }
92
93    /// Add a column to the table.
94    pub fn add_column(&mut self, col: ColumnDef) {
95        self.columns.push(col);
96    }
97
98    /// Builder: add a simple column.
99    pub fn column(mut self, name: &str, typ: &str) -> Self {
100        self.columns.push(ColumnDef {
101            name: name.to_string(),
102            typ: typ.to_string(),
103            nullable: true,
104            primary_key: false,
105        });
106        self
107    }
108
109    /// Builder: add a primary key column.
110    pub fn pk(mut self, name: &str, typ: &str) -> Self {
111        self.columns.push(ColumnDef {
112            name: name.to_string(),
113            typ: typ.to_string(),
114            nullable: false,
115            primary_key: true,
116        });
117        self
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    #[test]
126    fn test_schema_from_json() {
127        let json = r#"{
128            "tables": [{
129                "name": "users",
130                "columns": [
131                    { "name": "id", "type": "uuid", "nullable": false, "primary_key": true },
132                    { "name": "email", "type": "varchar", "nullable": false }
133                ]
134            }]
135        }"#;
136
137        let schema = Schema::from_json(json).unwrap();
138        assert_eq!(schema.tables.len(), 1);
139        assert_eq!(schema.tables[0].name, "users");
140        assert_eq!(schema.tables[0].columns.len(), 2);
141    }
142
143    #[test]
144    fn test_schema_to_validator() {
145        let schema = Schema {
146            tables: vec![
147                TableDef::new("users").pk("id", "uuid").column("email", "varchar"),
148            ],
149        };
150
151        let validator = schema.to_validator();
152        assert!(validator.validate_table("users").is_ok());
153        assert!(validator.validate_column("users", "id").is_ok());
154        assert!(validator.validate_column("users", "email").is_ok());
155    }
156
157    #[test]
158    fn test_table_builder() {
159        let table = TableDef::new("orders")
160            .pk("id", "uuid")
161            .column("total", "decimal")
162            .column("status", "varchar");
163
164        assert_eq!(table.columns.len(), 3);
165        assert!(table.columns[0].primary_key);
166    }
167}