pub mod cli;
pub mod error;
pub mod http;
pub mod perf;
use clap::Parser;
use std::time::Duration;
use colored::Colorize;
use cli::Cli;
use error::Result;
use http::{HttpClient, HttpRequest};
use perf::{DataFile, Dataset, PerfRunner, PerfReport, get_row_for_request, substitute, validate_template};
#[tokio::main]
async fn main() {
if let Err(e) = run().await {
eprintln!("{} {}", "Error:".red().bold(), e);
std::process::exit(1);
}
}
async fn run() -> Result<()> {
let cli = Cli::parse();
let mut request = HttpRequest::new(&cli.url)
.method(&cli.method)?
.headers_from_strings(&cli.headers)?
.timeout(Duration::from_secs(cli.timeout))
.follow_redirects(cli.follow_redirects);
if let Some(data) = &cli.data {
request = request.body(data.clone());
} else if let Some(file) = &cli.body_file {
request = request.body_from_file(file)?;
}
let data_file: Option<DataFile> = if let Some(ref path) = cli.data_file {
let df = DataFile::from_path(path)?;
let mut templates: Vec<String> = vec![cli.url.clone()];
templates.extend(cli.headers.iter().cloned());
if let Some(ref data) = cli.data {
templates.push(data.clone());
}
for tmpl in &templates {
validate_template(tmpl, df.columns())?;
}
Some(df)
} else {
None
};
if cli.is_perf_mode() {
run_perf_test(&cli, request, data_file).await?;
} else {
run_single_request(&cli, request, data_file.as_ref()).await?;
}
Ok(())
}
async fn run_single_request(cli: &Cli, request: HttpRequest, data_file: Option<&DataFile>) -> Result<()> {
let client = HttpClient::new(cli.verbose);
if let Some(df) = data_file {
for i in 0..df.len() {
let row = get_row_for_request(df, i);
let url = substitute(&request.url, row)?;
let substituted_headers: Vec<String> = cli
.headers
.iter()
.map(|h| substitute(h, row))
.collect::<Result<Vec<_>>>()?;
let body = request
.body
.as_ref()
.map(|b| substitute(b, row))
.transpose()?;
let mut row_request = HttpRequest::new(url)
.method(request.method.as_str())?
.timeout(request.timeout)
.follow_redirects(request.follow_redirects)
.headers_from_strings(&substituted_headers)?;
if let Some(b) = body {
row_request = row_request.body(b);
}
let response = client.execute(&row_request).await?;
response.print(cli.include_headers, cli.verbose);
}
} else {
let response = client.execute(&request).await?;
response.print(cli.include_headers, cli.verbose);
}
Ok(())
}
async fn run_perf_test(cli: &Cli, base_request: HttpRequest, data_file: Option<DataFile>) -> Result<()> {
println!("{}", "🚀 Starting Performance Test".cyan().bold());
println!(" URL: {}", cli.url.yellow());
println!(" Concurrency: {}", cli.concurrency);
println!(" Total Requests: {}", cli.total_requests);
println!();
let dataset = if let Some(file) = &cli.perf_file {
println!(" Dataset: {}", file.display().to_string().yellow());
Dataset::from_file(file)?
} else {
Dataset::simple(cli.total_requests)
};
let runner = PerfRunner::new(
cli.url.clone(),
base_request,
cli.concurrency,
cli.total_requests,
cli.verbose,
data_file,
);
let metrics = runner.run(&dataset).await?;
PerfReport::print(&metrics, &cli.output_format);
Ok(())
}