//! code 模式转译:language + code → argv + 临时脚本文件。 //! //! code 模式(`/v1/exec` 的 `code`/`language` 字段)由本模块转译为 argv,复用现有进程执行路径。 //! 走脚本文件而非 `-c`,避免多行 / 模块导入 / shebang 问题;脚本经 `tempfile` 写入 OS 临时目录, //! 返回的 `CodeTranslation.script_path`(`TempPath`)由调用方持有至执行结束——drop 时自动删除。 //! 流式执行场景须把 `CodeTranslation` move 进 SSE 闭包,确保子进程 open 脚本前文件不被删。 use std::io::Write; use tempfile::NamedTempFile; /// code 模式转译结果:argv 用于执行,script_path 须由调用方持有至执行结束。 pub struct CodeTranslation { pub argv: Vec, /// 临时脚本路径,持有至执行结束(RAII:drop 时自动删除) #[allow(dead_code)] pub script_path: tempfile::TempPath, } /// 语言 → (interpreter, 脚本扩展名)。P0 仅支持 python;node/java 留 P2。 pub fn interpreter_for(language: &str) -> Result<(&'static str, &'static str), String> { match language { "python" => Ok(("python3", "py")), other => Err(format!("不支持的语言: {other}(P0 仅支持 python)")), } } /// 写 code 到临时脚本文件,返回 argv 与 TempPath(调用方持有至执行结束)。 pub async fn translate(language: &str, code: &str) -> Result { let (interp, ext) = interpreter_for(language)?; // NamedTempFile 是同步 IO,文件小、写一次,无需 tokio 化。 let mut tmp = NamedTempFile::with_suffix(format!(".{ext}")) .map_err(|e| format!("创建临时脚本失败: {e}"))?; tmp.write_all(code.as_bytes()) .map_err(|e| format!("写入临时脚本失败: {e}"))?; // detach 成 TempPath:文件留在磁盘,TempPath drop 时删除。 let script_path = tmp.into_temp_path(); let argv = vec![ interp.to_string(), script_path.to_string_lossy().into_owned(), ]; Ok(CodeTranslation { argv, script_path }) } #[cfg(test)] mod tests { use super::*; #[test] fn interpreter_for_python_ok_others_rejected() { assert_eq!(interpreter_for("python").unwrap(), ("python3", "py")); assert!(interpreter_for("node").is_err()); assert!(interpreter_for("").is_err()); } #[tokio::test] async fn translate_python_writes_script_and_returns_argv() { let code = "print(1+1)\nimport sys\nprint(sys.argv)"; let trans = translate("python", code).await.unwrap(); assert_eq!(trans.argv[0], "python3"); assert!(trans.argv[1].ends_with(".py")); let on_disk = std::fs::read_to_string(&trans.argv[1]).unwrap(); assert_eq!(on_disk, code); } #[tokio::test] async fn translate_unsupported_language_errors() { assert!(translate("ruby", "puts 1").await.is_err()); } #[tokio::test] async fn translate_temp_path_keeps_file_until_drop() { let trans = translate("python", "x = 1").await.unwrap(); let path = trans.argv[1].clone(); assert!(std::path::Path::new(&path).exists(), "持有 TempPath 时文件应存在"); drop(trans); assert!(!std::path::Path::new(&path).exists(), "TempPath drop 后文件应删除"); } }