forge_core/schema/
registry.rs1use std::collections::HashMap;
2use std::sync::RwLock;
3
4use super::function::FunctionDef;
5use super::model::TableDef;
6
7pub struct SchemaRegistry {
10 tables: RwLock<HashMap<String, TableDef>>,
12
13 enums: RwLock<HashMap<String, EnumDef>>,
15
16 functions: RwLock<HashMap<String, FunctionDef>>,
18}
19
20impl SchemaRegistry {
21 pub fn new() -> Self {
23 Self {
24 tables: RwLock::new(HashMap::new()),
25 enums: RwLock::new(HashMap::new()),
26 functions: RwLock::new(HashMap::new()),
27 }
28 }
29
30 pub fn register_table(&self, table: TableDef) {
32 let mut tables = self.tables.write().unwrap();
33 tables.insert(table.name.clone(), table);
34 }
35
36 pub fn register_enum(&self, enum_def: EnumDef) {
38 let mut enums = self.enums.write().unwrap();
39 enums.insert(enum_def.name.clone(), enum_def);
40 }
41
42 pub fn register_function(&self, func: FunctionDef) {
44 let mut functions = self.functions.write().unwrap();
45 functions.insert(func.name.clone(), func);
46 }
47
48 pub fn get_table(&self, name: &str) -> Option<TableDef> {
50 let tables = self.tables.read().unwrap();
51 tables.get(name).cloned()
52 }
53
54 pub fn get_enum(&self, name: &str) -> Option<EnumDef> {
56 let enums = self.enums.read().unwrap();
57 enums.get(name).cloned()
58 }
59
60 pub fn get_function(&self, name: &str) -> Option<FunctionDef> {
62 let functions = self.functions.read().unwrap();
63 functions.get(name).cloned()
64 }
65
66 pub fn all_tables(&self) -> Vec<TableDef> {
68 let tables = self.tables.read().unwrap();
69 tables.values().cloned().collect()
70 }
71
72 pub fn all_enums(&self) -> Vec<EnumDef> {
74 let enums = self.enums.read().unwrap();
75 enums.values().cloned().collect()
76 }
77
78 pub fn all_functions(&self) -> Vec<FunctionDef> {
80 let functions = self.functions.read().unwrap();
81 functions.values().cloned().collect()
82 }
83
84 pub fn clear(&self) {
86 self.tables.write().unwrap().clear();
87 self.enums.write().unwrap().clear();
88 self.functions.write().unwrap().clear();
89 }
90}
91
92impl Default for SchemaRegistry {
93 fn default() -> Self {
94 Self::new()
95 }
96}
97
98#[derive(Debug, Clone)]
100pub struct EnumDef {
101 pub name: String,
103
104 pub sql_name: String,
106
107 pub variants: Vec<EnumVariant>,
109
110 pub doc: Option<String>,
112}
113
114impl EnumDef {
115 pub fn new(name: &str) -> Self {
117 Self {
118 name: name.to_string(),
119 sql_name: to_snake_case(name),
120 variants: Vec::new(),
121 doc: None,
122 }
123 }
124
125 pub fn to_create_type_sql(&self) -> String {
127 let values: Vec<String> = self
128 .variants
129 .iter()
130 .map(|v| format!("'{}'", v.sql_value))
131 .collect();
132
133 format!(
134 "CREATE TYPE {} AS ENUM (\n {}\n);",
135 self.sql_name,
136 values.join(",\n ")
137 )
138 }
139
140 pub fn to_typescript(&self) -> String {
142 let values: Vec<String> = self
143 .variants
144 .iter()
145 .map(|v| format!("'{}'", v.sql_value))
146 .collect();
147
148 format!("export type {} = {};", self.name, values.join(" | "))
149 }
150}
151
152#[derive(Debug, Clone)]
154pub struct EnumVariant {
155 pub name: String,
157
158 pub sql_value: String,
160
161 pub int_value: Option<i32>,
163
164 pub doc: Option<String>,
166}
167
168impl EnumVariant {
169 pub fn new(name: &str) -> Self {
171 Self {
172 name: name.to_string(),
173 sql_value: to_snake_case(name),
174 int_value: None,
175 doc: None,
176 }
177 }
178}
179
180fn to_snake_case(s: &str) -> String {
182 let mut result = String::new();
183 for (i, c) in s.chars().enumerate() {
184 if c.is_uppercase() {
185 if i > 0 {
186 result.push('_');
187 }
188 result.push(c.to_lowercase().next().unwrap());
189 } else {
190 result.push(c);
191 }
192 }
193 result
194}
195
196#[allow(dead_code)]
199static GLOBAL_REGISTRY: std::sync::LazyLock<SchemaRegistry> =
200 std::sync::LazyLock::new(SchemaRegistry::new);
201
202#[allow(dead_code)]
204pub fn global_registry() -> &'static SchemaRegistry {
205 &GLOBAL_REGISTRY
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211 use crate::schema::field::FieldDef;
212 use crate::schema::model::TableDef;
213 use crate::schema::types::RustType;
214
215 #[test]
216 fn test_registry_basic() {
217 let registry = SchemaRegistry::new();
218
219 let mut table = TableDef::new("users", "User");
220 table.fields.push(FieldDef::new("id", RustType::Uuid));
221
222 registry.register_table(table.clone());
223
224 let retrieved = registry.get_table("users").unwrap();
225 assert_eq!(retrieved.name, "users");
226 assert_eq!(retrieved.struct_name, "User");
227 }
228
229 #[test]
230 fn test_enum_def() {
231 let mut enum_def = EnumDef::new("ProjectStatus");
232 enum_def.variants.push(EnumVariant::new("Draft"));
233 enum_def.variants.push(EnumVariant::new("Active"));
234 enum_def.variants.push(EnumVariant::new("Completed"));
235
236 let sql = enum_def.to_create_type_sql();
237 assert!(sql.contains("CREATE TYPE project_status AS ENUM"));
238 assert!(sql.contains("'draft'"));
239 assert!(sql.contains("'active'"));
240
241 let ts = enum_def.to_typescript();
242 assert!(ts.contains("export type ProjectStatus"));
243 assert!(ts.contains("'draft'"));
244 }
245}