Un ERP empaqueta varios reportes en un solo .zip para adjuntarlo a un correo, y recibe zips de afuera — de un banco, de un contador — que hay que abrir. std/zip es Nyx puro, sin zlib, así que el mismo programa corre en el servidor y compilado a wasm32-wasi. Todo ocurre en memoria; la receta no escribe archivos.
// Nyx by Example: armar y leer un archivo .zip. // // El caso real: un ERP empaqueta varios reportes en un solo .zip para // adjuntarlo a un correo, y recibe zips de afuera (de un banco, de un // contador) que hay que abrir. std/zip es Nyx puro, sin zlib: el mismo // programa corre en el servidor y compilado a wasm32-wasi. // // Lo que muestra: // - zip_writer() + add(nombre, datos) + finish(): los bytes del .zip. Cada // entrada va con DEFLATE salvo que comprimir la agrande; // - zip_read() lee el índice (directorio central); entry(nombre) descomprime // UNA entrada y verifica su CRC-32; // - los topes contra bombas zip (ZipReadOpts): un archivo que llega de // afuera no se lee sin límites; // - un zip que no es zip da Err, no datos a medias. // // Correr: nyx run (todo en memoria; no escribe archivos) import "std/zip" import "std/error" fn armar() -> String { var w: ZipWriter = zip_writer() var ventas: String = "fecha,monto\n" var i: int = 1 while i <= 200 { ventas = ventas + "2026-09-" + int_to_string(1 + i % 28) + "," + int_to_string(i * 15) + "\n" i = i + 1 } w.add("ventas.csv", ventas) // se repite mucho: DEFLATE lo achica w.add("notas/lea-me.txt", "Cierre de septiembre.\n") return w.finish() } fn main() -> int { let bytes: String = armar() print("zip armado: " + int_to_string(bytes.length()) + " bytes") // Lo que llega de afuera se lee con topes. Los de por omisión (64 MB de // zip, 256 MB por entrada, 512 MB en total) se pueden ajustar. var opts: ZipReadOpts = zip_read_opts() opts.max_entry = 1024 * 1024 match zip_read(bytes, opts) { Result.Ok(z) => { var arch: ZipArchive = z for nombre: String in arch.names() { print("entrada: " + nombre) } if arch.has("ventas.csv") { print("tiene ventas.csv") } match arch.entry("notas/lea-me.txt") { Result.Ok(txt) => { print("contenido: " + txt.trim()) } Result.Err(e) => { print("no se pudo leer la entrada: " + error_to_string(e)) return 1 } } match arch.entry("ventas.csv") { Result.Ok(csv) => { print("ventas.csv: " + int_to_string(csv.length()) + " bytes descomprimidos") } Result.Err(e) => { print("no se pudo leer la entrada: " + error_to_string(e)) return 1 } } } Result.Err(e) => { print("zip inválido: " + error_to_string(e)) return 1 } } // Un archivo que no es zip: Err con el motivo. match zip_read("esto no es un zip", zip_read_opts()) { Result.Ok(_) => { print("inesperado: lo aceptó") return 1 } Result.Err(e) => { print("rechazado: " + e.msg) } } return 0 }
zip armado: 1361 bytes entrada: ventas.csv entrada: notas/lea-me.txt tiene ventas.csv contenido: Cierre de septiembre. ventas.csv: 3073 bytes descomprimidos rechazado: zip: no es un zip (falta el fin del directorio central)
Cómo funciona
zip_writer(), add(nombre, datos) y finish() producen los bytes del archivo; cada entrada va con DEFLATE salvo que comprimir la agrande. Leer son dos pasos: zip_read() carga el índice (el directorio central) y entry(nombre) descomprime UNA entrada y verifica su CRC-32, de modo que una entrada dañada es un error y no datos equivocados.
Un archivo que llega de afuera no se lee sin límites: ZipReadOpts acota lo que una bomba zip puede hacerle al programa. Y unos bytes que no son un zip vuelven como Err — la última línea de la salida — nunca como datos a medias.