forge_core/schema/
registry.rs1use std::collections::BTreeMap;
15use std::sync::RwLock;
16
17use super::function::FunctionDef;
18use super::model::TableDef;
19
20pub struct SchemaRegistry {
24 tables: RwLock<BTreeMap<String, TableDef>>,
26
27 enums: RwLock<BTreeMap<String, EnumDef>>,
29
30 functions: RwLock<BTreeMap<String, FunctionDef>>,
32}
33
34impl SchemaRegistry {
35 pub fn new() -> Self {
37 Self {
38 tables: RwLock::new(BTreeMap::new()),
39 enums: RwLock::new(BTreeMap::new()),
40 functions: RwLock::new(BTreeMap::new()),
41 }
42 }
43
44 pub fn register_table(&self, table: TableDef) {
46 let mut tables = self.tables.write().expect("schema registry lock poisoned");
47 tables.insert(table.name.clone(), table);
48 }
49
50 pub fn register_enum(&self, enum_def: EnumDef) {
52 let mut enums = self.enums.write().expect("schema registry lock poisoned");
53 enums.insert(enum_def.name.clone(), enum_def);
54 }
55
56 pub fn register_function(&self, func: FunctionDef) {
58 let mut functions = self
59 .functions
60 .write()
61 .expect("schema registry lock poisoned");
62 functions.insert(func.name.clone(), func);
63 }
64
65 pub fn get_table(&self, name: &str) -> Option<TableDef> {
67 let tables = self.tables.read().expect("schema registry lock poisoned");
68 tables.get(name).cloned()
69 }
70
71 pub fn get_enum(&self, name: &str) -> Option<EnumDef> {
73 let enums = self.enums.read().expect("schema registry lock poisoned");
74 enums.get(name).cloned()
75 }
76
77 pub fn get_function(&self, name: &str) -> Option<FunctionDef> {
79 let functions = self
80 .functions
81 .read()
82 .expect("schema registry lock poisoned");
83 functions.get(name).cloned()
84 }
85
86 pub fn all_tables(&self) -> Vec<TableDef> {
88 let tables = self.tables.read().expect("schema registry lock poisoned");
89 tables.values().cloned().collect()
90 }
91
92 pub fn all_enums(&self) -> Vec<EnumDef> {
94 let enums = self.enums.read().expect("schema registry lock poisoned");
95 enums.values().cloned().collect()
96 }
97
98 pub fn all_functions(&self) -> Vec<FunctionDef> {
100 let functions = self
101 .functions
102 .read()
103 .expect("schema registry lock poisoned");
104 functions.values().cloned().collect()
105 }
106
107 pub fn clear(&self) {
109 self.tables
110 .write()
111 .expect("schema registry lock poisoned")
112 .clear();
113 self.enums
114 .write()
115 .expect("schema registry lock poisoned")
116 .clear();
117 self.functions
118 .write()
119 .expect("schema registry lock poisoned")
120 .clear();
121 }
122}
123
124impl Default for SchemaRegistry {
125 fn default() -> Self {
126 Self::new()
127 }
128}
129
130#[derive(Debug, Clone)]
132pub struct EnumDef {
133 pub name: String,
135
136 pub sql_name: String,
138
139 pub variants: Vec<EnumVariant>,
141
142 pub doc: Option<String>,
144}
145
146impl EnumDef {
147 pub fn new(name: &str) -> Self {
149 Self {
150 name: name.to_string(),
151 sql_name: to_snake_case(name),
152 variants: Vec::new(),
153 doc: None,
154 }
155 }
156
157 pub fn to_create_type_sql(&self) -> String {
159 let values: Vec<String> = self
160 .variants
161 .iter()
162 .map(|v| format!("'{}'", v.sql_value))
163 .collect();
164
165 format!(
166 "CREATE TYPE {} AS ENUM (\n {}\n);",
167 self.sql_name,
168 values.join(",\n ")
169 )
170 }
171
172 pub fn to_typescript(&self) -> String {
174 let values: Vec<String> = self
175 .variants
176 .iter()
177 .map(|v| format!("'{}'", v.sql_value))
178 .collect();
179
180 format!("export type {} = {};", self.name, values.join(" | "))
181 }
182}
183
184#[derive(Debug, Clone)]
186pub struct EnumVariant {
187 pub name: String,
189
190 pub sql_value: String,
192
193 pub int_value: Option<i32>,
195
196 pub doc: Option<String>,
198}
199
200impl EnumVariant {
201 pub fn new(name: &str) -> Self {
203 Self {
204 name: name.to_string(),
205 sql_value: to_snake_case(name),
206 int_value: None,
207 doc: None,
208 }
209 }
210}
211
212use crate::util::to_snake_case;
213
214#[cfg(test)]
215#[allow(clippy::unwrap_used, clippy::indexing_slicing)]
216mod tests {
217 use super::*;
218 use crate::schema::field::FieldDef;
219 use crate::schema::model::TableDef;
220 use crate::schema::types::RustType;
221
222 #[test]
223 fn test_registry_basic() {
224 let registry = SchemaRegistry::new();
225
226 let mut table = TableDef::new("users", "User");
227 table.fields.push(FieldDef::new("id", RustType::Uuid));
228
229 registry.register_table(table.clone());
230
231 let retrieved = registry.get_table("users").unwrap();
232 assert_eq!(retrieved.name, "users");
233 assert_eq!(retrieved.struct_name, "User");
234 }
235
236 #[test]
237 fn test_enum_def() {
238 let mut enum_def = EnumDef::new("ProjectStatus");
239 enum_def.variants.push(EnumVariant::new("Draft"));
240 enum_def.variants.push(EnumVariant::new("Active"));
241 enum_def.variants.push(EnumVariant::new("Completed"));
242
243 let sql = enum_def.to_create_type_sql();
244 assert!(sql.contains("CREATE TYPE project_status AS ENUM"));
245 assert!(sql.contains("'draft'"));
246 assert!(sql.contains("'active'"));
247
248 let ts = enum_def.to_typescript();
249 assert!(ts.contains("export type ProjectStatus"));
250 assert!(ts.contains("'draft'"));
251 }
252}