tovuk 0.1.66

Deploy Rust workers, static frontends, and worker-static apps to Tovuk.
mod api;
mod args;
mod output;
mod transfer;

use std::{fs, path::PathBuf};

use serde_json::json;

use super::super::{
    args::CliOptions,
    auth::read_or_login_token,
    errors::{Result, agent_error, print_json},
};
use api::{
    complete_upload_response, delete_response, download_url_response, list_response,
    upload_url_response,
};
use args::{
    default_download_path, default_remote_path, required_storage_arg, storage_content_type,
};
use output::{print_delete_result, print_list_result, print_upload_result};
use transfer::{download_presigned_file, upload_presigned_file};

pub(crate) fn storage_command(cli: &CliOptions) -> Result<()> {
    match cli.args.first().map_or("list", String::as_str) {
        "list" => storage_list(cli),
        "upload" | "put" => storage_upload(cli),
        "download" | "get" => storage_download(cli),
        "delete" | "rm" => storage_delete(cli),
        "url" => storage_download_url(cli),
        _ => Err(agent_error(
            "unknown_storage_command",
            "Unknown storage command.",
            "Use `tovuk storage list`, `storage upload`, `storage download`, `storage delete`, or `storage url`.",
            cli.output.json,
        )),
    }
}

fn storage_list(cli: &CliOptions) -> Result<()> {
    let response = list_response(cli)?;
    if cli.output.json {
        return print_json(&response);
    }
    print_list_result(&response);
    Ok(())
}

fn storage_upload(cli: &CliOptions) -> Result<()> {
    let local_path = PathBuf::from(required_storage_arg(
        cli,
        1,
        "missing_storage_file",
        "Local file path is required.",
        "Use `tovuk storage upload --service <service> ./file.png uploads/file.png --public --json`.",
    )?);
    let remote_path =
        args::optional_storage_arg(cli, 2).map_or_else(|| default_remote_path(&local_path), Ok)?;
    let metadata = fs::metadata(&local_path).map_err(|error| {
        agent_error(
            "storage_file_unreadable",
            format!("Could not read local storage file: {error}"),
            "Pass a readable file path and retry the upload.",
            cli.output.json,
        )
    })?;
    if !metadata.is_file() {
        return Err(agent_error(
            "storage_file_unreadable",
            "Storage upload source must be a file.",
            "Pass a regular file path and retry the upload.",
            cli.output.json,
        ));
    }

    let content_type = storage_content_type(cli, &local_path);
    let token = read_or_login_token(cli)?;
    let upload = upload_url_response(
        cli,
        &token,
        &remote_path,
        &content_type,
        metadata.len(),
        cli.storage.public_read,
    )?;
    upload_presigned_file(cli, &upload, &local_path, metadata.len(), &content_type)?;
    let completed = complete_upload_response(
        cli,
        &token,
        &super::super::project::string_field(&upload, "path"),
    )?;

    if cli.output.json {
        return print_json(&completed);
    }
    print_upload_result(&completed);
    Ok(())
}

fn storage_download(cli: &CliOptions) -> Result<()> {
    let remote_path = required_storage_arg(
        cli,
        1,
        "missing_storage_path",
        "Storage path is required.",
        "Use `tovuk storage download --service <service> uploads/file.png ./file.png --json`.",
    )?;
    let destination = args::optional_storage_arg(cli, 2).map_or_else(
        || default_download_path(&remote_path),
        |value| Ok(PathBuf::from(value)),
    )?;
    let download = download_url_response(cli, &remote_path)?;
    let bytes = download_presigned_file(cli, &download, &destination)?;

    if cli.output.json {
        return print_json(&json!({
            "path": remote_path,
            "file": destination.display().to_string(),
            "bytes": bytes,
        }));
    }
    println!("downloaded {remote_path} to {}", destination.display());
    Ok(())
}

fn storage_delete(cli: &CliOptions) -> Result<()> {
    let remote_path = required_storage_arg(
        cli,
        1,
        "missing_storage_path",
        "Storage path is required.",
        "Use `tovuk storage delete --service <service> uploads/file.png --json`.",
    )?;
    let token = read_or_login_token(cli)?;
    let response = delete_response(cli, &token, &remote_path)?;
    if cli.output.json {
        return print_json(&response);
    }
    print_delete_result(&response);
    Ok(())
}

fn storage_download_url(cli: &CliOptions) -> Result<()> {
    let remote_path = required_storage_arg(
        cli,
        1,
        "missing_storage_path",
        "Storage path is required.",
        "Use `tovuk storage url --service <service> uploads/file.png --json`.",
    )?;
    let response = download_url_response(cli, &remote_path)?;
    if cli.output.json {
        return print_json(&response);
    }
    println!("{}", super::super::project::string_field(&response, "url"));
    Ok(())
}