use crate::cli_generator::types::{CliProject, Noun, Verb};
use crate::utils::error::{Error, Result};
use std::path::Path;
use tera::{Context, Tera};
pub struct DomainLayerGenerator {
tera: Tera,
}
impl DomainLayerGenerator {
pub fn new(template_dir: &Path) -> Result<Self> {
let pattern = format!("{}/**/*.tmpl", template_dir.display());
let tera = Tera::new(&pattern).map_err(|e| {
Error::with_context(
&format!("Failed to load templates from: {}", template_dir.display()),
&e.to_string(),
)
})?;
Ok(Self { tera })
}
pub fn generate(&self, project: &CliProject, output_dir: &Path) -> Result<()> {
let core_crate = project
.domain_crate
.as_ref()
.ok_or_else(|| Error::new("domain_crate is required for domain layer generation"))?;
let core_dir = output_dir.join("crates").join(core_crate);
let core_src = core_dir.join("src");
std::fs::create_dir_all(&core_src).map_err(|e| {
Error::with_context("Failed to create domain src directory", &e.to_string())
})?;
let mut context = Context::new();
context.insert("project_name", &project.name);
context.insert("core_crate", core_crate);
context.insert("version", &project.version);
context.insert("edition", &project.edition);
context.insert("license", &project.license);
context.insert("authors", &project.authors);
self.render_template(
"cli/core-crate/Cargo.toml.tmpl",
&context,
&core_dir.join("Cargo.toml"),
)?;
let domain_modules: Vec<String> = project.nouns.iter().map(|n| n.name.clone()).collect();
context.insert("domain_modules", &domain_modules);
if let Some(first_noun) = project.nouns.first() {
context.insert("domain_module", &first_noun.name);
}
self.render_template(
"cli/core-crate/src/lib.rs.tmpl",
&context,
&core_src.join("lib.rs"),
)?;
for noun in &project.nouns {
self.generate_domain_module(noun, project, &core_src, &context)?;
}
Ok(())
}
fn generate_domain_module(
&self, noun: &Noun, project: &CliProject, core_src: &Path, base_context: &Context,
) -> Result<()> {
let domain_dir = core_src.join(&noun.name);
std::fs::create_dir_all(&domain_dir)?;
let mut context = base_context.clone();
context.insert("domain_module", &noun.name);
if let Some(first_verb) = noun.verbs.first() {
context.insert("verb", &first_verb.name);
self.render_template(
"cli/core-crate/src/domain/mod.rs.tmpl",
&context,
&domain_dir.join("mod.rs"),
)?;
}
for verb in &noun.verbs {
self.generate_verb_function(verb, noun, project, &domain_dir, &context)?;
}
Ok(())
}
fn generate_verb_function(
&self, verb: &Verb, noun: &Noun, _project: &CliProject, domain_dir: &Path,
base_context: &Context,
) -> Result<()> {
let mut context = base_context.clone();
context.insert("verb", &verb.name);
context.insert("domain_module", &noun.name);
self.render_template(
"cli/core-crate/src/domain/verb.rs.tmpl",
&context,
&domain_dir.join(format!("{}.rs", verb.name)),
)?;
Ok(())
}
fn render_template(&self, template: &str, context: &Context, output: &Path) -> Result<()> {
let content = self.tera.render(template, context).map_err(|e| {
Error::with_context("Failed to render template", &format!("{}: {}", template, e))
})?;
if let Some(parent) = output.parent() {
std::fs::create_dir_all(parent)?;
}
std::fs::write(output, content).map_err(|e| {
Error::with_context(
&format!("Failed to write: {}", output.display()),
&e.to_string(),
)
})?;
Ok(())
}
}