use clap::Parser;
use std::path::PathBuf;
#[derive(Parser, Debug)]
#[command(name = "hurley")]
#[command(author = "Dursun Koc <dursunkoc@gmail.com>")]
#[command(version = "0.1.1")]
#[command(about = "A curl-like HTTP client with performance testing capabilities", long_about = None)]
pub struct Cli {
pub url: String,
#[arg(short = 'X', long, default_value = "GET")]
pub method: String,
#[arg(short = 'H', long = "header")]
pub headers: Vec<String>,
#[arg(short = 'd', long = "data")]
pub data: Option<String>,
#[arg(short = 'f', long = "file")]
pub body_file: Option<PathBuf>,
#[arg(short = 'i', long = "include")]
pub include_headers: bool,
#[arg(short = 'L', long = "location")]
pub follow_redirects: bool,
#[arg(short = 'v', long = "verbose")]
pub verbose: bool,
#[arg(long, default_value = "30")]
pub timeout: u64,
#[arg(long = "perf")]
pub perf_file: Option<PathBuf>,
#[arg(short = 'c', long = "concurrency", default_value = "1")]
pub concurrency: usize,
#[arg(short = 'n', long = "requests", default_value = "1")]
pub total_requests: usize,
#[arg(long = "output", default_value = "text")]
pub output_format: String,
#[arg(long = "data-file")]
pub data_file: Option<PathBuf>,
}
impl Cli {
pub fn is_perf_mode(&self) -> bool {
self.perf_file.is_some() || self.total_requests > 1 || self.concurrency > 1
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_values() {
let cli = Cli::parse_from(["hurley", "https://example.com"]);
assert_eq!(cli.url, "https://example.com");
assert_eq!(cli.method, "GET");
assert_eq!(cli.timeout, 30);
assert_eq!(cli.concurrency, 1);
assert_eq!(cli.total_requests, 1);
assert!(!cli.is_perf_mode());
}
#[test]
fn test_post_with_data() {
let cli = Cli::parse_from([
"hurley",
"-X", "POST",
"https://example.com",
"-d", r#"{"key": "value"}"#,
]);
assert_eq!(cli.method, "POST");
assert_eq!(cli.data, Some(r#"{"key": "value"}"#.to_string()));
}
#[test]
fn test_headers() {
let cli = Cli::parse_from([
"hurley",
"https://example.com",
"-H", "Content-Type: application/json",
"-H", "Authorization: Bearer token",
]);
assert_eq!(cli.headers.len(), 2);
assert_eq!(cli.headers[0], "Content-Type: application/json");
}
#[test]
fn test_perf_mode_with_concurrency() {
let cli = Cli::parse_from([
"hurley",
"https://example.com",
"-c", "10",
"-n", "100",
]);
assert!(cli.is_perf_mode());
assert_eq!(cli.concurrency, 10);
assert_eq!(cli.total_requests, 100);
}
#[test]
fn test_flags() {
let cli = Cli::parse_from([
"hurley",
"https://example.com",
"-i", "-L", "-v",
]);
assert!(cli.include_headers);
assert!(cli.follow_redirects);
assert!(cli.verbose);
}
#[test]
fn test_data_file_flag() {
let cli = Cli::parse_from([
"hurley",
"https://example.com",
"--data-file", "data.csv",
]);
assert_eq!(cli.data_file, Some(PathBuf::from("data.csv")));
}
#[test]
fn test_data_file_alone_not_perf_mode() {
let cli = Cli::parse_from([
"hurley",
"https://example.com",
"--data-file", "data.csv",
]);
assert!(!cli.is_perf_mode());
}
}