use crate::error::{Result, SpliceError};
use crate::validate::validate_rust_snippet;
use crate::write::write_atomic;
use std::fs;
use std::path::Path;
pub fn create_file_with_validation(
file_path: &Path,
code: &str,
workspace_dir: &Path,
validate_only: bool,
) -> Result<crate::validate::SnippetValidation> {
if file_path.exists() {
return Err(SpliceError::IoContext {
context: format!("File already exists: {}", file_path.display()),
source: std::io::Error::new(std::io::ErrorKind::AlreadyExists, "file exists"),
});
}
let validation = validate_rust_snippet(code, Some(workspace_dir))?;
if validate_only {
return Ok(validation);
}
if !validation.is_valid {
return Err(SpliceError::IoContext {
context: format!(
"Code validation failed with {} error(s). Use --validate-only to see errors.",
validation.errors.len()
),
source: std::io::Error::new(std::io::ErrorKind::InvalidData, "validation failed"),
});
}
if let Some(parent) = file_path.parent() {
if !parent.exists() {
fs::create_dir_all(parent)?;
}
}
write_atomic(file_path, code.as_bytes(), "create")?;
Ok(validation)
}
pub fn create_file_with_module(
file_path: &Path,
code: &str,
workspace_dir: &Path,
add_mod_declaration: bool,
) -> Result<crate::validate::SnippetValidation> {
let validation = create_file_with_validation(file_path, code, workspace_dir, false)?;
if add_mod_declaration {
add_module_declaration(file_path, workspace_dir)?;
}
Ok(validation)
}
fn add_module_declaration(file_path: &Path, workspace_dir: &Path) -> Result<()> {
let file_stem = file_path
.file_stem()
.and_then(|s| s.to_str())
.ok_or_else(|| SpliceError::IoContext {
context: "Cannot extract module name from file path".to_string(),
source: std::io::Error::new(std::io::ErrorKind::InvalidInput, "invalid path"),
})?;
let parent_dir = file_path.parent().ok_or_else(|| SpliceError::IoContext {
context: "File has no parent directory".to_string(),
source: std::io::Error::new(std::io::ErrorKind::InvalidInput, "no parent"),
})?;
let mod_rs = parent_dir.join("mod.rs");
let parent_module = if mod_rs.exists() {
mod_rs
} else {
if let Some(dir_name) = parent_dir.file_name().and_then(|n| n.to_str()) {
parent_dir
.parent()
.unwrap()
.join(format!("{}.rs", dir_name))
} else {
if parent_dir.ends_with("src") {
workspace_dir.join("src").join("lib.rs")
} else {
return Err(SpliceError::IoContext {
context: "Cannot determine parent module file".to_string(),
source: std::io::Error::new(std::io::ErrorKind::NotFound, "no parent module"),
});
}
}
};
if parent_module.exists() {
let content = fs::read_to_string(&parent_module)?;
let mod_decl = format!("mod {};", file_stem);
if content.contains(&mod_decl) {
return Ok(());
}
let mod_line = format!("mod {};", file_stem);
let new_content = if content.trim_end().ends_with('}') {
if let Some(pos) = content.rfind('}') {
let mut updated = content.clone();
updated.insert_str(pos, &format!("{}\n", mod_line));
updated
} else {
format!("{}\n{}\n", content.trim_end(), mod_line)
}
} else {
format!("{}\n{}\n", content.trim_end(), mod_line)
};
fs::write(&parent_module, new_content)?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_create_file_with_valid_code() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("test_module.rs");
let code = r#"
pub fn test_function() -> i32 {
42
}
"#;
let result = create_file_with_validation(
&file_path,
code,
temp_dir.path(),
false, );
assert!(result.is_ok());
assert!(file_path.exists());
let content = fs::read_to_string(&file_path).unwrap();
assert_eq!(content.trim(), code.trim());
}
#[test]
fn test_create_file_rejects_invalid_code() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("invalid.rs");
let code = r#"
pub fn invalid() {
let x: NonExistentType = 5;
}
"#;
let result = create_file_with_validation(&file_path, code, temp_dir.path(), false);
assert!(result.is_err());
assert!(!file_path.exists());
}
#[test]
fn test_validate_only_mode() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("test.rs");
let code = r#"
pub fn test() -> i32 { 42 }
"#;
let result = create_file_with_validation(
&file_path,
code,
temp_dir.path(),
true, );
assert!(result.is_ok());
assert!(!file_path.exists()); }
#[test]
fn test_prevent_overwrite_existing_file() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("existing.rs");
fs::write(&file_path, "old content").unwrap();
let code = r#"
pub fn new() -> i32 { 42 }
"#;
let result = create_file_with_validation(&file_path, code, temp_dir.path(), false);
assert!(result.is_err());
let content = fs::read_to_string(&file_path).unwrap();
assert_eq!(content, "old content");
}
#[test]
fn test_create_file_creates_parent_directories() {
let temp_dir = TempDir::new().unwrap();
let nested_path = temp_dir.path().join("nested").join("dir").join("file.rs");
let code = r#"
pub fn nested() -> i32 { 42 }
"#;
let result = create_file_with_validation(&nested_path, code, temp_dir.path(), false);
assert!(result.is_ok());
assert!(nested_path.exists());
assert!(nested_path.parent().unwrap().exists());
}
#[test]
fn test_create_file_with_empty_code() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("empty.rs");
let code = "";
let result = create_file_with_validation(&file_path, code, temp_dir.path(), false);
assert!(result.is_ok());
assert!(file_path.exists());
let content = fs::read_to_string(&file_path).unwrap();
assert_eq!(content, "");
}
#[test]
fn test_add_module_declaration_to_mod_rs() {
let temp_dir = TempDir::new().unwrap();
let src_dir = temp_dir.path().join("src");
let commands_dir = src_dir.join("commands");
fs::create_dir_all(&commands_dir).unwrap();
let mod_rs = commands_dir.join("mod.rs");
fs::write(&mod_rs, "// Module file\n").unwrap();
let new_file = commands_dir.join("new.rs");
fs::write(&new_file, "pub fn new() {}").unwrap();
let result = add_module_declaration(&new_file, temp_dir.path());
assert!(result.is_ok());
let content = fs::read_to_string(&mod_rs).unwrap();
assert!(content.contains("mod new;"));
}
#[test]
fn test_add_module_declaration_idempotent() {
let temp_dir = TempDir::new().unwrap();
let src_dir = temp_dir.path().join("src");
fs::create_dir_all(&src_dir).unwrap();
let mod_rs = src_dir.join("mod.rs");
fs::write(&mod_rs, "mod test;\n").unwrap();
let test_file = src_dir.join("test.rs");
fs::write(&test_file, "pub fn test() {}").unwrap();
let result = add_module_declaration(&test_file, temp_dir.path());
assert!(result.is_ok());
let content = fs::read_to_string(&mod_rs).unwrap();
let count = content.matches("mod test;").count();
assert_eq!(count, 1, "Module declaration should not be duplicated");
}
}