use crate::schema::{EnumShape, Field, Shape, StructShape};
fn ts_type(shape: &Shape) -> String {
match shape {
Shape::Bool => "boolean".into(),
Shape::Int { .. } | Shape::Float { .. } => "number".into(),
Shape::List(of) => format!("{}[]", ts_type(&of())),
Shape::Map(of) => format!("Record<string, {}>", ts_type(&of())),
Shape::Optional(of) => ts_type(&of()),
Shape::Struct(s) => s.name.into(),
Shape::Enum(e) => e.name.into(),
Shape::Any => "unknown".into(),
_ => "string".into(),
}
}
fn field(f: &Field) -> String {
let shape = (f.shape)();
let optional = !f.required || matches!(shape, Shape::Optional(_));
let doc = doc_line(f.doc, " ");
let mark = if optional { "?" } else { "" };
format!("{doc} {}{mark}: {};\n", f.name, ts_type(&shape))
}
fn doc_line(doc: &str, indent: &str) -> String {
let doc = doc.trim();
if doc.is_empty() {
String::new()
} else {
format!("{indent}/** {} */\n", doc.replace('\n', " "))
}
}
fn interface(s: &StructShape) -> String {
let fields: String = s.fields.iter().map(field).collect();
format!(
"{}export interface {} {{\n{fields}}}\n",
doc_line(s.doc, ""),
s.name
)
}
fn enumeration(e: &EnumShape) -> String {
let mut out = doc_line(e.doc, "");
let consts = lower_first(e.name);
out.push_str(&format!("export const {consts} = {{\n"));
for v in &e.variants {
out.push_str(&format!(" {}: {:?},\n", v.name.to_uppercase(), v.name));
}
out.push_str("} as const;\n");
let variants: Vec<String> = e
.variants
.iter()
.map(|v| match e.tag {
None => format!("{:?}", v.name),
Some(tag) => {
let fields: String = v
.fields
.iter()
.map(|f| format!(" {}", field(f).trim()))
.collect();
format!("{{ {tag}: {:?};{fields} }}", v.name)
}
})
.collect();
out.push_str(&format!(
"export type {} = {};\n",
e.name,
variants.join(" | ")
));
out
}
fn lower_first(name: &str) -> String {
let mut c = name.chars();
c.next()
.map(|f| f.to_lowercase().chain(c).collect())
.unwrap_or_default()
}
pub(super) fn declarations(shapes: &[Shape]) -> String {
shapes
.iter()
.filter_map(|s| match s {
Shape::Struct(s) => Some(interface(s)),
Shape::Enum(e) => Some(enumeration(e)),
_ => None,
})
.collect::<Vec<_>>()
.join("\n")
}