use crate::generators;
use crate::parser;
use crate::ZodArgs;
use anyhow::{Context, Result};
use notify_debouncer_mini::{new_debouncer, notify::RecursiveMode};
use std::fs;
use std::path::Path;
use std::sync::mpsc::channel;
use std::time::Duration;
pub fn run(args: ZodArgs) -> Result<()> {
generate(&args)?;
if args.watch {
watch(&args)?;
}
Ok(())
}
fn generate(args: &ZodArgs) -> Result<()> {
if args.verbose {
println!("Parsing Rust files in: {}", args.input.display());
}
let parsed_types = parser::parse_directory(&args.input)
.with_context(|| format!("Failed to parse directory: {}", args.input.display()))?;
if args.verbose {
println!(
"[ok] Found {} types with validation rules",
parsed_types.len()
);
}
let typescript_code =
generators::zod::generate(&parsed_types).context("Failed to generate Zod schemas")?;
if let Some(parent) = args.output.parent() {
fs::create_dir_all(parent)
.with_context(|| format!("Failed to create output directory: {}", parent.display()))?;
}
fs::write(&args.output, &typescript_code)
.with_context(|| format!("Failed to write output file: {}", args.output.display()))?;
println!("[ok] Generated Zod schemas: {}", args.output.display());
println!(" {} types processed", parsed_types.len());
Ok(())
}
fn watch(args: &ZodArgs) -> Result<()> {
println!(
"\n[watch] Watching for changes in: {}",
args.input.display()
);
println!("[watch] Press Ctrl+C to stop\n");
let (tx, rx) = channel();
let mut debouncer =
new_debouncer(Duration::from_millis(500), tx).context("Failed to create file watcher")?;
debouncer
.watcher()
.watch(args.input.as_ref(), RecursiveMode::Recursive)
.with_context(|| format!("Failed to watch directory: {}", args.input.display()))?;
loop {
match rx.recv() {
Ok(Ok(events)) => {
let rust_changes: Vec<_> =
events.iter().filter(|e| is_rust_file(&e.path)).collect();
if !rust_changes.is_empty() {
if args.verbose {
for event in &rust_changes {
println!("[change] {}", event.path.display());
}
}
println!("\n[watch] Changes detected, regenerating...");
match generate(args) {
Ok(()) => println!("[watch] Regeneration complete\n"),
Err(e) => {
eprintln!("[error] Regeneration failed: {}", e);
eprintln!("[watch] Waiting for more changes...\n");
}
}
}
}
Ok(Err(error)) => {
eprintln!("[error] Watch error: {:?}", error);
}
Err(e) => {
eprintln!("[error] Channel error: {}", e);
break;
}
}
}
Ok(())
}
fn is_rust_file(path: &Path) -> bool {
path.extension().map(|ext| ext == "rs").unwrap_or(false)
}