123 / Entrada, salida y datos

Armar y leer un archivo .zip

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.

123-zip-crear-y-leer.nxFuente →
// 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
}
Salidastdout
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.