tawasuyu

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

TOML 1572Shell 203Rust 109Markdown 993728 Definiciones

⎇ 7a6bfb4c4db7b90a94e22d1edac7d41786d265a9 crates/hammer-build/src/download.rs Rust · 78 líneas · 2.8 KB Historia Quién tocó qué crudo

Fase 4: patch_url / content_url remotos en .swm 7a6bfb4c4 · Sergio ·

1//! Descarga remota para `.swm`: `patch_url` (source_patch) y `content_url` (file_drop).
2//! Ver `docs/06-swm-format.md` §2. La red sólo se toca aquí y en `fetch` — nunca dentro del
3//! sandbox de build.
4//!
5//! Usa `curl` (ya presente en el rootfs del lab). Acepta cualquier esquema que curl entienda;
6//! en tests usamos `file://` para no depender de la red. La integridad la garantiza el
7//! llamador: el `file_drop` verifica BLAKE3 (`content_hash`) tras descargar; el `patch_url`
8//! se cubre indirectamente por el build reproducible + `expected_hash` del artefacto.
9
10use std::path::Path;
11use std::process::Command;
12
13/// Descarga `url` y devuelve su contenido en memoria. Falla con el stderr de curl si el
14/// código de salida no es 0 (incluye errores HTTP gracias a `-f`).
15pub fn fetch_url_bytes(url: &str) -> hammer_core::Result<Vec<u8>> {
16    let out = Command::new("curl")
17        .args([
18            "-fsSL",
19            "--retry",
20            "2",
21            "--connect-timeout",
22            "30",
23            "--max-time",
24            "300",
25            url,
26        ])
27        .output()
28        .map_err(|e| hammer_core::Error::Other(anyhow::anyhow!("spawn curl: {e}")))?;
29    if !out.status.success() {
30        return Err(hammer_core::Error::Other(anyhow::anyhow!(
31            "curl {url} falló (exit {}): {}",
32            out.status.code().map(|c| c.to_string()).unwrap_or_else(|| "?".into()),
33            String::from_utf8_lossy(&out.stderr).trim()
34        )));
35    }
36    Ok(out.stdout)
37}
38
39/// Descarga `url` y lo escribe en `dst` (creando los directorios padre).
40pub fn fetch_url_to_file(url: &str, dst: &Path) -> hammer_core::Result<()> {
41    let bytes = fetch_url_bytes(url)?;
42    if let Some(parent) = dst.parent() {
43        std::fs::create_dir_all(parent)?;
44    }
45    std::fs::write(dst, &bytes)?;
46    Ok(())
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52
53    #[test]
54    fn fetch_file_url_roundtrip() {
55        let d = tempfile::tempdir().unwrap();
56        let src = d.path().join("payload.txt");
57        std::fs::write(&src, b"contenido remoto\n").unwrap();
58        let url = format!("file://{}", src.display());
59        let got = fetch_url_bytes(&url).unwrap();
60        assert_eq!(got, b"contenido remoto\n");
61    }
62
63    #[test]
64    fn fetch_to_file_writes_and_creates_parents() {
65        let d = tempfile::tempdir().unwrap();
66        let src = d.path().join("p.txt");
67        std::fs::write(&src, b"abc").unwrap();
68        let dst = d.path().join("nested/dir/out.txt");
69        fetch_url_to_file(&format!("file://{}", src.display()), &dst).unwrap();
70        assert_eq!(std::fs::read(&dst).unwrap(), b"abc");
71    }
72
73    #[test]
74    fn missing_url_is_error() {
75        let err = fetch_url_bytes("file:///no/existe/aqui.bin").unwrap_err().to_string();
76        assert!(err.contains("curl"), "{err}");
77    }
78}