Skip to main content

shape_runtime/stdlib/
archive.rs

1//! Native `archive` module for creating and extracting zip/tar archives.
2//!
3//! Exports (post-cluster-#3, partial): archive.zip_extract, archive.tar_extract.
4//!
5//! Phase 2c partial migration: the two extract functions take `Array<int>`
6//! input (cluster #3 Array<T> marshal scope) and are wired here on the
7//! typed marshal layer. The two create functions (`archive.zip_create` /
8//! `archive.tar_create`) take `Array<{name: string, data: string}>` input
9//! which requires the typed-object marshal extension (cluster #4 in
10//! `docs/defections.md` 2026-05-06). They are NOT registered until that
11//! cluster lands; users calling them today get a "no such method" error
12//! rather than a half-typed shell. The create logic itself is uncomplicated
13//! (zip/tar writer over a Vec<u8> sink) and rebuilds in a few lines once
14//! the typed-object FromSlot impl exists.
15
16use super::byte_utils::bytes_from_i64_slice;
17use crate::marshal::register_typed_fn_1;
18use crate::module_exports::ModuleExports;
19use crate::typed_module_exports::{ConcreteReturn, ConcreteType, TypedReturn};
20
21/// Create the `archive` module with extraction functions registered.
22/// Create functions deferred to cluster #4 (see module doc comment).
23pub fn create_archive_module() -> ModuleExports {
24    let mut module = ModuleExports::new("std::core::archive");
25    module.description = "Archive extraction (zip, tar)".to_string();
26
27    // archive.zip_extract(data: Array<int>) -> Array<{name: string, data: string}>
28    register_typed_fn_1::<_, Vec<i64>>(
29        &mut module,
30        "zip_extract",
31        "Extract a zip archive from a byte array into an array of entries",
32        "data",
33        "Array<int>",
34        ConcreteType::ArrayObject("Array<{name: string, data: string}>".to_string()),
35        |data, _ctx| {
36            use std::io::{Cursor, Read};
37
38            let bytes = bytes_from_i64_slice(&data)
39                .map_err(|e| format!("archive.zip_extract(): {}", e))?;
40
41            let cursor = Cursor::new(bytes);
42            let mut archive = zip::ZipArchive::new(cursor)
43                .map_err(|e| format!("archive.zip_extract() invalid zip: {}", e))?;
44
45            let mut entries: Vec<Vec<(String, ConcreteReturn)>> = Vec::new();
46            for i in 0..archive.len() {
47                let mut file = archive.by_index(i).map_err(|e| {
48                    format!("archive.zip_extract() failed to read entry {}: {}", i, e)
49                })?;
50
51                if file.is_dir() {
52                    continue;
53                }
54
55                let name = file.name().to_string();
56                let mut contents = String::new();
57                file.read_to_string(&mut contents).map_err(|e| {
58                    format!("archive.zip_extract() failed to read '{}': {}", name, e)
59                })?;
60
61                entries.push(vec![
62                    ("name".to_string(), ConcreteReturn::String(name)),
63                    ("data".to_string(), ConcreteReturn::String(contents)),
64                ]);
65            }
66
67            Ok(TypedReturn::ArrayObjectPairs(entries))
68        },
69    );
70
71    // archive.tar_extract(data: Array<int>) -> Array<{name: string, data: string}>
72    register_typed_fn_1::<_, Vec<i64>>(
73        &mut module,
74        "tar_extract",
75        "Extract a tar archive from a byte array into an array of entries",
76        "data",
77        "Array<int>",
78        ConcreteType::ArrayObject("Array<{name: string, data: string}>".to_string()),
79        |data, _ctx| {
80            use std::io::{Cursor, Read};
81
82            let bytes = bytes_from_i64_slice(&data)
83                .map_err(|e| format!("archive.tar_extract(): {}", e))?;
84
85            let cursor = Cursor::new(bytes);
86            let mut archive = tar::Archive::new(cursor);
87
88            let mut entries: Vec<Vec<(String, ConcreteReturn)>> = Vec::new();
89            for entry_result in archive
90                .entries()
91                .map_err(|e| format!("archive.tar_extract() invalid tar: {}", e))?
92            {
93                let mut entry = entry_result
94                    .map_err(|e| format!("archive.tar_extract() failed to read entry: {}", e))?;
95
96                if entry.header().entry_type().is_dir() {
97                    continue;
98                }
99
100                let name = entry
101                    .path()
102                    .map_err(|e| format!("archive.tar_extract() invalid path: {}", e))?
103                    .to_string_lossy()
104                    .to_string();
105
106                let mut contents = String::new();
107                entry.read_to_string(&mut contents).map_err(|e| {
108                    format!("archive.tar_extract() failed to read '{}': {}", name, e)
109                })?;
110
111                entries.push(vec![
112                    ("name".to_string(), ConcreteReturn::String(name)),
113                    ("data".to_string(), ConcreteReturn::String(contents)),
114                ]);
115            }
116
117            Ok(TypedReturn::ArrayObjectPairs(entries))
118        },
119    );
120
121    module
122}