pixeluvw_supabase 0.1.0

A production-ready, high-performance Supabase SDK for Rust with middleware, retry logic, and Arc<Inner> architecture
Documentation
use convert_case::{Case, Casing};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt::Write;

/// Represents the Database Schema based on PostgREST OpenAPI spec.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Schema {
    /// definitions maps table/view names to their schema definition
    #[serde(rename = "definitions")]
    pub tables: HashMap<String, TableSchema>,
}

impl Schema {
    /// Generate Rust struct definitions for all tables in the 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, // usually "object"
}

impl TableSchema {
    /// Generate Rust struct definition for this table.
    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(),
        }
    }
}