use super::ddl::{Schema, SqlType, Table};
use crate::commands::scaffold_common::pascal_identifier_from_snake;
pub(crate) fn render(schema: &Schema, header: &str, type_suffix: &str) -> String {
let mut out = String::new();
out.push_str(header);
let mut first = true;
for (table_name, table) in schema.tables() {
if !first {
out.push('\n');
}
first = false;
out.push_str(&render_table(table_name, table, type_suffix));
}
out
}
fn render_table(table_name: &str, table: &Table, type_suffix: &str) -> String {
let type_name = format!("{}{type_suffix}", pascal_identifier_from_snake(table_name));
let mut out = format!("type {type_name} = {{\n");
for column in &table.columns {
let harn_type = harn_type_for(&column.sql_type, column.not_null);
out.push_str(&format!(" {}: {harn_type},\n", column.name));
}
out.push_str("}\n");
out
}
fn harn_type_for(sql_type: &SqlType, not_null: bool) -> String {
let mut ty = base_harn_type(&sql_type.base);
for _ in 0..sql_type.array_dims {
ty = format!("list<{ty}>");
}
if not_null {
ty
} else {
format!("{ty}?")
}
}
fn base_harn_type(base: &str) -> String {
const POINT: &str = "{x: float, y: float}";
match base {
"bool" | "boolean" => "bool",
"smallint" | "int2" | "smallserial" | "serial2" | "integer" | "int" | "int4" | "serial"
| "serial4" | "bigint" | "int8" | "bigserial" | "serial8" => "int",
"real" | "float4" | "double precision" | "float8" | "double" => "float",
"json" | "jsonb" => "any",
"bytea" => "bytes",
"hstore" => return "dict<string, string?>".to_string(),
"int4range" | "int8range" => return range_type("int"),
"numrange" | "daterange" | "tsrange" | "tstzrange" => return range_type("string"),
"point" => return POINT.to_string(),
"line" => return "{a: float, b: float, c: float}".to_string(),
"lseg" => return format!("{{start: {POINT}, end: {POINT}}}"),
"box" => return format!("{{upper_right: {POINT}, lower_left: {POINT}}}"),
"path" => return format!("{{closed: bool, points: list<{POINT}>}}"),
"polygon" => return format!("{{points: list<{POINT}>}}"),
"circle" => return format!("{{center: {POINT}, radius: float}}"),
_ => "string",
}
.to_string()
}
fn range_type(inner: &str) -> String {
format!("{{start: {inner}?, end: {inner}?, start_inclusive: bool, end_inclusive: bool}}")
}