iskra 0.2.4

A safe, modern, Rust-native data transfer tool.
Documentation


use std::env;
use crate::cli_args::*;
use crate::cli_parser::{reorder_global_options, extract_show_headers};
use crate::{IskraClient, IskraError};
use crate::util::clamp_timeout;
use clap::Parser;
/// Run the Iskra CLI.
pub async fn run_cli() -> Result<(), IskraError> {
    println!("[iskra debug] run_cli entry");
    // Pre-parse for --show-headers/--headers, remove them from args
    let orig_args: Vec<_> = env::args_os().collect();
    let (show_headers_flag, filtered_args) = extract_show_headers(orig_args.clone());
    println!("[iskra debug] orig_args: {:?}", orig_args);
    println!("[iskra debug] filtered_args: {:?}", filtered_args);
    // Hybrid custom parser: reorder global options before subcommand, then parse with clap
    let reordered_args = reorder_global_options(filtered_args);
    let mut cli = Cli::parse_from(reordered_args);
    // Set show_headers if found in pre-parse
    if show_headers_flag {
        cli.global.show_headers = true;
    }
    let timeout = clamp_timeout(cli.global.timeout);
    let client = if cli.global.no_decompress {
        IskraClient::new_with_timeout_and_decompression(std::time::Duration::from_secs(timeout), false)?
    } else {
        IskraClient::new_with_timeout(std::time::Duration::from_secs(timeout))?
    };

    match &cli.command {
        Commands::Get { url, header, query, output, range, resume } => {
            let headers: Vec<(&str, &str)> = header.iter().map(|(k, v)| (k.as_str(), v.as_str())).collect();
            let queries: Vec<(&str, &str)> = query.iter().map(|(k, v)| (k.as_str(), v.as_str())).collect();
            if let Some(path) = output {
                let downloaded = client.download_with_range("GET", url, None, &headers, &queries, std::path::Path::new(&path), range.clone(), *resume, None).await?;
                println!("Downloaded {downloaded} bytes to {path}");
            } else {
                let resp = client.get(url, &headers, &queries).await?;
                let status = resp.status();
                if cli.global.show_headers || !header.is_empty() {
                    println!("Headers:");
                    for (k, v) in resp.headers().iter() {
                        println!("{}: {}", k, v.to_str().unwrap_or("<binary>"));
                    }
                }
                let body = resp.text().await.map_err(|e| IskraError::Http { source: e, url: url.clone() })?;
                println!("Status: {}\n{}", status, body);
                if cli.global.fail && !status.is_success() {
                    eprintln!("Request failed with status: {}", status);
                    std::process::exit(1);
                }
            }
        }
        Commands::Post { url, body, header, query, output, range, resume } => {
            let headers: Vec<(&str, &str)> = header.iter().map(|(k, v)| (k.as_str(), v.as_str())).collect();
            let queries: Vec<(&str, &str)> = query.iter().map(|(k, v)| (k.as_str(), v.as_str())).collect();
            if let Some(path) = output {
                let downloaded = client.download_with_range("POST", url, Some(body), &headers, &queries, std::path::Path::new(&path), range.clone(), *resume, None).await?;
                println!("Downloaded {downloaded} bytes to {path}");
            } else {
                let resp = client.post(url, body, &headers, &queries).await?;
                let status = resp.status();
                if cli.global.show_headers || !header.is_empty() {
                    println!("Headers:");
                    for (k, v) in resp.headers().iter() {
                        println!("{}: {}", k, v.to_str().unwrap_or("<binary>"));
                    }
                }
                let body = resp.text().await.map_err(|e| IskraError::Http { source: e, url: url.clone() })?;
                println!("Status: {}\n{}", status, body);
                if cli.global.fail && !status.is_success() {
                    eprintln!("Request failed with status: {}", status);
                    std::process::exit(1);
                }
            }
        }
        Commands::Put { url, body, header, query, output, range, resume } => {
            let headers: Vec<(&str, &str)> = header.iter().map(|(k, v)| (k.as_str(), v.as_str())).collect();
            let queries: Vec<(&str, &str)> = query.iter().map(|(k, v)| (k.as_str(), v.as_str())).collect();
            if let Some(path) = output {
                let downloaded = client.download_with_range("PUT", url, Some(body), &headers, &queries, std::path::Path::new(&path), range.clone(), *resume, None).await?;
                println!("Downloaded {downloaded} bytes to {path}");
            } else {
                let resp = client.put(url, body, &headers, &queries).await?;
                let status = resp.status();
                if cli.global.show_headers || !header.is_empty() {
                    println!("Headers:");
                    for (k, v) in resp.headers().iter() {
                        println!("{}: {}", k, v.to_str().unwrap_or("<binary>"));
                    }
                }
                let body = resp.text().await.map_err(|e| IskraError::Http { source: e, url: url.clone() })?;
                println!("Status: {}\n{}", status, body);
                if cli.global.fail && !status.is_success() {
                    eprintln!("Request failed with status: {}", status);
                    std::process::exit(1);
                }
            }
        }
        Commands::Delete { url, header, query, output, range, resume } => {
            let headers: Vec<(&str, &str)> = header.iter().map(|(k, v)| (k.as_str(), v.as_str())).collect();
            let queries: Vec<(&str, &str)> = query.iter().map(|(k, v)| (k.as_str(), v.as_str())).collect();
            if let Some(path) = output {
                let downloaded = client.download_with_range("DELETE", url, None, &headers, &queries, std::path::Path::new(&path), range.clone(), *resume, None).await?;
                println!("Downloaded {downloaded} bytes to {path}");
            } else {
                let resp = client.delete(url, &headers, &queries).await?;
                let status = resp.status();
                if cli.global.show_headers || !header.is_empty() {
                    println!("Headers:");
                    for (k, v) in resp.headers().iter() {
                        println!("{}: {}", k, v.to_str().unwrap_or("<binary>"));
                    }
                }
                let body = resp.text().await.map_err(|e| IskraError::Http { source: e, url: url.clone() })?;
                println!("Status: {}\n{}", status, body);
                if cli.global.fail && !status.is_success() {
                    eprintln!("Request failed with status: {}", status);
                    std::process::exit(1);
                }
            }
        }
        Commands::Custom { method, url, header, query, output, body, trailing_body, range, resume } => {
            let headers: Vec<(&str, &str)> = header.iter().map(|(k, v)| (k.as_str(), v.as_str())).collect();
            let queries: Vec<(&str, &str)> = query.iter().map(|(k, v)| (k.as_str(), v.as_str())).collect();
            // Precedence: --body > trailing_body > None
            let body_str: Option<String> = if let Some(b) = body {
                Some(b.clone())
            } else if !trailing_body.is_empty() {
                Some(trailing_body.join(" "))
            } else {
                None
            };
            if let Some(path) = output {
                let downloaded = client.download_with_range(method, url, body_str.as_deref(), &headers, &queries, std::path::Path::new(&path), range.clone(), *resume, None).await?;
                println!("Downloaded {downloaded} bytes to {path}");
            } else {
                let resp = client.request(method, url, body_str.as_deref(), &headers, &queries).await?;
                let status = resp.status();
                if cli.global.show_headers || !header.is_empty() {
                    println!("Headers:");
                    for (k, v) in resp.headers().iter() {
                        println!("{}: {}", k, v.to_str().unwrap_or("<binary>"));
                    }
                }
                let body_text = resp.text().await.map_err(|e| IskraError::Http { source: e, url: url.clone() })?;
                println!("Status: {}\n{}", status, body_text);
                if cli.global.fail && !status.is_success() {
                    eprintln!("Request failed with status: {}", status);
                    std::process::exit(1);
                }
            }
        }
    }
    Ok(())
}