use std::path::Path;
use super::emit::Plan;
use super::model::validate_resource_name;
use super::naming::{pascal, snake};
use super::{Flags, GenerateError, ensure_project_root};
pub fn plan_service(project_root: &Path, name: &str) -> Result<Plan, GenerateError> {
ensure_project_root(project_root)?;
validate_resource_name(name)?;
let snake_name = snake(name);
let pascal_name = pascal(name);
let mut plan = Plan::new(project_root);
plan.create(
project_root.join("src").join("services").join(format!("{snake_name}.rs")),
render_service_file(&snake_name, &pascal_name),
);
let mod_path = project_root.join("src").join("services").join("mod.rs");
let existing_mod = std::fs::read_to_string(&mod_path).unwrap_or_default();
plan.modify(
mod_path,
super::schema_edit::add_mod_declaration(&existing_mod, &snake_name),
);
let main_path = project_root.join("src").join("main.rs");
let content = std::fs::read_to_string(&main_path).unwrap_or_default();
if !content.is_empty() {
plan.modify(
main_path,
super::schema_edit::update_main_rs(&content, &["services"], &[]),
);
}
Ok(plan)
}
fn render_service_file(snake_name: &str, pascal_name: &str) -> String {
format!(
r"//! Service generated by `autumn generate service`.
use autumn_web::prelude::*;
use autumn_web::service;
/// Service for `{snake_name}` business logic.
#[service]
pub trait {pascal_name}Service {{
fn deps();
}}
impl {pascal_name}ServiceImpl {{
// Add business logic methods here
}}
"
)
}
pub fn run(name: &str, flags: Flags) {
let cwd = match std::env::current_dir() {
Ok(d) => d,
Err(e) => {
eprintln!("Error: cannot determine current directory: {e}");
std::process::exit(1);
}
};
match plan_service(&cwd, name).and_then(|p| p.execute(flags)) {
Ok(()) => {}
Err(e) => {
eprintln!("Error: {e}");
std::process::exit(1);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
fn project() -> TempDir {
let tmp = TempDir::new().unwrap();
fs::write(tmp.path().join("Cargo.toml"), "[package]\nname=\"x\"\n").unwrap();
fs::create_dir_all(tmp.path().join("src")).unwrap();
fs::write(tmp.path().join("src/main.rs"), "fn main() {}\n").unwrap();
tmp
}
#[test]
fn plan_creates_service_file() {
let tmp = project();
let plan = plan_service(tmp.path(), "Order").unwrap();
assert!(
plan.actions
.iter()
.any(|action| action.path().ends_with("src/services/order.rs")),
"plan should create src/services/order.rs"
);
}
#[test]
fn execute_writes_service_skeleton() {
let tmp = project();
let plan = plan_service(tmp.path(), "Order").unwrap();
plan.execute(Flags::default()).unwrap();
let svc = fs::read_to_string(tmp.path().join("src/services/order.rs")).unwrap();
assert!(svc.contains("#[service]"));
assert!(svc.contains("pub trait OrderService"));
assert!(svc.contains("impl OrderServiceImpl"));
}
}