use clap::Parser; use colored::*; use config::Config; use reqwest::header::{HeaderMap, HeaderName, HeaderValue}; use serde_json::Value; use std::process; #[derive(Parser)] #[command(author, version, about)] struct Args { method: String, url: String, #[arg(short = 'H', long)] header: Vec, #[arg(short = 'd', long)] data: Option, #[arg(short = 'c', long)] config: Option, #[arg(long)] indent: Option, } fn print_colored_json(value: &Value, indent_level: usize, indent_spaces: usize) { let indent = " ".repeat(indent_spaces * indent_level); match value { Value::Object(map) => { println!("{}{}", indent, "{".bright_blue()); for (key, val) in map { print!( "{}{}: ", " ".repeat(indent_spaces * (indent_level + 1)), key.bright_cyan() ); print_colored_json(val, indent_level + 1, indent_spaces); } println!("{}{}", indent, "}".bright_blue()); } Value::Array(arr) => { println!("{}{}", indent, "[".bright_blue()); for val in arr { print_colored_json(val, indent_level + 1, indent_spaces); } println!("{}{}", indent, "]".bright_blue()); } Value::String(s) => println!("{}{}", indent, format!("\"{}\"", s).bright_yellow()), Value::Number(n) => println!("{}{}", indent, n.to_string().bright_magenta()), Value::Bool(b) => println!("{}{}", indent, b.to_string().bright_green()), Value::Null => println!("{}{}", indent, "null".bright_black()), } } fn colorize_status(status_code: u16) -> ColoredString { match status_code { 100..=199 => status_code.to_string().bright_blue(), 200..=299 => status_code.to_string().green(), 300..=399 => status_code.to_string().cyan(), 400..=499 => status_code.to_string().yellow(), 500..=599 => status_code.to_string().red(), _ => status_code.to_string().normal(), } } fn build_request( client: &reqwest::Client, method: &str, url: &str, headers: HeaderMap, ) -> Result { let method = method.to_uppercase(); let builder = match method.as_str() { "GET" => Ok(client.get(url)), "POST" => Ok(client.post(url)), "PUT" => Ok(client.put(url)), "DELETE" => Ok(client.delete(url)), "PATCH" => Ok(client.patch(url)), "OPTIONS" => Ok(client.request(reqwest::Method::OPTIONS, url)), "HEAD" => Ok(client.head(url)), other => Err(format!("Unsupported HTTP method: {}", other)), }?; Ok(builder.headers(headers)) } #[tokio::main] async fn main() { let args = Args::parse(); let client = reqwest::Client::new(); let config = match args.config.as_ref().map(|p| Config::from_file(p)) { Some(Ok(cfg)) => Some(cfg), Some(Err(e)) => { eprintln!("{}: Failed to load config: {}", "Error".red(), e); process::exit(1); } None => None, }; let mut headers = HeaderMap::new(); if let Some(cfg) = &config { for header_str in cfg.headers() { match header_str.split_once(':') { Some((key, value)) => { if let (Ok(k), Ok(v)) = ( HeaderName::from_bytes(key.trim().as_bytes()), HeaderValue::from_str(value.trim()), ) { headers.insert(k, v); } } None => eprintln!( "{}: Invalid header format in config: {}", "Warning".yellow(), header_str ), } } } for header_str in &args.header { match header_str.split_once(':') { Some((key, value)) => { if let (Ok(k), Ok(v)) = ( HeaderName::from_bytes(key.trim().as_bytes()), HeaderValue::from_str(value.trim()), ) { headers.insert(k, v); } } None => eprintln!( "{}: Invalid header format in CLI: {}", "Warning".yellow(), header_str ), } } let indent_spaces = args .indent .or_else(|| config.as_ref().map(|c| c.indent())) .unwrap_or(2); let mut request_builder = match build_request(&client, &args.method, &args.url, headers) { Ok(r) => r, Err(e) => { eprintln!("{}: {}", "Error".red(), e); process::exit(1); } }; if let Some(json_data) = args.data { match serde_json::from_str::(&json_data) { Ok(json_value) => request_builder = request_builder.json(&json_value), Err(e) => { eprintln!("{}: Invalid JSON body: {}", "Error".red(), e); process::exit(1); } } } let response = match request_builder.send().await { Ok(resp) => resp, Err(e) => { eprintln!("{}: Request failed: {}", "Error".red(), e); process::exit(1); } }; let status_code = response.status().as_u16(); println!("Status: {}", colorize_status(status_code)); let response_text = match response.text().await { Ok(t) => t, Err(e) => { eprintln!("{}: Failed to read response body: {}", "Error".red(), e); process::exit(1); } }; if let Ok(json_value) = serde_json::from_str::(&response_text) { print_colored_json(&json_value, 0, indent_spaces); } else { println!("{}", response_text); } }