use ferriorm_core::schema::Schema;
use ferriorm_core::utils::to_snake_case;
use std::fs;
use std::path::Path;
use crate::client::generate_client_module;
use crate::enums::generate_enums_module;
use crate::formatter::format_token_stream;
use crate::model::generate_model_module;
pub fn generate(schema: &Schema, output_dir: &Path) -> Result<(), GenerateError> {
fs::create_dir_all(output_dir)
.map_err(|e| GenerateError::Io(format!("Failed to create output dir: {e}")))?;
let mut mod_entries = Vec::new();
if !schema.enums.is_empty() {
let tokens = generate_enums_module(&schema.enums);
let code = format_token_stream(tokens);
write_file(output_dir, "enums.rs", &code)?;
mod_entries.push("pub mod enums;".to_string());
}
for model in &schema.models {
let model_tokens = generate_model_module(model);
let relation_types = crate::relations::gen_relation_types(model, schema);
let relation_include = crate::relations::gen_find_many_include(model, schema);
let tokens = quote::quote! {
#model_tokens
#relation_types
#relation_include
};
let code = format_token_stream(tokens);
let filename = format!("{}.rs", to_snake_case(&model.name));
write_file(output_dir, &filename, &code)?;
mod_entries.push(format!("pub mod {};", to_snake_case(&model.name)));
}
let client_tokens = generate_client_module(schema);
let client_code = format_token_stream(client_tokens);
write_file(output_dir, "client.rs", &client_code)?;
mod_entries.push("pub mod client;".to_string());
let mut mod_content = String::from(
"// AUTO-GENERATED by ferriorm. Do not edit.\n\
//\n\
// NOTE: The generated code uses `#[derive(sqlx::FromRow)]` which requires\n\
// `sqlx` as a direct dependency in your Cargo.toml. The derive macro expands\n\
// to code with absolute `::sqlx::` paths that cannot be resolved through\n\
// re-exports alone. See the installation guide for required dependencies.\n\n",
);
for entry in &mod_entries {
mod_content.push_str(entry);
mod_content.push('\n');
}
mod_content.push('\n');
mod_content.push_str("pub use client::FerriormClient;\n");
if !schema.enums.is_empty() {
mod_content.push_str("pub use enums::*;\n");
}
let mod_path = output_dir.join("mod.rs");
fs::write(&mod_path, mod_content)
.map_err(|e| GenerateError::Io(format!("Failed to write {}: {e}", mod_path.display())))?;
Ok(())
}
fn write_file(dir: &Path, filename: &str, content: &str) -> Result<(), GenerateError> {
let path = dir.join(filename);
let full_content = format!("// AUTO-GENERATED by ferriorm. Do not edit.\n\n{content}");
fs::write(&path, full_content)
.map_err(|e| GenerateError::Io(format!("Failed to write {}: {e}", path.display())))?;
Ok(())
}
#[derive(Debug)]
pub enum GenerateError {
Io(String),
CodeGen(String),
}
impl std::fmt::Display for GenerateError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Io(msg) => write!(f, "IO error: {msg}"),
Self::CodeGen(msg) => write!(f, "Code generation error: {msg}"),
}
}
}
impl std::error::Error for GenerateError {}