mecha10_cli/services/
build_source.rs1use anyhow::{Context, Result};
5use std::path::Path;
6
7pub fn extract_project_zip(zip_path: &Path, dest_dir: &Path) -> Result<()> {
15 let file = std::fs::File::open(zip_path).with_context(|| format!("Failed to open {}", zip_path.display()))?;
16 let mut archive = zip::ZipArchive::new(file).context("Failed to read project source zip")?;
17
18 for index in 0..archive.len() {
19 let mut entry = archive
20 .by_index(index)
21 .with_context(|| format!("Failed to read zip entry #{index}"))?;
22
23 let Some(relative_path) = entry.enclosed_name() else {
24 anyhow::bail!("Zip entry #{index} has an unsafe or invalid path: {:?}", entry.name());
25 };
26 let out_path = dest_dir.join(relative_path);
27
28 if entry.is_dir() {
29 std::fs::create_dir_all(&out_path)
30 .with_context(|| format!("Failed to create directory {}", out_path.display()))?;
31 continue;
32 }
33
34 if let Some(parent) = out_path.parent() {
35 std::fs::create_dir_all(parent)
36 .with_context(|| format!("Failed to create directory {}", parent.display()))?;
37 }
38
39 let mut out_file =
40 std::fs::File::create(&out_path).with_context(|| format!("Failed to create {}", out_path.display()))?;
41 std::io::copy(&mut entry, &mut out_file).with_context(|| format!("Failed to write {}", out_path.display()))?;
42 }
43
44 Ok(())
45}
46
47#[cfg(test)]
48mod tests {
49 use super::*;
50 use std::io::Write;
51 use tempfile::tempdir;
52
53 fn build_zip_with_entries(entries: &[(&str, &[u8])]) -> Vec<u8> {
54 let mut bytes = Vec::new();
55 {
56 let mut zip = zip::ZipWriter::new(std::io::Cursor::new(&mut bytes));
57 let options = zip::write::SimpleFileOptions::default();
58 for (name, contents) in entries {
59 zip.start_file(*name, options).unwrap();
60 zip.write_all(contents).unwrap();
61 }
62 zip.finish().unwrap();
63 }
64 bytes
65 }
66
67 #[test]
68 fn extract_project_zip_round_trips_a_nested_projects_files() {
69 let zip_bytes = build_zip_with_entries(&[
70 ("src/main.rs", b"fn main() {}"),
71 ("Cargo.toml", b"[package]\nname=\"x\"\n"),
72 ]);
73 let src_dir = tempdir().unwrap();
74 let zip_path = src_dir.path().join("out.zip");
75 std::fs::write(&zip_path, &zip_bytes).unwrap();
76
77 let dest_dir = tempdir().unwrap();
78 extract_project_zip(&zip_path, dest_dir.path()).unwrap();
79
80 assert_eq!(
81 std::fs::read_to_string(dest_dir.path().join("src/main.rs")).unwrap(),
82 "fn main() {}"
83 );
84 assert_eq!(
85 std::fs::read_to_string(dest_dir.path().join("Cargo.toml")).unwrap(),
86 "[package]\nname=\"x\"\n"
87 );
88 }
89
90 #[test]
91 fn extract_project_zip_errors_on_a_missing_zip_file() {
92 let dest_dir = tempdir().unwrap();
93 let err = extract_project_zip(&dest_dir.path().join("missing.zip"), dest_dir.path()).unwrap_err();
94 assert!(err.to_string().contains("Failed to open"));
95 }
96}