use std::collections::BTreeMap;
use std::sync::RwLock;
use super::function::FunctionDef;
use super::model::TableDef;
pub struct SchemaRegistry {
tables: RwLock<BTreeMap<String, TableDef>>,
enums: RwLock<BTreeMap<String, EnumDef>>,
functions: RwLock<BTreeMap<String, FunctionDef>>,
}
impl SchemaRegistry {
pub fn new() -> Self {
Self {
tables: RwLock::new(BTreeMap::new()),
enums: RwLock::new(BTreeMap::new()),
functions: RwLock::new(BTreeMap::new()),
}
}
pub fn register_table(&self, table: TableDef) {
let mut tables = self.tables.write().expect("schema registry lock poisoned");
tables.insert(table.name.clone(), table);
}
pub fn register_enum(&self, enum_def: EnumDef) {
let mut enums = self.enums.write().expect("schema registry lock poisoned");
enums.insert(enum_def.name.clone(), enum_def);
}
pub fn register_function(&self, func: FunctionDef) {
let mut functions = self
.functions
.write()
.expect("schema registry lock poisoned");
functions.insert(func.name.clone(), func);
}
pub fn get_table(&self, name: &str) -> Option<TableDef> {
let tables = self.tables.read().expect("schema registry lock poisoned");
tables.get(name).cloned()
}
pub fn get_enum(&self, name: &str) -> Option<EnumDef> {
let enums = self.enums.read().expect("schema registry lock poisoned");
enums.get(name).cloned()
}
pub fn get_function(&self, name: &str) -> Option<FunctionDef> {
let functions = self
.functions
.read()
.expect("schema registry lock poisoned");
functions.get(name).cloned()
}
pub fn all_tables(&self) -> Vec<TableDef> {
let tables = self.tables.read().expect("schema registry lock poisoned");
tables.values().cloned().collect()
}
pub fn all_enums(&self) -> Vec<EnumDef> {
let enums = self.enums.read().expect("schema registry lock poisoned");
enums.values().cloned().collect()
}
pub fn all_functions(&self) -> Vec<FunctionDef> {
let functions = self
.functions
.read()
.expect("schema registry lock poisoned");
functions.values().cloned().collect()
}
pub fn clear(&self) {
self.tables
.write()
.expect("schema registry lock poisoned")
.clear();
self.enums
.write()
.expect("schema registry lock poisoned")
.clear();
self.functions
.write()
.expect("schema registry lock poisoned")
.clear();
}
}
impl Default for SchemaRegistry {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct EnumDef {
pub name: String,
pub sql_name: String,
pub variants: Vec<EnumVariant>,
pub doc: Option<String>,
}
impl EnumDef {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
sql_name: to_snake_case(name),
variants: Vec::new(),
doc: None,
}
}
pub fn to_create_type_sql(&self) -> String {
let values: Vec<String> = self
.variants
.iter()
.map(|v| format!("'{}'", v.sql_value))
.collect();
format!(
"CREATE TYPE {} AS ENUM (\n {}\n);",
self.sql_name,
values.join(",\n ")
)
}
pub fn to_typescript(&self) -> String {
let values: Vec<String> = self
.variants
.iter()
.map(|v| format!("'{}'", v.sql_value))
.collect();
format!("export type {} = {};", self.name, values.join(" | "))
}
}
#[derive(Debug, Clone)]
pub struct EnumVariant {
pub name: String,
pub sql_value: String,
pub int_value: Option<i32>,
pub doc: Option<String>,
}
impl EnumVariant {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
sql_value: to_snake_case(name),
int_value: None,
doc: None,
}
}
}
use crate::util::to_snake_case;
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::indexing_slicing)]
mod tests {
use super::*;
use crate::schema::field::FieldDef;
use crate::schema::model::TableDef;
use crate::schema::types::RustType;
#[test]
fn test_registry_basic() {
let registry = SchemaRegistry::new();
let mut table = TableDef::new("users", "User");
table.fields.push(FieldDef::new("id", RustType::Uuid));
registry.register_table(table.clone());
let retrieved = registry.get_table("users").unwrap();
assert_eq!(retrieved.name, "users");
assert_eq!(retrieved.struct_name, "User");
}
#[test]
fn test_enum_def() {
let mut enum_def = EnumDef::new("ProjectStatus");
enum_def.variants.push(EnumVariant::new("Draft"));
enum_def.variants.push(EnumVariant::new("Active"));
enum_def.variants.push(EnumVariant::new("Completed"));
let sql = enum_def.to_create_type_sql();
assert!(sql.contains("CREATE TYPE project_status AS ENUM"));
assert!(sql.contains("'draft'"));
assert!(sql.contains("'active'"));
let ts = enum_def.to_typescript();
assert!(ts.contains("export type ProjectStatus"));
assert!(ts.contains("'draft'"));
}
}