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