pub mod rust_axum;
use anyhow::{Context, Result};
use clap::{Args, Subcommand};
use mockforge_core::codegen::backend_generator::{extract_routes, extract_schemas};
use mockforge_core::openapi::spec::OpenApiSpec;
use mockforge_plugin_core::backend_generator::{
BackendGenerationMetadata, BackendGenerationResult, BackendGeneratorConfig,
BackendGeneratorPlugin, Complexity, TodoCategory, TodoItem,
};
use mockforge_plugin_core::GeneratedFile;
use std::collections::HashMap;
use std::path::PathBuf;
#[derive(Debug, Subcommand)]
pub enum BackendCommand {
Generate(GenerateArgs),
List,
}
#[derive(Debug, Args)]
pub struct GenerateArgs {
#[arg(short, long)]
pub spec: PathBuf,
#[arg(short, long, default_value = "rust")]
pub backend: String,
#[arg(short, long, default_value = "./generated-backend")]
pub output: PathBuf,
#[arg(short, long)]
pub port: Option<u16>,
#[arg(long)]
pub with_tests: bool,
#[arg(long)]
pub with_docs: bool,
#[arg(long)]
pub database: Option<String>,
#[arg(long, default_value = "true")]
pub generate_todo_md: bool,
}
pub fn get_available_generators() -> Vec<Box<dyn BackendGeneratorPlugin>> {
vec![
Box::new(rust_axum::RustAxumGenerator::new()),
]
}
pub fn get_generator(backend_type: &str) -> Option<Box<dyn BackendGeneratorPlugin>> {
match backend_type {
"rust" | "rust-axum" | "axum" => Some(Box::new(rust_axum::RustAxumGenerator::new())),
_ => None,
}
}
pub fn list_generators() -> Vec<(String, String)> {
get_available_generators()
.iter()
.map(|gen| (gen.backend_type().to_string(), gen.backend_name().to_string()))
.collect()
}
pub async fn generate_backend_with_spec(
spec: &OpenApiSpec,
backend_type: &str,
config: &BackendGeneratorConfig,
) -> Result<BackendGenerationResult> {
let generator = get_generator(backend_type)
.ok_or_else(|| anyhow::anyhow!("Unknown backend type: {}", backend_type))?;
match backend_type {
"rust" | "rust-axum" | "axum" => rust_axum::generate_rust_axum_backend(spec, config).await,
_ => Err(anyhow::anyhow!("Backend type '{}' not yet implemented", backend_type)),
}
}
pub async fn handle_backend_command(command: BackendCommand) -> Result<()> {
match command {
BackendCommand::Generate(args) => handle_generate(args).await,
BackendCommand::List => handle_list(),
}
}
async fn handle_generate(args: GenerateArgs) -> Result<()> {
use colored::*;
use std::fs;
println!("{}", "🚀 Generating backend server code...".bright_green().bold());
println!("📄 Loading OpenAPI specification from: {}", args.spec.display());
let spec_path = if args.spec.is_absolute() {
args.spec.clone()
} else {
std::env::current_dir()
.context("Failed to get current working directory")?
.join(&args.spec)
};
let spec_path_str = match spec_path.canonicalize() {
Ok(canonical) => canonical.to_string_lossy().to_string(),
Err(_) => spec_path.to_string_lossy().to_string(), };
let spec = OpenApiSpec::from_file(&spec_path_str)
.await
.context("Failed to load OpenAPI specification")?;
println!("✅ Loaded specification: {} v{}", spec.spec.info.title, spec.spec.info.version);
fs::create_dir_all(&args.output).context("Failed to create output directory")?;
let mut config = BackendGeneratorConfig {
output_dir: args.output.to_string_lossy().to_string(),
port: args.port,
base_url: None,
with_tests: args.with_tests,
with_docs: args.with_docs,
database: args.database.clone(),
generate_todo_md: args.generate_todo_md,
options: HashMap::new(),
};
if config.port.is_none() {
if let Some(gen) = get_generator(&args.backend) {
config.port = Some(gen.default_port());
}
}
println!("⚙️ Generating {} backend...", args.backend);
let result = generate_backend_with_spec(&spec, &args.backend, &config)
.await
.context("Failed to generate backend code")?;
println!("📝 Writing {} files...", result.files.len());
for file in &result.files {
let file_path = args.output.join(PathBuf::from(&file.path));
if let Some(parent) = file_path.parent() {
fs::create_dir_all(parent)?;
}
fs::write(&file_path, &file.content)
.with_context(|| format!("Failed to write file: {}", file_path.display()))?;
}
println!("{}", "✅ Backend generation complete!".bright_green().bold());
println!("\n📊 Summary:");
println!(" - Framework: {}", result.metadata.backend_name);
println!(" - Operations: {}", result.metadata.operation_count);
println!(" - Schemas: {}", result.metadata.schema_count);
println!(" - Files generated: {}", result.files.len());
println!(" - TODOs: {}", result.todos.len());
println!("\n📁 Output directory: {}", args.output.display());
println!("\n🚀 Next steps:");
println!(" 1. cd {}", args.output.display());
println!(" 2. Review TODO.md for implementation tasks");
println!(" 3. Implement business logic in handlers/");
println!(" 4. Run: cargo run");
if !result.warnings.is_empty() {
println!("\n⚠️ Warnings:");
for warning in &result.warnings {
println!(" - {}", warning);
}
}
Ok(())
}
fn handle_list() -> Result<()> {
use colored::*;
println!("{}", "📋 Available Backend Generators:".bright_green().bold());
println!();
let generators = list_generators();
for (backend_type, name) in generators {
println!(" {} - {}", backend_type.bright_cyan(), name);
}
Ok(())
}