zippity 0.5.0

Library for asynchronously creating a ZIP file on the fly.
Documentation
#![allow(clippy::print_stdout)]
//! Example of a ZIP generating enpdpoint using `actix_web`.

use actix_web::{
    App, HttpResponse, HttpServer, Responder, get, http::header::ContentDisposition, web,
};
use zippity::Builder;

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    println!("Running server on 127.0.0.1:8080");
    HttpServer::new(|| App::new().service(index).service(zip_endpoint))
        .bind(("127.0.0.1", 8080))?
        .run()
        .await
}

#[get("/zip")]
async fn zip_endpoint(
    q: web::Query<std::collections::HashMap<String, String>>,
) -> actix_web::Result<impl Responder> {
    let seed = q
        .get("seed")
        .and_then(|s| s.parse::<u64>().ok())
        .unwrap_or(0);
    let file_name = format!("zippity_example-{seed}.zip");
    let directory_name = format!("zippity_example-{seed}/");
    let mut builder = Builder::<Vec<u8>>::new();
    for i in 0..5 {
        let data = make_svg(seed, i);
        let name = format!("{}image-{}.svg", &directory_name, i);
        builder
            .add_entry(name, data)
            .map_err(actix_web::error::ErrorInternalServerError)?;
    }
    builder
        .add_entry(directory_name, Vec::new())
        .map_err(actix_web::error::ErrorInternalServerError)?
        .directory();
    let responder = builder
        .build()
        .into_responder()
        .customize()
        .append_header(ContentDisposition::attachment(file_name));

    Ok(responder)
}

#[get("/")]
async fn index() -> impl Responder {
    HttpResponse::Ok()
        .content_type("text/html; charset=utf-8")
        .body(
            r#"<!DOCTYPE html>
<html><body>
  <form action="/zip" method="get">
    Seed: <input type="text" name="seed" value="42"/>
    <button type="submit">Generate ZIP</button>
  </form>
</body></html>
"#,
        )
}

fn make_svg(seed: u64, i: usize) -> Vec<u8> {
    let mut rng_state =
        ((seed.wrapping_mul(31).wrapping_add(i as u64)).wrapping_mul(0xab_cdef)) & 0xff_ffff;
    let mut random = || {
        rng_state = (rng_state.wrapping_mul(0x908_7654).wrapping_add(0x12_3456)) & 0xff_ffff;
        rng_state
    };
    // simple random-color rect based on seed + i
    let color = format!("#{:06x}", random());
    let circle_x = random() % 100 + 50;
    let circle_y = random() % 100 + 50;
    let svg = format!(
        r#"<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">
  <rect width="200" height="200" fill="{color}" />
  <circle cx="{circle_x}" cy="{circle_y}" r="80" fill="white" opacity="0.3"/>
</svg>"#,
    );
    svg.into_bytes()
}