use crate::ir::{ApiSpec, TypeDef};
use super::{Config, VariantMode};
use super::schema::{topological_sort_scc, get_dependencies};
use anyhow::Result;
use heck::ToLowerCamelCase;
pub fn generate(spec: &ApiSpec, config: &Config) -> Result<String> {
let mut output = String::new();
output.push_str("// SPDX-License-Identifier: PMPL-1.0-or-later\n");
output.push_str("// Generated by rescript-openapi - DO NOT EDIT\n");
output.push_str(&format!("// Source: {} v{}\n\n", spec.title, spec.version));
let sccs = topological_sort_scc(&spec.types);
for scc in sccs {
output.push_str(&generate_scc(&scc, config));
output.push('\n');
}
Ok(output)
}
pub fn generate_scc(scc: &[&TypeDef], config: &Config) -> String {
let mut output = String::new();
if scc.len() == 1 {
let type_def = scc[0];
let name = match type_def {
TypeDef::Record { name, .. } => name.to_lower_camel_case(),
TypeDef::Variant { name, .. } => name.to_lower_camel_case(),
TypeDef::Alias { name, .. } => name.to_lower_camel_case(),
};
let deps = get_dependencies(type_def);
let is_recursive = deps.contains(&name);
output.push_str(&generate_type(type_def, is_recursive, config));
} else {
for (i, type_def) in scc.iter().enumerate() {
if i == 0 {
output.push_str(&generate_type(type_def, true, config)); } else {
let def = generate_type(type_def, false, config);
let def = def.replacen("type ", "and ", 1);
output.push_str(&def);
}
}
}
output
}
pub fn generate_type(type_def: &TypeDef, is_rec: bool, config: &Config) -> String {
let mut output = String::new();
let keyword = if is_rec { "type rec" } else { "type" };
match type_def {
TypeDef::Record { name, doc, fields } => {
if let Some(doc) = doc {
output.push_str(&format!("/** {} */\n", doc));
}
let type_name = name.to_lower_camel_case();
output.push_str(&format!("{} {} = {{\n", keyword, type_name));
for field in fields {
if let Some(doc) = &field.doc {
output.push_str(&format!(" /** {} */\n", doc));
}
if field.name != field.original_name {
output.push_str(&format!(" @as(\"{}\") ", field.original_name));
} else {
output.push_str(" ");
}
output.push_str(&format!("{}: {},\n", field.name, field.ty.to_rescript()));
}
output.push_str("}\n");
}
TypeDef::Variant { name, doc, cases } => {
if let Some(doc) = doc {
output.push_str(&format!("/** {} */\n", doc));
}
let type_name = name.to_lower_camel_case();
let has_payloads = cases.iter().any(|c| c.payload.is_some());
if has_payloads {
output.push_str(&format!("{} {} =\n", keyword, type_name));
for case in cases {
match &case.payload {
Some(ty) => {
output.push_str(&format!(" | {}({})\n", case.name, ty.to_rescript()));
}
None => {
output.push_str(&format!(" | {}\n", case.name));
}
}
}
} else if config.variant_mode == VariantMode::Standard {
output.push_str(&format!("{} {} =\n", keyword, type_name));
for case in cases {
output.push_str(&format!(" | @as(\"{}\") {}\n", case.original_name, case.name));
}
} else {
output.push_str(&format!("{} {} = [\n", keyword, type_name));
for case in cases {
output.push_str(&format!(" | #{}\n", case.name));
}
output.push_str("]\n");
}
}
TypeDef::Alias { name, doc, target } => {
if let Some(doc) = doc {
output.push_str(&format!("/** {} */\n", doc));
}
let type_name = name.to_lower_camel_case();
output.push_str(&format!("{} {} = {}\n", keyword, type_name, target.to_rescript()));
}
}
output
}