use convert_case::{Case, Casing};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt::Write;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Schema {
#[serde(rename = "definitions")]
pub tables: HashMap<String, TableSchema>,
}
impl Schema {
pub fn generate_structs(&self) -> String {
let mut code = String::new();
writeln!(code, "// Generated by pixeluvw_supabase").unwrap();
writeln!(code, "use serde::{{Serialize, Deserialize}};").unwrap();
writeln!(code, "use chrono::{{DateTime, Utc}};").unwrap();
writeln!(code, "use uuid::Uuid;").unwrap();
writeln!(code).unwrap();
for (name, table) in &self.tables {
code.push_str(&table.to_rust_struct(name));
writeln!(code).unwrap();
}
code
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TableSchema {
#[serde(default)]
pub description: Option<String>,
pub properties: HashMap<String, ColumnSchema>,
#[serde(default)]
pub required: Vec<String>,
#[serde(rename = "type")]
pub type_: String, }
impl TableSchema {
pub fn to_rust_struct(&self, table_name: &str) -> String {
let mut code = String::new();
let struct_name = table_name.to_case(Case::Pascal);
if let Some(desc) = &self.description {
writeln!(code, "/// {}", desc).unwrap();
} else {
writeln!(code, "/// Table: {}", table_name).unwrap();
}
writeln!(code, "#[derive(Debug, Clone, Serialize, Deserialize)]").unwrap();
writeln!(code, "pub struct {} {{", struct_name).unwrap();
for (prop_name, prop) in &self.properties {
let mut rust_type = prop.map_to_rust_type();
let is_required = self.required.contains(prop_name);
if !is_required {
rust_type = format!("Option<{}>", rust_type);
}
let field_name = prop_name.to_case(Case::Snake);
if field_name != *prop_name {
writeln!(code, " #[serde(rename = \"{}\")]", prop_name).unwrap();
}
if let Some(desc) = &prop.description {
writeln!(code, " /// {}", desc.replace("\n", " ")).unwrap();
}
writeln!(code, " pub {}: {},", field_name, rust_type).unwrap();
}
writeln!(code, "}}").unwrap();
code
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ColumnSchema {
#[serde(rename = "type")]
pub type_: Option<String>,
pub format: Option<String>,
pub description: Option<String>,
#[serde(default)]
pub default: Option<serde_json::Value>,
}
impl ColumnSchema {
pub fn map_to_rust_type(&self) -> String {
match self.type_.as_deref() {
Some("integer") => {
if self.format.as_deref() == Some("int8") {
"i64".to_string()
} else {
"i32".to_string()
}
}
Some("number") => "f64".to_string(),
Some("boolean") => "bool".to_string(),
Some("string") => match self.format.as_deref() {
Some("date-time") => "DateTime<Utc>".to_string(),
Some("uuid") => "Uuid".to_string(),
_ => "String".to_string(),
},
_ => "serde_json::Value".to_string(),
}
}
}